@cobaltcore-dev/aurora 1.3.0 → 2.1.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.
Files changed (150) hide show
  1. package/dist/client/ContentHeader-fKKZVu-o.mjs +116 -0
  2. package/dist/client/ContentHeader-fKKZVu-o.mjs.map +1 -0
  3. package/dist/client/DeleteFlavorModal-Be2GFiB_.mjs +802 -0
  4. package/dist/client/DeleteFlavorModal-Be2GFiB_.mjs.map +1 -0
  5. package/dist/client/{FiltersInput-CKI6zWnm.mjs → FiltersInput-Becwxo_J.mjs} +36 -33
  6. package/dist/client/FiltersInput-Becwxo_J.mjs.map +1 -0
  7. package/dist/client/{FloatingIpActionModals-Ch8uozH9.mjs → FloatingIpActionModals-Cq10WljB.mjs} +2 -2
  8. package/dist/client/{FloatingIpActionModals-Ch8uozH9.mjs.map → FloatingIpActionModals-Cq10WljB.mjs.map} +1 -1
  9. package/dist/client/ImageToastNotifications-BWfzOUMc.mjs +1135 -0
  10. package/dist/client/ImageToastNotifications-BWfzOUMc.mjs.map +1 -0
  11. package/dist/client/RouteIdLevelDefaultError-DPF_x2tG.mjs +26 -0
  12. package/dist/client/RouteIdLevelDefaultError-DPF_x2tG.mjs.map +1 -0
  13. package/dist/client/StorageNotFound-B-zVinVD.mjs +30 -0
  14. package/dist/client/StorageNotFound-B-zVinVD.mjs.map +1 -0
  15. package/dist/client/_flavorId-0PioWpEp.mjs +276 -0
  16. package/dist/client/_flavorId-0PioWpEp.mjs.map +1 -0
  17. package/dist/client/_flavorId-B7fp6Nk8.mjs +22 -0
  18. package/dist/client/_flavorId-B7fp6Nk8.mjs.map +1 -0
  19. package/dist/client/{_flavorId-Dg7MS0Af.mjs → _flavorId-CgueqKTs.mjs} +4 -3
  20. package/dist/client/_flavorId-CgueqKTs.mjs.map +1 -0
  21. package/dist/client/{_floatingIpId-DNn6ABCB.mjs → _floatingIpId-B8r64AL9.mjs} +6 -6
  22. package/dist/client/{_floatingIpId-DNn6ABCB.mjs.map → _floatingIpId-B8r64AL9.mjs.map} +1 -1
  23. package/dist/client/{_floatingIpId-C9xfRWot.mjs → _floatingIpId-lEobpOER.mjs} +2 -2
  24. package/dist/client/{_floatingIpId-C9xfRWot.mjs.map → _floatingIpId-lEobpOER.mjs.map} +1 -1
  25. package/dist/client/{_imageId-8JIbFmGB.mjs → _imageId-Ccv8PedW.mjs} +4 -3
  26. package/dist/client/_imageId-Ccv8PedW.mjs.map +1 -0
  27. package/dist/client/_imageId-CiqEQB_J.mjs +558 -0
  28. package/dist/client/_imageId-CiqEQB_J.mjs.map +1 -0
  29. package/dist/client/_imageId-D9LR-02T.mjs +22 -0
  30. package/dist/client/_imageId-D9LR-02T.mjs.map +1 -0
  31. package/dist/client/{_projectId-DPzv0CCp.mjs → _projectId-BDARsteX.mjs} +4 -4
  32. package/dist/client/_projectId-BDARsteX.mjs.map +1 -0
  33. package/dist/client/{_projectId-DfeVJy0G.mjs → _projectId-CtyAHqhH.mjs} +2 -2
  34. package/dist/client/_projectId-CtyAHqhH.mjs.map +1 -0
  35. package/dist/client/_projectId-D3VOH5lN.mjs +94 -0
  36. package/dist/client/_projectId-D3VOH5lN.mjs.map +1 -0
  37. package/dist/client/{_projectId-B3wmSala.mjs → _projectId-ZBk6vka0.mjs} +83 -81
  38. package/dist/client/_projectId-ZBk6vka0.mjs.map +1 -0
  39. package/dist/client/{_securityGroupId-C6eHmrx_.mjs → _securityGroupId-BwgZnoiR.mjs} +2 -2
  40. package/dist/client/_securityGroupId-BwgZnoiR.mjs.map +1 -0
  41. package/dist/client/{_securityGroupId-DmplhDgt.mjs → _securityGroupId-Cz2NUHdB.mjs} +750 -741
  42. package/dist/client/_securityGroupId-Cz2NUHdB.mjs.map +1 -0
  43. package/dist/client/{_storageType-CzZ4u9x_.mjs → _storageType-BNpP1A2N.mjs} +3 -2
  44. package/dist/client/_storageType-BNpP1A2N.mjs.map +1 -0
  45. package/dist/client/_storageType-BxSGKhAI.mjs +15 -0
  46. package/dist/client/_storageType-BxSGKhAI.mjs.map +1 -0
  47. package/dist/client/_storageType-DZYCmiLt.mjs +30 -0
  48. package/dist/client/_storageType-DZYCmiLt.mjs.map +1 -0
  49. package/dist/client/_storageType-UqAMP-8d.mjs +859 -0
  50. package/dist/client/_storageType-UqAMP-8d.mjs.map +1 -0
  51. package/dist/client/_storageType-u51_7OwJ.mjs +7 -0
  52. package/dist/client/_storageType-u51_7OwJ.mjs.map +1 -0
  53. package/dist/client/components/{Error → Errors}/RouteError.d.ts +5 -0
  54. package/dist/client/components/Errors/RouteIdLevelDefaultError.d.ts +18 -0
  55. package/dist/client/components/Errors/ServiceLevelDefaultError.d.ts +9 -0
  56. package/dist/client/components/ListToolbar/SelectedFilters.d.ts +7 -2
  57. package/dist/client/components/ListToolbar/types.d.ts +6 -0
  58. package/dist/client/components/ListToolbar/valueLabels.d.ts +11 -0
  59. package/dist/client/{flavors-B1SDOPDu.mjs → flavors-Ce3J-cFa.mjs} +2 -2
  60. package/dist/client/{flavors-B1SDOPDu.mjs.map → flavors-Ce3J-cFa.mjs.map} +1 -1
  61. package/dist/client/flavors-D5ThLaXO.mjs +666 -0
  62. package/dist/client/flavors-D5ThLaXO.mjs.map +1 -0
  63. package/dist/client/floatingips-Cz9k80cc.mjs.map +1 -1
  64. package/dist/client/{floatingips-DrCvtXtT.mjs → floatingips-DXnRlLvV.mjs} +7 -7
  65. package/dist/client/{floatingips-DrCvtXtT.mjs.map → floatingips-DXnRlLvV.mjs.map} +1 -1
  66. package/dist/client/floatingips-HRbuLxiN.mjs +22 -0
  67. package/dist/client/floatingips-HRbuLxiN.mjs.map +1 -0
  68. package/dist/client/floatingips-nN1kQmsu.mjs +14 -0
  69. package/dist/client/floatingips-nN1kQmsu.mjs.map +1 -0
  70. package/dist/client/{images-BGQv9kKT.mjs → images-C8M3ysWR.mjs} +810 -770
  71. package/dist/client/images-C8M3ysWR.mjs.map +1 -0
  72. package/dist/client/{images-CPmMa4SQ.mjs → images-Cf5R28NR.mjs} +2 -2
  73. package/dist/client/{images-CPmMa4SQ.mjs.map → images-Cf5R28NR.mjs.map} +1 -1
  74. package/dist/client/index.js +419 -415
  75. package/dist/client/index.js.map +1 -1
  76. package/dist/client/{objects-BR3eeSNH.mjs → objects-BudUAfWY.mjs} +5478 -5519
  77. package/dist/client/objects-BudUAfWY.mjs.map +1 -0
  78. package/dist/client/objects-Dc7sKUr8.mjs +67 -0
  79. package/dist/client/objects-Dc7sKUr8.mjs.map +1 -0
  80. package/dist/client/objects-Dw7oeTq2.mjs +80 -0
  81. package/dist/client/objects-Dw7oeTq2.mjs.map +1 -0
  82. package/dist/client/objects-_g7nGIn5.mjs +25 -0
  83. package/dist/client/objects-_g7nGIn5.mjs.map +1 -0
  84. package/dist/client/objects-eBxvY6_n.mjs +78 -0
  85. package/dist/client/objects-eBxvY6_n.mjs.map +1 -0
  86. package/dist/client/projects-yiK0HGSA.mjs.map +1 -1
  87. package/dist/client/{securitygroups-DEJQBcxm.mjs → securitygroups-67fFxiwt.mjs} +2 -2
  88. package/dist/client/{securitygroups-DEJQBcxm.mjs.map → securitygroups-67fFxiwt.mjs.map} +1 -1
  89. package/dist/client/securitygroups-CtKne8UQ.mjs.map +1 -1
  90. package/dist/client/securitygroups-DXrfyuVF.mjs +580 -0
  91. package/dist/client/securitygroups-DXrfyuVF.mjs.map +1 -0
  92. package/dist/client/securitygroups-WLB1_IM6.mjs +22 -0
  93. package/dist/client/securitygroups-WLB1_IM6.mjs.map +1 -0
  94. package/dist/client/securitygroups-o0IXZM6S.mjs +14 -0
  95. package/dist/client/securitygroups-o0IXZM6S.mjs.map +1 -0
  96. package/dist/client/serviceCatalog-B3lKWC9V.mjs +6 -0
  97. package/dist/client/serviceCatalog-B3lKWC9V.mjs.map +1 -0
  98. package/dist/client/storageProviders-CJ5rOh13.mjs +15 -0
  99. package/dist/client/storageProviders-CJ5rOh13.mjs.map +1 -0
  100. package/dist/client/trpcClient.d.ts +102 -4
  101. package/dist/client/useModalTracking-BYd37pXm.mjs +37 -0
  102. package/dist/client/useModalTracking-BYd37pXm.mjs.map +1 -0
  103. package/dist/client/useSecurityGroupPermissions--hK4aR8Y.mjs +294 -0
  104. package/dist/client/useSecurityGroupPermissions--hK4aR8Y.mjs.map +1 -0
  105. package/dist/client/utils/serviceCatalog.d.ts +9 -0
  106. package/dist/client/utils/storageProviders.d.ts +44 -0
  107. package/dist/server/index.d.ts +102 -4
  108. package/dist/server/index.js +1319 -416
  109. package/package.json +4 -4
  110. package/dist/client/ContentHeader-CPJbgYWn.mjs +0 -115
  111. package/dist/client/ContentHeader-CPJbgYWn.mjs.map +0 -1
  112. package/dist/client/DeleteFlavorModal-V44QCJBd.mjs +0 -773
  113. package/dist/client/DeleteFlavorModal-V44QCJBd.mjs.map +0 -1
  114. package/dist/client/FiltersInput-CKI6zWnm.mjs.map +0 -1
  115. package/dist/client/ImageToastNotifications-m6c3tVPb.mjs +0 -1152
  116. package/dist/client/ImageToastNotifications-m6c3tVPb.mjs.map +0 -1
  117. package/dist/client/_flavorId-Dg7MS0Af.mjs.map +0 -1
  118. package/dist/client/_flavorId-kHl-b8rI.mjs +0 -232
  119. package/dist/client/_flavorId-kHl-b8rI.mjs.map +0 -1
  120. package/dist/client/_imageId-8JIbFmGB.mjs.map +0 -1
  121. package/dist/client/_imageId-BBAlRioC.mjs +0 -574
  122. package/dist/client/_imageId-BBAlRioC.mjs.map +0 -1
  123. package/dist/client/_projectId-7Nl1MQTJ.mjs +0 -99
  124. package/dist/client/_projectId-7Nl1MQTJ.mjs.map +0 -1
  125. package/dist/client/_projectId-B3wmSala.mjs.map +0 -1
  126. package/dist/client/_projectId-DPzv0CCp.mjs.map +0 -1
  127. package/dist/client/_projectId-DfeVJy0G.mjs.map +0 -1
  128. package/dist/client/_securityGroupId-C6eHmrx_.mjs.map +0 -1
  129. package/dist/client/_securityGroupId-DmplhDgt.mjs.map +0 -1
  130. package/dist/client/_storageType-BRWWJtow.mjs +0 -14
  131. package/dist/client/_storageType-BRWWJtow.mjs.map +0 -1
  132. package/dist/client/_storageType-C5gS9eut.mjs +0 -845
  133. package/dist/client/_storageType-C5gS9eut.mjs.map +0 -1
  134. package/dist/client/_storageType-CzZ4u9x_.mjs.map +0 -1
  135. package/dist/client/_storageType-zeSZe--V.mjs +0 -7
  136. package/dist/client/_storageType-zeSZe--V.mjs.map +0 -1
  137. package/dist/client/flavors-D8_PzQUB.mjs +0 -628
  138. package/dist/client/flavors-D8_PzQUB.mjs.map +0 -1
  139. package/dist/client/images-BGQv9kKT.mjs.map +0 -1
  140. package/dist/client/objects-AERkhfiM.mjs +0 -66
  141. package/dist/client/objects-AERkhfiM.mjs.map +0 -1
  142. package/dist/client/objects-BR3eeSNH.mjs.map +0 -1
  143. package/dist/client/objects-DVccbrL1.mjs +0 -66
  144. package/dist/client/objects-DVccbrL1.mjs.map +0 -1
  145. package/dist/client/securitygroups-BjhgLBHm.mjs +0 -620
  146. package/dist/client/securitygroups-BjhgLBHm.mjs.map +0 -1
  147. package/dist/client/useModalTracking-BcOKTYRN.mjs +0 -33
  148. package/dist/client/useModalTracking-BcOKTYRN.mjs.map +0 -1
  149. package/dist/client/useSecurityGroupPermissions-BOyLQLdC.mjs +0 -227
  150. package/dist/client/useSecurityGroupPermissions-BOyLQLdC.mjs.map +0 -1
@@ -7,7 +7,11 @@ var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
8
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
9
9
  var __commonJS = (cb, mod) => function __require() {
10
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
+ try {
11
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
12
+ } catch (e) {
13
+ throw mod = 0, e;
14
+ }
11
15
  };
