@attraccess/plugin-rabbitmq 0.1.0-nightly.1076.1 → 0.1.0-nightly.1079.1

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.
package/dist/index.js CHANGED
@@ -34,17 +34,14 @@ __export(plugin_exports, {
34
34
  });
35
35
  module.exports = __toCommonJS(plugin_exports);
36
36
  var import_plugins_backend_sdk3 = require("@attraccess/plugins-backend-sdk");
37
- var import_common7 = require("@nestjs/common");
37
+ var import_common5 = require("@nestjs/common");
38
38
 
39
- // backend/rabbitmq-detection.controller.ts
39
+ // backend/detection/service.ts
40
40
  var import_plugins_backend_sdk = require("@attraccess/plugins-backend-sdk");
41
- var import_common2 = require("@nestjs/common");
42
-
43
- // backend/rabbitmq-detection.service.ts
44
41
  var import_common = require("@nestjs/common");
45
42
  var import_node_crypto2 = require("node:crypto");
46
43
 
47
- // backend/rabbitmq-management-transport.ts
44
+ // backend/management/transport.ts
48
45
  var import_node_crypto = require("node:crypto");
49
46
  var import_node_https = require("node:https");
50
47
  var import_node_net = require("node:net");
@@ -175,7 +172,7 @@ function describeManagementError(error, timeoutMs) {
175
172
  }
176
173
  }
177
174
 
178
- // backend/rabbitmq-detection.service.ts
175
+ // backend/detection/service.ts
179
176
  var PLUGIN_CONTEXT = /* @__PURE__ */ Symbol.for("attraccess.plugin.context");
180
177
  var CACHE_TTL_MS = 6e4;
181
178
  var PROBE_TIMEOUT_MS = 5e3;
@@ -302,8 +299,6 @@ RabbitmqDetectionService = __decorateClass([
302
299
  (0, import_common.Injectable)(),
303
300
  __decorateParam(0, (0, import_common.Inject)(PLUGIN_CONTEXT))
304
301
  ], RabbitmqDetectionService);
305
-
306
- // backend/rabbitmq-detection.controller.ts
307
302
  var RabbitmqDetectionController = class {
308
303
  // esbuild does not emit decorator metadata, so Nest cannot infer constructor
309
304
  // types for injection — always inject by an explicit token.
@@ -315,18 +310,18 @@ var RabbitmqDetectionController = class {
315
310
  }
316
311
  };
317
312
  __decorateClass([
318
- (0, import_common2.Get)("detection/:mqttServerId"),
319
- __decorateParam(0, (0, import_common2.Param)("mqttServerId", import_common2.ParseIntPipe)),
320
- __decorateParam(1, (0, import_common2.Query)("refresh"))
313
+ (0, import_common.Get)("detection/:mqttServerId"),
314
+ __decorateParam(0, (0, import_common.Param)("mqttServerId", import_common.ParseIntPipe)),
315
+ __decorateParam(1, (0, import_common.Query)("refresh"))
321
316
  ], RabbitmqDetectionController.prototype, "detect", 1);
322
317
  RabbitmqDetectionController = __decorateClass([
323
318
  (0, import_plugins_backend_sdk.Auth)("resources.update"),
324
- (0, import_common2.Controller)("rabbitmq"),
325
- __decorateParam(0, (0, import_common2.Inject)(RabbitmqDetectionService))
319
+ (0, import_common.Controller)("rabbitmq"),
320
+ __decorateParam(0, (0, import_common.Inject)(RabbitmqDetectionService))
326
321
  ], RabbitmqDetectionController);
327
322
 
328
- // backend/rabbitmq-management-client.ts
329
- var import_common3 = require("@nestjs/common");
323
+ // backend/management/client.ts
324
+ var import_common2 = require("@nestjs/common");
330
325
  var REQUEST_TIMEOUT_MS = 1e4;
331
326
  var RabbitmqManagementClient = class {
332
327
  // Builds the management API base URL from the generic MQTT config (same
@@ -343,9 +338,9 @@ var RabbitmqManagementClient = class {
343
338
  try {
344
339
  response = await managementRequest(config, `${base}/api${path}`, method, body, REQUEST_TIMEOUT_MS);
345
340
  } catch (error) {
346
- throw new import_common3.HttpException(
341
+ throw new import_common2.HttpException(
347
342
  `Cannot reach the RabbitMQ management API at ${base}: ${describeManagementError(error, REQUEST_TIMEOUT_MS)}`,
348
- import_common3.HttpStatus.BAD_GATEWAY
343
+ import_common2.HttpStatus.BAD_GATEWAY
349
344
  );
350
345
  }
351
346
  if (!response.ok) {
@@ -360,26 +355,26 @@ var RabbitmqManagementClient = class {
360
355
  const upstream = await this.parseErrorBody(response);
361
356
  const reason = upstream?.reason || upstream?.error || "";
362
357
  if (response.status === 401) {
363
- return new import_common3.HttpException(
358
+ return new import_common2.HttpException(
364
359
  "The RabbitMQ management API rejected the configured MQTT server credentials (401). Check the username/password configured for this MQTT server.",
365
- import_common3.HttpStatus.BAD_GATEWAY
360
+ import_common2.HttpStatus.BAD_GATEWAY
366
361
  );
367
362
  }
368
363
  if (response.status === 403 || /not.?authori[sz]ed/i.test(reason)) {
369
- return new import_common3.HttpException(
364
+ return new import_common2.HttpException(
370
365
  'The configured MQTT server user lacks management privileges on RabbitMQ. User management requires a user with the "administrator" tag.',
371
- import_common3.HttpStatus.BAD_GATEWAY
366
+ import_common2.HttpStatus.BAD_GATEWAY
372
367
  );
373
368
  }
374
369
  if (response.status === 404) {
375
- return new import_common3.HttpException("Not found on the RabbitMQ side.", import_common3.HttpStatus.NOT_FOUND);
370
+ return new import_common2.HttpException("Not found on the RabbitMQ side.", import_common2.HttpStatus.NOT_FOUND);
376
371
  }
377
372
  if (response.status === 400) {
378
- return new import_common3.HttpException("RabbitMQ rejected the request.", import_common3.HttpStatus.BAD_REQUEST);
373
+ return new import_common2.HttpException("RabbitMQ rejected the request.", import_common2.HttpStatus.BAD_REQUEST);
379
374
  }
380
- return new import_common3.HttpException(
375
+ return new import_common2.HttpException(
381
376
  `RabbitMQ management API request failed (HTTP ${response.status}).`,
382
- import_common3.HttpStatus.BAD_GATEWAY
377
+ import_common2.HttpStatus.BAD_GATEWAY
383
378
  );
384
379
  }
385
380
  async parseBody(response) {
@@ -390,9 +385,9 @@ var RabbitmqManagementClient = class {
390
385
  try {
391
386
  return JSON.parse(text);
392
387
  } catch {
393
- throw new import_common3.HttpException(
388
+ throw new import_common2.HttpException(
394
389
  "The RabbitMQ management API returned a response that is not valid JSON.",
395
- import_common3.HttpStatus.BAD_GATEWAY
390
+ import_common2.HttpStatus.BAD_GATEWAY
396
391
  );
397
392
  }
398
393
  }
@@ -405,15 +400,12 @@ var RabbitmqManagementClient = class {
405
400
  }
406
401
  };
407
402
  RabbitmqManagementClient = __decorateClass([
408
- (0, import_common3.Injectable)()
403
+ (0, import_common2.Injectable)()
409
404
  ], RabbitmqManagementClient);
410
405
 
411
- // backend/rabbitmq-users.controller.ts
406
+ // backend/users/service.ts
412
407
  var import_plugins_backend_sdk2 = require("@attraccess/plugins-backend-sdk");
413
- var import_common5 = require("@nestjs/common");
414
-
415
- // backend/rabbitmq-users.service.ts
416
- var import_common4 = require("@nestjs/common");
408
+ var import_common3 = require("@nestjs/common");
417
409
  var PLUGIN_CONTEXT2 = /* @__PURE__ */ Symbol.for("attraccess.plugin.context");
418
410
  var RabbitmqUsersService = class {
419
411
  constructor(context, client) {
@@ -460,7 +452,7 @@ var RabbitmqUsersService = class {
460
452
  const existing = await this.findUser(config, username);
461
453
  const password = dto.password?.length ? dto.password : void 0;
462
454
  if (!existing && !password) {
463
- throw new import_common4.HttpException("Password is required when creating a new user.", import_common4.HttpStatus.BAD_REQUEST);
455
+ throw new import_common3.HttpException("Password is required when creating a new user.", import_common3.HttpStatus.BAD_REQUEST);
464
456
  }
465
457
  const body = { tags: (dto.tags ?? this.normalizeTags(existing?.tags)).join(",") };
466
458
  if (password) {
@@ -480,9 +472,9 @@ var RabbitmqUsersService = class {
480
472
  this.assertValidName(username, "Username");
481
473
  const config = await this.requireConfig(mqttServerId);
482
474
  if (config.username !== null && config.username === username) {
483
- throw new import_common4.HttpException(
475
+ throw new import_common3.HttpException(
484
476
  "Refusing to delete the user the MQTT server connection is configured with.",
485
- import_common4.HttpStatus.BAD_REQUEST
477
+ import_common3.HttpStatus.BAD_REQUEST
486
478
  );
487
479
  }
488
480
  await this.client.request(config, "DELETE", `/users/${encodeURIComponent(username)}`);
@@ -520,7 +512,7 @@ var RabbitmqUsersService = class {
520
512
  try {
521
513
  return await this.client.request(config, "GET", `/users/${encodeURIComponent(username)}`);
522
514
  } catch (error) {
523
- if (error instanceof import_common4.HttpException && error.getStatus() === import_common4.HttpStatus.NOT_FOUND) {
515
+ if (error instanceof import_common3.HttpException && error.getStatus() === import_common3.HttpStatus.NOT_FOUND) {
524
516
  return null;
525
517
  }
526
518
  throw error;
@@ -529,7 +521,7 @@ var RabbitmqUsersService = class {
529
521
  async requireConfig(mqttServerId) {
530
522
  const config = await this.context.getMqttServerConfig(mqttServerId);
531
523
  if (!config) {
532
- throw new import_common4.HttpException("MQTT server not found.", import_common4.HttpStatus.NOT_FOUND);
524
+ throw new import_common3.HttpException("MQTT server not found.", import_common3.HttpStatus.NOT_FOUND);
533
525
  }
534
526
  return config;
535
527
  }
@@ -544,20 +536,18 @@ var RabbitmqUsersService = class {
544
536
  }
545
537
  assertValidName(value, label) {
546
538
  if (typeof value !== "string" || value.trim().length === 0) {
547
- throw new import_common4.HttpException(`${label} must not be empty.`, import_common4.HttpStatus.BAD_REQUEST);
539
+ throw new import_common3.HttpException(`${label} must not be empty.`, import_common3.HttpStatus.BAD_REQUEST);
548
540
  }
549
541
  if (value.length > 255) {
550
- throw new import_common4.HttpException(`${label} must be at most 255 characters.`, import_common4.HttpStatus.BAD_REQUEST);
542
+ throw new import_common3.HttpException(`${label} must be at most 255 characters.`, import_common3.HttpStatus.BAD_REQUEST);
551
543
  }
552
544
  }
553
545
  };
554
546
  RabbitmqUsersService = __decorateClass([
555
- (0, import_common4.Injectable)(),
556
- __decorateParam(0, (0, import_common4.Inject)(PLUGIN_CONTEXT2)),
557
- __decorateParam(1, (0, import_common4.Inject)(RabbitmqManagementClient))
547
+ (0, import_common3.Injectable)(),
548
+ __decorateParam(0, (0, import_common3.Inject)(PLUGIN_CONTEXT2)),
549
+ __decorateParam(1, (0, import_common3.Inject)(RabbitmqManagementClient))
558
550
  ], RabbitmqUsersService);
559
-
560
- // backend/rabbitmq-users.controller.ts
561
551
  var RabbitmqUsersController = class {
562
552
  // esbuild does not emit decorator metadata, so Nest cannot infer constructor
563
553
  // types for injection — always inject by an explicit token.
@@ -575,53 +565,52 @@ var RabbitmqUsersController = class {
575
565
  }
576
566
  setPermissions(mqttServerId, username, dto) {
577
567
  if (!dto || typeof dto.vhost !== "string") {
578
- throw new import_common5.HttpException("A vhost is required.", import_common5.HttpStatus.BAD_REQUEST);
568
+ throw new import_common3.HttpException("A vhost is required.", import_common3.HttpStatus.BAD_REQUEST);
579
569
  }
580
570
  return this.users.setPermissions(mqttServerId, username, dto);
581
571
  }
582
572
  clearPermissions(mqttServerId, username, vhost) {
583
573
  if (typeof vhost !== "string" || vhost.length === 0) {
584
- throw new import_common5.HttpException("A vhost query parameter is required.", import_common5.HttpStatus.BAD_REQUEST);
574
+ throw new import_common3.HttpException("A vhost query parameter is required.", import_common3.HttpStatus.BAD_REQUEST);
585
575
  }
586
576
  return this.users.clearPermissions(mqttServerId, username, vhost);
587
577
  }
588
578
  };
589
579
  __decorateClass([
590
- (0, import_common5.Get)("users/:mqttServerId"),
591
- __decorateParam(0, (0, import_common5.Param)("mqttServerId", import_common5.ParseIntPipe))
580
+ (0, import_common3.Get)("users/:mqttServerId"),
581
+ __decorateParam(0, (0, import_common3.Param)("mqttServerId", import_common3.ParseIntPipe))
592
582
  ], RabbitmqUsersController.prototype, "list", 1);
593
583
  __decorateClass([
594
- (0, import_common5.Put)("users/:mqttServerId/:username"),
595
- __decorateParam(0, (0, import_common5.Param)("mqttServerId", import_common5.ParseIntPipe)),
596
- __decorateParam(1, (0, import_common5.Param)("username")),
597
- __decorateParam(2, (0, import_common5.Body)())
584
+ (0, import_common3.Put)("users/:mqttServerId/:username"),
585
+ __decorateParam(0, (0, import_common3.Param)("mqttServerId", import_common3.ParseIntPipe)),
586
+ __decorateParam(1, (0, import_common3.Param)("username")),
587
+ __decorateParam(2, (0, import_common3.Body)())
598
588
  ], RabbitmqUsersController.prototype, "upsert", 1);
599
589
  __decorateClass([
600
- (0, import_common5.Delete)("users/:mqttServerId/:username"),
601
- __decorateParam(0, (0, import_common5.Param)("mqttServerId", import_common5.ParseIntPipe)),
602
- __decorateParam(1, (0, import_common5.Param)("username"))
590
+ (0, import_common3.Delete)("users/:mqttServerId/:username"),
591
+ __decorateParam(0, (0, import_common3.Param)("mqttServerId", import_common3.ParseIntPipe)),
592
+ __decorateParam(1, (0, import_common3.Param)("username"))
603
593
  ], RabbitmqUsersController.prototype, "remove", 1);
604
594
  __decorateClass([
605
- (0, import_common5.Put)("users/:mqttServerId/:username/permissions"),
606
- __decorateParam(0, (0, import_common5.Param)("mqttServerId", import_common5.ParseIntPipe)),
607
- __decorateParam(1, (0, import_common5.Param)("username")),
608
- __decorateParam(2, (0, import_common5.Body)())
595
+ (0, import_common3.Put)("users/:mqttServerId/:username/permissions"),
596
+ __decorateParam(0, (0, import_common3.Param)("mqttServerId", import_common3.ParseIntPipe)),
597
+ __decorateParam(1, (0, import_common3.Param)("username")),
598
+ __decorateParam(2, (0, import_common3.Body)())
609
599
  ], RabbitmqUsersController.prototype, "setPermissions", 1);
610
600
  __decorateClass([
611
- (0, import_common5.Delete)("users/:mqttServerId/:username/permissions"),
612
- __decorateParam(0, (0, import_common5.Param)("mqttServerId", import_common5.ParseIntPipe)),
613
- __decorateParam(1, (0, import_common5.Param)("username")),
614
- __decorateParam(2, (0, import_common5.Query)("vhost"))
601
+ (0, import_common3.Delete)("users/:mqttServerId/:username/permissions"),
602
+ __decorateParam(0, (0, import_common3.Param)("mqttServerId", import_common3.ParseIntPipe)),
603
+ __decorateParam(1, (0, import_common3.Param)("username")),
604
+ __decorateParam(2, (0, import_common3.Query)("vhost"))
615
605
  ], RabbitmqUsersController.prototype, "clearPermissions", 1);
616
606
  RabbitmqUsersController = __decorateClass([
617
607
  (0, import_plugins_backend_sdk2.Auth)("resources.update"),
618
- (0, import_common5.Controller)("rabbitmq"),
619
- __decorateParam(0, (0, import_common5.Inject)(RabbitmqUsersService))
608
+ (0, import_common3.Controller)("rabbitmq"),
609
+ __decorateParam(0, (0, import_common3.Inject)(RabbitmqUsersService))
620
610
  ], RabbitmqUsersController);
621
611
 
622
- // backend/rabbitmq-credential-provisioning.provider.ts
623
- var import_crypto = require("crypto");
624
- var import_common6 = require("@nestjs/common");
612
+ // backend/credentials/provider.ts
613
+ var import_common4 = require("@nestjs/common");
625
614
 
626
615
  // ../../../node_modules/.pnpm/async-mutex@0.5.0/node_modules/async-mutex/index.mjs
627
616
  var E_TIMEOUT = new Error("timeout while waiting for mutex to become available");
@@ -829,7 +818,31 @@ var Mutex = class {
829
818
  }
830
819
  };
831
820
 
832
- // backend/rabbitmq-credential-provisioning.provider.ts
821
+ // backend/credentials/provider.ts
822
+ var import_crypto = require("crypto");
823
+ function escapeRegex(value) {
824
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
825
+ }
826
+ function mqttFilterToRegex(filter) {
827
+ const terminalMultiLevel = filter.endsWith("/#");
828
+ const prefix = terminalMultiLevel ? filter.slice(0, -2) : filter;
829
+ const translated = prefix.split("/").map((segment) => {
830
+ if (segment === "+") {
831
+ return "[^.]+";
832
+ }
833
+ if (segment === "#") {
834
+ return ".*";
835
+ }
836
+ return segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
837
+ }).join("\\.");
838
+ return terminalMultiLevel ? `${translated}(?:\\..*)?` : translated;
839
+ }
840
+ function mqttFiltersToRegex(filters) {
841
+ if (filters.length === 0) {
842
+ return "$(?!)";
843
+ }
844
+ return `^(?:${filters.map(mqttFilterToRegex).join("|")})$`;
845
+ }
833
846
  var _RabbitmqCredentialProvisioningProvider = class _RabbitmqCredentialProvisioningProvider {
834
847
  constructor(context) {
835
848
  __publicField(this, "context", context);
@@ -839,46 +852,114 @@ var _RabbitmqCredentialProvisioningProvider = class _RabbitmqCredentialProvision
839
852
  __publicField(this, "detection");
840
853
  this.detection = new RabbitmqDetectionService(context);
841
854
  }
842
- async supports(config) {
843
- return (await this.detection.detect(config.id)).isRabbitMQ;
855
+ assertNotManagementUser(config, username) {
856
+ if (config.username === username) {
857
+ throw new import_common4.BadRequestException("Refusing to modify the MQTT server management identity.");
858
+ }
844
859
  }
845
- provision(request2) {
846
- return this.writeCredential(request2);
860
+ assertName(value, label) {
861
+ if (typeof value !== "string" || value.trim().length === 0 || value.length > 255) {
862
+ throw new import_common4.BadRequestException(`${label} must be a non-empty string no longer than 255 characters.`);
863
+ }
847
864
  }
848
- rotate(request2) {
849
- return this.writeCredential(request2);
865
+ assertTopicFilter(filter) {
866
+ if (filter.includes(".")) {
867
+ throw new import_common4.BadRequestException("RabbitMQ MQTT topic policies cannot contain dots.");
868
+ }
869
+ const segments = filter.split("/");
870
+ for (const [index, segment] of segments.entries()) {
871
+ if (segment.includes("+") && segment !== "+" || segment.includes("#") && (segment !== "#" || index !== segments.length - 1)) {
872
+ throw new import_common4.BadRequestException(
873
+ "MQTT wildcards must occupy a complete topic level, and # must be the final level."
874
+ );
875
+ }
876
+ }
850
877
  }
851
- async revoke(request2) {
878
+ assertRequest(request2) {
879
+ this.assertName(request2.identity, "Identity");
852
880
  this.assertName(request2.username, "Username");
853
- const config = await this.requireConfig(request2.mqttServerId);
854
- this.assertNotManagementUser(config, request2.username);
881
+ this.assertName(request2.vhost, "Vhost");
882
+ if (!Array.isArray(request2.topicPolicy.publish) || !Array.isArray(request2.topicPolicy.subscribe)) {
883
+ throw new import_common4.BadRequestException("Publish and subscribe topic policies must be arrays.");
884
+ }
885
+ for (const filter of [...request2.topicPolicy.publish, ...request2.topicPolicy.subscribe]) {
886
+ if (typeof filter !== "string" || filter.length === 0 || filter.length > 1024) {
887
+ throw new import_common4.BadRequestException("MQTT topic filters must be non-empty strings no longer than 1024 characters.");
888
+ }
889
+ this.assertTopicFilter(filter);
890
+ }
891
+ }
892
+ async requireConfig(mqttServerId) {
893
+ const config = await this.context.getMqttServerConfig(mqttServerId);
894
+ if (!config) {
895
+ throw new import_common4.BadRequestException("MQTT server not found.");
896
+ }
897
+ return config;
898
+ }
899
+ async failAfterRollback(error, rollback) {
900
+ const results = await Promise.allSettled(rollback.map((operation) => operation()));
901
+ const failures = results.filter((result) => result.status === "rejected").map((result) => result.reason);
902
+ if (failures.length > 0) {
903
+ throw new AggregateError([error, ...failures], "Credential provisioning failed and rollback was incomplete.");
904
+ }
905
+ throw error;
906
+ }
907
+ async restorePermission(config, path, permissions) {
908
+ await this.client.request(config, permissions ? "PUT" : "DELETE", path, permissions ?? void 0);
909
+ }
910
+ async restorePermissions(config, vhost, username, permissions, topicPermissions) {
911
+ const results = await Promise.allSettled([
912
+ this.restorePermission(config, `/permissions/${vhost}/${username}`, permissions),
913
+ this.restorePermission(config, `/topic-permissions/${vhost}/${username}`, topicPermissions)
914
+ ]);
915
+ const failures = results.filter((result) => result.status === "rejected").map((result) => result.reason);
916
+ if (failures.length > 0) {
917
+ throw new AggregateError(failures, "Failed to restore RabbitMQ permissions.");
918
+ }
919
+ }
920
+ async getOptional(config, path) {
855
921
  try {
856
- await this.client.request(config, "DELETE", `/users/${encodeURIComponent(request2.username)}`);
922
+ return await this.client.request(config, "GET", path);
857
923
  } catch (error) {
858
- if (error instanceof import_common6.HttpException && error.getStatus() === import_common6.HttpStatus.NOT_FOUND) return;
924
+ if (error instanceof import_common4.HttpException && error.getStatus() === import_common4.HttpStatus.NOT_FOUND) {
925
+ return null;
926
+ }
859
927
  throw error;
860
928
  }
861
929
  }
862
- async writeCredential(request2) {
863
- this.assertRequest(request2);
864
- const config = await this.requireConfig(request2.mqttServerId);
865
- this.assertNotManagementUser(config, request2.username);
866
- const lockKey = `${config.id}:${request2.vhost}`;
867
- let lock = _RabbitmqCredentialProvisioningProvider.vhostLocks.get(lockKey);
868
- if (!lock) {
869
- lock = { mutex: new Mutex(), users: 0 };
870
- _RabbitmqCredentialProvisioningProvider.vhostLocks.set(lockKey, lock);
871
- }
872
- lock.users += 1;
930
+ async getTopicPermissions(config, vhost, username) {
931
+ return this.getOptional(config, `/topic-permissions/${vhost}/${username}`);
932
+ }
933
+ async getPermissions(config, vhost, username) {
934
+ return this.getOptional(config, `/permissions/${vhost}/${username}`);
935
+ }
936
+ async exists(config, path) {
873
937
  try {
874
- return await lock.mutex.runExclusive(() => this.writeCredentialLocked(request2, config));
875
- } finally {
876
- lock.users -= 1;
877
- if (lock.users === 0 && _RabbitmqCredentialProvisioningProvider.vhostLocks.get(lockKey) === lock) {
878
- _RabbitmqCredentialProvisioningProvider.vhostLocks.delete(lockKey);
938
+ await this.client.request(config, "GET", path);
939
+ return true;
940
+ } catch (error) {
941
+ if (error instanceof import_common4.HttpException && error.getStatus() === import_common4.HttpStatus.NOT_FOUND) {
942
+ return false;
879
943
  }
944
+ throw error;
880
945
  }
881
946
  }
947
+ async userExists(config, username) {
948
+ return this.exists(config, `/users/${username}`);
949
+ }
950
+ async writePermissions(config, request2, vhost, username) {
951
+ const subscriptionQueue = `mqtt-subscription-${escapeRegex(request2.identity)}.*`;
952
+ await this.client.request(config, "PUT", `/permissions/${vhost}/${username}`, {
953
+ configure: `^${subscriptionQueue}$`,
954
+ write: `^(amq\\.topic|${subscriptionQueue})$`,
955
+ read: `^(amq\\.topic|${subscriptionQueue})$`
956
+ });
957
+ await this.client.request(config, "PUT", `/topic-permissions/${vhost}/${username}`, {
958
+ exchange: "amq.topic",
959
+ write: mqttFiltersToRegex(request2.topicPolicy.publish),
960
+ read: mqttFiltersToRegex(request2.topicPolicy.subscribe)
961
+ });
962
+ }
882
963
  async writeCredentialLocked(request2, config) {
883
964
  const password = (0, import_crypto.randomBytes)(24).toString("base64url");
884
965
  const vhost = encodeURIComponent(request2.vhost);
@@ -915,140 +996,49 @@ var _RabbitmqCredentialProvisioningProvider = class _RabbitmqCredentialProvision
915
996
  password
916
997
  };
917
998
  }
918
- async writePermissions(config, request2, vhost, username) {
919
- const subscriptionQueue = `mqtt-subscription-${escapeRegex(request2.identity)}.*`;
920
- await this.client.request(config, "PUT", `/permissions/${vhost}/${username}`, {
921
- configure: `^${subscriptionQueue}$`,
922
- write: `^(amq\\.topic|${subscriptionQueue})$`,
923
- read: `^(amq\\.topic|${subscriptionQueue})$`
924
- });
925
- await this.client.request(config, "PUT", `/topic-permissions/${vhost}/${username}`, {
926
- exchange: "amq.topic",
927
- write: mqttFiltersToRegex(request2.topicPolicy.publish),
928
- read: mqttFiltersToRegex(request2.topicPolicy.subscribe)
929
- });
930
- }
931
- async userExists(config, username) {
932
- return this.exists(config, `/users/${username}`);
933
- }
934
- async exists(config, path) {
999
+ async writeCredential(request2) {
1000
+ this.assertRequest(request2);
1001
+ const config = await this.requireConfig(request2.mqttServerId);
1002
+ this.assertNotManagementUser(config, request2.username);
1003
+ const lockKey = `${config.id}:${request2.vhost}`;
1004
+ let lock = _RabbitmqCredentialProvisioningProvider.vhostLocks.get(lockKey);
1005
+ if (!lock) {
1006
+ lock = { mutex: new Mutex(), users: 0 };
1007
+ _RabbitmqCredentialProvisioningProvider.vhostLocks.set(lockKey, lock);
1008
+ }
1009
+ lock.users += 1;
935
1010
  try {
936
- await this.client.request(config, "GET", path);
937
- return true;
938
- } catch (error) {
939
- if (error instanceof import_common6.HttpException && error.getStatus() === import_common6.HttpStatus.NOT_FOUND) {
940
- return false;
1011
+ return await lock.mutex.runExclusive(() => this.writeCredentialLocked(request2, config));
1012
+ } finally {
1013
+ lock.users -= 1;
1014
+ if (lock.users === 0 && _RabbitmqCredentialProvisioningProvider.vhostLocks.get(lockKey) === lock) {
1015
+ _RabbitmqCredentialProvisioningProvider.vhostLocks.delete(lockKey);
941
1016
  }
942
- throw error;
943
1017
  }
944
1018
  }
945
- async getPermissions(config, vhost, username) {
946
- return this.getOptional(config, `/permissions/${vhost}/${username}`);
947
- }
948
- async getTopicPermissions(config, vhost, username) {
949
- return this.getOptional(config, `/topic-permissions/${vhost}/${username}`);
950
- }
951
- async getOptional(config, path) {
1019
+ async revoke(request2) {
1020
+ this.assertName(request2.username, "Username");
1021
+ const config = await this.requireConfig(request2.mqttServerId);
1022
+ this.assertNotManagementUser(config, request2.username);
952
1023
  try {
953
- return await this.client.request(config, "GET", path);
1024
+ await this.client.request(config, "DELETE", `/users/${encodeURIComponent(request2.username)}`);
954
1025
  } catch (error) {
955
- if (error instanceof import_common6.HttpException && error.getStatus() === import_common6.HttpStatus.NOT_FOUND) {
956
- return null;
957
- }
1026
+ if (error instanceof import_common4.HttpException && error.getStatus() === import_common4.HttpStatus.NOT_FOUND) return;
958
1027
  throw error;
959
1028
  }
960
1029
  }
961
- async restorePermissions(config, vhost, username, permissions, topicPermissions) {
962
- const results = await Promise.allSettled([
963
- this.restorePermission(config, `/permissions/${vhost}/${username}`, permissions),
964
- this.restorePermission(config, `/topic-permissions/${vhost}/${username}`, topicPermissions)
965
- ]);
966
- const failures = results.filter((result) => result.status === "rejected").map((result) => result.reason);
967
- if (failures.length > 0) {
968
- throw new AggregateError(failures, "Failed to restore RabbitMQ permissions.");
969
- }
970
- }
971
- async restorePermission(config, path, permissions) {
972
- await this.client.request(config, permissions ? "PUT" : "DELETE", path, permissions ?? void 0);
973
- }
974
- async failAfterRollback(error, rollback) {
975
- const results = await Promise.allSettled(rollback.map((operation) => operation()));
976
- const failures = results.filter((result) => result.status === "rejected").map((result) => result.reason);
977
- if (failures.length > 0) {
978
- throw new AggregateError([error, ...failures], "Credential provisioning failed and rollback was incomplete.");
979
- }
980
- throw error;
981
- }
982
- async requireConfig(mqttServerId) {
983
- const config = await this.context.getMqttServerConfig(mqttServerId);
984
- if (!config) {
985
- throw new import_common6.BadRequestException("MQTT server not found.");
986
- }
987
- return config;
988
- }
989
- assertRequest(request2) {
990
- this.assertName(request2.identity, "Identity");
991
- this.assertName(request2.username, "Username");
992
- this.assertName(request2.vhost, "Vhost");
993
- if (!Array.isArray(request2.topicPolicy.publish) || !Array.isArray(request2.topicPolicy.subscribe)) {
994
- throw new import_common6.BadRequestException("Publish and subscribe topic policies must be arrays.");
995
- }
996
- for (const filter of [...request2.topicPolicy.publish, ...request2.topicPolicy.subscribe]) {
997
- if (typeof filter !== "string" || filter.length === 0 || filter.length > 1024) {
998
- throw new import_common6.BadRequestException("MQTT topic filters must be non-empty strings no longer than 1024 characters.");
999
- }
1000
- this.assertTopicFilter(filter);
1001
- }
1002
- }
1003
- assertTopicFilter(filter) {
1004
- if (filter.includes(".")) {
1005
- throw new import_common6.BadRequestException("RabbitMQ MQTT topic policies cannot contain dots.");
1006
- }
1007
- const segments = filter.split("/");
1008
- for (const [index, segment] of segments.entries()) {
1009
- if (segment.includes("+") && segment !== "+" || segment.includes("#") && (segment !== "#" || index !== segments.length - 1)) {
1010
- throw new import_common6.BadRequestException(
1011
- "MQTT wildcards must occupy a complete topic level, and # must be the final level."
1012
- );
1013
- }
1014
- }
1030
+ rotate(request2) {
1031
+ return this.writeCredential(request2);
1015
1032
  }
1016
- assertName(value, label) {
1017
- if (typeof value !== "string" || value.trim().length === 0 || value.length > 255) {
1018
- throw new import_common6.BadRequestException(`${label} must be a non-empty string no longer than 255 characters.`);
1019
- }
1033
+ provision(request2) {
1034
+ return this.writeCredential(request2);
1020
1035
  }
1021
- assertNotManagementUser(config, username) {
1022
- if (config.username === username) {
1023
- throw new import_common6.BadRequestException("Refusing to modify the MQTT server management identity.");
1024
- }
1036
+ async supports(config) {
1037
+ return (await this.detection.detect(config.id)).isRabbitMQ;
1025
1038
  }
1026
1039
  };
1027
1040
  __publicField(_RabbitmqCredentialProvisioningProvider, "vhostLocks", /* @__PURE__ */ new Map());
1028
1041
  var RabbitmqCredentialProvisioningProvider = _RabbitmqCredentialProvisioningProvider;
1029
- function mqttFiltersToRegex(filters) {
1030
- if (filters.length === 0) {
1031
- return "$(?!)";
1032
- }
1033
- return `^(?:${filters.map(mqttFilterToRegex).join("|")})$`;
1034
- }
1035
- function mqttFilterToRegex(filter) {
1036
- const terminalMultiLevel = filter.endsWith("/#");
1037
- const prefix = terminalMultiLevel ? filter.slice(0, -2) : filter;
1038
- const translated = prefix.split("/").map((segment) => {
1039
- if (segment === "+") {
1040
- return "[^.]+";
1041
- }
1042
- if (segment === "#") {
1043
- return ".*";
1044
- }
1045
- return segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1046
- }).join("\\.");
1047
- return terminalMultiLevel ? `${translated}(?:\\..*)?` : translated;
1048
- }
1049
- function escapeRegex(value) {
1050
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1051
- }
1052
1042
 
1053
1043
  // backend/plugin.ts
1054
1044
  var PLUGIN_CONTEXT3 = /* @__PURE__ */ Symbol.for("attraccess.plugin.context");
@@ -1064,8 +1054,8 @@ var RabbitmqService = class {
1064
1054
  }
1065
1055
  };
1066
1056
  RabbitmqService = __decorateClass([
1067
- (0, import_common7.Injectable)(),
1068
- __decorateParam(0, (0, import_common7.Inject)(PLUGIN_CONTEXT3))
1057
+ (0, import_common5.Injectable)(),
1058
+ __decorateParam(0, (0, import_common5.Inject)(PLUGIN_CONTEXT3))
1069
1059
  ], RabbitmqService);
1070
1060
  var RabbitmqController = class {
1071
1061
  // esbuild does not emit decorator metadata, so Nest cannot infer constructor
@@ -1078,12 +1068,12 @@ var RabbitmqController = class {
1078
1068
  }
1079
1069
  };
1080
1070
  __decorateClass([
1081
- (0, import_common7.Get)("status")
1071
+ (0, import_common5.Get)("status")
1082
1072
  ], RabbitmqController.prototype, "status", 1);
1083
1073
  RabbitmqController = __decorateClass([
1084
1074
  (0, import_plugins_backend_sdk3.Auth)("resources.update"),
1085
- (0, import_common7.Controller)("rabbitmq"),
1086
- __decorateParam(0, (0, import_common7.Inject)(RabbitmqService))
1075
+ (0, import_common5.Controller)("rabbitmq"),
1076
+ __decorateParam(0, (0, import_common5.Inject)(RabbitmqService))
1087
1077
  ], RabbitmqController);
1088
1078
  var RabbitmqPluginModule = class {
1089
1079
  };
@@ -105,6 +105,9 @@ var { createContext, useCallback: useCallback$2, useContext, useEffect: useEffec
105
105
  var contextKey = Symbol.for("attraccess.plugin.live-updates.context");
106
106
  var contexts = globalThis;
107
107
  contexts[contextKey] ??= createContext(null);
108
+ var identityKey = Symbol.for("attraccess.plugin.live-updates.identity");
109
+ var identities = globalThis;
110
+ identities[identityKey] ??= createContext(null);
108
111
  //#endregion
109
112
  //#region ../../../libs/plugins-frontend-sdk/src/lib/frontend.standard-drawer.tsx
110
113
  var { DrawerBackdrop, DrawerContent, DrawerDialog } = await importShared("@heroui/react");
@@ -112,7 +115,7 @@ var { DrawerBackdrop, DrawerContent, DrawerDialog } = await importShared("@herou
112
115
  //#region ../../../libs/plugins-frontend-sdk/src/lib/frontend.standard-modal.tsx
113
116
  var { Modal: Modal$3, ModalBackdrop: ModalBackdrop$3, ModalContainer: ModalContainer$3, ModalDialog: ModalDialog$3 } = await importShared("@heroui/react");
114
117
  //#endregion
115
- //#region frontend/src/detection.ts
118
+ //#region frontend/src/detection/detection.ts
116
119
  var { useCallback: useCallback$1, useEffect: useEffect$3, useState: useState$3 } = await importShared("react");
117
120
  var api$1 = createPluginApiClient("/api/rabbitmq/detection");
118
121
  function fetchDetection(mqttServerId, refresh) {
@@ -185,7 +188,7 @@ function useDetection(mqttServerId) {
185
188
  };
186
189
  }
187
190
  //#endregion
188
- //#region frontend/src/RabbitmqListBadge.tsx
191
+ //#region frontend/src/users/RabbitmqListBadge.tsx
189
192
  var { Chip: Chip$2 } = await importShared("@heroui/react");
190
193
  var { RabbitIcon: RabbitIcon$1 } = await importShared("lucide-react");
191
194
  function RabbitmqListBadge({ mqttServerId }) {
@@ -347,8 +350,8 @@ var useRabbitmqTranslations = () => useTranslations({
347
350
  de: de_default
348
351
  }, { escapeValues: false });
349
352
  //#endregion
350
- //#region frontend/src/RabbitmqStatusPanel.tsx
351
- var { Alert: Alert$3, AlertContent: AlertContent$3, AlertDescription: AlertDescription$3, Button: Button$3, Card: Card$1, Chip: Chip$1, Spinner: Spinner$1 } = await importShared("@heroui/react");
353
+ //#region frontend/src/detection/RabbitmqStatusPanel.tsx
354
+ var { Alert: Alert$4, AlertContent: AlertContent$4, AlertDescription: AlertDescription$4, Button: Button$5, Card: Card$2, Chip: Chip$1, Spinner: Spinner$2 } = await importShared("@heroui/react");
352
355
  var { CheckCircle2Icon, RabbitIcon, RefreshCwIcon: RefreshCwIcon$1, XCircleIcon } = await importShared("lucide-react");
353
356
  function StatusRow({ ok, label, value }) {
354
357
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
@@ -369,10 +372,10 @@ function RabbitmqStatusPanel({ mqttServerId }) {
369
372
  const { t } = useRabbitmqTranslations();
370
373
  const { result, loading, refresh } = useDetection(mqttServerId);
371
374
  if (!result?.isRabbitMQ) return null;
372
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card$1, {
375
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card$2, {
373
376
  "data-cy": `rabbitmq-status-panel-${mqttServerId}`,
374
377
  className: "rmq:w-full rmq:border rmq:border-default-200 rmq:dark:border-default-100",
375
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card$1.Header, {
378
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card$2.Header, {
376
379
  className: "rmq:flex rmq:flex-row rmq:items-center rmq:justify-between rmq:gap-2",
377
380
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
378
381
  className: "rmq:flex rmq:items-center rmq:gap-2",
@@ -389,7 +392,7 @@ function RabbitmqStatusPanel({ mqttServerId }) {
389
392
  children: t("status.detected")
390
393
  })
391
394
  ]
392
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$3, {
395
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$5, {
393
396
  isIconOnly: true,
394
397
  size: "sm",
395
398
  variant: "ghost",
@@ -397,9 +400,9 @@ function RabbitmqStatusPanel({ mqttServerId }) {
397
400
  "data-cy": `rabbitmq-status-refresh-${mqttServerId}`,
398
401
  onPress: refresh,
399
402
  isDisabled: loading,
400
- 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" })
403
+ children: loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner$2, { size: "sm" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RefreshCwIcon$1, { className: "rmq:w-4 rmq:h-4" })
401
404
  })]
402
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card$1.Content, {
405
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card$2.Content, {
403
406
  className: "rmq:flex rmq:flex-col rmq:gap-1",
404
407
  children: [
405
408
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatusRow, {
@@ -442,17 +445,17 @@ function RabbitmqStatusPanel({ mqttServerId }) {
442
445
  children: result.managementApi || "—"
443
446
  })]
444
447
  }),
445
- result.error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$3, {
448
+ result.error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$4, {
446
449
  status: result.reachable ? "warning" : "danger",
447
450
  className: "rmq:mt-2",
448
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertContent$3, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDescription$3, { children: result.error }) })
451
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertContent$4, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDescription$4, { children: result.error }) })
449
452
  })
450
453
  ]
451
454
  })]
452
455
  });
453
456
  }
454
457
  //#endregion
455
- //#region frontend/src/users-api.ts
458
+ //#region frontend/src/users/users-api.ts
456
459
  var DEFAULT_MQTT_PERMISSIONS = {
457
460
  configure: "^mqtt-subscription-.*$",
458
461
  write: "^(amq\\.topic|mqtt-subscription-.*)$",
@@ -484,8 +487,8 @@ function clearPermissions(mqttServerId, username, vhost) {
484
487
  });
485
488
  }
486
489
  //#endregion
487
- //#region frontend/src/RabbitmqPermissionsModal.tsx
488
- 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");
490
+ //#region frontend/src/users/permissions/RabbitmqPermissionsModal.tsx
491
+ var { Alert: Alert$3, AlertContent: AlertContent$3, AlertDescription: AlertDescription$3, Button: Button$4, 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");
489
492
  var { PlusIcon: PlusIcon$1, Trash2Icon: Trash2Icon$1 } = await importShared("lucide-react");
490
493
  var { useEffect: useEffect$2, useState: useState$2 } = await importShared("react");
491
494
  function PermissionEditor({ row, busy, onChange, onSave, onRemove }) {
@@ -503,7 +506,7 @@ function PermissionEditor({ row, busy, onChange, onSave, onRemove }) {
503
506
  " ",
504
507
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: row.vhost })
505
508
  ]
506
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$2, {
509
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$4, {
507
510
  isIconOnly: true,
508
511
  size: "sm",
509
512
  variant: "ghost",
@@ -554,7 +557,7 @@ function PermissionEditor({ row, busy, onChange, onSave, onRemove }) {
554
557
  }),
555
558
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
556
559
  className: "rmq:flex rmq:flex-wrap rmq:justify-end rmq:gap-2",
557
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$2, {
560
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$4, {
558
561
  size: "sm",
559
562
  variant: "ghost",
560
563
  onPress: () => onChange({
@@ -564,7 +567,7 @@ function PermissionEditor({ row, busy, onChange, onSave, onRemove }) {
564
567
  isDisabled: busy,
565
568
  "data-cy": `rabbitmq-permissions-mqtt-defaults-button-${row.vhost}`,
566
569
  children: t("permissions.defaults")
567
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$2, {
570
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$4, {
568
571
  size: "sm",
569
572
  variant: "primary",
570
573
  onPress: onSave,
@@ -691,7 +694,7 @@ function RabbitmqPermissionsModal({ mqttServerId, isOpen, user, vhosts, onClose,
691
694
  autoComplete: "off",
692
695
  "data-cy": "rabbitmq-permissions-add-vhost-input"
693
696
  })]
694
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$2, {
697
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$4, {
695
698
  variant: "secondary",
696
699
  onPress: addVhost,
697
700
  isDisabled: newVhost.trim().length === 0,
@@ -699,14 +702,14 @@ function RabbitmqPermissionsModal({ mqttServerId, isOpen, user, vhosts, onClose,
699
702
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlusIcon$1, { className: "rmq:w-4 rmq:h-4" }), t("permissions.add")]
700
703
  })]
701
704
  }),
702
- error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$2, {
705
+ error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$3, {
703
706
  status: "danger",
704
707
  "data-cy": "rabbitmq-permissions-error-alert",
705
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertContent$2, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDescription$2, { children: tMessage(error) }) })
708
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertContent$3, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDescription$3, { children: tMessage(error) }) })
706
709
  })
707
710
  ]
708
711
  }),
709
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalFooter$2, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$2, {
712
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalFooter$2, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$4, {
710
713
  variant: "secondary",
711
714
  onPress: onClose,
712
715
  "data-cy": "rabbitmq-permissions-close-button",
@@ -717,13 +720,9 @@ function RabbitmqPermissionsModal({ mqttServerId, isOpen, user, vhosts, onClose,
717
720
  });
718
721
  }
719
722
  //#endregion
720
- //#region frontend/src/RabbitmqUserFormModal.tsx
721
- 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");
723
+ //#region frontend/src/users/useRabbitmqUserFormModalState.tsx
722
724
  var { useEffect: useEffect$1, useState: useState$1 } = await importShared("react");
723
- function parseTags(value) {
724
- return value.split(",").map((tag) => tag.trim()).filter((tag) => tag.length > 0);
725
- }
726
- function RabbitmqUserFormModal({ mqttServerId, isOpen, user, vhosts, onClose, onSaved }) {
725
+ function useRabbitmqUserFormModalState({ mqttServerId, isOpen, user, vhosts, onClose, onSaved }) {
727
726
  const { t, tMessage } = useRabbitmqTranslations();
728
727
  const isEdit = user !== null;
729
728
  const [username, setUsername] = useState$1("");
@@ -775,6 +774,42 @@ function RabbitmqUserFormModal({ mqttServerId, isOpen, user, vhosts, onClose, on
775
774
  setSaving(false);
776
775
  }
777
776
  };
777
+ return {
778
+ t,
779
+ tMessage,
780
+ isEdit,
781
+ username,
782
+ setUsername,
783
+ password,
784
+ setPassword,
785
+ tags,
786
+ setTags,
787
+ grantMqttDefaults,
788
+ setGrantMqttDefaults,
789
+ vhost,
790
+ setVhost,
791
+ saving,
792
+ error,
793
+ handleSubmit,
794
+ mqttServerId,
795
+ isOpen,
796
+ user,
797
+ vhosts,
798
+ onClose
799
+ };
800
+ }
801
+ //#endregion
802
+ //#region frontend/src/users/RabbitmqUserFormModal.tsx
803
+ var { Alert: Alert$2, AlertContent: AlertContent$2, AlertDescription: AlertDescription$2, Button: Button$3, 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");
804
+ function RabbitmqUserFormModal({ mqttServerId, isOpen, user, vhosts, onClose, onSaved }) {
805
+ const { t, tMessage, isEdit, username, setUsername, password, setPassword, tags, setTags, grantMqttDefaults, setGrantMqttDefaults, vhost, setVhost, saving, error, handleSubmit } = useRabbitmqUserFormModalState({
806
+ mqttServerId,
807
+ isOpen,
808
+ user,
809
+ vhosts,
810
+ onClose,
811
+ onSaved
812
+ });
778
813
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Modal$1, {
779
814
  isOpen,
780
815
  onOpenChange: (open) => {
@@ -854,18 +889,18 @@ function RabbitmqUserFormModal({ mqttServerId, isOpen, user, vhosts, onClose, on
854
889
  })
855
890
  ]
856
891
  }),
857
- error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$1, {
892
+ error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$2, {
858
893
  status: "danger",
859
894
  "data-cy": "rabbitmq-user-form-error-alert",
860
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertContent$1, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDescription$1, { children: tMessage(error) }) })
895
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertContent$2, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDescription$2, { children: tMessage(error) }) })
861
896
  })
862
897
  ]
863
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ModalFooter$1, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
898
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ModalFooter$1, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$3, {
864
899
  variant: "secondary",
865
900
  onPress: onClose,
866
901
  "data-cy": "rabbitmq-user-form-cancel-button",
867
902
  children: t("common.cancel")
868
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
903
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$3, {
869
904
  variant: "primary",
870
905
  type: "submit",
871
906
  isPending: saving,
@@ -876,28 +911,13 @@ function RabbitmqUserFormModal({ mqttServerId, isOpen, user, vhosts, onClose, on
876
911
  }) })
877
912
  });
878
913
  }
914
+ function parseTags(value) {
915
+ return value.split(",").map((tag) => tag.trim()).filter((tag) => tag.length > 0);
916
+ }
879
917
  //#endregion
880
- //#region frontend/src/RabbitmqUserPanel.tsx
881
- var { Alert, AlertContent, AlertDescription, Button, Card, Chip, Modal, ModalBackdrop, ModalBody, ModalContainer, ModalDialog, ModalFooter, ModalHeader, ModalHeading, Spinner, Table, TableBody, TableCell, TableColumn, TableContent, TableHeader, TableRow, TableScrollContainer } = await importShared("@heroui/react");
882
- var { KeyRoundIcon, PencilIcon, PlusIcon, RefreshCwIcon, Trash2Icon, UsersIcon } = await importShared("lucide-react");
918
+ //#region frontend/src/users/useRabbitmqUserPanelState.tsx
883
919
  var { useCallback, useEffect, useRef, useState } = await importShared("react");
884
- function PermissionChips({ user }) {
885
- const { t } = useRabbitmqTranslations();
886
- if (user.permissions.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
887
- className: "rmq:text-xs rmq:text-default-400",
888
- children: t("users.none")
889
- });
890
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
891
- className: "rmq:flex rmq:flex-wrap rmq:gap-1",
892
- children: user.permissions.map((permission) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Chip, {
893
- size: "sm",
894
- variant: "soft",
895
- title: t("permissions.summary", { ...permission }),
896
- children: permission.vhost
897
- }, permission.vhost))
898
- });
899
- }
900
- function RabbitmqUserPanel({ mqttServerId }) {
920
+ function useRabbitmqUserPanelState({ mqttServerId }) {
901
921
  const { t, tMessage, language } = useRabbitmqTranslations();
902
922
  const { result } = useDetection(mqttServerId);
903
923
  const [data, setData] = useState(null);
@@ -916,6 +936,20 @@ function RabbitmqUserPanel({ mqttServerId }) {
916
936
  mounted.current = false;
917
937
  };
918
938
  }, []);
939
+ const confirmDelete = async () => {
940
+ if (!userToDelete) return;
941
+ setDeleting(true);
942
+ setDeleteError(null);
943
+ try {
944
+ await deleteUser(mqttServerId, userToDelete.name);
945
+ setUserToDelete(null);
946
+ reload();
947
+ } catch (err) {
948
+ setDeleteError(err instanceof Error ? err.message : { key: "users.deleteError" });
949
+ } finally {
950
+ setDeleting(false);
951
+ }
952
+ };
919
953
  const reload = useCallback(async () => {
920
954
  setLoading(true);
921
955
  setLoadError(null);
@@ -935,83 +969,161 @@ function RabbitmqUserPanel({ mqttServerId }) {
935
969
  useEffect(() => {
936
970
  if (manageable) reload();
937
971
  }, [manageable, reload]);
938
- if (!manageable) return null;
939
- const confirmDelete = async () => {
940
- if (!userToDelete) return;
941
- setDeleting(true);
942
- setDeleteError(null);
943
- try {
944
- await deleteUser(mqttServerId, userToDelete.name);
945
- setUserToDelete(null);
946
- reload();
947
- } catch (err) {
948
- setDeleteError(err instanceof Error ? err.message : { key: "users.deleteError" });
949
- } finally {
950
- setDeleting(false);
951
- }
972
+ return {
973
+ t,
974
+ tMessage,
975
+ language,
976
+ data,
977
+ loading,
978
+ loadError,
979
+ formOpen,
980
+ setFormOpen,
981
+ formUser,
982
+ setFormUser,
983
+ permissionsUser,
984
+ setPermissionsUser,
985
+ userToDelete,
986
+ setUserToDelete,
987
+ deleting,
988
+ setDeleting,
989
+ deleteError,
990
+ setDeleteError,
991
+ reload,
992
+ manageable,
993
+ mqttServerId,
994
+ confirmDelete
952
995
  };
996
+ }
997
+ //#endregion
998
+ //#region frontend/src/users/RabbitmqUserPanelCardHeader.tsx
999
+ var { Button: Button$2, Card: Card$1, Spinner: Spinner$1 } = await importShared("@heroui/react");
1000
+ var { PlusIcon, RefreshCwIcon, UsersIcon } = await importShared("lucide-react");
1001
+ function RabbitmqUserPanelCardHeader({ t, reload, loading, mqttServerId, setFormUser, setFormOpen }) {
1002
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card$1.Header, {
1003
+ className: "rmq:flex rmq:flex-row rmq:items-center rmq:justify-between rmq:gap-2",
1004
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1005
+ className: "rmq:flex rmq:items-center rmq:gap-2",
1006
+ 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", {
1007
+ className: "rmq:text-base rmq:font-semibold rmq:text-default-700",
1008
+ children: t("users.title")
1009
+ })]
1010
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1011
+ className: "rmq:flex rmq:items-center rmq:gap-2",
1012
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$2, {
1013
+ isIconOnly: true,
1014
+ size: "sm",
1015
+ variant: "ghost",
1016
+ "aria-label": t("users.reload"),
1017
+ onPress: () => void reload(),
1018
+ isDisabled: loading,
1019
+ "data-cy": `rabbitmq-user-panel-reload-${mqttServerId}`,
1020
+ children: loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner$1, { size: "sm" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RefreshCwIcon, { className: "rmq:w-4 rmq:h-4" })
1021
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$2, {
1022
+ size: "sm",
1023
+ variant: "primary",
1024
+ onPress: () => {
1025
+ setFormUser(null);
1026
+ setFormOpen(true);
1027
+ },
1028
+ "data-cy": `rabbitmq-user-panel-create-button-${mqttServerId}`,
1029
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlusIcon, { className: "rmq:w-4 rmq:h-4" }), t("users.add")]
1030
+ })]
1031
+ })]
1032
+ });
1033
+ }
1034
+ //#endregion
1035
+ //#region frontend/src/users/RabbitmqUserPanelModalBackdrop.tsx
1036
+ var { Alert: Alert$1, AlertContent: AlertContent$1, AlertDescription: AlertDescription$1, Button: Button$1, ModalBackdrop, ModalBody, ModalContainer, ModalDialog, ModalFooter, ModalHeader, ModalHeading } = await importShared("@heroui/react");
1037
+ function RabbitmqUserPanelModalBackdrop({ t, userToDelete, deleteError, tMessage, confirmDelete, deleting }) {
1038
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalBackdrop, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalContainer, {
1039
+ size: "sm",
1040
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalDialog, { children: ({ close }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1041
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalHeading, { children: t("users.deleteTitle") }) }),
1042
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ModalBody, {
1043
+ className: "rmq:flex rmq:flex-col rmq:gap-3",
1044
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: t("users.deleteDescription", { name: userToDelete?.name }) }), deleteError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$1, {
1045
+ status: "danger",
1046
+ "data-cy": "rabbitmq-user-delete-error-alert",
1047
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertContent$1, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDescription$1, { children: tMessage(deleteError) }) })
1048
+ })]
1049
+ }),
1050
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ModalFooter, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
1051
+ variant: "secondary",
1052
+ onPress: close,
1053
+ "data-cy": "rabbitmq-user-delete-cancel-button",
1054
+ children: t("common.cancel")
1055
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
1056
+ variant: "danger",
1057
+ onPress: () => void confirmDelete(),
1058
+ isPending: deleting,
1059
+ "data-cy": "rabbitmq-user-delete-confirm-button",
1060
+ children: t("common.delete")
1061
+ })] })
1062
+ ] }) })
1063
+ }) });
1064
+ }
1065
+ //#endregion
1066
+ //#region frontend/src/users/RabbitmqUserPanel.tsx
1067
+ var { Alert, AlertContent, AlertDescription, Button, Card, Chip, Modal, Spinner, Table, TableBody, TableCell, TableColumn, TableContent, TableHeader, TableRow, TableScrollContainer } = await importShared("@heroui/react");
1068
+ var { KeyRoundIcon, PencilIcon, Trash2Icon } = await importShared("lucide-react");
1069
+ function PermissionChips({ user }) {
1070
+ const { t } = useRabbitmqTranslations();
1071
+ if (user.permissions.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1072
+ className: "rmq:text-xs rmq:text-default-400",
1073
+ children: t("users.none")
1074
+ });
1075
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1076
+ className: "rmq:flex rmq:flex-wrap rmq:gap-1",
1077
+ children: user.permissions.map((permission) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Chip, {
1078
+ size: "sm",
1079
+ variant: "soft",
1080
+ title: t("permissions.summary", { ...permission }),
1081
+ children: permission.vhost
1082
+ }, permission.vhost))
1083
+ });
1084
+ }
1085
+ function RabbitmqUserPanel({ mqttServerId }) {
1086
+ const model = useRabbitmqUserPanelState({ mqttServerId });
1087
+ if (!model.manageable) return null;
953
1088
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card, {
954
1089
  "data-cy": `rabbitmq-user-panel-${mqttServerId}`,
955
1090
  className: "rmq:w-full rmq:border rmq:border-default-200 rmq:dark:border-default-100",
956
1091
  children: [
957
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card.Header, {
958
- className: "rmq:flex rmq:flex-row rmq:items-center rmq:justify-between rmq:gap-2",
959
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
960
- className: "rmq:flex rmq:items-center rmq:gap-2",
961
- 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", {
962
- className: "rmq:text-base rmq:font-semibold rmq:text-default-700",
963
- children: t("users.title")
964
- })]
965
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
966
- className: "rmq:flex rmq:items-center rmq:gap-2",
967
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
968
- isIconOnly: true,
969
- size: "sm",
970
- variant: "ghost",
971
- "aria-label": t("users.reload"),
972
- onPress: () => void reload(),
973
- isDisabled: loading,
974
- "data-cy": `rabbitmq-user-panel-reload-${mqttServerId}`,
975
- 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" })
976
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
977
- size: "sm",
978
- variant: "primary",
979
- onPress: () => {
980
- setFormUser(null);
981
- setFormOpen(true);
982
- },
983
- "data-cy": `rabbitmq-user-panel-create-button-${mqttServerId}`,
984
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlusIcon, { className: "rmq:w-4 rmq:h-4" }), t("users.add")]
985
- })]
986
- })]
1092
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RabbitmqUserPanelCardHeader, {
1093
+ t: model.t,
1094
+ reload: model.reload,
1095
+ loading: model.loading,
1096
+ mqttServerId,
1097
+ setFormUser: model.setFormUser,
1098
+ setFormOpen: model.setFormOpen
987
1099
  }),