12
16
  var __export = (target, all) => {
13
17
  for (var name in all)
@@ -27306,7 +27310,7 @@ var require_pathHelpers = __commonJS({
27306
27310
  });
27307
27311
  exports2.encodeOpenstackPathSegment = encodeOpenstackPathSegment2;
27308
27312
  exports2.encodeOpenstackPath = encodeOpenstackPath;
27309
- exports2.validateAndEncodeResourceId = validateAndEncodeResourceId6;
27313
+ exports2.validateAndEncodeResourceId = validateAndEncodeResourceId8;
27310
27314
  var error_1 = require_error();
27311
27315
  function encodeOpenstackPathSegment2(segment, label = "Path segment") {
27312
27316
  if (!segment || typeof segment !== "string") {
@@ -27328,10 +27332,10 @@ var require_pathHelpers = __commonJS({
27328
27332
  return segments.map((seg, idx) => encodeOpenstackPathSegment2(seg, `Path segment ${idx}`)).join("/");
27329
27333
  }
27330
27334
  __name(encodeOpenstackPath, "encodeOpenstackPath");
27331
- function validateAndEncodeResourceId6(id, resourceType = "Resource") {
27335
+ function validateAndEncodeResourceId8(id, resourceType = "Resource") {
27332
27336
  return encodeOpenstackPathSegment2(id, `${resourceType} ID`);
27333
27337
  }
27334
- __name(validateAndEncodeResourceId6, "validateAndEncodeResourceId");
27338
+ __name(validateAndEncodeResourceId8, "validateAndEncodeResourceId");
27335
27339
  }
27336
27340
  });
27337
27341
 
@@ -28654,6 +28658,22 @@ var openstackErrorMiddleware = t.middleware(async ({ next }) => {
28654
28658
  }
28655
28659
  return result;
28656
28660
  });
28661
+ function mapScopeError(error, ctx, opts) {
28662
+ if (error instanceof import_signal_openstack.SignalOpenstackApiError && error.statusCode === 401) {
28663
+ if (ctx.validateSession() === true) {
28664
+ return new import_server.TRPCError({
28665
+ code: "NOT_FOUND",
28666
+ message: opts.notFoundMessage
28667
+ });
28668
+ }
28669
+ return new import_server.TRPCError({
28670
+ code: "UNAUTHORIZED",
28671
+ message: "Your session is no longer valid. Please sign in again."
28672
+ });
28673
+ }
28674
+ return error;
28675
+ }
28676
+ __name(mapScopeError, "mapScopeError");
28657
28677
  var router = t.router;
28658
28678
  var auroraRouter = t.router;
28659
28679
  var mergeRouters = t.mergeRouters;
@@ -28679,13 +28699,26 @@ var domainScopedInputSchema = import_zod.z.object({
28679
28699
  var projectScopedProcedure = protectedProcedure.input(projectScopedInputSchema).use(/* @__PURE__ */ __name(async function rescopeToProject(opts) {
28680
28700
  const { ctx, next, input } = opts;
28681
28701
  const { project_id } = input;
28682
- const openstackSession = await ctx.rescopeSession({
28683
- projectId: project_id
28684
- });
28702
+ let openstackSession;
28703
+ try {
28704
+ openstackSession = await ctx.rescopeSession({
28705
+ projectId: project_id
28706
+ });
28707
+ } catch (error) {
28708
+ throw mapScopeError(error, ctx, {
28709
+ notFoundMessage: "Project not found or not accessible with your current session."
28710
+ });
28711
+ }
28685
28712
  if (!openstackSession) {
28713
+ if (ctx.validateSession() === false) {
28714
+ throw new import_server.TRPCError({
28715
+ code: "UNAUTHORIZED",
28716
+ message: "Your session is no longer valid. Please log in again."
28717
+ });
28718
+ }
28686
28719
  throw new import_server.TRPCError({
28687
- code: "UNAUTHORIZED",
28688
- message: "Failed to scope session to project. User may not have access to this project."
28720
+ code: "NOT_FOUND",
28721
+ message: "Project not found or not accessible with your current session."
28689
28722
  });
28690
28723
  }
28691
28724
  return next({
@@ -28713,13 +28746,26 @@ var domainScopedProcedure = protectedProcedure.input(domainScopedInputSchema).us
28713
28746
  message: `Access denied. User does not have access to domain ${domain_id}`
28714
28747
  });
28715
28748
  }
28716
- const openstackSession = await ctx.rescopeSession({
28717
- domainId: domain_id
28718
- });
28749
+ let openstackSession;
28750
+ try {
28751
+ openstackSession = await ctx.rescopeSession({
28752
+ domainId: domain_id
28753
+ });
28754
+ } catch (error) {
28755
+ throw mapScopeError(error, ctx, {
28756
+ notFoundMessage: "Domain not found or not accessible with your current session."
28757
+ });
28758
+ }
28719
28759
  if (!openstackSession) {
28760
+ if (ctx.validateSession() === false) {
28761
+ throw new import_server.TRPCError({
28762
+ code: "UNAUTHORIZED",
28763
+ message: "Your session is no longer valid. Please log in again."
28764
+ });
28765
+ }
28720
28766
  throw new import_server.TRPCError({
28721
- code: "UNAUTHORIZED",
28722
- message: "Failed to scope session to domain. Please try again or contact support."
28767
+ code: "NOT_FOUND",
28768
+ message: "Domain not found or not accessible with your current session."
28723
28769
  });
28724
28770
  }
28725
28771
  return next({
@@ -28781,9 +28827,16 @@ var sessionRouter = {
28781
28827
  setCurrentScope: protectedProcedure.input(discriminatedSchema).mutation(async ({ input, ctx }) => {
28782
28828
  switch (input.type) {
28783
28829
  case "domain": {
28784
- const session = await ctx.rescopeSession({
28785
- domainId: input.domainId
28786
- });
28830
+ let session;
28831
+ try {
28832
+ session = await ctx.rescopeSession({
28833
+ domainId: input.domainId
28834
+ });
28835
+ } catch (error) {
28836
+ throw mapScopeError(error, ctx, {
28837
+ notFoundMessage: "This domain doesn't exist or is not accessible with your current session. Select an available domain."
28838
+ });
28839
+ }
28787
28840
  const token = session?.getToken();
28788
28841
  if (!token?.tokenData.domain) {
28789
28842
  throw new import_server2.TRPCError({
@@ -28797,9 +28850,16 @@ var sessionRouter = {
28797
28850
  };
28798
28851
  }
28799
28852
  case "project": {
28800
- const session = await ctx.rescopeSession({
28801
- projectId: input.projectId
28802
- });
28853
+ let session;
28854
+ try {
28855
+ session = await ctx.rescopeSession({
28856
+ projectId: input.projectId
28857
+ });
28858
+ } catch (error) {
28859
+ throw mapScopeError(error, ctx, {
28860
+ notFoundMessage: "This project doesn't exist or is not accessible from your current domain. Please select a project from your current domain."
28861
+ });
28862
+ }
28803
28863
  const token = session?.getToken();
28804
28864
  if (!token?.tokenData.project) {
28805
28865
  throw new import_server2.TRPCError({
@@ -29033,7 +29093,13 @@ function filterBySearchParams(items, searchTerm, searchFields) {
29033
29093
  const searchLower = searchTerm.toLowerCase().trim();
29034
29094
  return items.filter((item) => searchFields.some((field) => {
29035
29095
  const value = item[field];
29036
- return typeof value === "string" && value.toLowerCase().includes(searchLower);
29096
+ if (typeof value === "string") {
29097
+ return value.toLowerCase().includes(searchLower);
29098
+ }
29099
+ if (typeof value === "number") {
29100
+ return value.toString().includes(searchLower);
29101
+ }
29102
+ return false;
29037
29103
  }));
29038
29104
  }
29039
29105
  __name(filterBySearchParams, "filterBySearchParams");
@@ -29986,7 +30052,10 @@ var imageRouter = {
29986
30052
  let filteredImages = allImages;
29987
30053
  if (hasSearchTerm) {
29988
30054
  filteredImages = filterBySearchParams(filteredImages, queryInput.name, [
29989
- "name"
30055
+ "id",
30056
+ "name",
30057
+ "owner",
30058
+ "size"
29990
30059
  ]);
29991
30060
  }
29992
30061
  if (queryInput.visibility && queryInput.visibility !== "all") {
@@ -30066,7 +30135,10 @@ var imageRouter = {
30066
30135
  let filteredImages = allImages;
30067
30136
  if (queryInput.name && queryInput.name.trim()) {
30068
30137
  filteredImages = filterBySearchParams(filteredImages, queryInput.name, [
30069
- "name"
30138
+ "id",
30139
+ "name",
30140
+ "owner",
30141
+ "size"
30070
30142
  ]);
30071
30143
  }
30072
30144
  if (queryInput.visibility && queryInput.visibility !== "all") {
@@ -30605,6 +30677,7 @@ var imageRouter = {
30605
30677
  });
30606
30678
  if (name) {
30607
30679
  filteredImages = filterBySearchParams(filteredImages, name, [
30680
+ "id",
30608
30681
  "name"
30609
30682
  ]);
30610
30683
  }
@@ -31168,9 +31241,18 @@ function includesSearchTerm(flavor, searchTerm) {
31168
31241
  const searchableValues = [
31169
31242
  flavor.id,
31170
31243
  flavor.name,
31171
- flavor.description
31244
+ flavor.description,
31245
+ flavor.vcpus,
31246
+ flavor.ram,
31247
+ flavor.disk,
31248
+ flavor.swap
31172
31249
  ];
31173
- return searchableValues.some((value) => value != null && typeof value === "string" && regex.test(value));
31250
+ return searchableValues.some((value) => {
31251
+ if (value == null) return false;
31252
+ if (typeof value === "string") return regex.test(value);
31253
+ if (typeof value === "number") return regex.test(value.toString());
31254
+ return false;
31255
+ });
31174
31256
  }
31175
31257
  __name(includesSearchTerm, "includesSearchTerm");
31176
31258
  function filterAndSortFlavors(flavors, searchTerm, sortBy, sortDirection) {
@@ -31191,6 +31273,26 @@ function filterAndSortFlavors(flavors, searchTerm, sortBy, sortDirection) {
31191
31273
  return result;
31192
31274
  }
31193
31275
  __name(filterAndSortFlavors, "filterAndSortFlavors");
31276
+ function supportsDescriptionField(microversion) {
31277
+ const [major, minor] = microversion.split(".").map(Number);
31278
+ return major > 2 || major === 2 && minor >= 55;
31279
+ }
31280
+ __name(supportsDescriptionField, "supportsDescriptionField");
31281
+ async function getMaxMicroversion(compute) {
31282
+ try {
31283
+ const response = await compute.get("");
31284
+ const data = await response.json();
31285
+ if (data.version) {
31286
+ return data.version.version || "2.1";
31287
+ } else if (data.versions) {
31288
+ const currentVersion = data.versions.find((v) => v.status === "CURRENT");
31289
+ return currentVersion?.version || "2.1";
31290
+ }
31291
+ } catch {
31292
+ }
31293
+ return "2.1";
31294
+ }
31295
+ __name(getMaxMicroversion, "getMaxMicroversion");
31194
31296
  async function getFlavorById(compute, flavorId) {
31195
31297
  if (!flavorId || flavorId.trim() === "") {
31196
31298
  throw new import_server7.TRPCError({
@@ -31213,7 +31315,13 @@ async function getFlavorById(compute, flavorId) {
31213
31315
  }
31214
31316
  let response;
31215
31317
  try {
31216
- response = await compute.get(`flavors/${encodedId}`);
31318
+ const maxVersion = await getMaxMicroversion(compute);
31319
+ const options = supportsDescriptionField(maxVersion) ? {
31320
+ headers: {
31321
+ "OpenStack-API-Version": `compute ${maxVersion}`
31322
+ }
31323
+ } : void 0;
31324
+ response = await compute.get(`flavors/${encodedId}`, options);
31217
31325
  } catch (error) {
31218
31326
  if (error instanceof import_server7.TRPCError) throw error;
31219
31327
  const statusCode = getStatusCodeFromError(error);
@@ -31245,11 +31353,20 @@ __name(getFlavorById, "getFlavorById");
31245
31353
  async function fetchFlavors(compute, isPublic) {
31246
31354
  let response;
31247
31355
  try {
31248
- response = await compute.get("flavors/detail", {
31356
+ const maxVersion = await getMaxMicroversion(compute);
31357
+ const options = supportsDescriptionField(maxVersion) ? {
31249
31358
  queryParams: {
31250
31359
  is_public: isPublic
31360
+ },
31361
+ headers: {
31362
+ "OpenStack-API-Version": `compute ${maxVersion}`
31251
31363
  }
31252
- });
31364
+ } : {
31365
+ queryParams: {
31366
+ is_public: isPublic
31367
+ }
31368
+ };
31369
+ response = await compute.get("flavors/detail", options);
31253
31370
  } catch (error) {
31254
31371
  if (error instanceof import_server7.TRPCError) throw error;
31255
31372
  const statusCode = getStatusCodeFromError(error);
@@ -31278,16 +31395,32 @@ async function fetchFlavors(compute, isPublic) {
31278
31395
  }
31279
31396
  }
31280
31397
  __name(fetchFlavors, "fetchFlavors");
31281
- async function createFlavor(compute, flavorData) {
31398
+ async function createFlavor(compute, flavorData, microversion) {
31282
31399
  const requestBody = {
31283
31400
  flavor: flavorData
31284
31401
  };
31402
+ const headers = {};
31403
+ if (microversion && flavorData.description && supportsDescriptionField(microversion)) {
31404
+ headers["OpenStack-API-Version"] = `compute ${microversion}`;
31405
+ }
31285
31406
  let response;
31286
31407
  try {
31287
- response = await compute.post("flavors", requestBody);
31408
+ response = await compute.post("flavors", requestBody, Object.keys(headers).length ? {
31409
+ headers
31410
+ } : void 0);
31288
31411
  } catch (error) {
31289
31412
  if (error instanceof import_server7.TRPCError) throw error;
31290
31413
  const statusCode = getStatusCodeFromError(error);
31414
+ if (statusCode === 400) {
31415
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
31416
+ if (errorMessage.includes("description")) {
31417
+ throw new import_server7.TRPCError({
31418
+ code: "BAD_REQUEST",
31419
+ message: errorMessage,
31420
+ cause: error
31421
+ });
31422
+ }
31423
+ }
31291
31424
  handleHttpError(statusCode, CREATE_FLAVOR_STATUS_MAP, ERROR_CODES.CREATE_FLAVOR_FAILED);
31292
31425
  }
31293
31426
  if (!response.ok) {
@@ -31298,7 +31431,6 @@ async function createFlavor(compute, flavorData) {
31298
31431
  const jsonData = JSON.parse(rawData);
31299
31432
  return jsonData.flavor;
31300
31433
  } catch (error) {
31301
- console.error("Error parsing flavor response:", error);
31302
31434
  throw new import_server7.TRPCError({
31303
31435
  code: "INTERNAL_SERVER_ERROR",
31304
31436
  message: ERROR_CODES.CREATE_FLAVOR_FAILED,
@@ -31579,6 +31711,38 @@ __name(removeTenantAccess, "removeTenantAccess");
31579
31711
  var import_server8 = require("@trpc/server");
31580
31712
  var import_signal_openstack3 = __toESM(require_esm());
31581
31713
  var flavorRouter = {
31714
+ getComputeApiVersion: projectScopedProcedure.input(import_zod9.z.object({
31715
+ project_id: import_zod9.z.string()
31716
+ })).query(async ({ ctx }) => {
31717
+ try {
31718
+ const compute = ctx.openstack?.service("compute");
31719
+ if (!compute) {
31720
+ throw new import_server8.TRPCError({
31721
+ code: "INTERNAL_SERVER_ERROR",
31722
+ message: ERROR_CODES.COMPUTE_SERVICE_UNAVAILABLE
31723
+ });
31724
+ }
31725
+ const response = await compute.get("");
31726
+ const data = await response.json();
31727
+ let versionString = "2.1";
31728
+ if (data.version) {
31729
+ versionString = data.version.version || "2.1";
31730
+ } else if (data.versions) {
31731
+ const currentVersion = data.versions.find((v) => v.status === "CURRENT");
31732
+ versionString = currentVersion?.version || "2.1";
31733
+ }
31734
+ return {
31735
+ version: versionString,
31736
+ supportsDescription: supportsDescriptionField(versionString)
31737
+ };
31738
+ } catch (error) {
31739
+ console.error("Failed to detect Nova version:", error);
31740
+ return {
31741
+ version: "2.1",
31742
+ supportsDescription: false
31743
+ };
31744
+ }
31745
+ }),
31582
31746
  getFlavorById: projectScopedProcedure.input(import_zod9.z.object({
31583
31747
  project_id: import_zod9.z.string(),
31584
31748
  flavorId: import_zod9.z.string()
@@ -31666,7 +31830,8 @@ var flavorRouter = {
31666
31830
  swap: import_zod9.z.number().optional(),
31667
31831
  rxtx_factor: import_zod9.z.number().optional(),
31668
31832
  "OS-FLV-EXT-DATA:ephemeral": import_zod9.z.number().optional(),
31669
- "os-flavor-access:is_public": import_zod9.z.boolean().optional()
31833
+ "os-flavor-access:is_public": import_zod9.z.boolean().optional(),
31834
+ description: import_zod9.z.string().optional()
31670
31835
  })
31671
31836
  })).mutation(async ({ input, ctx }) => {
31672
31837
  try {
@@ -31678,11 +31843,35 @@ var flavorRouter = {
31678
31843
  message: ERROR_CODES.COMPUTE_SERVICE_UNAVAILABLE
31679
31844
  });
31680
31845
  }
31846
+ let supportsDescription = false;
31847
+ let detectedVersion = "2.1";
31848
+ try {
31849
+ const versionResponse = await compute.get("");
31850
+ const versionData = await versionResponse.json();
31851
+ let versionString = "2.1";
31852
+ if (versionData.version) {
31853
+ versionString = versionData.version.version || "2.1";
31854
+ } else if (versionData.versions) {
31855
+ const currentVersion = versionData.versions.find((v) => v.status === "CURRENT");
31856
+ versionString = currentVersion?.version || "2.1";
31857
+ }
31858
+ detectedVersion = versionString;
31859
+ supportsDescription = supportsDescriptionField(versionString);
31860
+ } catch {
31861
+ supportsDescription = false;
31862
+ }
31863
+ const flavorToSend = supportsDescription ? flavor : {
31864
+ ...flavor
31865
+ };
31866
+ if (!supportsDescription) {
31867
+ const { description, ...rest } = flavorToSend;
31868
+ Object.assign(flavorToSend, rest);
31869
+ }
31681
31870
  const flavorData = {
31682
- ...flavor,
31871
+ ...flavorToSend,
31683
31872
  "OS-FLV-EXT-DATA:ephemeral": flavor["OS-FLV-EXT-DATA:ephemeral"] || 0
31684
31873
  };
31685
- const result = await createFlavor(compute, flavorData);
31874
+ const result = await createFlavor(compute, flavorData, supportsDescription ? detectedVersion : void 0);
31686
31875
  return result;
31687
31876
  } catch (error) {
31688
31877
  if (error instanceof import_server8.TRPCError) {
@@ -32490,7 +32679,7 @@ function buildObjectMetadataHeaders(options) {
32490
32679
  }
32491
32680
  __name(buildObjectMetadataHeaders, "buildObjectMetadataHeaders");
32492
32681
  function mapErrorResponseToTRPCError2(error, context) {
32493
- const { operation, container, object, additionalInfo } = context;
32682
+ const { operation, container, object, additionalInfo, preconditionFailedMeansAlreadyExists } = context;
32494
32683
  const baseMessage = `Failed to ${operation}`;
32495
32684
  const containerInfo = container ? ` container: ${container}` : "";
32496
32685
  const objectInfo = object ? `, object: ${object}` : "";
@@ -32521,6 +32710,14 @@ function mapErrorResponseToTRPCError2(error, context) {
32521
32710
  code: "CONFLICT",
32522
32711
  message: `Conflict - ${operation}${containerInfo}${objectInfo}${extraInfo}`
32523
32712
  });
32713
+ case 412:
32714
+ return preconditionFailedMeansAlreadyExists ? new import_server10.TRPCError({
32715
+ code: "CONFLICT",
32716
+ message: `Conflict - ${operation}${containerInfo}${objectInfo} - already exists${extraInfo}`
32717
+ }) : new import_server10.TRPCError({
32718
+ code: "PRECONDITION_FAILED",
32719
+ message: `Precondition failed - ${operation}${containerInfo}${objectInfo}${extraInfo}`
32720
+ });
32524
32721
  case 413:
32525
32722
  return new import_server10.TRPCError({
32526
32723
  code: "PAYLOAD_TOO_LARGE",
@@ -32814,6 +33011,11 @@ var createContainerInputSchema = baseContainerInputSchema.extend({
32814
33011
  tempUrlKey: import_zod11.z.string().optional(),
32815
33012
  tempUrlKey2: import_zod11.z.string().optional()
32816
33013
  });
33014
+ var createContainerResultSchema = import_zod11.z.object({
33015
+ created: import_zod11.z.literal(true),
33016
+ optionsApplied: import_zod11.z.boolean(),
33017
+ optionsError: import_zod11.z.string().optional()
33018
+ });
32817
33019
  var updateContainerMetadataInputSchema = baseContainerInputSchema.extend({
32818
33020
  metadata: import_zod11.z.record(import_zod11.z.string(), import_zod11.z.string()).optional(),
32819
33021
  removeMetadata: import_zod11.z.array(import_zod11.z.string()).optional(),
@@ -33151,21 +33353,54 @@ var swiftRouter = {
33151
33353
  createContainer: projectScopedProcedure.input(projectScopedInputSchema.extend(createContainerInputSchema.shape)).mutation(async ({ input, ctx }) => {
33152
33354
  return withErrorHandling2(async () => {
33153
33355
  const { account, container, ...options } = input;
33356
+ const { storagePolicy, ...applyOptions } = options;
33154
33357
  const openstackSession = ctx.openstack;
33155
33358
  const swift = openstackSession?.service("swift");
33156
33359
  validateSwiftService(swift);
33157
- const headers = buildContainerMetadataHeaders(options);
33158
33360
  const accountPath = account || "";
33159
33361
  const url = accountPath ? `${accountPath}/${encodeURIComponent(container)}` : encodeURIComponent(container);
33160
- await swift.put(url, void 0, {
33161
- headers
33362
+ const probeHeaders = buildContainerMetadataHeaders({
33363
+ storagePolicy
33364
+ });
33365
+ const response = await swift.put(url, void 0, {
33366
+ headers: probeHeaders
33162
33367
  }).catch((error) => {
33163
33368
  throw mapErrorResponseToTRPCError2(error, {
33164
33369
  operation: "create container",
33165
33370
  container
33166
33371
  });
33167
33372
  });
33168
- return true;
33373
+ if (response.status === 202) {
33374
+ throw new import_server11.TRPCError({
33375
+ code: "CONFLICT",
33376
+ message: `Conflict - create container - container already exists: ${container}`
33377
+ });
33378
+ }
33379
+ const optionHeaders = buildContainerMetadataHeaders(applyOptions);
33380
+ if (Object.keys(optionHeaders).length === 0) {
33381
+ return {
33382
+ created: true,
33383
+ optionsApplied: true
33384
+ };
33385
+ }
33386
+ let optionsError;
33387
+ await swift.post(url, void 0, {
33388
+ headers: optionHeaders
33389
+ }).catch((error) => {
33390
+ optionsError = mapErrorResponseToTRPCError2(error, {
33391
+ operation: "apply container settings",
33392
+ container,
33393
+ additionalInfo: "container was created but its metadata/ACL/quota could not be applied"
33394
+ }).message;
33395
+ });
33396
+ return optionsError === void 0 ? {
33397
+ created: true,
33398
+ optionsApplied: true
33399
+ } : {
33400
+ created: true,
33401
+ optionsApplied: false,
33402
+ optionsError
33403
+ };
33169
33404
  }, "create container");
33170
33405
  }),
33171
33406
  /**
@@ -33591,7 +33826,8 @@ var swiftRouter = {
33591
33826
  const normalizedPath = normalizeFolderPath(folderPath);
33592
33827
  const headers = {
33593
33828
  "Content-Type": "application/directory",
33594
- "Content-Length": "0"
33829
+ "Content-Length": "0",
33830
+ "If-None-Match": "*"
33595
33831
  };
33596
33832
  if (metadata) {
33597
33833
  Object.entries(metadata).forEach(([key, value]) => {
@@ -33600,14 +33836,14 @@ var swiftRouter = {
33600
33836
  }
33601
33837
  const accountPath = account || "";
33602
33838
  const url = accountPath ? `${accountPath}/${encodeURIComponent(container)}/${encodeURIComponent(normalizedPath)}` : `${encodeURIComponent(container)}/${encodeURIComponent(normalizedPath)}`;
33603
- await swift.put(url, {
33604
- headers,
33605
- body: new ArrayBuffer(0)
33839
+ await swift.put(url, new ArrayBuffer(0), {
33840
+ headers
33606
33841
  }).catch((error) => {
33607
33842
  throw mapErrorResponseToTRPCError2(error, {
33608
33843
  operation: "create folder",
33609
33844
  container,
33610
- object: normalizedPath
33845
+ object: normalizedPath,
33846
+ preconditionFailedMeansAlreadyExists: true
33611
33847
  });
33612
33848
  });
33613
33849
  return true;
@@ -34368,6 +34604,16 @@ __name(resolveEC2Credential, "resolveEC2Credential");
34368
34604
 
34369
34605
  // src/server/Storage/clients/s3Client.ts
34370
34606
  var import_client_s3 = require("@aws-sdk/client-s3");
34607
+
34608
+ // src/server/Storage/constants.ts
34609
+ var S3_MAX_KEYS_PER_REQUEST = 1e3;
34610
+ var S3_PRESIGN_MAX_EXPIRY_SECONDS = 604800;
34611
+ var S3_MAX_SCAN_PAGES = 20;
34612
+ var S3_CONNECTION_TIMEOUT_MS = 5e3;
34613
+ var S3_MAX_BUFFERED_VERSIONS_PER_KEY = 2e4;
34614
+ var MAX_REPORTED_DELETE_ERRORS = 100;
34615
+
34616
+ // src/server/Storage/clients/s3Client.ts
34371
34617
  function createS3Client(access, secret, endpoint, region = "default") {
34372
34618
  if (!access?.trim()) {
34373
34619
  throw new Error("S3 access key is required");
@@ -34385,7 +34631,10 @@ function createS3Client(access, secret, endpoint, region = "default") {
34385
34631
  accessKeyId: access,
34386
34632
  secretAccessKey: secret
34387
34633
  },
34388
- forcePathStyle: true
34634
+ forcePathStyle: true,
34635
+ requestHandler: {
34636
+ connectionTimeout: S3_CONNECTION_TIMEOUT_MS
34637
+ }
34389
34638
  });
34390
34639
  }
34391
34640
  __name(createS3Client, "createS3Client");
@@ -34496,6 +34745,8 @@ var cephUploadProcedure = protectedProcedure.use(/* @__PURE__ */ __name(async fu
34496
34745
  var import_server13 = require("@trpc/server");
34497
34746
  var import_signal_openstack4 = __toESM(require_esm());
34498
34747
  var S3_ERROR_MAP = {
34748
+ NotFound: "NOT_FOUND",
34749
+ Forbidden: "FORBIDDEN",
34499
34750
  NoSuchBucket: "NOT_FOUND",
34500
34751
  NoSuchKey: "NOT_FOUND",
34501
34752
  NoSuchUpload: "NOT_FOUND",
@@ -34506,6 +34757,7 @@ var S3_ERROR_MAP = {
34506
34757
  BucketAlreadyExists: "CONFLICT",
34507
34758
  BucketAlreadyOwnedByYou: "CONFLICT",
34508
34759
  BucketNotEmpty: "PRECONDITION_FAILED",
34760
+ PreconditionFailed: "CONFLICT",
34509
34761
  InvalidBucketState: "BAD_REQUEST",
34510
34762
  VersioningNotEnabled: "PRECONDITION_FAILED",
34511
34763
  AccessDenied: "FORBIDDEN",
@@ -34530,7 +34782,7 @@ function mapS3ErrorToTRPCError(error, context) {
34530
34782
  }
34531
34783
  const s3Error = error;
34532
34784
  const errorCode = s3Error.Code ?? s3Error.name ?? "";
34533
- const trpcCode = S3_ERROR_MAP[errorCode] ?? "INTERNAL_SERVER_ERROR";
34785
+ const trpcCode = errorCode === "PreconditionFailed" && !context.preconditionFailedMeansAlreadyExists ? "PRECONDITION_FAILED" : S3_ERROR_MAP[errorCode] ?? "INTERNAL_SERVER_ERROR";
34534
34786
  if (!S3_ERROR_MAP[errorCode] && errorCode) {
34535
34787
  import_signal_openstack4.logger.warn("Unmapped S3 error code", {
34536
34788
  errorCode,
@@ -34549,6 +34801,8 @@ function mapS3ErrorToTRPCError(error, context) {
34549
34801
  parts.push("Invalid access key \u2014 your credentials may be expired or incorrect");
34550
34802
  } else if (errorCode === "BucketNotEmpty") {
34551
34803
  parts.push("The bucket is not empty. Some objects or versions may still exist. Use 'Empty Bucket' first to delete all contents.");
34804
+ } else if (errorCode === "PreconditionFailed") {
34805
+ parts.push(context.preconditionFailedMeansAlreadyExists ? "The object already exists" : "Precondition failed");
34552
34806
  } else if (s3Error.message) {
34553
34807
  parts.push(s3Error.message);
34554
34808
  }
@@ -34565,12 +34819,6 @@ __name(mapS3ErrorToTRPCError, "mapS3ErrorToTRPCError");
34565
34819
 
34566
34820
  // src/server/Storage/types/ceph.ts
34567
34821
  var import_zod13 = require("zod");
34568
-
34569
- // src/server/Storage/constants.ts
34570
- var S3_MAX_KEYS_PER_REQUEST = 1e3;
34571
- var S3_PRESIGN_MAX_EXPIRY_SECONDS = 604800;
34572
-
34573
- // src/server/Storage/types/ceph.ts
34574
34822
  var ec2CredentialSchema = import_zod13.z.object({
34575
34823
  id: import_zod13.z.string(),
34576
34824
  access: import_zod13.z.string(),
@@ -34606,6 +34854,59 @@ var createBucketOutputSchema = import_zod13.z.object({
34606
34854
  var deleteBucketInputSchema = projectScopedInputSchema.extend({
34607
34855
  bucketName: import_zod13.z.string().min(1)
34608
34856
  });
34857
+ var headBucketInputSchema = projectScopedInputSchema.extend({
34858
+ bucketName: existingBucketNameSchema
34859
+ });
34860
+ var bucketStateInputSchema = projectScopedInputSchema.extend({
34861
+ bucketName: existingBucketNameSchema
34862
+ });
34863
+ var bucketStateOutputSchema = import_zod13.z.object({
34864
+ /**
34865
+ * Raw versioning status, straight from `GetBucketVersioning` — the same value
34866
+ * `versioning.getStatus` reports.
34867
+ *
34868
+ * Exposed here so a caller that needs to tell "Suspended" from "Unversioned" (the bucket
34869
+ * header renders a different badge for each) does not have to issue a second
34870
+ * `GetBucketVersioning` alongside this one. `isVersioningEnabled` below is the collapsed
34871
+ * form most callers actually want.
34872
+ */
34873
+ status: import_zod13.z.enum([
34874
+ "Enabled",
34875
+ "Suspended",
34876
+ "Unversioned"
34877
+ ]),
34878
+ isVersioningEnabled: import_zod13.z.boolean(),
34879
+ /**
34880
+ * No current (non-delete-marker) objects. Always exact, never affected by `isPartialScan`:
34881
+ * it comes from a dedicated one-key `ListObjectsV2`, which by definition lists only current
34882
+ * objects and omits delete markers, so "no keys returned" *is* emptiness.
34883
+ *
34884
+ * Deliberately a boolean, not a count - no caller ever needed more than the yes/no, and a
34885
+ * one-key probe has no count to give.
34886
+ */
34887
+ isEmpty: import_zod13.z.boolean(),
34888
+ /**
34889
+ * Every key in the bucket is hidden behind a delete marker: versioning is on, the scan saw
34890
+ * entries, and none of them was a real (non-delete-marker) version.
34891
+ *
34892
+ * Forced to `false` under `isPartialScan`, because a truncated scan that happened to see only
34893
+ * delete markers cannot tell "no real versions exist" from "no real versions yet".
34894
+ */
34895
+ hasOnlyDeleteMarkers: import_zod13.z.boolean(),
34896
+ /** At least one non-current version or delete marker exists. Reliable when true. */
34897
+ hasOldVersionsOrDeleteMarkers: import_zod13.z.boolean(),
34898
+ /**
34899
+ * True when the version-history scan stopped before it could rule the history flags in either
34900
+ * direction: it hit the page ceiling, the request was aborted, or S3 returned a truncated
34901
+ * page carrying no continuation marker to follow.
34902
+ *
34903
+ * It says nothing about `isEmpty` or `isVersioningEnabled`, which are established by their own
34904
+ * single requests. Of the two flags it does cover, `hasOldVersionsOrDeleteMarkers: true`
34905
+ * remains reliable - finding one such entry is proof - while `false` means "none found so
34906
+ * far", and `hasOnlyDeleteMarkers` is forced to `false` outright.
34907
+ */
34908
+ isPartialScan: import_zod13.z.boolean()
34909
+ });
34609
34910
  var s3StatusSchema = import_zod13.z.object({
34610
34911
  hasCredentials: import_zod13.z.boolean()
34611
34912
  });
@@ -34673,6 +34974,9 @@ var deleteVersionsBulkInputSchema = projectScopedInputSchema.extend({
34673
34974
  versionId: import_zod13.z.string().min(1)
34674
34975
  })).min(1).max(1e4)
34675
34976
  });
34977
+ var deleteNonCurrentVersionsInputSchema = projectScopedInputSchema.extend({
34978
+ containerName: existingBucketNameSchema
34979
+ });
34676
34980
  var deletedObjectSchema = import_zod13.z.object({
34677
34981
  key: import_zod13.z.string(),
34678
34982
  versionId: import_zod13.z.string().optional(),
@@ -34691,6 +34995,22 @@ var deleteObjectsBulkOutputSchema = import_zod13.z.object({
34691
34995
  deletedCount: import_zod13.z.number().int().nonnegative(),
34692
34996
  errorCount: import_zod13.z.number().int().nonnegative()
34693
34997
  });
34998
+ var deleteNonCurrentVersionsOutputSchema = import_zod13.z.object({
34999
+ errors: import_zod13.z.array(deleteObjectErrorSchema),
35000
+ deletedCount: import_zod13.z.number().int().nonnegative(),
35001
+ errorCount: import_zod13.z.number().int().nonnegative(),
35002
+ /**
35003
+ * The run did not cover the whole bucket, so non-current versions may survive.
35004
+ * Set when the request was aborted (including during the last page's deletes),
35005
+ * when S3 returned a truncated page without a continuation marker, and when a
35006
+ * key had to be skipped (NoCurrentVersion, MissingVersionId, TooManyVersions) -
35007
+ * the last of which also records an entry in `errors`, so a caller must treat
35008
+ * this flag and `errorCount` as independent rather than as alternatives.
35009
+ * The counts remain accurate for what was actually deleted - this flag only
35010
+ * denies that the wipe was exhaustive, which a bare success count would imply.
35011
+ */
35012
+ isPartial: import_zod13.z.boolean()
35013
+ });
34694
35014
  var createFolderInputSchema2 = projectScopedInputSchema.extend({
34695
35015
  containerName: import_zod13.z.string().min(1),
34696
35016
  folderPath: import_zod13.z.string().min(1)
@@ -34809,19 +35129,6 @@ var bucketPolicyStatementSchema = import_zod13.z.object({
34809
35129
  message: "Principal object must contain at least one of: AWS, Service, or Federated"
34810
35130
  });
34811
35131
  }
34812
- if (typeof val === "object" && val.AWS) {
34813
- const arns = Array.isArray(val.AWS) ? val.AWS : [
34814
- val.AWS
34815
- ];
34816
- for (const arn of arns) {
34817
- if (arn !== "*" && !/^arn:aws:iam::\d{12}:(?:root|user\/.+|role\/.+)$/.test(arn)) {
34818
- ctx.addIssue({
34819
- code: "custom",
34820
- message: `Invalid AWS principal ARN format: ${arn}. Expected arn:aws:iam::ACCOUNT-ID:root or arn:aws:iam::ACCOUNT-ID:(user|role)/NAME`
34821
- });
34822
- }
34823
- }
34824
- }
34825
35132
  }),
34826
35133
  NotPrincipal: import_zod13.z.union([
34827
35134
  import_zod13.z.string(),
@@ -35218,6 +35525,193 @@ var containerRouter = {
35218
35525
  }
35219
35526
  }),
35220
35527
  /**
35528
+ * Existence probe for a single bucket — one HeadBucket request, no body.
35529
+ *
35530
+ * Exists for route guards that must answer "is there such a bucket?" before rendering a
35531
+ * bucket page. `list` would answer it too, but ListBuckets only returns buckets the
35532
+ * caller *owns*: a bucket reachable through a policy would read as missing. HeadBucket
35533
+ * asks about the bucket itself, so 404 means 404 and 403 means 403.
35534
+ *
35535
+ * @throws TRPCError NOT_FOUND - bucket does not exist (SDK error name `NotFound`)
35536
+ * @throws TRPCError FORBIDDEN - no credentials, or the bucket exists but is not readable
35537
+ */
35538
+ head: cephProtectedProcedure.input(headBucketInputSchema).query(async ({ ctx, input }) => {
35539
+ const s3 = ctx.getCephClient();
35540
+ const { bucketName } = input;
35541
+ try {
35542
+ await s3.send(new import_client_s32.HeadBucketCommand({
35543
+ Bucket: bucketName
35544
+ }));
35545
+ return {
35546
+ exists: true
35547
+ };
35548
+ } catch (error) {
35549
+ throw mapS3ErrorToTRPCError(error, {
35550
+ operation: "head bucket",
35551
+ bucket: bucketName
35552
+ });
35553
+ }
35554
+ }),
35555
+ /**
35556
+ * Authoritative bucket emptiness/version state.
35557
+ *
35558
+ * Replaces three separate client-side probes (`useBucketInfo`, `DeleteBucketModal`,
35559
+ * `EmptyBucketModal`) that each read only the first page of `objects.list` and derived
35560
+ * `isBucketEmpty`/`hasOldVersionsOrDeleteMarkers` from it — silently wrong for buckets whose
35561
+ * first 100 (or 1000) entries don't tell the whole story.
35562
+ *
35563
+ * Cost, honestly:
35564
+ * - unversioned bucket: **exactly two** requests, whatever its size. Without a version
35565
+ * history there is nothing to scan, so both history flags are false by definition.
35566
+ * - versioned bucket with both an old version (or delete marker) and a surviving real
35567
+ * version: **three** — one page settles both history flags and the scan stops.
35568
+ * - versioned bucket whose history is clean: the scan has nothing to find and no way to know
35569
+ * that early, so it runs to the end of the bucket or to `S3_MAX_SCAN_PAGES`.
35570
+ * - versioned bucket holding *only* delete markers, with no surviving non-current version —
35571
+ * what a `NoncurrentVersionExpiration` lifecycle rule leaves behind once it has expired the
35572
+ * real versions: same cost as the clean-history shape, and for the same reason. The scan
35573
+ * sees `hasOldVersionsOrDeleteMarkers` on page one but can only prove `hasOnlyDeleteMarkers`
35574
+ * by reaching the end, since any later page could still hold a real version.
35575
+ *
35576
+ * The last two are the shapes that cost real round-trips. Both are bounded, and in both the
35577
+ * scan is doing work that has an answer to produce — it is not spinning to re-learn something
35578
+ * already known. Callers that force `staleTime: 0` (the two destructive modals, deliberately)
35579
+ * pay it on every open.
35580
+ *
35581
+ * `isEmpty` is deliberately established by its own one-key `ListObjectsV2` rather than by the
35582
+ * scan: that command lists current objects only, so it answers emptiness exactly, in one
35583
+ * request, on a bucket of any size. That matters beyond speed — emptiness gates "Delete
35584
+ * Bucket", and deriving it from a scan that may be truncated left large buckets permanently
35585
+ * unconfirmable and therefore permanently undeletable, with a "refresh and try again" that
35586
+ * could never succeed.
35587
+ *
35588
+ * Versioning status is read from S3 here, not taken from client input, so the answer can't be
35589
+ * computed against a stale client-cached status. It is also returned raw as `status`, so a
35590
+ * caller needing the three-way value has no reason to issue a second `GetBucketVersioning`
35591
+ * against `versioning.getStatus` alongside this call.
35592
+ *
35593
+ * @throws TRPCError NOT_FOUND - bucket does not exist
35594
+ * @throws TRPCError FORBIDDEN - no credentials or access denied
35595
+ */
35596
+ getState: cephProtectedProcedure.input(bucketStateInputSchema).query(async ({ ctx, input }) => {
35597
+ const s3 = ctx.getCephClient();
35598
+ const { bucketName } = input;
35599
+ let status;
35600
+ let isVersioningEnabled;
35601
+ let isEmpty;
35602
+ try {
35603
+ const [versioningResponse, currentObjectsResponse] = await Promise.all([
35604
+ s3.send(new import_client_s32.GetBucketVersioningCommand({
35605
+ Bucket: bucketName
35606
+ }), {
35607
+ abortSignal: ctx.req.signal
35608
+ }),
35609
+ s3.send(new import_client_s32.ListObjectsV2Command({
35610
+ Bucket: bucketName,
35611
+ MaxKeys: 1
35612
+ }), {
35613
+ abortSignal: ctx.req.signal
35614
+ })
35615
+ ]);
35616
+ status = versioningResponse.Status ?? "Unversioned";
35617
+ isVersioningEnabled = status === "Enabled" || status === "Suspended";
35618
+ isEmpty = (currentObjectsResponse.KeyCount ?? currentObjectsResponse.Contents?.length ?? 0) === 0;
35619
+ } catch (error) {
35620
+ console.error("getState: failed to read bucket versioning status or emptiness", {
35621
+ bucket: bucketName,
35622
+ error
35623
+ });
35624
+ throw mapS3ErrorToTRPCError(error, {
35625
+ operation: "get bucket state",
35626
+ bucket: bucketName
35627
+ });
35628
+ }
35629
+ if (!isVersioningEnabled) {
35630
+ return bucketStateOutputSchema.parse({
35631
+ status,
35632
+ isVersioningEnabled: false,
35633
+ isEmpty,
35634
+ hasOnlyDeleteMarkers: false,
35635
+ hasOldVersionsOrDeleteMarkers: false,
35636
+ isPartialScan: false
35637
+ });
35638
+ }
35639
+ let hasOldVersionOrDeleteMarker = false;
35640
+ let hasRealVersion = false;
35641
+ let anyEntrySeen = false;
35642
+ let keyMarker;
35643
+ let versionIdMarker;
35644
+ let pages = 0;
35645
+ let isPartialScan = false;
35646
+ while (true) {
35647
+ if (ctx.req.signal?.aborted) {
35648
+ isPartialScan = true;
35649
+ break;
35650
+ }
35651
+ let response;
35652
+ try {
35653
+ response = await s3.send(new import_client_s32.ListObjectVersionsCommand({
35654
+ Bucket: bucketName,
35655
+ MaxKeys: S3_MAX_KEYS_PER_REQUEST,
35656
+ KeyMarker: keyMarker,
35657
+ VersionIdMarker: versionIdMarker
35658
+ }), {
35659
+ abortSignal: ctx.req.signal
35660
+ });
35661
+ } catch (error) {
35662
+ if (ctx.req.signal?.aborted) {
35663
+ isPartialScan = true;
35664
+ break;
35665
+ }
35666
+ console.error("getState: version scan failed", {
35667
+ bucket: bucketName,
35668
+ pages,
35669
+ error
35670
+ });
35671
+ throw mapS3ErrorToTRPCError(error, {
35672
+ operation: "get bucket state",
35673
+ bucket: bucketName
35674
+ });
35675
+ }
35676
+ for (const v of response.Versions ?? []) {
35677
+ anyEntrySeen = true;
35678
+ hasRealVersion = true;
35679
+ if (!v.IsLatest) {
35680
+ hasOldVersionOrDeleteMarker = true;
35681
+ }
35682
+ }
35683
+ if ((response.DeleteMarkers?.length ?? 0) > 0) {
35684
+ anyEntrySeen = true;
35685
+ hasOldVersionOrDeleteMarker = true;
35686
+ }
35687
+ pages++;
35688
+ if (hasOldVersionOrDeleteMarker && hasRealVersion) {
35689
+ break;
35690
+ }
35691
+ if (!response.IsTruncated) {
35692
+ break;
35693
+ }
35694
+ if (!response.NextKeyMarker) {
35695
+ isPartialScan = true;
35696
+ break;
35697
+ }
35698
+ if (pages >= S3_MAX_SCAN_PAGES) {
35699
+ isPartialScan = true;
35700
+ break;
35701
+ }
35702
+ keyMarker = response.NextKeyMarker;
35703
+ versionIdMarker = response.NextVersionIdMarker;
35704
+ }
35705
+ return bucketStateOutputSchema.parse({
35706
+ status,
35707
+ isVersioningEnabled,
35708
+ isEmpty,
35709
+ hasOnlyDeleteMarkers: !isPartialScan && anyEntrySeen && !hasRealVersion,
35710
+ hasOldVersionsOrDeleteMarkers: hasOldVersionOrDeleteMarker,
35711
+ isPartialScan
35712
+ });
35713
+ }),
35714
+ /**
35221
35715
  * Create a new S3 bucket.
35222
35716
  *
35223
35717
  * Uses AWS SDK CreateBucketCommand. The AWS SDK automatically adds LocationConstraint
@@ -35866,11 +36360,239 @@ var objectRouter = {
35866
36360
  return bulkDeleteItems(s3, containerName, uniqueVersions, ctx.req.signal, "delete versions");
35867
36361
  }),
35868
36362
  /**
36363
+ * Delete every non-current version and delete marker in a bucket, keeping
36364
+ * each key's current live version intact ("Delete Versions" bucket action).
36365
+ *
36366
+ * Deliberately NOT built on top of `deleteAll` (which the "Empty Bucket"
36367
+ * checkbox also uses): that procedure's full-wipe branch discards
36368
+ * `IsLatest` entirely and re-scans from scratch whenever a page is
36369
+ * exhausted, specifically so it can catch delete markers created by its
36370
+ * own non-versioned-delete branch. Bolting a third "keep the latest"
36371
+ * mode onto that already-intricate loop would risk regressing the other
36372
+ * two. This procedure keeps its own single-pass scan instead.
36373
+ *
36374
+ * ⚠️ IsLatest correctness across pages:
36375
+ * `IsLatest` is computed by S3 at *list time*, not stored permanently. If a
36376
+ * key's versions are split across two list pages and we deleted anything
36377
+ * from that key after page 1 but before fetching page 2, the remaining
36378
+ * version would be re-evaluated as IsLatest=true on page 2 - resurrecting
36379
+ * a version (or worse, a deleted object) that should have stayed removed.
36380
+ * To avoid this, a key's records are only ever acted on once its full
36381
+ * group has been seen: the last key-group on every truncated page is
36382
+ * buffered (`pendingKey`/`pendingItems`) and merged with the next page's
36383
+ * leading group (S3 returns Versions/DeleteMarkers sorted by key, so a
36384
+ * split key's remaining records always appear at the very start of the
36385
+ * next page) before that group is decided on and deleted.
36386
+ *
36387
+ * Per completed key-group: if the current (IsLatest) record is a regular
36388
+ * version, every other record in the group is deleted and the current
36389
+ * version is kept. If the current record is a delete marker, the entire
36390
+ * group - including the marker - is deleted, since a versioned bucket has
36391
+ * no other way to represent "this key was deleted" once its history is
36392
+ * gone; keeping the marker without its superseded versions would leave the
36393
+ * key listed in the "Deleted" tab with nothing left to restore.
36394
+ *
36395
+ * No second pass is needed (unlike `deleteAll`'s full-wipe branch): every
36396
+ * deletion here targets a specific `VersionId`, which never creates a new
36397
+ * delete marker, so nothing new can appear behind the scan as it
36398
+ * progresses. One pass over the bucket is exhaustive.
36399
+ *
36400
+ * @throws TRPCError NOT_FOUND - bucket does not exist
36401
+ * @throws TRPCError FORBIDDEN - no credentials or access denied
36402
+ */
36403
+ deleteNonCurrentVersions: cephProtectedProcedure.input(deleteNonCurrentVersionsInputSchema).mutation(async ({ ctx, input }) => {
36404
+ const s3 = ctx.getCephClient();
36405
+ const { containerName } = input;
36406
+ const errors = [];
36407
+ let deletedCount = 0;
36408
+ let errorCount = 0;
36409
+ let isPartial = false;
36410
+ const recordErrors = /* @__PURE__ */ __name((newErrors) => {
36411
+ errorCount += newErrors.length;
36412
+ for (const error of newErrors) {
36413
+ if (errors.length >= MAX_REPORTED_DELETE_ERRORS) break;
36414
+ errors.push(error);
36415
+ }
36416
+ }, "recordErrors");
36417
+ let keyMarker;
36418
+ let versionIdMarker;
36419
+ let pendingKey;
36420
+ let pendingItems = [];
36421
+ let sameMarkerCount = 0;
36422
+ const MAX_SAME_MARKER = 5;
36423
+ let skipKey;
36424
+ while (true) {
36425
+ if (ctx.req.signal?.aborted) {
36426
+ isPartial = true;
36427
+ break;
36428
+ }
36429
+ let response;
36430
+ try {
36431
+ response = await s3.send(new import_client_s33.ListObjectVersionsCommand({
36432
+ Bucket: containerName,
36433
+ MaxKeys: S3_MAX_KEYS_PER_REQUEST,
36434
+ KeyMarker: keyMarker,
36435
+ VersionIdMarker: versionIdMarker
36436
+ }), {
36437
+ abortSignal: ctx.req.signal
36438
+ });
36439
+ } catch (error) {
36440
+ if (ctx.req.signal?.aborted) {
36441
+ isPartial = true;
36442
+ break;
36443
+ }
36444
+ throw mapS3ErrorToTRPCError(error, {
36445
+ operation: "delete non-current versions",
36446
+ bucket: containerName
36447
+ });
36448
+ }
36449
+ const combined = [
36450
+ ...(response.Versions ?? []).map((v) => ({
36451
+ Key: v.Key ?? "",
36452
+ VersionId: v.VersionId,
36453
+ IsLatest: v.IsLatest ?? false,
36454
+ isDeleteMarker: false
36455
+ })),
36456
+ ...(response.DeleteMarkers ?? []).map((dm) => ({
36457
+ Key: dm.Key ?? "",
36458
+ VersionId: dm.VersionId,
36459
+ IsLatest: dm.IsLatest ?? false,
36460
+ isDeleteMarker: true
36461
+ }))
36462
+ ].sort((a, b) => a.Key < b.Key ? -1 : a.Key > b.Key ? 1 : 0);
36463
+ const pageGroups = [];
36464
+ for (const item of combined) {
36465
+ const last = pageGroups[pageGroups.length - 1];
36466
+ if (last && last.key === item.Key) {
36467
+ last.items.push(item);
36468
+ } else {
36469
+ pageGroups.push({
36470
+ key: item.Key,
36471
+ items: [
36472
+ item
36473
+ ]
36474
+ });
36475
+ }
36476
+ }
36477
+ if (pendingItems.length > 0) {
36478
+ if (pageGroups.length > 0 && pageGroups[0].key === pendingKey) {
36479
+ pageGroups[0].items = [
36480
+ ...pendingItems,
36481
+ ...pageGroups[0].items
36482
+ ];
36483
+ } else {
36484
+ pageGroups.unshift({
36485
+ key: pendingKey,
36486
+ items: pendingItems
36487
+ });
36488
+ }
36489
+ pendingItems = [];
36490
+ pendingKey = void 0;
36491
+ }
36492
+ if (skipKey !== void 0) {
36493
+ const remaining = pageGroups.filter((group) => group.key !== skipKey);
36494
+ if (remaining.length === pageGroups.length) skipKey = void 0;
36495
+ pageGroups.length = 0;
36496
+ pageGroups.push(...remaining);
36497
+ }
36498
+ let groupsToProcess = pageGroups;
36499
+ if (response.IsTruncated && response.NextKeyMarker && pageGroups.length > 0) {
36500
+ const markerIndex = pageGroups.findIndex((group) => group.key === response.NextKeyMarker);
36501
+ const index = markerIndex >= 0 ? markerIndex : pageGroups.length - 1;
36502
+ const deferredGroup = pageGroups[index];
36503
+ groupsToProcess = pageGroups.filter((_, i) => i !== index);
36504
+ if (deferredGroup.items.length > S3_MAX_BUFFERED_VERSIONS_PER_KEY) {
36505
+ console.error(`[deleteNonCurrentVersions] Abandoning key with more than ${S3_MAX_BUFFERED_VERSIONS_PER_KEY} versions in bucket ${containerName}`);
36506
+ recordErrors([
36507
+ {
36508
+ key: deferredGroup.key,
36509
+ code: "TooManyVersions",
36510
+ message: `Key has more than ${S3_MAX_BUFFERED_VERSIONS_PER_KEY} versions and was skipped`
36511
+ }
36512
+ ]);
36513
+ isPartial = true;
36514
+ skipKey = deferredGroup.key;
36515
+ } else {
36516
+ pendingKey = deferredGroup.key;
36517
+ pendingItems = deferredGroup.items;
36518
+ }
36519
+ }
36520
+ const itemsToDelete = [];
36521
+ for (const group of groupsToProcess) {
36522
+ const current = group.items.find((item) => item.IsLatest);
36523
+ if (current === void 0) {
36524
+ console.error(`[deleteNonCurrentVersions] Skipping key with no current version in bucket ${containerName}`);
36525
+ recordErrors([
36526
+ {
36527
+ key: group.key,
36528
+ code: "NoCurrentVersion",
36529
+ message: "No version flagged as current; skipped to avoid deleting the live object"
36530
+ }
36531
+ ]);
36532
+ isPartial = true;
36533
+ continue;
36534
+ }
36535
+ const keepCurrentVersion = !current.isDeleteMarker;
36536
+ for (const item of group.items) {
36537
+ if (keepCurrentVersion && item === current) continue;
36538
+ if (!item.VersionId) {
36539
+ console.error(`[deleteNonCurrentVersions] Skipping item without VersionId in bucket ${containerName}`);
36540
+ recordErrors([
36541
+ {
36542
+ key: item.Key,
36543
+ code: "MissingVersionId",
36544
+ message: "Version record carried no VersionId; skipped to avoid creating a delete marker"
36545
+ }
36546
+ ]);
36547
+ isPartial = true;
36548
+ continue;
36549
+ }
36550
+ itemsToDelete.push({
36551
+ key: item.Key,
36552
+ versionId: item.VersionId
36553
+ });
36554
+ }
36555
+ }
36556
+ if (itemsToDelete.length > 0) {
36557
+ const pageResult = await bulkDeleteItems(s3, containerName, itemsToDelete, ctx.req.signal, "delete non-current versions");
36558
+ deletedCount += pageResult.deletedCount;
36559
+ recordErrors(pageResult.errors);
36560
+ }
36561
+ if (!response.IsTruncated || !response.NextKeyMarker) {
36562
+ if (response.IsTruncated || ctx.req.signal?.aborted) isPartial = true;
36563
+ break;
36564
+ }
36565
+ const currentMarkerKey = `${keyMarker ?? ""}:${versionIdMarker ?? ""}`;
36566
+ const newMarkerKey = `${response.NextKeyMarker ?? ""}:${response.NextVersionIdMarker ?? ""}`;
36567
+ if (currentMarkerKey === newMarkerKey) {
36568
+ sameMarkerCount++;
36569
+ if (sameMarkerCount >= MAX_SAME_MARKER) {
36570
+ throw new import_server14.TRPCError({
36571
+ code: "INTERNAL_SERVER_ERROR",
36572
+ message: `Pagination stalled: markers not advancing after ${MAX_SAME_MARKER} iterations while deleting non-current versions from bucket ${containerName}.`
36573
+ });
36574
+ }
36575
+ } else {
36576
+ sameMarkerCount = 0;
36577
+ }
36578
+ keyMarker = response.NextKeyMarker;
36579
+ versionIdMarker = response.NextVersionIdMarker;
36580
+ }
36581
+ return deleteNonCurrentVersionsOutputSchema.parse({
36582
+ errors,
36583
+ deletedCount,
36584
+ errorCount,
36585
+ isPartial
36586
+ });
36587
+ }),
36588
+ /**
35869
36589
  * Create a folder (zero-byte object with key ending in "/").
35870
36590
  *
35871
36591
  * In S3, folders are virtual - they're just zero-byte objects with keys ending in "/".
35872
- * This operation is idempotent - creating an existing folder succeeds.
36592
+ * The write is conditional (IfNoneMatch: "*"), so creating a folder that
36593
+ * already exists throws CONFLICT instead of silently overwriting it.
35873
36594
  *
36595
+ * @throws TRPCError CONFLICT - a folder (or object) with this key already exists
35874
36596
  * @throws TRPCError NOT_FOUND - bucket does not exist
35875
36597
  * @throws TRPCError FORBIDDEN - no credentials or access denied
35876
36598
  */
@@ -35883,14 +36605,16 @@ var objectRouter = {
35883
36605
  Bucket: containerName,
35884
36606
  Key: normalizedPath,
35885
36607
  Body: Buffer.from(""),
35886
- ContentLength: 0
36608
+ ContentLength: 0,
36609
+ IfNoneMatch: "*"
35887
36610
  }));
35888
36611
  return true;
35889
36612
  } catch (error) {
35890
36613
  throw mapS3ErrorToTRPCError(error, {
35891
36614
  operation: "create folder",
35892
36615
  bucket: containerName,
35893
- key: normalizedPath
36616
+ key: normalizedPath,
36617
+ preconditionFailedMeansAlreadyExists: true
35894
36618
  });
35895
36619
  }
35896
36620
  }),
@@ -36630,7 +37354,6 @@ var ec2CredentialRouter = {
36630
37354
 
36631
37355
  // src/server/Storage/routers/ceph/versioningRouter.ts
36632
37356
  var import_client_s34 = require("@aws-sdk/client-s3");
36633
- var import_zod17 = require("zod");
36634
37357
 
36635
37358
  // src/server/Storage/types/versioning.ts
36636
37359
  var import_zod16 = require("zod");
@@ -36703,6 +37426,72 @@ var listObjectVersionsInputSchema = projectScopedInputSchema.extend({
36703
37426
  bucket: import_zod16.z.string().min(1),
36704
37427
  key: import_zod16.z.string().min(1)
36705
37428
  });
37429
+ var checkDeletedContentInputSchema = projectScopedInputSchema.extend({
37430
+ bucket: import_zod16.z.string().min(1),
37431
+ prefix: import_zod16.z.string().optional(),
37432
+ folders: import_zod16.z.array(import_zod16.z.string()).max(1e3).optional()
37433
+ }).refine((input) => input.prefix !== void 0 || (input.folders?.length ?? 0) > 0, {
37434
+ message: "Either prefix or a non-empty folders array is required",
37435
+ path: [
37436
+ "prefix"
37437
+ ]
37438
+ }).refine((input) => input.folders?.every((folder) => folder.startsWith(input.prefix ?? "")) ?? true, {
37439
+ message: "Every folder must live under prefix",
37440
+ path: [
37441
+ "folders"
37442
+ ]
37443
+ });
37444
+ var checkDeletedContentOutputSchema = import_zod16.z.array(import_zod16.z.object({
37445
+ prefix: import_zod16.z.string(),
37446
+ hasDeletedContent: import_zod16.z.boolean(),
37447
+ isFolderDeleted: import_zod16.z.boolean(),
37448
+ folderDeleteMarkerVersionId: import_zod16.z.string().optional(),
37449
+ folderMarkerVersionId: import_zod16.z.string().optional(),
37450
+ isPartialScan: import_zod16.z.boolean()
37451
+ }));
37452
+
37453
+ // src/server/Storage/helpers/versionScan.ts
37454
+ function folderPrefixOf(key, prefix) {
37455
+ if (!key.startsWith(prefix)) return void 0;
37456
+ const rest = key.slice(prefix.length);
37457
+ const i = rest.indexOf("/");
37458
+ return i === -1 ? void 0 : prefix + rest.slice(0, i + 1);
37459
+ }
37460
+ __name(folderPrefixOf, "folderPrefixOf");
37461
+ function isFolderCovered(folderPrefix, stoppedAtKey) {
37462
+ return stoppedAtKey === void 0 ? true : folderPrefix < stoppedAtKey && !stoppedAtKey.startsWith(folderPrefix);
37463
+ }
37464
+ __name(isFolderCovered, "isFolderCovered");
37465
+ function longestCommonPrefix(folders) {
37466
+ if (folders.length === 0) return "";
37467
+ let common = folders[0];
37468
+ for (let i = 1; i < folders.length && common.length > 0; i++) {
37469
+ const folder = folders[i];
37470
+ let j = 0;
37471
+ while (j < common.length && j < folder.length && common[j] === folder[j]) j++;
37472
+ common = common.slice(0, j);
37473
+ }
37474
+ if (common.endsWith("/") && folders.some((folder) => folder === common)) {
37475
+ common = common.slice(0, -1);
37476
+ }
37477
+ const lastSlash = common.lastIndexOf("/");
37478
+ return lastSlash === -1 ? "" : common.slice(0, lastSlash + 1);
37479
+ }
37480
+ __name(longestCommonPrefix, "longestCommonPrefix");
37481
+ function greatestKeyOnPage(versions, deleteMarkers) {
37482
+ let greatest;
37483
+ for (const entries of [
37484
+ versions,
37485
+ deleteMarkers
37486
+ ]) {
37487
+ for (const entry of entries ?? []) {
37488
+ const key = entry.Key;
37489
+ if (key !== void 0 && (greatest === void 0 || key > greatest)) greatest = key;
37490
+ }
37491
+ }
37492
+ return greatest;
37493
+ }
37494
+ __name(greatestKeyOnPage, "greatestKeyOnPage");
36706
37495
 
36707
37496
  // src/server/Storage/routers/ceph/versioningRouter.ts
36708
37497
  var versioningRouter = {
@@ -36948,104 +37737,144 @@ var versioningRouter = {
36948
37737
  /**
36949
37738
  * Check if folders contain deleted files (files with delete markers) or if the folder marker itself is deleted.
36950
37739
  *
36951
- * For each folder, checks:
36952
- * 1. If the folder marker itself (key ending in "/") has a delete marker
36953
- * 2. If any nested objects have delete markers
37740
+ * Performs a single paginated, delimiter-less scan of the parent prefix (either the
37741
+ * caller-supplied `prefix`, or — for backward compatibility — the longest common prefix of
37742
+ * `folders`) and attributes every key it sees to its direct-child folder. This replaces the
37743
+ * previous design of one unbounded scan per folder: the union of what N per-folder scans
37744
+ * would return is exactly what one scan of their common parent returns, minus "loose" objects
37745
+ * that live directly under the parent (which aren't attributed to any folder and are ignored).
36954
37746
  *
36955
- * Note: This can be expensive for many folders (N S3 requests for N folders).
36956
- * Use sparingly and consider caching.
37747
+ * Cost: at most `S3_MAX_SCAN_PAGES` requests total, independent of how many folders are being
37748
+ * checked (as opposed to the old N-scans-for-N-folders behaviour).
37749
+ *
37750
+ * Honours `ctx.req.signal`: if the caller navigates away mid-scan, the loop stops and returns
37751
+ * whatever it has accumulated so far, flagged via `isPartialScan`.
37752
+ *
37753
+ * `isPartialScan` (per folder) is `true` when the scan hit the page ceiling (or was aborted)
37754
+ * before fully covering that folder's key range. It is the caller's job to treat it as
37755
+ * "unknown", not as "confirmed empty": `hasDeletedContent: false` together with
37756
+ * `isPartialScan: true` means "not fully checked", not "clean". A `true` value for
37757
+ * `hasDeletedContent` or `isFolderDeleted` is always reliable, partial scan or not — an
37758
+ * incomplete scan can only miss a delete marker, never invent one.
36957
37759
  *
36958
37760
  * @throws TRPCError NOT_FOUND - bucket does not exist
36959
37761
  * @throws TRPCError FORBIDDEN - no credentials or access denied
36960
37762
  */
36961
- checkDeletedContent: cephProtectedProcedure.input(import_zod17.z.object({
36962
- project_id: import_zod17.z.string(),
36963
- bucket: import_zod17.z.string().min(1),
36964
- folders: import_zod17.z.array(import_zod17.z.string()).max(100)
36965
- })).query(async ({ ctx, input }) => {
37763
+ checkDeletedContent: cephProtectedProcedure.input(checkDeletedContentInputSchema).query(async ({ ctx, input }) => {
36966
37764
  const s3 = ctx.getCephClient();
36967
- try {
36968
- const results = await Promise.all(input.folders.map(async (folderPrefix) => {
36969
- try {
36970
- let hasDeletedNestedObjects = false;
36971
- let isFolderMarkerDeleted = false;
36972
- let folderDeleteMarkerVersionId;
36973
- let folderMarkerVersionId;
36974
- let keyMarker;
36975
- let versionIdMarker;
36976
- do {
36977
- const response = await s3.send(new import_client_s34.ListObjectVersionsCommand({
36978
- Bucket: input.bucket,
36979
- Prefix: folderPrefix,
36980
- MaxKeys: 100,
36981
- KeyMarker: keyMarker,
36982
- VersionIdMarker: versionIdMarker
36983
- }));
36984
- if (!folderMarkerVersionId && response.Versions) {
36985
- const folderMarkerVersions = response.Versions.filter((v) => v.Key === folderPrefix);
36986
- if (folderMarkerVersions.length > 0) {
36987
- const latestVersion = folderMarkerVersions.sort((a, b) => {
36988
- const aTime = a.LastModified?.getTime() ?? 0;
36989
- const bTime = b.LastModified?.getTime() ?? 0;
36990
- return bTime - aTime;
36991
- })[0];
36992
- if (latestVersion) {
36993
- folderMarkerVersionId = latestVersion.VersionId;
36994
- }
36995
- }
36996
- }
36997
- if (!isFolderMarkerDeleted && response.DeleteMarkers) {
36998
- const folderMarkerDeleteMarkers = response.DeleteMarkers.filter((dm) => dm.Key === folderPrefix);
36999
- if (folderMarkerDeleteMarkers.length > 0) {
37000
- const latestFolderMarker = folderMarkerDeleteMarkers.find((dm) => dm.IsLatest);
37001
- if (latestFolderMarker) {
37002
- isFolderMarkerDeleted = true;
37003
- folderDeleteMarkerVersionId = latestFolderMarker.VersionId;
37004
- }
37005
- }
37006
- }
37007
- if (!hasDeletedNestedObjects) {
37008
- const deleteMarkersExcludingFolder = response.DeleteMarkers?.filter((dm) => dm.Key !== folderPrefix && dm.IsLatest === true) ?? [];
37009
- hasDeletedNestedObjects = deleteMarkersExcludingFolder.length > 0;
37010
- }
37011
- if (hasDeletedNestedObjects && isFolderMarkerDeleted && folderMarkerVersionId) {
37012
- break;
37013
- }
37014
- if (!response.IsTruncated || !response.NextKeyMarker) {
37015
- break;
37016
- }
37017
- keyMarker = response.NextKeyMarker;
37018
- versionIdMarker = response.NextVersionIdMarker;
37019
- } while (true);
37020
- return {
37021
- prefix: folderPrefix,
37022
- hasDeletedContent: isFolderMarkerDeleted || hasDeletedNestedObjects,
37023
- isFolderDeleted: isFolderMarkerDeleted,
37024
- folderDeleteMarkerVersionId,
37025
- folderMarkerVersionId
37026
- };
37027
- } catch {
37028
- return {
37029
- prefix: folderPrefix,
37030
- hasDeletedContent: false,
37031
- isFolderDeleted: false
37032
- };
37765
+ const scanPrefix = input.prefix ?? longestCommonPrefix(input.folders ?? []);
37766
+ const acc = /* @__PURE__ */ new Map();
37767
+ const getOrCreate = /* @__PURE__ */ __name((folder) => {
37768
+ let entry = acc.get(folder);
37769
+ if (!entry) {
37770
+ entry = {
37771
+ hasDeletedNested: false,
37772
+ isFolderDeleted: false
37773
+ };
37774
+ acc.set(folder, entry);
37775
+ }
37776
+ return entry;
37777
+ }, "getOrCreate");
37778
+ let keyMarker;
37779
+ let versionIdMarker;
37780
+ let pages = 0;
37781
+ let stoppedAtKey;
37782
+ while (true) {
37783
+ if (ctx.req.signal?.aborted) {
37784
+ stoppedAtKey = keyMarker ?? "";
37785
+ break;
37786
+ }
37787
+ let response;
37788
+ try {
37789
+ response = await s3.send(new import_client_s34.ListObjectVersionsCommand({
37790
+ Bucket: input.bucket,
37791
+ Prefix: scanPrefix || void 0,
37792
+ MaxKeys: S3_MAX_KEYS_PER_REQUEST,
37793
+ KeyMarker: keyMarker,
37794
+ VersionIdMarker: versionIdMarker
37795
+ }), {
37796
+ abortSignal: ctx.req.signal
37797
+ });
37798
+ } catch (error) {
37799
+ if (ctx.req.signal?.aborted) {
37800
+ stoppedAtKey = keyMarker ?? "";
37801
+ break;
37033
37802
  }
37034
- }));
37035
- return results;
37036
- } catch (error) {
37037
- throw mapS3ErrorToTRPCError(error, {
37038
- operation: "check deleted content",
37039
- bucket: input.bucket
37040
- });
37803
+ console.error("checkDeletedContent scan failed", {
37804
+ bucket: input.bucket,
37805
+ prefix: scanPrefix,
37806
+ pages,
37807
+ error
37808
+ });
37809
+ throw mapS3ErrorToTRPCError(error, {
37810
+ operation: "check deleted content",
37811
+ bucket: input.bucket
37812
+ });
37813
+ }
37814
+ for (const v of response.Versions ?? []) {
37815
+ const key = v.Key ?? "";
37816
+ const folder = folderPrefixOf(key, scanPrefix);
37817
+ if (!folder) continue;
37818
+ if (key === folder) {
37819
+ const entry = getOrCreate(folder);
37820
+ const lastModified = v.LastModified?.getTime() ?? 0;
37821
+ if (entry.folderMarkerVersionId === void 0 || lastModified > (entry.folderMarkerLastModified ?? -1)) {
37822
+ entry.folderMarkerVersionId = v.VersionId;
37823
+ entry.folderMarkerLastModified = lastModified;
37824
+ }
37825
+ }
37826
+ }
37827
+ for (const dm of response.DeleteMarkers ?? []) {
37828
+ const key = dm.Key ?? "";
37829
+ const folder = folderPrefixOf(key, scanPrefix);
37830
+ if (!folder) continue;
37831
+ const entry = getOrCreate(folder);
37832
+ if (key === folder) {
37833
+ if (dm.IsLatest) {
37834
+ entry.isFolderDeleted = true;
37835
+ entry.folderDeleteMarkerVersionId = dm.VersionId;
37836
+ }
37837
+ } else if (dm.IsLatest === true) {
37838
+ entry.hasDeletedNested = true;
37839
+ }
37840
+ }
37841
+ pages++;
37842
+ if (!response.IsTruncated) {
37843
+ stoppedAtKey = void 0;
37844
+ break;
37845
+ }
37846
+ if (!response.NextKeyMarker) {
37847
+ stoppedAtKey = greatestKeyOnPage(response.Versions, response.DeleteMarkers) ?? keyMarker ?? "";
37848
+ break;
37849
+ }
37850
+ if (pages >= S3_MAX_SCAN_PAGES) {
37851
+ stoppedAtKey = response.NextKeyMarker;
37852
+ break;
37853
+ }
37854
+ keyMarker = response.NextKeyMarker;
37855
+ versionIdMarker = response.NextVersionIdMarker;
37041
37856
  }
37857
+ const folders = input.folders ?? [
37858
+ ...acc.keys()
37859
+ ];
37860
+ return checkDeletedContentOutputSchema.parse(folders.map((prefix) => {
37861
+ const entry = acc.get(prefix);
37862
+ return {
37863
+ prefix,
37864
+ hasDeletedContent: (entry?.isFolderDeleted ?? false) || (entry?.hasDeletedNested ?? false),
37865
+ isFolderDeleted: entry?.isFolderDeleted ?? false,
37866
+ folderDeleteMarkerVersionId: entry?.folderDeleteMarkerVersionId,
37867
+ folderMarkerVersionId: entry?.folderMarkerVersionId,
37868
+ isPartialScan: !isFolderCovered(prefix, stoppedAtKey)
37869
+ };
37870
+ }));
37042
37871
  })
37043
37872
  };
37044
37873
 
37045
37874
  // src/server/Storage/routers/ceph/bucketPolicyRouter.ts
37046
37875
  var import_client_s35 = require("@aws-sdk/client-s3");
37047
37876
  var import_server16 = require("@trpc/server");
37048
- var import_zod18 = require("zod");
37877
+ var import_zod17 = require("zod");
37049
37878
  var policySetRateLimits = /* @__PURE__ */ new Map();
37050
37879
  function checkPolicySetRateLimit(bucketName, projectId) {
37051
37880
  const key = `${projectId}:${bucketName}`;
@@ -37081,12 +37910,12 @@ function validateResourceARNsMatchBucket(policy, bucketName) {
37081
37910
  statement.Resource
37082
37911
  ];
37083
37912
  for (const resource of resources) {
37084
- const arnRegex = /^arn:aws:s3:::([^/]+)/;
37913
+ const arnRegex = /^arn:aws:s3::[^:/]*:([^/]+)/;
37085
37914
  const match = resource.match(arnRegex);
37086
37915
  if (!match) {
37087
37916
  throw new import_server16.TRPCError({
37088
37917
  code: "BAD_REQUEST",
37089
- message: `Policy Resource '${resource}' is not a valid S3 bucket ARN for bucket '${bucketName}'. Expected: arn:aws:s3:::${bucketName} or arn:aws:s3:::${bucketName}/*`
37918
+ message: `Policy Resource '${resource}' is not a valid S3 bucket ARN for bucket '${bucketName}'. Expected: arn:aws:s3:::${bucketName}, arn:aws:s3:::${bucketName}/* or arn:aws:s3::TENANT:${bucketName}`
37090
37919
  });
37091
37920
  }
37092
37921
  if (match[1] !== bucketName) {
@@ -37121,12 +37950,22 @@ function validatePolicySemantics(policy) {
37121
37950
  }
37122
37951
  }
37123
37952
  __name(validatePolicySemantics, "validatePolicySemantics");
37953
+ function parseStoredPolicy(policyText) {
37954
+ try {
37955
+ const result = bucketPolicyDocumentSchema.safeParse(JSON.parse(policyText));
37956
+ return result.success ? result.data : null;
37957
+ } catch {
37958
+ return null;
37959
+ }
37960
+ }
37961
+ __name(parseStoredPolicy, "parseStoredPolicy");
37124
37962
  var bucketPolicyRouter = {
37125
37963
  /**
37126
37964
  * Get the current bucket policy.
37127
37965
  *
37128
- * Returns the policy as both a parsed object and raw JSON string.
37129
- * Returns null if no policy is set (not an error).
37966
+ * Returns the policy as both a parsed object and raw JSON string. `policyText` is always the
37967
+ * document exactly as RGW stored it; `policy` is null when no policy is set, and also when the
37968
+ * stored document doesn't fit our schema — the editor shows the raw text either way.
37130
37969
  *
37131
37970
  * @throws TRPCError NOT_FOUND - bucket does not exist
37132
37971
  * @throws TRPCError FORBIDDEN - no credentials or access denied
@@ -37145,10 +37984,8 @@ var bucketPolicyRouter = {
37145
37984
  policyText: null
37146
37985
  };
37147
37986
  }
37148
- const policyObj = JSON.parse(policyText);
37149
- const policy = bucketPolicyDocumentSchema.parse(policyObj);
37150
37987
  return {
37151
- policy,
37988
+ policy: parseStoredPolicy(policyText),
37152
37989
  policyText
37153
37990
  };
37154
37991
  } catch (error) {
@@ -37210,7 +38047,7 @@ var bucketPolicyRouter = {
37210
38047
  message: "Invalid JSON format"
37211
38048
  });
37212
38049
  }
37213
- if (error instanceof import_zod18.z.ZodError) {
38050
+ if (error instanceof import_zod17.z.ZodError) {
37214
38051
  const zodError = error;
37215
38052
  const firstError = zodError.issues?.[0];
37216
38053
  if (!firstError) {
@@ -37857,40 +38694,40 @@ var buildObjectStorageRouters = /* @__PURE__ */ __name((policyDir) => ({
37857
38694
  }), "buildObjectStorageRouters");
37858
38695
 
37859
38696
  // src/server/Project/routers/projectRouter.ts
37860
- var import_zod20 = require("zod");
38697
+ var import_zod19 = require("zod");
37861
38698
  var import_server19 = require("@trpc/server");
37862
38699
 
37863
38700
  // src/server/Project/types/models.ts
37864
- var import_zod19 = require("zod");
37865
- var baseProjectSchema = import_zod19.z.object({
37866
- id: import_zod19.z.string(),
37867
- name: import_zod19.z.string(),
37868
- enabled: import_zod19.z.boolean(),
37869
- domain_id: import_zod19.z.string().nullable().optional(),
37870
- parent_id: import_zod19.z.string().nullable().optional(),
37871
- is_domain: import_zod19.z.boolean().nullable().optional(),
37872
- tags: import_zod19.z.array(import_zod19.z.string()).nullable().optional(),
37873
- options: import_zod19.z.record(import_zod19.z.string(), import_zod19.z.string().or(import_zod19.z.boolean())).nullable().optional(),
37874
- description: import_zod19.z.string().nullable().optional(),
37875
- links: import_zod19.z.object({
37876
- self: import_zod19.z.string().url().nullable().optional()
38701
+ var import_zod18 = require("zod");
38702
+ var baseProjectSchema = import_zod18.z.object({
38703
+ id: import_zod18.z.string(),
38704
+ name: import_zod18.z.string(),
38705
+ enabled: import_zod18.z.boolean(),
38706
+ domain_id: import_zod18.z.string().nullable().optional(),
38707
+ parent_id: import_zod18.z.string().nullable().optional(),
38708
+ is_domain: import_zod18.z.boolean().nullable().optional(),
38709
+ tags: import_zod18.z.array(import_zod18.z.string()).nullable().optional(),
38710
+ options: import_zod18.z.record(import_zod18.z.string(), import_zod18.z.string().or(import_zod18.z.boolean())).nullable().optional(),
38711
+ description: import_zod18.z.string().nullable().optional(),
38712
+ links: import_zod18.z.object({
38713
+ self: import_zod18.z.string().url().nullable().optional()
37877
38714
  }).optional()
37878
38715
  });
37879
38716
  var projectSchema = baseProjectSchema.extend({
37880
- domain_name: import_zod19.z.string().optional(),
37881
- parents: import_zod19.z.array(import_zod19.z.object({
38717
+ domain_name: import_zod18.z.string().optional(),
38718
+ parents: import_zod18.z.array(import_zod18.z.object({
37882
38719
  project: baseProjectSchema
37883
38720
  })).optional()
37884
38721
  });
37885
- var projectResponseSchema = import_zod19.z.object({
38722
+ var projectResponseSchema = import_zod18.z.object({
37886
38723
  project: projectSchema
37887
38724
  });
37888
- var projectsResponseSchema = import_zod19.z.object({
37889
- projects: import_zod19.z.array(projectSchema),
37890
- links: import_zod19.z.object({
37891
- self: import_zod19.z.string().url().optional(),
37892
- previous: import_zod19.z.string().url().nullable().optional(),
37893
- next: import_zod19.z.string().url().nullable().optional()
38725
+ var projectsResponseSchema = import_zod18.z.object({
38726
+ projects: import_zod18.z.array(projectSchema),
38727
+ links: import_zod18.z.object({
38728
+ self: import_zod18.z.string().url().optional(),
38729
+ previous: import_zod18.z.string().url().nullable().optional(),
38730
+ next: import_zod18.z.string().url().nullable().optional()
37894
38731
  }).optional()
37895
38732
  });
37896
38733
 
@@ -37977,10 +38814,10 @@ var projectRouter = {
37977
38814
  return void 0;
37978
38815
  }
37979
38816
  const domainsData = domainsResponse ? await domainsResponse.json().catch(() => null) : null;
37980
- const domainsResponseSchema = import_zod20.z.object({
37981
- domains: import_zod20.z.array(import_zod20.z.object({
37982
- id: import_zod20.z.string(),
37983
- name: import_zod20.z.string()
38817
+ const domainsResponseSchema = import_zod19.z.object({
38818
+ domains: import_zod19.z.array(import_zod19.z.object({
38819
+ id: import_zod19.z.string(),
38820
+ name: import_zod19.z.string()
37984
38821
  }))
37985
38822
  });
37986
38823
  const parsedDomains = domainsResponseSchema.safeParse(domainsData);
@@ -38000,8 +38837,8 @@ var projectRouter = {
38000
38837
  * filtered by the optional search term. Uses the OpenStack /v3/auth/projects
38001
38838
  * endpoint which works with any valid token.
38002
38839
  */
38003
- searchProjects: protectedProcedure.input(import_zod20.z.object({
38004
- search: import_zod20.z.string().optional()
38840
+ searchProjects: protectedProcedure.input(import_zod19.z.object({
38841
+ search: import_zod19.z.string().optional()
38005
38842
  }).optional()).query(async ({ ctx, input }) => {
38006
38843
  if (!ctx.openstack) {
38007
38844
  throw new import_server19.TRPCError({
@@ -38031,8 +38868,65 @@ var projectRouter = {
38031
38868
  projects.sort((a, b) => a.name.localeCompare(b.name));
38032
38869
  return projects;
38033
38870
  }),
38034
- getProject: protectedProcedure.input(import_zod20.z.object({
38035
- projectId: import_zod20.z.string()
38871
+ /**
38872
+ * List projects with unified search functionality
38873
+ *
38874
+ * Returns all projects that the authenticated user has access to,
38875
+ * filtered by ID, name, and description using the standard filterBySearchParams helper.
38876
+ * This endpoint provides consistent search behavior with other resources in the system.
38877
+ */
38878
+ listProjectsWithSearch: protectedProcedure.input(import_zod19.z.object({
38879
+ searchTerm: import_zod19.z.string().optional()
38880
+ })).query(async ({ ctx, input }) => {
38881
+ if (!ctx.openstack) {
38882
+ throw new import_server19.TRPCError({
38883
+ code: "UNAUTHORIZED",
38884
+ message: "No authenticated session"
38885
+ });
38886
+ }
38887
+ const token = ctx.openstack.getToken();
38888
+ if (!token?.authToken) {
38889
+ throw new import_server19.TRPCError({
38890
+ code: "UNAUTHORIZED",
38891
+ message: "No auth token available"
38892
+ });
38893
+ }
38894
+ const [response, domainsResponse] = await Promise.all([
38895
+ callIdentityAPI(ctx.identityEndpoint, token.authToken, "auth/projects"),
38896
+ callIdentityAPI(ctx.identityEndpoint, token.authToken, "auth/domains").catch(() => null)
38897
+ ]);
38898
+ const data = await response.json();
38899
+ const parsedData = projectsResponseSchema.safeParse(data);
38900
+ if (!parsedData.success) {
38901
+ console.error("Zod Parsing Error:", parsedData.error.message);
38902
+ return void 0;
38903
+ }
38904
+ const domainsData = domainsResponse ? await domainsResponse.json().catch(() => null) : null;
38905
+ const domainsResponseSchema = import_zod19.z.object({
38906
+ domains: import_zod19.z.array(import_zod19.z.object({
38907
+ id: import_zod19.z.string(),
38908
+ name: import_zod19.z.string()
38909
+ }))
38910
+ });
38911
+ const parsedDomains = domainsResponseSchema.safeParse(domainsData);
38912
+ const domainMap = new Map((parsedDomains.success ? parsedDomains.data.domains : []).map((d) => [
38913
+ d.id,
38914
+ d.name
38915
+ ]));
38916
+ const projectsWithDomain = parsedData.data.projects.map((project) => ({
38917
+ ...project,
38918
+ domain_name: project.domain_id ? domainMap.get(project.domain_id) ?? void 0 : void 0
38919
+ }));
38920
+ return filterBySearchParams(projectsWithDomain, input.searchTerm, [
38921
+ "id",
38922
+ "name",
38923
+ "description",
38924
+ "domain_name",
38925
+ "domain_id"
38926
+ ]);
38927
+ }),
38928
+ getProject: protectedProcedure.input(import_zod19.z.object({
38929
+ projectId: import_zod19.z.string()
38036
38930
  })).query(async ({ ctx, input }) => {
38037
38931
  if (!ctx.openstack) {
38038
38932
  throw new import_server19.TRPCError({
@@ -38142,16 +39036,16 @@ __name(validateOpenstackService, "validateOpenstackService");
38142
39036
  var import_signal_openstack7 = __toESM(require_esm());
38143
39037
 
38144
39038
  // src/server/Network/types/floatingIp.ts
38145
- var import_zod22 = require("zod");
39039
+ var import_zod21 = require("zod");
38146
39040
 
38147
39041
  // src/server/Network/types/index.ts
38148
- var import_zod21 = require("zod");
38149
- var ISO8601TimestampSchema = import_zod21.z.string().brand("ISO8601Timestamp");
38150
- var SortDirSchema = import_zod21.z.enum([
39042
+ var import_zod20 = require("zod");
39043
+ var ISO8601TimestampSchema = import_zod20.z.string().brand("ISO8601Timestamp");
39044
+ var SortDirSchema = import_zod20.z.enum([
38151
39045
  "asc",
38152
39046
  "desc"
38153
39047
  ]);
38154
- var NetworkPortStatusSchema = import_zod21.z.enum([
39048
+ var NetworkPortStatusSchema = import_zod20.z.enum([
38155
39049
  "ACTIVE",
38156
39050
  "DOWN",
38157
39051
  "BUILD",
@@ -38159,153 +39053,153 @@ var NetworkPortStatusSchema = import_zod21.z.enum([
38159
39053
  ]);
38160
39054
 
38161
39055
  // src/server/Network/types/floatingIp.ts
38162
- var FloatingIpStatusSchema = import_zod22.z.enum([
39056
+ var FloatingIpStatusSchema = import_zod21.z.enum([
38163
39057
  "ACTIVE",
38164
39058
  "DOWN",
38165
39059
  "ERROR"
38166
39060
  ]);
38167
- var PortDetailsSchema = import_zod22.z.object({
39061
+ var PortDetailsSchema = import_zod21.z.object({
38168
39062
  /** The status of the port */
38169
39063
  status: FloatingIpStatusSchema,
38170
39064
  /** The name of the port */
38171
- name: import_zod22.z.string(),
39065
+ name: import_zod21.z.string(),
38172
39066
  /** Administrative state of the port (true = UP, false = DOWN) */
38173
- admin_state_up: import_zod22.z.boolean(),
39067
+ admin_state_up: import_zod21.z.boolean(),
38174
39068
  /** The ID of the network the port is attached to */
38175
- network_id: import_zod22.z.string(),
39069
+ network_id: import_zod21.z.string(),
38176
39070
  /** The owner of the port device */
38177
- device_owner: import_zod22.z.string(),
39071
+ device_owner: import_zod21.z.string(),
38178
39072
  /** The MAC address of the port */
38179
- mac_address: import_zod22.z.string(),
39073
+ mac_address: import_zod21.z.string(),
38180
39074
  /** The ID of the device the port is attached to */
38181
- device_id: import_zod22.z.string()
39075
+ device_id: import_zod21.z.string()
38182
39076
  });
38183
- var PortForwardingSchema = import_zod22.z.object({
39077
+ var PortForwardingSchema = import_zod21.z.object({
38184
39078
  /** Port forwarding protocol */
38185
- protocol: import_zod22.z.enum([
39079
+ protocol: import_zod21.z.enum([
38186
39080
  "tcp",
38187
39081
  "udp"
38188
39082
  ]),
38189
39083
  /** The internal fixed IP address associated with the port forwarding */
38190
- internal_ip_address: import_zod22.z.string(),
39084
+ internal_ip_address: import_zod21.z.string(),
38191
39085
  /** The internal port number or starting port of the range */
38192
- internal_port: import_zod22.z.number().optional(),
39086
+ internal_port: import_zod21.z.number().optional(),
38193
39087
  /** The internal port range (e.g., "1024:2048") */
38194
- internal_port_range: import_zod22.z.string().optional(),
39088
+ internal_port_range: import_zod21.z.string().optional(),
38195
39089
  /** The internal port ID for the port forwarding (requires floating-ip-port-forwarding-detail extension) */
38196
- internal_port_id: import_zod22.z.string().optional(),
39090
+ internal_port_id: import_zod21.z.string().optional(),
38197
39091
  /** The external port number or starting port of the range */
38198
- external_port: import_zod22.z.number().optional(),
39092
+ external_port: import_zod21.z.number().optional(),
38199
39093
  /** The external port range (e.g., "8000:9000") */
38200
- external_port_range: import_zod22.z.string().optional(),
39094
+ external_port_range: import_zod21.z.string().optional(),
38201
39095
  /** The ID of the port forwarding resource */
38202
- id: import_zod22.z.string(),
39096
+ id: import_zod21.z.string(),
38203
39097
  /** Description of the port forwarding rule (requires floating-ip-port-forwarding-description extension) */
38204
- description: import_zod22.z.string().nullable().optional()
39098
+ description: import_zod21.z.string().nullable().optional()
38205
39099
  });
38206
- var FloatingIpSchema = import_zod22.z.object({
39100
+ var FloatingIpSchema = import_zod21.z.object({
38207
39101
  /** The ID of the router for the floating IP (null if not associated) */
38208
- router_id: import_zod22.z.string().nullable(),
39102
+ router_id: import_zod21.z.string().nullable(),
38209
39103
  /** A human-readable description for the resource */
38210
- description: import_zod22.z.string().nullable().optional(),
39104
+ description: import_zod21.z.string().nullable().optional(),
38211
39105
  /** Whether this is a distributed floating IP (requires floating-ip-distributed extension) */
38212
- distributed: import_zod22.z.boolean().optional(),
39106
+ distributed: import_zod21.z.boolean().optional(),
38213
39107
  /** A valid DNS domain (requires dns-integration extension) */
38214
- dns_domain: import_zod22.z.string().optional(),
39108
+ dns_domain: import_zod21.z.string().optional(),
38215
39109
  /** A valid DNS name (requires dns-integration extension) */
38216
- dns_name: import_zod22.z.string().optional(),
39110
+ dns_name: import_zod21.z.string().optional(),
38217
39111
  /** Time at which the resource was created (UTC ISO8601 format, requires standard-attr-timestamp extension) */
38218
39112
  created_at: ISO8601TimestampSchema.optional(),
38219
39113
  /** Time at which the resource was last updated (UTC ISO8601 format, requires standard-attr-timestamp extension) */
38220
39114
  updated_at: ISO8601TimestampSchema.optional(),
38221
39115
  /** The revision number of the resource (set by server, for concurrency control) */
38222
- revision_number: import_zod22.z.number(),
39116
+ revision_number: import_zod21.z.number(),
38223
39117
  /** The ID of the project that owns the floating IP */
38224
- project_id: import_zod22.z.string(),
39118
+ project_id: import_zod21.z.string(),
38225
39119
  /** The ID of the tenant (deprecated, use project_id) */
38226
- tenant_id: import_zod22.z.string(),
39120
+ tenant_id: import_zod21.z.string(),
38227
39121
  /** The ID of the network associated with the floating IP */
38228
- floating_network_id: import_zod22.z.string(),
39122
+ floating_network_id: import_zod21.z.string(),
38229
39123
  /** The fixed IP address associated with the floating IP (null if not associated) */
38230
- fixed_ip_address: import_zod22.z.string().nullable(),
39124
+ fixed_ip_address: import_zod21.z.string().nullable(),
38231
39125
  /** The floating IP address */
38232
- floating_ip_address: import_zod22.z.string(),
39126
+ floating_ip_address: import_zod21.z.string(),
38233
39127
  /** The ID of the port associated with the floating IP (null if not associated) */
38234
- port_id: import_zod22.z.string().nullable(),
39128
+ port_id: import_zod21.z.string().nullable(),
38235
39129
  /** The ID of the floating IP address */
38236
- id: import_zod22.z.string(),
39130
+ id: import_zod21.z.string(),
38237
39131
  /** The status of the floating IP */
38238
39132
  status: FloatingIpStatusSchema,
38239
39133
  /** The information of the associated port (null if not associated, requires fip-port-details extension) */
38240
39134
  port_details: PortDetailsSchema.nullable().optional(),
38241
39135
  /** The list of tags on the resource (requires standard-attr-tag extension) */
38242
- tags: import_zod22.z.array(import_zod22.z.string()).optional(),
39136
+ tags: import_zod21.z.array(import_zod21.z.string()).optional(),
38243
39137
  /** The associated port forwarding resources for the floating IP (requires expose-port-forwarding-in-fip extension) */
38244
- port_forwardings: import_zod22.z.array(PortForwardingSchema).nullish(),
39138
+ port_forwardings: import_zod21.z.array(PortForwardingSchema).nullish(),
38245
39139
  /** The ID of the QoS policy of the network where this floating IP is plugged (requires qos-fip extension) */
38246
- qos_network_policy_id: import_zod22.z.string().optional(),
39140
+ qos_network_policy_id: import_zod21.z.string().optional(),
38247
39141
  /** The ID of the QoS policy associated with the floating IP (requires qos extension) */
38248
- qos_policy_id: import_zod22.z.string().optional()
38249
- });
38250
- var FloatingIpIdInputSchema = import_zod22.z.object({
38251
- project_id: import_zod22.z.string(),
38252
- floatingip_id: import_zod22.z.string()
38253
- });
38254
- var FloatingIpCreateRequestSchema = import_zod22.z.object({
38255
- project_id: import_zod22.z.string().trim().min(1, "project_id must be a non-empty string"),
38256
- floating_network_id: import_zod22.z.string(),
38257
- fixed_ip_address: import_zod22.z.string().optional(),
38258
- floating_ip_address: import_zod22.z.string().optional(),
38259
- port_id: import_zod22.z.string().optional(),
38260
- subnet_id: import_zod22.z.string().optional(),
38261
- distributed: import_zod22.z.boolean().optional(),
38262
- description: import_zod22.z.string().optional(),
38263
- dns_domain: import_zod22.z.string().optional(),
38264
- dns_name: import_zod22.z.string().optional(),
38265
- qos_policy_id: import_zod22.z.string().optional()
38266
- });
38267
- var FloatingIpUpdateRequestSchema = import_zod22.z.object({
38268
- project_id: import_zod22.z.string(),
38269
- floatingip_id: import_zod22.z.string(),
38270
- port_id: import_zod22.z.string().nullable(),
38271
- fixed_ip_address: import_zod22.z.string().optional(),
38272
- description: import_zod22.z.string().optional(),
38273
- distributed: import_zod22.z.boolean().optional()
38274
- });
38275
- var FloatingIpResponseSchema = import_zod22.z.object({
39142
+ qos_policy_id: import_zod21.z.string().optional()
39143
+ });
39144
+ var FloatingIpIdInputSchema = import_zod21.z.object({
39145
+ project_id: import_zod21.z.string(),
39146
+ floatingip_id: import_zod21.z.string()
39147
+ });
39148
+ var FloatingIpCreateRequestSchema = import_zod21.z.object({
39149
+ project_id: import_zod21.z.string().trim().min(1, "project_id must be a non-empty string"),
39150
+ floating_network_id: import_zod21.z.string(),
39151
+ fixed_ip_address: import_zod21.z.string().optional(),
39152
+ floating_ip_address: import_zod21.z.string().optional(),
39153
+ port_id: import_zod21.z.string().optional(),
39154
+ subnet_id: import_zod21.z.string().optional(),
39155
+ distributed: import_zod21.z.boolean().optional(),
39156
+ description: import_zod21.z.string().optional(),
39157
+ dns_domain: import_zod21.z.string().optional(),
39158
+ dns_name: import_zod21.z.string().optional(),
39159
+ qos_policy_id: import_zod21.z.string().optional()
39160
+ });
39161
+ var FloatingIpUpdateRequestSchema = import_zod21.z.object({
39162
+ project_id: import_zod21.z.string(),
39163
+ floatingip_id: import_zod21.z.string(),
39164
+ port_id: import_zod21.z.string().nullable(),
39165
+ fixed_ip_address: import_zod21.z.string().optional(),
39166
+ description: import_zod21.z.string().optional(),
39167
+ distributed: import_zod21.z.boolean().optional()
39168
+ });
39169
+ var FloatingIpResponseSchema = import_zod21.z.object({
38276
39170
  floatingip: FloatingIpSchema
38277
39171
  });
38278
- var FloatingIpListResponseSchema = import_zod22.z.object({
39172
+ var FloatingIpListResponseSchema = import_zod21.z.object({
38279
39173
  /** A list of floating IP objects */
38280
- floatingips: import_zod22.z.array(FloatingIpSchema)
39174
+ floatingips: import_zod21.z.array(FloatingIpSchema)
38281
39175
  });
38282
- var FloatingIpQueryParametersSchema = import_zod22.z.object({
39176
+ var FloatingIpQueryParametersSchema = import_zod21.z.object({
38283
39177
  /** Project ID for rescoping (required for projectScopedProcedure) */
38284
- project_id: import_zod22.z.string(),
39178
+ project_id: import_zod21.z.string(),
38285
39179
  /** Filter by the ID of the floating IP */
38286
- id: import_zod22.z.string().optional(),
39180
+ id: import_zod21.z.string().optional(),
38287
39181
  /** Filter by the ID of the router for the floating IP */
38288
- router_id: import_zod22.z.string().nullable().optional(),
39182
+ router_id: import_zod21.z.string().nullable().optional(),
38289
39183
  /** Filter by the status of the floating IP */
38290
39184
  status: FloatingIpStatusSchema.optional(),
38291
39185
  /** Filter by the ID of the project that owns the resource */
38292
- tenant_id: import_zod22.z.string().optional(),
39186
+ tenant_id: import_zod21.z.string().optional(),
38293
39187
  /** Filter by the revision number of the resource */
38294
- revision_number: import_zod22.z.number().optional(),
39188
+ revision_number: import_zod21.z.number().optional(),
38295
39189
  /** Filter by the human-readable description of the resource */
38296
- description: import_zod22.z.string().nullable().optional(),
39190
+ description: import_zod21.z.string().nullable().optional(),
38297
39191
  /** Filter by the ID of the network associated with the floating IP */
38298
- floating_network_id: import_zod22.z.string().optional(),
39192
+ floating_network_id: import_zod21.z.string().optional(),
38299
39193
  /** Filter by the fixed IP address associated with the floating IP */
38300
- fixed_ip_address: import_zod22.z.string().optional(),
39194
+ fixed_ip_address: import_zod21.z.string().optional(),
38301
39195
  /** Filter by the floating IP address */
38302
- floating_ip_address: import_zod22.z.string().optional(),
39196
+ floating_ip_address: import_zod21.z.string().optional(),
38303
39197
  /** Filter by the ID of a port associated with the floating IP */
38304
- port_id: import_zod22.z.string().nullable().optional(),
39198
+ port_id: import_zod21.z.string().nullable().optional(),
38305
39199
  /** Sort direction (asc or desc) */
38306
39200
  sort_dir: SortDirSchema.optional(),
38307
39201
  /** Sort key - valid keys: fixed_ip_address, floating_ip_address, floating_network_id, id, router_id, status, tenant_id, project_id */
38308
- sort_key: import_zod22.z.enum([
39202
+ sort_key: import_zod21.z.enum([
38309
39203
  "fixed_ip_address",
38310
39204
  "floating_ip_address",
38311
39205
  "floating_network_id",
@@ -38316,73 +39210,73 @@ var FloatingIpQueryParametersSchema = import_zod22.z.object({
38316
39210
  "project_id"
38317
39211
  ]).optional(),
38318
39212
  /** Filter by tags (comma-separated, resources must match all tags) - requires standard-attr-tag extension */
38319
- tags: import_zod22.z.array(import_zod22.z.string()).optional(),
39213
+ tags: import_zod21.z.array(import_zod21.z.string()).optional(),
38320
39214
  /** Filter by tags with OR logic (comma-separated, resources match any tag) */
38321
- "tags-any": import_zod22.z.string().optional(),
39215
+ "tags-any": import_zod21.z.string().optional(),
38322
39216
  /** Exclude resources with these tags (comma-separated, resources must match all excluded tags) */
38323
- "not-tags": import_zod22.z.string().optional(),
39217
+ "not-tags": import_zod21.z.string().optional(),
38324
39218
  /** Exclude resources with these tags using OR logic (comma-separated, resources match any excluded tag) */
38325
- "not-tags-any": import_zod22.z.string().optional(),
39219
+ "not-tags-any": import_zod21.z.string().optional(),
38326
39220
  /** Specific fields to return (can be repeated) */
38327
- fields: import_zod22.z.union([
38328
- import_zod22.z.string(),
38329
- import_zod22.z.array(import_zod22.z.string())
39221
+ fields: import_zod21.z.union([
39222
+ import_zod21.z.string(),
39223
+ import_zod21.z.array(import_zod21.z.string())
38330
39224
  ]).optional(),
38331
39225
  /** Pagination limit */
38332
- limit: import_zod22.z.number().optional(),
39226
+ limit: import_zod21.z.number().optional(),
38333
39227
  /** Pagination marker (ID of the last item in the previous list) */
38334
- marker: import_zod22.z.string().optional(),
39228
+ marker: import_zod21.z.string().optional(),
38335
39229
  /** Pagination direction (false = ascending, true = descending) */
38336
- page_reverse: import_zod22.z.boolean().optional(),
39230
+ page_reverse: import_zod21.z.boolean().optional(),
38337
39231
  // BFF-side search (filtered in BFF layer, not sent to OpenStack)
38338
- searchTerm: import_zod22.z.string().optional()
38339
- });
38340
- var ExternalNetworksQuerySchema = import_zod22.z.object({
38341
- project_id: import_zod22.z.string(),
38342
- "router:external": import_zod22.z.literal(true).default(true)
38343
- });
38344
- var ExternalNetworkSchema = import_zod22.z.object({
38345
- id: import_zod22.z.string(),
38346
- name: import_zod22.z.string(),
38347
- project_id: import_zod22.z.string(),
38348
- "router:external": import_zod22.z.boolean().optional(),
38349
- shared: import_zod22.z.boolean(),
39232
+ searchTerm: import_zod21.z.string().optional()
39233
+ });
39234
+ var ExternalNetworksQuerySchema = import_zod21.z.object({
39235
+ project_id: import_zod21.z.string(),
39236
+ "router:external": import_zod21.z.literal(true).default(true)
39237
+ });
39238
+ var ExternalNetworkSchema = import_zod21.z.object({
39239
+ id: import_zod21.z.string(),
39240
+ name: import_zod21.z.string(),
39241
+ project_id: import_zod21.z.string(),
39242
+ "router:external": import_zod21.z.boolean().optional(),
39243
+ shared: import_zod21.z.boolean(),
38350
39244
  status: NetworkPortStatusSchema,
38351
39245
  updated_at: ISO8601TimestampSchema.optional(),
38352
- is_default: import_zod22.z.boolean().optional()
39246
+ is_default: import_zod21.z.boolean().optional()
38353
39247
  });
38354
- var ExternalNetworksResponseSchema = import_zod22.z.object({
38355
- networks: import_zod22.z.array(ExternalNetworkSchema)
39248
+ var ExternalNetworksResponseSchema = import_zod21.z.object({
39249
+ networks: import_zod21.z.array(ExternalNetworkSchema)
38356
39250
  });
38357
- var DnsDomainSchema = import_zod22.z.object({
38358
- id: import_zod22.z.string(),
38359
- name: import_zod22.z.string()
39251
+ var DnsDomainSchema = import_zod21.z.object({
39252
+ id: import_zod21.z.string(),
39253
+ name: import_zod21.z.string()
38360
39254
  });
38361
- var DnsDomainResponseSchema = import_zod22.z.object({
38362
- zones: import_zod22.z.array(DnsDomainSchema)
39255
+ var DnsDomainResponseSchema = import_zod21.z.object({
39256
+ zones: import_zod21.z.array(DnsDomainSchema)
38363
39257
  });
38364
- var AvailablePortsQuerySchema = import_zod22.z.object({
38365
- project_id: import_zod22.z.string(),
39258
+ var AvailablePortsQuerySchema = import_zod21.z.object({
39259
+ project_id: import_zod21.z.string(),
38366
39260
  /** Only ACTIVE ports are eligible for floating IP association */
38367
- status: import_zod22.z.literal("ACTIVE").default("ACTIVE"),
39261
+ status: import_zod21.z.literal("ACTIVE").default("ACTIVE"),
38368
39262
  /** Only administratively UP ports are eligible for floating IP association */
38369
- admin_state_up: import_zod22.z.literal(true).default(true)
39263
+ admin_state_up: import_zod21.z.literal(true).default(true)
38370
39264
  });
38371
- var AvailablePortSchema = import_zod22.z.object({
39265
+ var AvailablePortSchema = import_zod21.z.object({
38372
39266
  /** The ID of the port */
38373
- id: import_zod22.z.string(),
39267
+ id: import_zod21.z.string(),
38374
39268
  /** Human-readable name of the port */
38375
- name: import_zod22.z.string().nullable().optional(),
39269
+ name: import_zod21.z.string().nullable().optional(),
38376
39270
  /** List of fixed IPs assigned to the port, each entry pairs an IP address with its subnet. */
38377
- fixed_ips: import_zod22.z.array(import_zod22.z.object({
39271
+ fixed_ips: import_zod21.z.array(import_zod21.z.object({
38378
39272
  /** The fixed IP address assigned to the port */
38379
- ip_address: import_zod22.z.string(),
39273
+ ip_address: import_zod21.z.string(),
38380
39274
  /** The ID of the subnet the IP belongs to */
38381
- subnet_id: import_zod22.z.string().optional()
39275
+ subnet_id: import_zod21.z.string().optional()
38382
39276
  })).optional()
38383
39277
  });
38384
- var AvailablePortsResponseSchema = import_zod22.z.object({
38385
- ports: import_zod22.z.array(AvailablePortSchema)
39278
+ var AvailablePortsResponseSchema = import_zod21.z.object({
39279
+ ports: import_zod21.z.array(AvailablePortSchema)
38386
39280
  });
38387
39281
 
38388
39282
  // src/server/Network/helpers/errorHandling.ts
@@ -38490,6 +39384,7 @@ var floatingIpRouter = {
38490
39384
  const data = await response.json();
38491
39385
  const { floatingips } = parseOrThrow(FloatingIpListResponseSchema, data, "floatingIpRouter.list");
38492
39386
  return filterBySearchParams(floatingips, searchTerm, [
39387
+ "id",
38493
39388
  "description",
38494
39389
  "floating_ip_address",
38495
39390
  "fixed_ip_address",
@@ -38643,110 +39538,112 @@ var floatingIpRouter = {
38643
39538
  };
38644
39539
 
38645
39540
  // src/server/Network/types/securityGroup.ts
38646
- var import_zod23 = require("zod");
38647
- var securityGroupRuleSchema = import_zod23.z.object({
38648
- id: import_zod23.z.string(),
38649
- direction: import_zod23.z.enum([
39541
+ var import_zod22 = require("zod");
39542
+ var securityGroupRuleSchema = import_zod22.z.object({
39543
+ id: import_zod22.z.string(),
39544
+ direction: import_zod22.z.enum([
38650
39545
  "ingress",
38651
39546
  "egress"
38652
39547
  ]).optional(),
38653
- ethertype: import_zod23.z.enum([
39548
+ ethertype: import_zod22.z.enum([
38654
39549
  "IPv4",
38655
39550
  "IPv6"
38656
39551
  ]).optional(),
38657
- description: import_zod23.z.string().nullable().optional(),
38658
- security_group_id: import_zod23.z.string().optional(),
38659
- protocol: import_zod23.z.string().nullable().optional(),
38660
- port_range_min: import_zod23.z.number().nullable().optional(),
38661
- port_range_max: import_zod23.z.number().nullable().optional(),
38662
- remote_ip_prefix: import_zod23.z.string().nullable().optional(),
38663
- remote_group_id: import_zod23.z.string().nullable().optional(),
38664
- remote_address_group_id: import_zod23.z.string().nullable().optional(),
38665
- tenant_id: import_zod23.z.string().nullable().optional(),
38666
- project_id: import_zod23.z.string().nullable().optional(),
38667
- revision_number: import_zod23.z.number().optional(),
38668
- tags: import_zod23.z.array(import_zod23.z.string()).optional(),
38669
- created_at: import_zod23.z.string().optional(),
38670
- updated_at: import_zod23.z.string().nullable().optional()
39552
+ description: import_zod22.z.string().nullable().optional(),
39553
+ security_group_id: import_zod22.z.string().optional(),
39554
+ protocol: import_zod22.z.string().nullable().optional(),
39555
+ port_range_min: import_zod22.z.number().nullable().optional(),
39556
+ port_range_max: import_zod22.z.number().nullable().optional(),
39557
+ remote_ip_prefix: import_zod22.z.string().nullable().optional(),
39558
+ remote_group_id: import_zod22.z.string().nullable().optional(),
39559
+ remote_address_group_id: import_zod22.z.string().nullable().optional(),
39560
+ tenant_id: import_zod22.z.string().nullable().optional(),
39561
+ project_id: import_zod22.z.string().nullable().optional(),
39562
+ revision_number: import_zod22.z.number().optional(),
39563
+ tags: import_zod22.z.array(import_zod22.z.string()).optional(),
39564
+ created_at: import_zod22.z.string().optional(),
39565
+ updated_at: import_zod22.z.string().nullable().optional()
38671
39566
  });
38672
- var securityGroupSchema = import_zod23.z.object({
38673
- id: import_zod23.z.string(),
38674
- name: import_zod23.z.string().nullable().optional(),
38675
- description: import_zod23.z.string().nullable().optional(),
38676
- tenant_id: import_zod23.z.string().nullable().optional(),
38677
- project_id: import_zod23.z.string().nullable().optional(),
38678
- stateful: import_zod23.z.boolean().optional(),
38679
- shared: import_zod23.z.boolean().optional(),
38680
- tags: import_zod23.z.array(import_zod23.z.string()).optional(),
38681
- security_group_rules: import_zod23.z.array(securityGroupRuleSchema).optional(),
38682
- revision_number: import_zod23.z.number().optional(),
38683
- created_at: import_zod23.z.string().optional(),
38684
- updated_at: import_zod23.z.string().nullable().optional()
38685
- });
38686
- var securityGroupsResponseSchema = import_zod23.z.object({
38687
- security_groups: import_zod23.z.array(securityGroupSchema)
38688
- });
38689
- var securityGroupResponseSchema = import_zod23.z.object({
39567
+ var securityGroupSchema = import_zod22.z.object({
39568
+ id: import_zod22.z.string(),
39569
+ name: import_zod22.z.string().nullable().optional(),
39570
+ description: import_zod22.z.string().nullable().optional(),
39571
+ tenant_id: import_zod22.z.string().nullable().optional(),
39572
+ project_id: import_zod22.z.string().nullable().optional(),
39573
+ stateful: import_zod22.z.boolean().optional(),
39574
+ shared: import_zod22.z.boolean().optional(),
39575
+ tags: import_zod22.z.array(import_zod22.z.string()).optional(),
39576
+ security_group_rules: import_zod22.z.array(securityGroupRuleSchema).optional(),
39577
+ revision_number: import_zod22.z.number().optional(),
39578
+ created_at: import_zod22.z.string().optional(),
39579
+ updated_at: import_zod22.z.string().nullable().optional()
39580
+ });
39581
+ var securityGroupsResponseSchema = import_zod22.z.object({
39582
+ security_groups: import_zod22.z.array(securityGroupSchema)
39583
+ });
39584
+ var securityGroupResponseSchema = import_zod22.z.object({
38690
39585
  security_group: securityGroupSchema
38691
39586
  });
38692
- var securityGroupRuleResponseSchema = import_zod23.z.object({
39587
+ var securityGroupRuleResponseSchema = import_zod22.z.object({
38693
39588
  security_group_rule: securityGroupRuleSchema
38694
39589
  });
38695
39590
  var listSecurityGroupsInputSchema = projectScopedInputSchema.extend({
38696
39591
  // Sorting
38697
- sort_key: import_zod23.z.string().optional(),
39592
+ sort_key: import_zod22.z.string().optional(),
38698
39593
  sort_dir: SortDirSchema.optional(),
38699
39594
  // Basic filtering
38700
- name: import_zod23.z.string().optional(),
38701
- description: import_zod23.z.string().optional(),
38702
- tenant_id: import_zod23.z.string().optional(),
38703
- shared: import_zod23.z.boolean().optional(),
39595
+ name: import_zod22.z.string().optional(),
39596
+ description: import_zod22.z.string().optional(),
39597
+ tenant_id: import_zod22.z.string().optional(),
39598
+ shared: import_zod22.z.boolean().optional(),
38704
39599
  // Tag-based filtering (string values follow Neutron semantics)
38705
- tags: import_zod23.z.string().optional(),
38706
- tags_any: import_zod23.z.string().optional(),
38707
- not_tags: import_zod23.z.string().optional(),
38708
- not_tags_any: import_zod23.z.string().optional(),
39600
+ tags: import_zod22.z.string().optional(),
39601
+ tags_any: import_zod22.z.string().optional(),
39602
+ not_tags: import_zod22.z.string().optional(),
39603
+ not_tags_any: import_zod22.z.string().optional(),
39604
+ // Filtered in the BFF layer, not forwarded to Neutron
39605
+ stateful: import_zod22.z.boolean().optional(),
38709
39606
  // BFF-side search (filtered in BFF layer, not sent to OpenStack)
38710
- searchTerm: import_zod23.z.string().optional()
39607
+ searchTerm: import_zod22.z.string().optional()
38711
39608
  });
38712
39609
  var getSecurityGroupByIdInputSchema = projectScopedInputSchema.extend({
38713
- securityGroupId: import_zod23.z.string()
39610
+ securityGroupId: import_zod22.z.string()
38714
39611
  });
38715
39612
  var createSecurityGroupInputSchema = projectScopedInputSchema.extend({
38716
- name: import_zod23.z.string().min(1, "Name is required"),
38717
- description: import_zod23.z.string().optional(),
38718
- stateful: import_zod23.z.boolean().optional()
39613
+ name: import_zod22.z.string().min(1, "Name is required"),
39614
+ description: import_zod22.z.string().optional(),
39615
+ stateful: import_zod22.z.boolean().optional()
38719
39616
  });
38720
39617
  var deleteSecurityGroupInputSchema = projectScopedInputSchema.extend({
38721
- securityGroupId: import_zod23.z.string()
39618
+ securityGroupId: import_zod22.z.string()
38722
39619
  });
38723
39620
  var updateSecurityGroupInputSchema = projectScopedInputSchema.extend({
38724
- securityGroupId: import_zod23.z.string(),
38725
- name: import_zod23.z.string().min(1, "Name is required").optional(),
38726
- description: import_zod23.z.string().optional(),
38727
- stateful: import_zod23.z.boolean().optional()
39621
+ securityGroupId: import_zod22.z.string(),
39622
+ name: import_zod22.z.string().min(1, "Name is required").optional(),
39623
+ description: import_zod22.z.string().optional(),
39624
+ stateful: import_zod22.z.boolean().optional()
38728
39625
  });
38729
39626
  var deleteSecurityGroupRuleInputSchema = projectScopedInputSchema.extend({
38730
- ruleId: import_zod23.z.string()
39627
+ ruleId: import_zod22.z.string()
38731
39628
  });
38732
39629
  var createSecurityGroupRuleInputSchema = projectScopedInputSchema.extend({
38733
- security_group_id: import_zod23.z.string(),
38734
- direction: import_zod23.z.enum([
39630
+ security_group_id: import_zod22.z.string(),
39631
+ direction: import_zod22.z.enum([
38735
39632
  "ingress",
38736
39633
  "egress"
38737
39634
  ]),
38738
- ethertype: import_zod23.z.enum([
39635
+ ethertype: import_zod22.z.enum([
38739
39636
  "IPv4",
38740
39637
  "IPv6"
38741
39638
  ]).default("IPv4"),
38742
- description: import_zod23.z.string().optional(),
38743
- protocol: import_zod23.z.string().nullable().optional(),
38744
- port_range_min: import_zod23.z.number().int().max(65535).nullable().optional(),
38745
- port_range_max: import_zod23.z.number().int().max(65535).nullable().optional(),
39639
+ description: import_zod22.z.string().optional(),
39640
+ protocol: import_zod22.z.string().nullable().optional(),
39641
+ port_range_min: import_zod22.z.number().int().max(65535).nullable().optional(),
39642
+ port_range_max: import_zod22.z.number().int().max(65535).nullable().optional(),
38746
39643
  // Transform empty strings to undefined for proper validation
38747
- remote_ip_prefix: import_zod23.z.string().nullable().optional().transform((val) => val === "" ? void 0 : val),
38748
- remote_group_id: import_zod23.z.string().nullable().optional().transform((val) => val === "" ? void 0 : val),
38749
- remote_address_group_id: import_zod23.z.string().nullable().optional().transform((val) => val === "" ? void 0 : val)
39644
+ remote_ip_prefix: import_zod22.z.string().nullable().optional().transform((val) => val === "" ? void 0 : val),
39645
+ remote_group_id: import_zod22.z.string().nullable().optional().transform((val) => val === "" ? void 0 : val),
39646
+ remote_address_group_id: import_zod22.z.string().nullable().optional().transform((val) => val === "" ? void 0 : val)
38750
39647
  });
38751
39648
 
38752
39649
  // src/server/Network/helpers/securityGroupHelpers.ts
@@ -39048,6 +39945,10 @@ var deduplicateSecurityGroupsById = /* @__PURE__ */ __name((items) => {
39048
39945
  }
39049
39946
  return Array.from(seen.values());
39050
39947
  }, "deduplicateSecurityGroupsById");
39948
+ var filterSecurityGroupsByStateful = /* @__PURE__ */ __name((items, stateful) => {
39949
+ if (stateful === void 0) return items;
39950
+ return items.filter((item) => (item.stateful ?? true) === stateful);
39951
+ }, "filterSecurityGroupsByStateful");
39051
39952
  var sortSecurityGroups = /* @__PURE__ */ __name((items, sortKey, sortDir = "asc") => {
39052
39953
  if (!sortKey) return items;
39053
39954
  return [
@@ -39099,7 +40000,7 @@ __name(fetchSecurityGroupsWithParams, "fetchSecurityGroupsWithParams");
39099
40000
  var securityGroupRouter = {
39100
40001
  list: projectScopedProcedure.input(listSecurityGroupsInputSchema).query(async ({ input, ctx }) => {
39101
40002
  return withErrorHandling3(async () => {
39102
- const { searchTerm, project_id, shared, sort_key, sort_dir, ...queryInput } = input;
40003
+ const { searchTerm, project_id, shared, stateful, sort_key, sort_dir, ...queryInput } = input;
39103
40004
  const network = getNetworkService(ctx);
39104
40005
  if (shared !== void 0) {
39105
40006
  const securityGroups = await fetchSecurityGroupsWithParams(network, {
@@ -39108,7 +40009,7 @@ var securityGroupRouter = {
39108
40009
  sort_key,
39109
40010
  sort_dir
39110
40011
  });
39111
- return filterBySearchParams(securityGroups, searchTerm, [
40012
+ return filterBySearchParams(filterSecurityGroupsByStateful(securityGroups, stateful), searchTerm, [
39112
40013
  "name",
39113
40014
  "description",
39114
40015
  "id"
@@ -39129,6 +40030,7 @@ var securityGroupRouter = {
39129
40030
  ...ownGroups,
39130
40031
  ...sharedGroups
39131
40032
  ]);
40033
+ combined = filterSecurityGroupsByStateful(combined, stateful);
39132
40034
  combined = filterBySearchParams(combined, searchTerm, [
39133
40035
  "name",
39134
40036
  "description",
@@ -39214,13 +40116,15 @@ var securityGroupRouter = {
39214
40116
  };
39215
40117
 
39216
40118
  // src/server/Network/routers/securityGroupRuleRouter.ts
40119
+ var import_signal_openstack9 = __toESM(require_esm());
39217
40120
  var SECURITY_GROUP_RULES_BASE_URL = "v2.0/security-group-rules";
39218
40121
  var securityGroupRuleRouter = {
39219
40122
  delete: projectScopedProcedure.input(deleteSecurityGroupRuleInputSchema).mutation(async ({ input, ctx }) => {
39220
40123
  return withErrorHandling3(async () => {
39221
40124
  const { ruleId } = input;
39222
40125
  const network = getNetworkService(ctx);
39223
- const response = await network.del(`${SECURITY_GROUP_RULES_BASE_URL}/${ruleId}`);
40126
+ const encodedRuleId = (0, import_signal_openstack9.validateAndEncodeResourceId)(ruleId, "Security group rule");
40127
+ const response = await network.del(`${SECURITY_GROUP_RULES_BASE_URL}/${encodedRuleId}`);
39224
40128
  if (!response?.ok) {
39225
40129
  throw SecurityGroupRuleErrorHandlers.delete(response, ruleId);
39226
40130
  }
@@ -39268,42 +40172,42 @@ var securityGroupRuleRouter = {
39268
40172
  };
39269
40173
 
39270
40174
  // src/server/Network/types/rbacPolicy.ts
39271
- var import_zod24 = require("zod");
39272
- var rbacPolicySchema = import_zod24.z.object({
39273
- id: import_zod24.z.string(),
39274
- object_type: import_zod24.z.enum([
40175
+ var import_zod23 = require("zod");
40176
+ var rbacPolicySchema = import_zod23.z.object({
40177
+ id: import_zod23.z.string(),
40178
+ object_type: import_zod23.z.enum([
39275
40179
  "qos_policy",
39276
40180
  "network",
39277
40181
  "security_group"
39278
40182
  ]),
39279
- object_id: import_zod24.z.string(),
39280
- action: import_zod24.z.enum([
40183
+ object_id: import_zod23.z.string(),
40184
+ action: import_zod23.z.enum([
39281
40185
  "access_as_shared",
39282
40186
  "access_as_external"
39283
40187
  ]),
39284
- target_tenant: import_zod24.z.string(),
39285
- tenant_id: import_zod24.z.string().nullable().optional(),
39286
- project_id: import_zod24.z.string().nullable().optional()
40188
+ target_tenant: import_zod23.z.string(),
40189
+ tenant_id: import_zod23.z.string().nullable().optional(),
40190
+ project_id: import_zod23.z.string().nullable().optional()
39287
40191
  });
39288
- var rbacPoliciesResponseSchema = import_zod24.z.object({
39289
- rbac_policies: import_zod24.z.array(rbacPolicySchema)
40192
+ var rbacPoliciesResponseSchema = import_zod23.z.object({
40193
+ rbac_policies: import_zod23.z.array(rbacPolicySchema)
39290
40194
  });
39291
- var rbacPolicyResponseSchema = import_zod24.z.object({
40195
+ var rbacPolicyResponseSchema = import_zod23.z.object({
39292
40196
  rbac_policy: rbacPolicySchema
39293
40197
  });
39294
40198
  var listRBACPoliciesForSecurityGroupInputSchema = projectScopedInputSchema.extend({
39295
- securityGroupId: import_zod24.z.string()
40199
+ securityGroupId: import_zod23.z.string()
39296
40200
  });
39297
40201
  var createRBACPolicyInputSchema = projectScopedInputSchema.extend({
39298
- securityGroupId: import_zod24.z.string(),
39299
- targetTenant: import_zod24.z.string().trim().min(1, "Target project ID is required")
40202
+ securityGroupId: import_zod23.z.string(),
40203
+ targetTenant: import_zod23.z.string().trim().min(1, "Target project ID is required")
39300
40204
  });
39301
40205
  var updateRBACPolicyInputSchema = projectScopedInputSchema.extend({
39302
- policyId: import_zod24.z.string(),
39303
- targetTenant: import_zod24.z.string().trim().min(1, "Target project ID is required")
40206
+ policyId: import_zod23.z.string(),
40207
+ targetTenant: import_zod23.z.string().trim().min(1, "Target project ID is required")
39304
40208
  });
39305
40209
  var deleteRBACPolicyInputSchema = projectScopedInputSchema.extend({
39306
- policyId: import_zod24.z.string()
40210
+ policyId: import_zod23.z.string()
39307
40211
  });
39308
40212
 
39309
40213
  // src/server/Network/helpers/rbacPolicyHelpers.ts
@@ -39461,6 +40365,7 @@ function parseRBACPoliciesListResponse(data, operation) {
39461
40365
  __name(parseRBACPoliciesListResponse, "parseRBACPoliciesListResponse");
39462
40366
 
39463
40367
  // src/server/Network/routers/rbacPolicyRouter.ts
40368
+ var import_signal_openstack10 = __toESM(require_esm());
39464
40369
  var RBAC_POLICIES_BASE_URL = "v2.0/rbac-policies";
39465
40370
  var rbacPolicyRouter = {
39466
40371
  list: projectScopedProcedure.input(listRBACPoliciesForSecurityGroupInputSchema).query(async ({ input, ctx }) => {
@@ -39509,7 +40414,8 @@ var rbacPolicyRouter = {
39509
40414
  target_tenant: targetTenant
39510
40415
  }
39511
40416
  };
39512
- const response = await network.put(`${RBAC_POLICIES_BASE_URL}/${policyId}`, requestBody);
40417
+ const encodedPolicyId = (0, import_signal_openstack10.validateAndEncodeResourceId)(policyId, "RBAC policy");
40418
+ const response = await network.put(`${RBAC_POLICIES_BASE_URL}/${encodedPolicyId}`, requestBody);
39513
40419
  if (!response.ok) {
39514
40420
  throw RBACPolicyErrorHandlers.update(response, policyId);
39515
40421
  }
@@ -39521,7 +40427,8 @@ var rbacPolicyRouter = {
39521
40427
  return withErrorHandling3(async () => {
39522
40428
  const { policyId } = input;
39523
40429
  const network = getNetworkService(ctx);
39524
- const response = await network.del(`${RBAC_POLICIES_BASE_URL}/${policyId}`);
40430
+ const encodedPolicyId = (0, import_signal_openstack10.validateAndEncodeResourceId)(policyId, "RBAC policy");
40431
+ const response = await network.del(`${RBAC_POLICIES_BASE_URL}/${encodedPolicyId}`);
39525
40432
  if (!response.ok) {
39526
40433
  throw RBACPolicyErrorHandlers.delete(response, policyId);
39527
40434
  }
@@ -39686,10 +40593,6 @@ var NETWORK_MAPPINGS = {
39686
40593
  engine: "network",
39687
40594
  rule: "create_security_group_rule"
39688
40595
  },
39689
- "network:security_group_rules:update": {
39690
- engine: "network",
39691
- rule: "update_security_group_rule"
39692
- },
39693
40596
  "network:security_group_rules:delete": {
39694
40597
  engine: "network",
39695
40598
  rule: "delete_security_group_rule"
@@ -39766,7 +40669,7 @@ var buildAppRouter = /* @__PURE__ */ __name((policyDir, extraRouters = []) => {
39766
40669
  }, "buildAppRouter");
39767
40670
 
39768
40671
  // src/server/context.ts
39769
- var import_signal_openstack9 = __toESM(require_esm());
40672
+ var import_signal_openstack11 = __toESM(require_esm());
39770
40673
 
39771
40674
  // src/server/sessionCookie.ts
39772
40675
  var import_cookie = require("@fastify/cookie");
@@ -39854,7 +40757,7 @@ async function createContext(opts, config) {
39854
40757
  writable: false
39855
40758
  });
39856
40759
  if (currentAuthToken) {
39857
- openstackSession = await (0, import_signal_openstack9.SignalOpenstackSession)(normalizedEndpoint, {
40760
+ openstackSession = await (0, import_signal_openstack11.SignalOpenstackSession)(normalizedEndpoint, {
39858
40761
  auth: {
39859
40762
  identity: {
39860
40763
  methods: [
@@ -39917,7 +40820,7 @@ async function createContext(opts, config) {
39917
40820
  }
39918
40821
  }, "getUserInfo");
39919
40822
  const createSession = /* @__PURE__ */ __name(async (params) => {
39920
- openstackSession = await (0, import_signal_openstack9.SignalOpenstackSession)(normalizedEndpoint, {
40823
+ openstackSession = await (0, import_signal_openstack11.SignalOpenstackSession)(normalizedEndpoint, {
39921
40824
  auth: {
39922
40825
  identity: {
39923
40826
  methods: [
@@ -40061,7 +40964,7 @@ __name(createContext, "createContext");
40061
40964
  // src/server/server.ts
40062
40965
  var import_path2 = __toESM(require("path"));
40063
40966
  var import_node_stream4 = require("stream");
40064
- var import_zod25 = require("zod");
40967
+ var import_zod24 = require("zod");
40065
40968
 
40066
40969
  // src/server/aurora-fastify-plugins/csrfProtection.ts
40067
40970
  var import_csrf_protection = __toESM(require("@fastify/csrf-protection"));
@@ -40262,7 +41165,7 @@ async function createServer(config) {
40262
41165
  router: appRouter,
40263
41166
  createContext: /* @__PURE__ */ __name((opts) => createContext(opts, contextConfig), "createContext"),
40264
41167
  onError: /* @__PURE__ */ __name((err) => {
40265
- if (err.error.cause instanceof import_zod25.ZodError) err.error.message = err.error.cause.issues.map((e) => e.message).join(",");
41168
+ if (err.error.cause instanceof import_zod24.ZodError) err.error.message = err.error.cause.issues.map((e) => e.message).join(",");
40266
41169
  }, "onError")
40267
41170
  }
40268
41171
  });