988
1100
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card.Content, {
989
1101
  className: "rmq:flex rmq:flex-col rmq:gap-3",
990
1102
  children: [
991
- loadError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert, {
1103
+ model.loadError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert, {
992
1104
  status: "danger",
993
1105
  "data-cy": "rabbitmq-user-panel-error-alert",
994
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertContent, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDescription, { children: tMessage(loadError) }) })
1106
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertContent, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDescription, { children: model.tMessage(model.loadError) }) })
995
1107
  }),
996
- !data && !loadError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1108
+ !model.data && !model.loadError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
997
1109
  className: "rmq:flex rmq:items-center rmq:justify-center rmq:p-4",
998
1110
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { "data-cy": "rabbitmq-user-panel-loading-spinner" })
999
1111
  }),
1000
- data && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Table, {
1112
+ model.data && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Table, {
1001
1113
  "data-cy": "rabbitmq-user-panel-table",
1002
1114
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableScrollContainer, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableContent, {
1003
- "aria-label": t("users.title"),
1115
+ "aria-label": model.t("users.title"),
1004
1116
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableHeader, { children: [
1005
1117
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableColumn, {
1006
1118
  isRowHeader: true,
1007
- children: t("users.username")
1119
+ children: model.t("users.username")
1008
1120
  }),
1009
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableColumn, { children: t("users.tags") }),
1010
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableColumn, { children: t("users.access") }),
1011
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableColumn, { children: t("users.actions") })
1121
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableColumn, { children: model.t("users.tags") }),
1122
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableColumn, { children: model.t("users.access") }),
1123
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableColumn, { children: model.t("users.actions") })
1012
1124
  ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableBody, {
1013
- items: data.users,
1014
- dependencies: [language],
1125
+ items: model.data.users,
1126
+ dependencies: [model.language],
1015
1127
  children: (user) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableRow, {
1016
1128
  id: user.name,
1017
1129
  children: [
@@ -1021,7 +1133,7 @@ function RabbitmqUserPanel({ mqttServerId }) {
1021
1133
  }),
1022
1134
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, { children: user.tags.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1023
1135
  className: "rmq:text-xs rmq:text-default-400",
1024
- children: t("users.none")
1136
+ children: model.t("users.none")
1025
1137
  }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1026
1138
  className: "rmq:flex rmq:flex-wrap rmq:gap-1",
1027
1139
  children: user.tags.map((tag) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Chip, {
@@ -1039,10 +1151,10 @@ function RabbitmqUserPanel({ mqttServerId }) {
1039
1151
  isIconOnly: true,
1040
1152
  size: "sm",
1041
1153
  variant: "ghost",
1042
- "aria-label": t("users.edit", { name: user.name }),
1154
+ "aria-label": model.t("users.edit", { name: user.name }),
1043
1155
  onPress: () => {
1044
- setFormUser(user);
1045
- setFormOpen(true);
1156
+ model.setFormUser(user);
1157
+ model.setFormOpen(true);
1046
1158
  },
1047
1159
  "data-cy": `rabbitmq-user-edit-button-${user.name}`,
1048
1160
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PencilIcon, { className: "rmq:w-4 rmq:h-4" })
@@ -1051,8 +1163,8 @@ function RabbitmqUserPanel({ mqttServerId }) {
1051
1163
  isIconOnly: true,
1052
1164
  size: "sm",
1053
1165
  variant: "ghost",
1054
- "aria-label": t("users.permissions", { name: user.name }),
1055
- onPress: () => setPermissionsUser(user),
1166
+ "aria-label": model.t("users.permissions", { name: user.name }),
1167
+ onPress: () => model.setPermissionsUser(user),
1056
1168
  "data-cy": `rabbitmq-user-permissions-button-${user.name}`,
1057
1169
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(KeyRoundIcon, { className: "rmq:w-4 rmq:h-4" })
1058
1170
  }),
@@ -1060,10 +1172,10 @@ function RabbitmqUserPanel({ mqttServerId }) {
1060
1172
  isIconOnly: true,
1061
1173
  size: "sm",
1062
1174
  variant: "ghost",
1063
- "aria-label": t("users.delete", { name: user.name }),
1175
+ "aria-label": model.t("users.delete", { name: user.name }),
1064
1176
  onPress: () => {
1065
- setDeleteError(null);
1066
- setUserToDelete(user);
1177
+ model.setDeleteError(null);
1178
+ model.setUserToDelete(user);
1067
1179
  },
1068
1180
  "data-cy": `rabbitmq-user-delete-button-${user.name}`,
1069
1181
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Trash2Icon, { className: "rmq:w-4 rmq:h-4 rmq:text-danger" })
@@ -1079,52 +1191,27 @@ function RabbitmqUserPanel({ mqttServerId }) {
1079
1191
  }),
1080
1192
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RabbitmqUserFormModal, {
1081
1193
  mqttServerId,
1082
- isOpen: formOpen,
1083
- user: formUser,
1084
- vhosts: data?.vhosts ?? [],
1085
- onClose: () => setFormOpen(false),
1086
- onSaved: () => void reload()
1194
+ isOpen: model.formOpen,
1195
+ user: model.formUser,
1196
+ vhosts: model.data?.vhosts ?? [],
1197
+ onClose: () => model.setFormOpen(false),
1198
+ onSaved: () => void model.reload()
1087
1199
  }),
1088
1200
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RabbitmqPermissionsModal, {
1089
1201
  mqttServerId,
1090
- isOpen: permissionsUser !== null,
1091
- user: permissionsUser,
1092
- vhosts: data?.vhosts ?? [],
1093
- onClose: () => setPermissionsUser(null),
1094
- onSaved: () => void reload()
1202
+ isOpen: model.permissionsUser !== null,
1203
+ user: model.permissionsUser,
1204
+ vhosts: model.data?.vhosts ?? [],
1205
+ onClose: () => model.setPermissionsUser(null),
1206
+ onSaved: () => void model.reload()
1095
1207
  }),
1096
1208
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Modal, {
1097
- isOpen: userToDelete !== null,
1209
+ isOpen: model.userToDelete !== null,
1098
1210
  onOpenChange: (open) => {
1099
- if (!open) setUserToDelete(null);
1211
+ if (!open) model.setUserToDelete(null);
1100
1212
  },
1101
1213
  "data-cy": `rabbitmq-user-delete-modal-${mqttServerId}`,
1102
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalBackdrop, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalContainer, {
1103
- size: "sm",
1104
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalDialog, { children: ({ close }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1105
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalHeading, { children: t("users.deleteTitle") }) }),
1106
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ModalBody, {
1107
- className: "rmq:flex rmq:flex-col rmq:gap-3",
1108
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: t("users.deleteDescription", { name: userToDelete?.name }) }), deleteError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert, {
1109
- status: "danger",
1110
- "data-cy": "rabbitmq-user-delete-error-alert",
1111
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertContent, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDescription, { children: tMessage(deleteError) }) })
1112
- })]
1113
- }),
1114
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ModalFooter, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
1115
- variant: "secondary",
1116
- onPress: close,
1117
- "data-cy": "rabbitmq-user-delete-cancel-button",
1118
- children: t("common.cancel")
1119
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
1120
- variant: "danger",
1121
- onPress: () => void confirmDelete(),
1122
- isPending: deleting,
1123
- "data-cy": "rabbitmq-user-delete-confirm-button",
1124
- children: t("common.delete")
1125
- })] })
1126
- ] }) })
1127
- }) })
1214
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RabbitmqUserPanelModalBackdrop, { ...model })
1128
1215
  })
1129
1216
  ]
1130
1217
  });
@@ -75,7 +75,7 @@ var exportSet = /* @__PURE__ */ new Set([
75
75
  ]);
76
76
  var moduleMap = { "./plugin": () => {
77
77
  dynamicLoadingCss(["style.css"], false, "./plugin");
78
- return __federation_import("./__federation_expose_Plugin-CZ3ZqxcO.js").then((module) => Object.keys(module).every((item) => exportSet.has(item)) ? () => module.default : () => module);
78
+ return __federation_import("./__federation_expose_Plugin-5jDTnovW.js").then((module) => Object.keys(module).every((item) => exportSet.has(item)) ? () => module.default : () => module);
79
79
  } };
80
80
  var seen = {};
81
81
  var dynamicLoadingCss = (cssFilePaths, dontAppendStylesToHead, exposeItemName) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@attraccess/plugin-rabbitmq",
3
- "version": "0.1.0-nightly.1076.1",
3
+ "version": "0.1.0-nightly.1079.1",
4
4
  "description": "Official Attraccess integration for RabbitMQ.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {