@cobaltcore-dev/aurora 0.8.1 → 0.10.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 (98) hide show
  1. package/README.md +47 -13
  2. package/dist/client/AuroraApp.d.ts +14 -0
  3. package/dist/client/ContentHeader-D4jlOG-9.mjs +96 -0
  4. package/dist/client/ContentHeader-D4jlOG-9.mjs.map +1 -0
  5. package/dist/client/DeleteVersionsModal-CyYZfB8d.mjs +331 -0
  6. package/dist/client/DeleteVersionsModal-CyYZfB8d.mjs.map +1 -0
  7. package/dist/client/Slot-CWb612-_.mjs +28 -0
  8. package/dist/client/Slot-CWb612-_.mjs.map +1 -0
  9. package/dist/client/{SortInput-VK7IYqQv.mjs → SortInput-DLcusjGZ.mjs} +8 -8
  10. package/dist/client/SortInput-DLcusjGZ.mjs.map +1 -0
  11. package/dist/client/_auth-DXJkv9QO.mjs.map +1 -1
  12. package/dist/client/{_flavorId-iZE2j210.mjs → _flavorId-DsD2VTKA.mjs} +3 -3
  13. package/dist/client/{_flavorId-iZE2j210.mjs.map → _flavorId-DsD2VTKA.mjs.map} +1 -1
  14. package/dist/client/{_flavorId-DU4gcFna.mjs → _flavorId-Dy7EYQum.mjs} +2 -2
  15. package/dist/client/{_flavorId-DU4gcFna.mjs.map → _flavorId-Dy7EYQum.mjs.map} +1 -1
  16. package/dist/client/{_floatingIpId-B5GMSLeQ.mjs → _floatingIpId-BjVbeNw_.mjs} +2 -2
  17. package/dist/client/{_floatingIpId-B5GMSLeQ.mjs.map → _floatingIpId-BjVbeNw_.mjs.map} +1 -1
  18. package/dist/client/{_floatingIpId-C2-BeRmF.mjs → _floatingIpId-j17rCQqG2.mjs} +2 -2
  19. package/dist/client/_floatingIpId-j17rCQqG2.mjs.map +1 -0
  20. package/dist/client/{_imageId-zmaSymWe.mjs → _imageId-BjfhqAje.mjs} +2 -2
  21. package/dist/client/{_imageId-zmaSymWe.mjs.map → _imageId-BjfhqAje.mjs.map} +1 -1
  22. package/dist/client/{_pcaId-BwTvJJgh.mjs → _pcaId-Bo7yHkNW.mjs} +3 -3
  23. package/dist/client/{_pcaId-BwTvJJgh.mjs.map → _pcaId-Bo7yHkNW.mjs.map} +1 -1
  24. package/dist/client/{_pcaId-DUHQd0rB.mjs → _pcaId-CKkCVC7b.mjs} +2 -2
  25. package/dist/client/{_pcaId-DUHQd0rB.mjs.map → _pcaId-CKkCVC7b.mjs.map} +1 -1
  26. package/dist/client/_projectId-B_2sZKk-.mjs.map +1 -1
  27. package/dist/client/_projectId-CARHuZTU.mjs +106 -0
  28. package/dist/client/_projectId-CARHuZTU.mjs.map +1 -0
  29. package/dist/client/_projectId-CY8W0IF6.mjs +27 -0
  30. package/dist/client/_projectId-CY8W0IF6.mjs.map +1 -0
  31. package/dist/client/_projectId-Dbck_MFa.mjs +290 -0
  32. package/dist/client/_projectId-Dbck_MFa.mjs.map +1 -0
  33. package/dist/client/{_securityGroupId-DYxmXUOP.mjs → _securityGroupId-CkN0CGVg.mjs} +3 -3
  34. package/dist/client/{_securityGroupId-DYxmXUOP.mjs.map → _securityGroupId-CkN0CGVg.mjs.map} +1 -1
  35. package/dist/client/{_securityGroupId-fhK1CuZh.mjs → _securityGroupId-gSEZbBII.mjs} +2 -2
  36. package/dist/client/{_securityGroupId-fhK1CuZh.mjs.map → _securityGroupId-gSEZbBII.mjs.map} +1 -1
  37. package/dist/client/{_storageType-CepuevDG.mjs → _storageType-6k8lUnQo.mjs} +2 -2
  38. package/dist/client/{_storageType-CepuevDG.mjs.map → _storageType-6k8lUnQo.mjs.map} +1 -1
  39. package/dist/client/_storageType-CLTxXjQZ.mjs +3048 -0
  40. package/dist/client/_storageType-CLTxXjQZ.mjs.map +1 -0
  41. package/dist/client/{constants-J5nm9hbP.mjs → constants-PMXUGI4Q.mjs} +2 -2
  42. package/dist/client/{constants-J5nm9hbP.mjs.map → constants-PMXUGI4Q.mjs.map} +1 -1
  43. package/dist/client/{flavors-8bZVlzzb.mjs → flavors-BclEwobO.mjs} +2 -2
  44. package/dist/client/{flavors-8bZVlzzb.mjs.map → flavors-BclEwobO.mjs.map} +1 -1
  45. package/dist/client/{flavors-BfsEBUE-.mjs → flavors-p2TKcqP-.mjs} +4 -4
  46. package/dist/client/{flavors-BfsEBUE-.mjs.map → flavors-p2TKcqP-.mjs.map} +1 -1
  47. package/dist/client/{floatingips-Dq4DXQYb.mjs → floatingips-ph0ZxJw8.mjs} +3 -3
  48. package/dist/client/{floatingips-Dq4DXQYb.mjs.map → floatingips-ph0ZxJw8.mjs.map} +1 -1
  49. package/dist/client/{images-BPnTuKFO2.mjs → images-BblnyWJq.mjs} +4 -4
  50. package/dist/client/images-BblnyWJq.mjs.map +1 -0
  51. package/dist/client/{images-8FOgju2f.mjs → images-CXdghaMW.mjs} +2 -2
  52. package/dist/client/{images-8FOgju2f.mjs.map → images-CXdghaMW.mjs.map} +1 -1
  53. package/dist/client/index.js +261 -247
  54. package/dist/client/index.js.map +1 -1
  55. package/dist/client/{md-CYTrL5dq.mjs → md-CyCflQee.mjs} +10 -28
  56. package/dist/client/{md-CYTrL5dq.mjs.map → md-CyCflQee.mjs.map} +1 -1
  57. package/dist/client/{objects-DKWp9RtR.mjs → objects-B9Jh3SMG.mjs} +4 -3
  58. package/dist/client/objects-B9Jh3SMG.mjs.map +1 -0
  59. package/dist/client/objects-Bw25cE1m.mjs +5970 -0
  60. package/dist/client/objects-Bw25cE1m.mjs.map +1 -0
  61. package/dist/client/objects-o2Cj_ndZ.mjs.map +1 -1
  62. package/dist/client/{pca-5wOBf_KI.mjs → pca-CiLPHmK7.mjs} +4 -4
  63. package/dist/client/{pca-5wOBf_KI.mjs.map → pca-CiLPHmK7.mjs.map} +1 -1
  64. package/dist/client/{pca-dhrOFfrE.mjs → pca-DUrQ_tIg.mjs} +2 -2
  65. package/dist/client/{pca-dhrOFfrE.mjs.map → pca-DUrQ_tIg.mjs.map} +1 -1
  66. package/dist/client/{projects-B_PPyZD1.mjs → projects-B5topuei.mjs} +2 -2
  67. package/dist/client/projects-B5topuei.mjs.map +1 -0
  68. package/dist/client/projects-CHYn7L5e.mjs.map +1 -1
  69. package/dist/client/projects-DNd3UTas.mjs +110 -0
  70. package/dist/client/projects-DNd3UTas.mjs.map +1 -0
  71. package/dist/client/projects-yiK0HGSA.mjs.map +1 -1
  72. package/dist/client/routeInfo-Dy9l-wFB.mjs +31 -0
  73. package/dist/client/routeInfo-Dy9l-wFB.mjs.map +1 -0
  74. package/dist/client/{securitygroups-CNFLu9zS.mjs → securitygroups-CcA2TpAt.mjs} +2 -2
  75. package/dist/client/{securitygroups-CNFLu9zS.mjs.map → securitygroups-CcA2TpAt.mjs.map} +1 -1
  76. package/dist/client/{useListWithFiltering-v2A0-SZb.mjs → useListWithFiltering-CVzhMyEA.mjs} +2 -2
  77. package/dist/client/{useListWithFiltering-v2A0-SZb.mjs.map → useListWithFiltering-CVzhMyEA.mjs.map} +1 -1
  78. package/dist/server/index.js +282 -48
  79. package/package.json +3 -3
  80. package/dist/client/ContentHeader-C51H95X8.mjs +0 -85
  81. package/dist/client/ContentHeader-C51H95X8.mjs.map +0 -1
  82. package/dist/client/SortInput-VK7IYqQv.mjs.map +0 -1
  83. package/dist/client/_floatingIpId-C2-BeRmF.mjs.map +0 -1
  84. package/dist/client/_projectId-C8BaEHUj.mjs +0 -273
  85. package/dist/client/_projectId-C8BaEHUj.mjs.map +0 -1
  86. package/dist/client/_projectId-COt93OEF.mjs +0 -84
  87. package/dist/client/_projectId-COt93OEF.mjs.map +0 -1
  88. package/dist/client/_storageType-B-qGcGUQ.mjs +0 -3244
  89. package/dist/client/_storageType-B-qGcGUQ.mjs.map +0 -1
  90. package/dist/client/images-BPnTuKFO2.mjs.map +0 -1
  91. package/dist/client/objects-DKWp9RtR.mjs.map +0 -1
  92. package/dist/client/objects-DaCuy_CB.mjs +0 -5708
  93. package/dist/client/objects-DaCuy_CB.mjs.map +0 -1
  94. package/dist/client/projects-B_PPyZD1.mjs.map +0 -1
  95. package/dist/client/projects-Dmewygrp.mjs +0 -105
  96. package/dist/client/projects-Dmewygrp.mjs.map +0 -1
  97. package/dist/client/routeInfo-CHiJfum5.mjs +0 -73
  98. package/dist/client/routeInfo-CHiJfum5.mjs.map +0 -1
@@ -27847,16 +27847,26 @@ var sessionRouter = {
27847
27847
  password: import_zod2.z.string(),
27848
27848
  domainName: import_zod2.z.string()
27849
27849
  })).mutation(async ({ input, ctx }) => {
27850
- const openstackSession = await ctx.createSession({
27851
- user: input.user,
27852
- password: input.password,
27853
- domain: input.domainName
27854
- });
27855
- const tokenData = openstackSession.getToken()?.tokenData;
27856
- if (!tokenData) {
27857
- throw new Error("Could not get token data");
27850
+ try {
27851
+ const openstackSession = await ctx.createSession({
27852
+ user: input.user,
27853
+ password: input.password,
27854
+ domain: input.domainName
27855
+ });
27856
+ const tokenData = openstackSession.getToken()?.tokenData;
27857
+ if (!tokenData) {
27858
+ throw new Error("Could not get token data");
27859
+ }
27860
+ return tokenData;
27861
+ } catch (error) {
27862
+ if (error && typeof error === "object" && "statusCode" in error && error.statusCode === 401) {
27863
+ throw new import_server2.TRPCError({
27864
+ code: "UNAUTHORIZED",
27865
+ message: "Invalid credentials. Please check your domain, username, and password."
27866
+ });
27867
+ }
27868
+ throw error;
27858
27869
  }
27859
- return tokenData;
27860
27870
  }),
27861
27871
  terminateUserSession: protectedProcedure.mutation(async ({ ctx }) => {
27862
27872
  ctx.terminateSession();
@@ -33332,7 +33342,10 @@ var S3_ERROR_MAP = {
33332
33342
  KeyTooLongError: "BAD_REQUEST",
33333
33343
  MalformedPolicy: "BAD_REQUEST",
33334
33344
  EntityTooLarge: "PAYLOAD_TOO_LARGE",
33335
- EntityTooSmall: "BAD_REQUEST"
33345
+ EntityTooSmall: "BAD_REQUEST",
33346
+ // Generic errors that might be unmapped
33347
+ UnknownError: "INTERNAL_SERVER_ERROR",
33348
+ InternalError: "INTERNAL_SERVER_ERROR"
33336
33349
  };
33337
33350
  function mapS3ErrorToTRPCError(error, context) {
33338
33351
  if (error instanceof import_server12.TRPCError) {
@@ -33344,7 +33357,8 @@ function mapS3ErrorToTRPCError(error, context) {
33344
33357
  if (!S3_ERROR_MAP[errorCode] && errorCode) {
33345
33358
  import_signal_openstack.logger.warn("Unmapped S3 error code", {
33346
33359
  errorCode,
33347
- operation: context.operation
33360
+ operation: context.operation,
33361
+ errorName: s3Error.name
33348
33362
  });
33349
33363
  }
33350
33364
  const parts = [
@@ -33356,9 +33370,14 @@ function mapS3ErrorToTRPCError(error, context) {
33356
33370
  parts.push("Access denied \u2014 your credentials are valid but lack permissions for this operation");
33357
33371
  } else if (errorCode === "InvalidAccessKeyId" || errorCode === "SignatureDoesNotMatch" || errorCode === "TokenRefreshRequired" || errorCode === "RequestTimeTooSkewed") {
33358
33372
  parts.push("Invalid access key \u2014 your credentials may be expired or incorrect");
33373
+ } else if (errorCode === "BucketNotEmpty") {
33374
+ parts.push("The bucket is not empty. Some objects or versions may still exist. Use 'Empty Bucket' first to delete all contents.");
33359
33375
  } else if (s3Error.message) {
33360
33376
  parts.push(s3Error.message);
33361
33377
  }
33378
+ if (!S3_ERROR_MAP[errorCode] && errorCode) {
33379
+ parts.push(`(Error code: ${errorCode})`);
33380
+ }
33362
33381
  throw new import_server12.TRPCError({
33363
33382
  code: trpcCode,
33364
33383
  message: parts.join(" \u2014 "),
@@ -33414,6 +33433,16 @@ var s3ObjectSchema = import_zod13.z.object({
33414
33433
  etag: import_zod13.z.string().optional(),
33415
33434
  storageClass: import_zod13.z.string().optional()
33416
33435
  });
33436
+ var s3ObjectVersionSchema = import_zod13.z.object({
33437
+ key: import_zod13.z.string(),
33438
+ versionId: import_zod13.z.string(),
33439
+ isLatest: import_zod13.z.boolean(),
33440
+ lastModified: import_zod13.z.string().optional(),
33441
+ size: import_zod13.z.number(),
33442
+ etag: import_zod13.z.string().optional(),
33443
+ storageClass: import_zod13.z.string().optional(),
33444
+ isDeleteMarker: import_zod13.z.boolean().default(false)
33445
+ });
33417
33446
  var s3FolderPrefixSchema = import_zod13.z.object({
33418
33447
  prefix: import_zod13.z.string()
33419
33448
  });
@@ -33422,13 +33451,19 @@ var listObjectsInputSchema2 = projectScopedInputSchema.extend({
33422
33451
  prefix: import_zod13.z.string().optional(),
33423
33452
  delimiter: import_zod13.z.string().optional(),
33424
33453
  maxKeys: import_zod13.z.number().min(1).max(1e3).default(1e3),
33425
- continuationToken: import_zod13.z.string().optional()
33454
+ continuationToken: import_zod13.z.string().optional(),
33455
+ keyMarker: import_zod13.z.string().optional(),
33456
+ versionIdMarker: import_zod13.z.string().optional(),
33457
+ showVersions: import_zod13.z.boolean().optional().default(false)
33426
33458
  });
33427
33459
  var listObjectsOutputSchema = import_zod13.z.object({
33428
33460
  objects: import_zod13.z.array(s3ObjectSchema),
33429
33461
  folders: import_zod13.z.array(s3FolderPrefixSchema),
33430
33462
  isTruncated: import_zod13.z.boolean(),
33431
- nextContinuationToken: import_zod13.z.string().optional()
33463
+ nextContinuationToken: import_zod13.z.string().optional(),
33464
+ versions: import_zod13.z.array(s3ObjectVersionSchema).optional(),
33465
+ nextKeyMarker: import_zod13.z.string().optional(),
33466
+ nextVersionIdMarker: import_zod13.z.string().optional()
33432
33467
  });
33433
33468
  var getObjectDetailsInputSchema = projectScopedInputSchema.extend({
33434
33469
  containerName: import_zod13.z.string().min(1),
@@ -33699,6 +33734,16 @@ var containerRouter = {
33699
33734
  const s3 = ctx.getCephClient();
33700
33735
  const { bucketName, enableVersioning } = input;
33701
33736
  try {
33737
+ const listResponse = await s3.send(new import_client_s32.ListBucketsCommand({}));
33738
+ const existingBucket = listResponse.Buckets?.find((b) => b.Name === bucketName);
33739
+ if (existingBucket) {
33740
+ throw mapS3ErrorToTRPCError(Object.assign(new Error("Bucket already exists"), {
33741
+ Code: "BucketAlreadyExists"
33742
+ }), {
33743
+ operation: "create bucket",
33744
+ bucket: bucketName
33745
+ });
33746
+ }
33702
33747
  await s3.send(new import_client_s32.CreateBucketCommand({
33703
33748
  Bucket: bucketName
33704
33749
  }));
@@ -33765,17 +33810,72 @@ var import_server13 = require("@trpc/server");
33765
33810
  var import_zod14 = require("zod");
33766
33811
  var deleteAllObjectsInputSchema = import_zod14.z.object({
33767
33812
  project_id: import_zod14.z.string(),
33768
- containerName: import_zod14.z.string().min(1)
33813
+ containerName: import_zod14.z.string().min(1),
33814
+ includeVersionsAndDeleteMarkers: import_zod14.z.boolean().optional().default(true)
33769
33815
  });
33770
33816
  var objectRouter = {
33771
33817
  /**
33772
33818
  * List objects in a container with optional prefix filtering and pagination.
33773
33819
  * Returns both objects and "folders" (CommonPrefixes).
33820
+ * When showVersions=true, returns all versions including delete markers.
33774
33821
  */
33775
33822
  list: cephProtectedProcedure.input(listObjectsInputSchema2).query(async ({ ctx, input }) => {
33776
33823
  const s3 = ctx.getCephClient();
33777
- const { containerName, prefix, delimiter = "/", maxKeys, continuationToken } = input;
33824
+ const { containerName, prefix, delimiter = "/", maxKeys, continuationToken, keyMarker, versionIdMarker, showVersions } = input;
33778
33825
  try {
33826
+ if (showVersions) {
33827
+ const response2 = await s3.send(new import_client_s33.ListObjectVersionsCommand({
33828
+ Bucket: containerName,
33829
+ Prefix: prefix || void 0,
33830
+ Delimiter: delimiter || void 0,
33831
+ MaxKeys: maxKeys,
33832
+ KeyMarker: keyMarker,
33833
+ VersionIdMarker: versionIdMarker
33834
+ }));
33835
+ const allVersions = [
33836
+ ...(response2.Versions ?? []).map((v) => ({
33837
+ key: v.Key ?? "",
33838
+ versionId: v.VersionId ?? "",
33839
+ isLatest: v.IsLatest ?? false,
33840
+ lastModified: v.LastModified?.toISOString(),
33841
+ size: v.Size ?? 0,
33842
+ etag: v.ETag,
33843
+ storageClass: v.StorageClass,
33844
+ isDeleteMarker: false
33845
+ })),
33846
+ ...(response2.DeleteMarkers ?? []).map((dm) => ({
33847
+ key: dm.Key ?? "",
33848
+ versionId: dm.VersionId ?? "",
33849
+ isLatest: dm.IsLatest ?? false,
33850
+ lastModified: dm.LastModified?.toISOString(),
33851
+ size: 0,
33852
+ etag: void 0,
33853
+ storageClass: void 0,
33854
+ isDeleteMarker: true
33855
+ }))
33856
+ ];
33857
+ const filteredVersions = delimiter ? allVersions.filter((v) => {
33858
+ const relativePath = prefix ? v.key.slice(prefix.length) : v.key;
33859
+ return !relativePath.includes(delimiter);
33860
+ }) : allVersions;
33861
+ const versions = filteredVersions.map((v) => s3ObjectVersionSchema.parse(v)).sort((a, b) => {
33862
+ if (a.key !== b.key) return a.key.localeCompare(b.key);
33863
+ if (!a.lastModified || !b.lastModified) return 0;
33864
+ return new Date(b.lastModified).getTime() - new Date(a.lastModified).getTime();
33865
+ });
33866
+ const folders2 = (response2.CommonPrefixes ?? []).map((cp) => s3FolderPrefixSchema.parse({
33867
+ prefix: cp.Prefix ?? ""
33868
+ }));
33869
+ return listObjectsOutputSchema.parse({
33870
+ objects: [],
33871
+ folders: folders2,
33872
+ isTruncated: response2.IsTruncated ?? false,
33873
+ nextContinuationToken: response2.NextKeyMarker,
33874
+ versions,
33875
+ nextKeyMarker: response2.NextKeyMarker,
33876
+ nextVersionIdMarker: response2.NextVersionIdMarker
33877
+ });
33878
+ }
33779
33879
  const response = await s3.send(new import_client_s33.ListObjectsV2Command({
33780
33880
  Bucket: containerName,
33781
33881
  Prefix: prefix || void 0,
@@ -33837,46 +33937,140 @@ var objectRouter = {
33837
33937
  /**
33838
33938
  * Delete all objects in a bucket (empty the bucket).
33839
33939
  * Uses batched DeleteObjectsCommand for efficiency (up to 1000 objects per request).
33840
- * Loops until all objects are deleted.
33940
+ * Loops until all objects and versions are deleted.
33941
+ *
33942
+ * For versioned buckets, deletes all versions and delete markers to ensure the bucket
33943
+ * is truly empty and can be deleted.
33841
33944
  */
33842
33945
  deleteAll: cephProtectedProcedure.input(deleteAllObjectsInputSchema).mutation(async ({ ctx, input }) => {
33843
33946
  const s3 = ctx.getCephClient();
33844
- const { containerName } = input;
33947
+ const { containerName, includeVersionsAndDeleteMarkers } = input;
33845
33948
  let totalDeleted = 0;
33846
- let continuationToken;
33847
33949
  try {
33848
- while (true) {
33849
- const listResponse = await s3.send(new import_client_s33.ListObjectsV2Command({
33850
- Bucket: containerName,
33851
- MaxKeys: S3_MAX_KEYS_PER_REQUEST,
33852
- ContinuationToken: continuationToken
33853
- }));
33854
- const objects = listResponse.Contents ?? [];
33855
- if (objects.length === 0) break;
33856
- const objectsWithoutKeys = objects.filter((obj) => !obj.Key);
33857
- if (objectsWithoutKeys.length > 0) {
33858
- throw new Error(`Encountered ${objectsWithoutKeys.length} object(s) without Key field in S3 list response. Cannot proceed with deletion.`);
33859
- }
33860
- const objectsToDelete = objects.map((obj) => ({
33861
- Key: obj.Key
33862
- }));
33863
- const deleteResponse = await s3.send(new import_client_s33.DeleteObjectsCommand({
33864
- Bucket: containerName,
33865
- Delete: {
33866
- Objects: objectsToDelete
33950
+ if (includeVersionsAndDeleteMarkers) {
33951
+ let keyMarker;
33952
+ let versionIdMarker;
33953
+ let consecutiveEmptyScans = 0;
33954
+ const MAX_EMPTY_SCANS = 3;
33955
+ let sameMarkerCount = 0;
33956
+ const MAX_SAME_MARKER = 5;
33957
+ while (true) {
33958
+ const listResponse = await s3.send(new import_client_s33.ListObjectVersionsCommand({
33959
+ Bucket: containerName,
33960
+ MaxKeys: S3_MAX_KEYS_PER_REQUEST,
33961
+ KeyMarker: keyMarker,
33962
+ VersionIdMarker: versionIdMarker
33963
+ }));
33964
+ const versions = listResponse.Versions ?? [];
33965
+ const deleteMarkers = listResponse.DeleteMarkers ?? [];
33966
+ const allItems = [
33967
+ ...versions,
33968
+ ...deleteMarkers
33969
+ ];
33970
+ console.log(`[deleteAll] Found ${versions.length} versions, ${deleteMarkers.length} delete markers, total: ${allItems.length}`);
33971
+ if (allItems.length === 0) {
33972
+ consecutiveEmptyScans++;
33973
+ console.log(`[deleteAll] Empty scan ${consecutiveEmptyScans}/${MAX_EMPTY_SCANS}. Total deleted: ${totalDeleted}`);
33974
+ if (consecutiveEmptyScans >= MAX_EMPTY_SCANS) {
33975
+ console.log(`[deleteAll] Bucket is now empty. Total deleted: ${totalDeleted}`);
33976
+ break;
33977
+ }
33978
+ keyMarker = void 0;
33979
+ versionIdMarker = void 0;
33980
+ continue;
33981
+ }
33982
+ consecutiveEmptyScans = 0;
33983
+ const itemsWithoutKeys = allItems.filter((item) => !item.Key);
33984
+ if (itemsWithoutKeys.length > 0) {
33985
+ throw new Error(`Encountered ${itemsWithoutKeys.length} item(s) without Key field in S3 list response. Cannot proceed with deletion.`);
33986
+ }
33987
+ const objectsToDelete = allItems.map((item) => ({
33988
+ Key: item.Key,
33989
+ VersionId: item.VersionId
33990
+ }));
33991
+ console.log(`[deleteAll] Deleting batch of ${objectsToDelete.length} items from bucket ${containerName}`);
33992
+ const deleteResponse = await s3.send(new import_client_s33.DeleteObjectsCommand({
33993
+ Bucket: containerName,
33994
+ Delete: {
33995
+ Objects: objectsToDelete
33996
+ }
33997
+ }));
33998
+ const deletedCount = deleteResponse.Deleted?.length ?? 0;
33999
+ totalDeleted += deletedCount;
34000
+ console.log(`[deleteAll] Successfully deleted ${deletedCount} items. Total so far: ${totalDeleted}`);
34001
+ if (deleteResponse.Errors && deleteResponse.Errors.length > 0) {
34002
+ console.error(`[deleteAll] Errors during deletion:`, deleteResponse.Errors);
34003
+ const errorKeys = deleteResponse.Errors.map((e) => e.Key).join(", ");
34004
+ throw new import_server13.TRPCError({
34005
+ code: "INTERNAL_SERVER_ERROR",
34006
+ message: `Failed to delete some objects: ${errorKeys}`
34007
+ });
34008
+ }
34009
+ if (listResponse.IsTruncated) {
34010
+ const currentMarkerKey = `${keyMarker || ""}:${versionIdMarker || ""}`;
34011
+ const newMarkerKey = `${listResponse.NextKeyMarker || ""}:${listResponse.NextVersionIdMarker || ""}`;
34012
+ if (currentMarkerKey === newMarkerKey) {
34013
+ sameMarkerCount++;
34014
+ if (sameMarkerCount >= MAX_SAME_MARKER) {
34015
+ throw new import_server13.TRPCError({
34016
+ code: "INTERNAL_SERVER_ERROR",
34017
+ message: `Pagination stalled: markers not advancing after ${MAX_SAME_MARKER} iterations. The bucket may still contain objects.`
34018
+ });
34019
+ }
34020
+ } else {
34021
+ sameMarkerCount = 0;
34022
+ }
34023
+ keyMarker = listResponse.NextKeyMarker;
34024
+ versionIdMarker = listResponse.NextVersionIdMarker;
34025
+ } else {
34026
+ keyMarker = void 0;
34027
+ versionIdMarker = void 0;
34028
+ sameMarkerCount = 0;
33867
34029
  }
33868
- }));
33869
- const deletedCount = deleteResponse.Deleted?.length ?? 0;
33870
- totalDeleted += deletedCount;
33871
- if (deleteResponse.Errors && deleteResponse.Errors.length > 0) {
33872
- const errorKeys = deleteResponse.Errors.map((e) => e.Key).join(", ");
33873
- throw new import_server13.TRPCError({
33874
- code: "INTERNAL_SERVER_ERROR",
33875
- message: `Failed to delete some objects: ${errorKeys}`
33876
- });
33877
34030
  }
33878
- if (!listResponse.IsTruncated) break;
33879
- continuationToken = listResponse.NextContinuationToken;
34031
+ console.log(`[deleteAll] Completed. Total deleted: ${totalDeleted}`);
34032
+ } else {
34033
+ let continuationToken;
34034
+ do {
34035
+ const listResponse = await s3.send(new import_client_s33.ListObjectsV2Command({
34036
+ Bucket: containerName,
34037
+ MaxKeys: S3_MAX_KEYS_PER_REQUEST,
34038
+ ContinuationToken: continuationToken
34039
+ }));
34040
+ const objects = listResponse.Contents ?? [];
34041
+ console.log(`[deleteAll] Found ${objects.length} current objects to delete`);
34042
+ if (objects.length === 0) {
34043
+ console.log(`[deleteAll] No more current objects to delete`);
34044
+ break;
34045
+ }
34046
+ const objectsWithoutKeys = objects.filter((obj) => !obj.Key);
34047
+ if (objectsWithoutKeys.length > 0) {
34048
+ throw new Error(`Encountered ${objectsWithoutKeys.length} object(s) without Key field in S3 list response. Cannot proceed with deletion.`);
34049
+ }
34050
+ const objectsToDelete = objects.map((obj) => ({
34051
+ Key: obj.Key
34052
+ }));
34053
+ console.log(`[deleteAll] Deleting batch of ${objectsToDelete.length} current objects from bucket ${containerName}`);
34054
+ const deleteResponse = await s3.send(new import_client_s33.DeleteObjectsCommand({
34055
+ Bucket: containerName,
34056
+ Delete: {
34057
+ Objects: objectsToDelete
34058
+ }
34059
+ }));
34060
+ const deletedCount = deleteResponse.Deleted?.length ?? 0;
34061
+ totalDeleted += deletedCount;
34062
+ console.log(`[deleteAll] Successfully deleted ${deletedCount} objects. Total so far: ${totalDeleted}`);
34063
+ if (deleteResponse.Errors && deleteResponse.Errors.length > 0) {
34064
+ console.error(`[deleteAll] Errors during deletion:`, deleteResponse.Errors);
34065
+ const errorKeys = deleteResponse.Errors.map((e) => e.Key).join(", ");
34066
+ throw new import_server13.TRPCError({
34067
+ code: "INTERNAL_SERVER_ERROR",
34068
+ message: `Failed to delete some objects: ${errorKeys}`
34069
+ });
34070
+ }
34071
+ continuationToken = listResponse.NextContinuationToken;
34072
+ } while (continuationToken);
34073
+ console.log(`[deleteAll] Completed. Total deleted: ${totalDeleted} current objects`);
33880
34074
  }
33881
34075
  return totalDeleted;
33882
34076
  } catch (error) {
@@ -33892,6 +34086,10 @@ var objectRouter = {
33892
34086
  * Uses AWS SDK DeleteObjectCommand. Deleting a non-existent object is considered
33893
34087
  * a success (S3 is idempotent for deletes).
33894
34088
  *
34089
+ * For folders (keys ending with "/"), recursively deletes all objects inside the folder
34090
+ * first, then deletes the folder marker itself. This ensures that in versioned buckets,
34091
+ * the entire folder content gets delete markers, not just the folder object.
34092
+ *
33895
34093
  * @throws TRPCError NOT_FOUND - bucket does not exist
33896
34094
  * @throws TRPCError FORBIDDEN - no credentials or access denied
33897
34095
  */
@@ -33899,6 +34097,42 @@ var objectRouter = {
33899
34097
  const s3 = ctx.getCephClient();
33900
34098
  const { containerName, objectKey } = input;
33901
34099
  try {
34100
+ const isFolder = objectKey.endsWith("/");
34101
+ if (isFolder) {
34102
+ let continuationToken;
34103
+ let totalDeleted = 0;
34104
+ do {
34105
+ const listResponse = await s3.send(new import_client_s33.ListObjectsV2Command({
34106
+ Bucket: containerName,
34107
+ Prefix: objectKey,
34108
+ MaxKeys: S3_MAX_KEYS_PER_REQUEST,
34109
+ ContinuationToken: continuationToken
34110
+ }));
34111
+ const objects = listResponse.Contents ?? [];
34112
+ const objectsToDelete = objects.filter((obj) => obj.Key !== objectKey);
34113
+ if (objectsToDelete.length > 0) {
34114
+ const deleteResponse = await s3.send(new import_client_s33.DeleteObjectsCommand({
34115
+ Bucket: containerName,
34116
+ Delete: {
34117
+ Objects: objectsToDelete.map((obj) => ({
34118
+ Key: obj.Key
34119
+ }))
34120
+ }
34121
+ }));
34122
+ totalDeleted += deleteResponse.Deleted?.length ?? 0;
34123
+ if (deleteResponse.Errors && deleteResponse.Errors.length > 0) {
34124
+ console.error(`[delete folder] Errors during batch deletion:`, deleteResponse.Errors);
34125
+ const errorKeys = deleteResponse.Errors.map((e) => e.Key).join(", ");
34126
+ throw new import_server13.TRPCError({
34127
+ code: "INTERNAL_SERVER_ERROR",
34128
+ message: `Failed to delete some objects in folder: ${errorKeys}`
34129
+ });
34130
+ }
34131
+ }
34132
+ continuationToken = listResponse.NextContinuationToken;
34133
+ } while (continuationToken);
34134
+ console.log(`[delete folder] Deleted ${totalDeleted} objects from folder ${objectKey}`);
34135
+ }
33902
34136
  await s3.send(new import_client_s33.DeleteObjectCommand({
33903
34137
  Bucket: containerName,
33904
34138
  Key: objectKey
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cobaltcore-dev/aurora",
3
- "version": "0.8.1",
3
+ "version": "0.10.0",
4
4
  "private": false,
5
5
  "description": "Aurora OpenStack dashboard — server and client library",
6
6
  "license": "Apache-2.0",
@@ -108,9 +108,9 @@
108
108
  "vite-plugin-svgr": "^5.0.0",
109
109
  "vite-tsconfig-paths": "^6.1.1",
110
110
  "vitest": "^4.1.2",
111
- "@cobaltcore-dev/aurora-config": "0.0.1",
111
+ "@cobaltcore-dev/policy-engine": "2.0.0",
112
112
  "@cobaltcore-dev/signal-openstack": "1.0.0",
113
- "@cobaltcore-dev/policy-engine": "2.0.0"
113
+ "@cobaltcore-dev/aurora-config": "0.0.1"
114
114
  },
115
115
  "scripts": {
116
116
  "build": "pnpm build:server && pnpm build:client",
@@ -1,85 +0,0 @@
1
- import { A as e, C as t, Q as n, Y as r, c as i, o as a, st as o } from "./build-BdRRmNf5.mjs";
2
- import { jsx as s, jsxs as c } from "react/jsx-runtime";
3
- import { useState as l } from "react";
4
- import { Trans as u, useLingui as d } from "@lingui/react";
5
- //#region src/client/components/ClipboardText.tsx
6
- var f = ({ text: n, tooltipContent: o, className: u, truncateAt: f, showTooltip: p = !0, ...m }) => {
7
- let { i18n: h, _: g } = d(), [_, v] = l(!1), [y, b] = l(!1), x = `copyableTooltip inline-flex items-center ${u || ""}`, S = async (e) => {
8
- e.preventDefault(), e.stopPropagation();
9
- try {
10
- await navigator.clipboard.writeText(n), v(!0), b(!1), setTimeout(() => v(!1), 2e3);
11
- } catch (e) {
12
- console.error("Failed to copy text:", e);
13
- }
14
- }, C = f && n.length > f ? `${n.slice(0, f)}...` : n, w = () => _ ? o || h._({ id: "u+VWhB" }) : h._({ id: "he3ygx" }), T = _ && p || y && p;
15
- return /*#__PURE__*/ s("div", {
16
- ...m,
17
- className: x,
18
- children: /*#__PURE__*/ c(e, {
19
- open: T,
20
- children: [/*#__PURE__*/ s(a, {
21
- onClick: S,
22
- onMouseEnter: () => !_ && b(!0),
23
- onMouseLeave: () => b(!1),
24
- "aria-label": h._({
25
- id: "Wbg1jv",
26
- values: { text: n }
27
- }),
28
- className: "cursor-pointer",
29
- asChild: !0,
30
- "data-testid": "clipboard-copy-trigger",
31
- children: /*#__PURE__*/ s("div", {
32
- className: "group",
33
- children: /*#__PURE__*/ c(r, {
34
- direction: "horizontal",
35
- gap: "1",
36
- className: "items-center hover:underline",
37
- children: [/*#__PURE__*/ s("span", {
38
- className: "select-none",
39
- children: C
40
- }), /*#__PURE__*/ s(i, {
41
- icon: _ ? "check" : "contentCopy",
42
- size: "18"
43
- })]
44
- })
45
- })
46
- }), /*#__PURE__*/ s(t, { children: w() })]
47
- })
48
- });
49
- };
50
- //#endregion
51
- //#region src/client/components/ContentHeader/ContentHeader.tsx
52
- function p({ title: e, projectId: t, description: r, actions: i }) {
53
- return /*#__PURE__*/ c("header", { children: [
54
- /*#__PURE__*/ c("div", {
55
- className: "flex flex-col sm:flex-row sm:items-center sm:justify-between",
56
- children: [/*#__PURE__*/ s(n, { children: e }), /*#__PURE__*/ c("div", {
57
- className: "text-theme-light flex shrink-0 items-center gap-1 text-sm",
58
- children: [/*#__PURE__*/ c("span", {
59
- className: "font-semibold",
60
- children: [
61
- /*#__PURE__*/ s(u, { id: "mSfwLL" }),
62
- ":",
63
- " "
64
- ]
65
- }), /*#__PURE__*/ s(f, {
66
- text: t,
67
- truncateAt: 15
68
- })]
69
- })]
70
- }),
71
- r && /*#__PURE__*/ s("p", {
72
- className: "text-sm font-normal",
73
- children: r
74
- }),
75
- /*#__PURE__*/ s(o, { className: "mt-4" }),
76
- i && /*#__PURE__*/ s("div", {
77
- className: "mt-3 flex justify-end",
78
- children: i
79
- })
80
- ] });
81
- }
82
- //#endregion
83
- export { f as n, p as t };
84
-
85
- //# sourceMappingURL=ContentHeader-C51H95X8.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ContentHeader-C51H95X8.mjs","names":["React","useState","Tooltip","TooltipTrigger","TooltipContent","Icon","Stack","ClipboardText","text","tooltipContent","className","truncateAt","showTooltip","props","useLingui","copied","setCopied","isHovering","setIsHovering","combinedClassName","handleCopy","e","preventDefault","stopPropagation","navigator","clipboard","writeText","setTimeout","err","console","error","displayText","length","slice","getTooltipContent","t","tooltipIsOpen","div","open","onClick","onMouseEnter","onMouseLeave","aria-label","asChild","data-testid","direction","gap","span","icon","size","Divider","ContentHeading","ClipboardText","ContentHeader","title","projectId","description","actions","header","div","className","span","text","truncateAt","p"],"sources":["../../src/client/components/ClipboardText.tsx","../../src/client/components/ContentHeader/ContentHeader.tsx"],"sourcesContent":["import React, { useState } from \"react\"\nimport { Tooltip, TooltipTrigger, TooltipContent, Icon, Stack } from \"@cloudoperators/juno-ui-components\"\nimport { useLingui } from \"@lingui/react/macro\"\n\nexport interface ClipboardTextProps extends React.HTMLAttributes<HTMLDivElement> {\n tooltipContent?: string\n text: string\n className?: string\n truncateAt?: number\n showTooltip?: boolean\n}\n\nconst ClipboardText: React.FC<ClipboardTextProps> = ({\n text,\n tooltipContent,\n className,\n truncateAt,\n showTooltip = true,\n ...props\n}) => {\n const { t } = useLingui()\n const [copied, setCopied] = useState(false)\n const [isHovering, setIsHovering] = useState(false)\n\n const combinedClassName = `copyableTooltip inline-flex items-center ${className || \"\"}`\n\n const handleCopy = async (e: React.MouseEvent) => {\n e.preventDefault()\n e.stopPropagation()\n\n try {\n await navigator.clipboard.writeText(text)\n setCopied(true)\n setIsHovering(false)\n setTimeout(() => setCopied(false), 2000)\n } catch (err) {\n console.error(\"Failed to copy text:\", err)\n }\n }\n\n const displayText = truncateAt && text.length > truncateAt ? `${text.slice(0, truncateAt)}...` : text\n\n // Determine tooltip content based on state\n const getTooltipContent = () => {\n if (copied) {\n return tooltipContent || t`Copied to clipboard!`\n }\n return t`Copy`\n }\n\n const tooltipIsOpen = (copied && showTooltip) || (isHovering && showTooltip)\n\n return (\n <div {...props} className={combinedClassName}>\n <Tooltip open={tooltipIsOpen}>\n <TooltipTrigger\n onClick={handleCopy}\n onMouseEnter={() => !copied && setIsHovering(true)}\n onMouseLeave={() => setIsHovering(false)}\n aria-label={t`Copy ${text} to clipboard`}\n className=\"cursor-pointer\"\n asChild\n data-testid=\"clipboard-copy-trigger\"\n >\n <div className=\"group\">\n <Stack direction=\"horizontal\" gap=\"1\" className=\"items-center hover:underline\">\n <span className=\"select-none\">{displayText}</span>\n <Icon icon={copied ? \"check\" : \"contentCopy\"} size=\"18\" />\n </Stack>\n </div>\n </TooltipTrigger>\n <TooltipContent>{getTooltipContent()}</TooltipContent>\n </Tooltip>\n </div>\n )\n}\n\nexport default ClipboardText\n","import type { ReactNode } from \"react\"\nimport { Divider, ContentHeading } from \"@cloudoperators/juno-ui-components\"\nimport { Trans } from \"@lingui/react/macro\"\nimport ClipboardText from \"../ClipboardText\"\n\ninterface ContentHeaderProps {\n title: string\n projectId: string\n description?: string | null\n actions?: ReactNode\n}\n\nexport function ContentHeader({ title, projectId, description, actions }: ContentHeaderProps) {\n return (\n <header>\n <div className=\"flex flex-col sm:flex-row sm:items-center sm:justify-between\">\n <ContentHeading>{title}</ContentHeading>\n <div className=\"text-theme-light flex shrink-0 items-center gap-1 text-sm\">\n <span className=\"font-semibold\">\n <Trans>Project ID</Trans>:{\" \"}\n </span>\n <ClipboardText text={projectId} truncateAt={15} />\n </div>\n </div>\n {description && <p className=\"text-sm font-normal\">{description}</p>}\n <Divider className=\"mt-4\" />\n {actions && <div className=\"mt-3 flex justify-end\">{actions}</div>}\n </header>\n )\n}\n"],"mappings":";;;;;AAYA,IAAMO,KAA+C,EACnDC,SACAC,mBACAC,cACAC,eACAC,iBAAc,IACd,GAAGC,QACJ;CACC,IAAM,EAAA,MAAA,GAAA,GAAA,MAAQC,EAAAA,GACR,CAACC,GAAQC,KAAaf,EAAS,EAAA,GAC/B,CAACgB,GAAYC,KAAiBjB,EAAS,EAAA,GAEvCkB,IAAoB,6CAA6CT,KAAa,MAE9EU,IAAa,OAAOC,MAAAA;EAExBA,AADAA,EAAEC,eAAc,GAChBD,EAAEE,gBAAe;EAEjB,IAAI;GAIFI,AAHA,MAAMH,UAAUC,UAAUC,UAAUlB,CAAAA,GACpCQ,EAAU,EAAA,GACVE,EAAc,EAAA,GACdS,iBAAiBX,EAAU,EAAA,GAAQ,GAAA;EACrC,SAASY,GAAK;GACZC,QAAQC,MAAM,wBAAwBF,CAAAA;EACxC;CACF,GAEMG,IAAcpB,KAAcH,EAAKwB,SAASrB,IAAa,GAAGH,EAAKyB,MAAM,GAAGtB,CAAAA,EAAY,OAAOH,GAG3F0B,UACAnB,IACKN,KAAkB0B,EAAAA,EAAC,EAAA,IAAA,SAAqB,CAAA,IAE1CA,EAAAA,EAAC,EAAA,IAAA,SAAK,CAAA,GAGTC,IAAgB,KAAWxB,KAAiBK,KAAcL;CAEhE,OACE,gBAACyB,OAAAA;EAAK,GAAGxB;EAAOH,WAAWS;YACzB,gBAACjB,GAAAA;GAAQoC,MAAMF;cACb,gBAACjC,GAAAA;IACCoC,SAASnB;IACToB,oBAAoB,CAACzB,KAAUG,EAAc,EAAA;IAC7CuB,oBAAoBvB,EAAc,EAAA;IAClCwB,cAAYP,EAAAA,EAAC;;eAAQ3B,QAAAA;IAAkB,CAAA;IACvCE,WAAU;IACViC,SAAO;IACPC,eAAY;cAEZ,gBAACP,OAAAA;KAAI3B,WAAU;eACb,gBAACJ,GAAAA;MAAMuC,WAAU;MAAaC,KAAI;MAAIpC,WAAU;iBAC9C,gBAACqC,QAAAA;OAAKrC,WAAU;iBAAeqB;UAC/B,gBAAC1B,GAAAA;OAAK2C,MAAMjC,IAAS,UAAU;OAAekC,MAAK;;;;OAIzD,gBAAC7C,GAAAA,EAAAA,UAAgB8B,EAAAA,EAAAA,CAAAA,CAAAA;;;AAIzB;;;AC/DA,SAAgBmB,EAAc,EAAEC,UAAOC,cAAWC,gBAAaC,cAA6B;CAC1F,OACE,gBAACC,UAAAA,EAAAA,UAAAA;EACC,gBAACC,OAAAA;GAAIC,WAAU;cACb,gBAACT,GAAAA,EAAAA,UAAgBG,EAAAA,CAAAA,GACjB,gBAACK,OAAAA;IAAIC,WAAU;eACb,gBAACC,QAAAA;KAAKD,WAAU;;MACd,gBAAA,GAAA,EAAA,IAAA,SAAA,CAAA;MAAyB;MAAE;;QAE7B,gBAACR,GAAAA;KAAcU,MAAMP;KAAWQ,YAAY;;;;EAG/CP,KAAe,gBAACQ,KAAAA;GAAEJ,WAAU;aAAuBJ;;EACpD,gBAACN,GAAAA,EAAQU,WAAU,OAAA,CAAA;EAClBH,KAAW,gBAACE,OAAAA;GAAIC,WAAU;aAAyBH;;;AAG1D"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"SortInput-VK7IYqQv.mjs","names":["React","Select","SelectOption","SortButton","InputGroup","SortInput","sortBy","onSortByChange","sortDirection","onSortDirectionChange","options","useLingui","getSelectProps","className","onChange","value","label","t","getSortButtonProps","order","map","option"],"sources":["../../src/client/components/ListToolbar/SortInput.tsx"],"sourcesContent":["import React from \"react\"\nimport { useLingui } from \"@lingui/react/macro\"\nimport {\n Select,\n SelectOption,\n SortButton,\n InputGroup,\n SelectProps,\n ButtonProps,\n} from \"@cloudoperators/juno-ui-components\"\nimport { SortOption } from \"./types\"\n\nexport interface SortInputProps {\n sortBy?: string | number | string[]\n onSortByChange: (param?: string | number | string[]) => void\n sortDirection: \"asc\" | \"desc\"\n onSortDirectionChange: (direction: \"asc\" | \"desc\") => void\n options: SortOption[]\n}\n\nexport const SortInput: React.FC<SortInputProps> = ({\n sortBy,\n onSortByChange,\n sortDirection,\n onSortDirectionChange,\n options,\n}) => {\n const { t } = useLingui()\n\n const getSelectProps = (): SelectProps & { \"data-testid\"?: string } => ({\n className: \"flex-grow\",\n onChange: onSortByChange,\n value: sortBy,\n \"data-testid\": \"sort-select\",\n label: t`Sort by`,\n })\n\n const getSortButtonProps = (): Omit<ButtonProps, \"onChange\"> & {\n \"data-testid\"?: string\n order: \"asc\" | \"desc\"\n onChange: (order: \"asc\" | \"desc\") => void\n } => ({\n \"data-testid\": \"direction-toggle\",\n order: sortDirection,\n onChange: onSortDirectionChange,\n })\n\n return (\n <InputGroup className=\"flex w-full items-end sm:w-auto\">\n <Select {...getSelectProps()}>\n {options.map((option) => (\n <SelectOption key={option.value} value={option.value}>\n {option.label}\n </SelectOption>\n ))}\n </Select>\n <SortButton {...getSortButtonProps()} className=\"shadow-none\" />\n </InputGroup>\n )\n}\n"],"mappings":";;;;AAoBA,IAAaK,KAAuC,EAClDC,WACAC,mBACAC,kBACAC,0BACAC,iBACD;CACC,IAAM,EAAA,MAAA,GAAA,GAAA,MAAQC,EAAAA,GAERC,WAAkE;EACtEC,WAAW;EACXC,UAAUP;EACVQ,OAAOT;EACP,eAAe;EACfU,OAAOC,EAAAA,EAAC,EAAA,IAAA,SAAQ,CAAA;CAClB,IAEMC,WAIA;EACJ,eAAe;EACfC,OAAOX;EACPM,UAAUL;CACZ;CAEA,OACE,gBAACL,GAAAA;EAAWS,WAAU;aACpB,gBAACZ,GAAAA;GAAQ,GAAGW,EAAAA;aACTF,EAAQU,KAAKC,MACZ,gBAACnB,GAAAA;IAAgCa,OAAOM,EAAON;cAC5CM,EAAOL;MADSK,EAAON,KAAK,CAAA;MAKnC,gBAACZ,GAAAA;GAAY,GAAGe,EAAAA;GAAsBL,WAAU;;;AAGtD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"_floatingIpId-C2-BeRmF.mjs","names":["formatFloatingIpStatus","status","charAt","slice","toLowerCase","Fragment","DescriptionDefinition","DescriptionList","DescriptionTerm","Stack","TwoColumnDescriptionList","items","mid","Math","ceil","length","firstColumn","slice","secondColumn","gap","className","alignTerms","map","label","value","Stack","ButtonRow","Button","ContentHeading","formatFloatingIpStatus","TwoColumnDescriptionList","FloatingIpActionModals","FloatingIpDetailsView","floatingIp","useLingui","basicInfoItems","label","t","value","id","description","project_id","status","created_at","Date","toLocaleString","updated_at","tags","join","networkRoutingItems","floating_ip_address","floating_network_id","fixed_ip_address","port_details","name","mac_address","network_id","device_owner","device_id","router_id","port_id","qos_policy_id","port_forwardings","map","port","dnsItems","dns_domain","dns_name","p","className","toggleEditModal","toggleAttachModal","toggleDetachModal","toggleReleaseModal","onClick","direction","gap","items","useNavigate","Button","ContentHeading","Stack","Spinner","Trans","useProjectId","trpcReact","FloatingIpDetailsView","Route","RouteComponent","floatingIpId","useParams","projectId","navigate","data","floatingIp","isLoading","isError","error","network","getById","useQuery","project_id","floatingip_id","handleBack","to","params","errorMessage","message","floating_ip_address","component"],"sources":["../../src/client/utils/formatFloatingIpStatus.ts","../../src/client/routes/_auth/projects/$projectId/network/floatingips/$floatingIpId/-components/TwoColumnDescriptionList.tsx","../../src/client/routes/_auth/projects/$projectId/network/floatingips/$floatingIpId/-components/-details/FloatingIpDetailsView.tsx","../../src/client/routes/_auth/projects/$projectId/network/floatingips/$floatingIpId/index.tsx?tsr-split=component"],"sourcesContent":["import type { FloatingIpStatus } from \"@/server/Network/types/floatingIp\"\n\n/**\n * Formats a floating IP status value from uppercase enum to title case.\n * Example: \"ACTIVE\" → \"Active\", \"DOWN\" → \"Down\", \"ERROR\" → \"Error\"\n */\nexport const formatFloatingIpStatus = (status: FloatingIpStatus) => {\n return status.charAt(0) + status.slice(1).toLowerCase()\n}\n","import { Fragment } from \"react\"\nimport { DescriptionDefinition, DescriptionList, DescriptionTerm, Stack } from \"@cloudoperators/juno-ui-components\"\n\nexport type DetailListItem = {\n label: string\n value: string\n}\n\ninterface TwoColumnDescriptionListProps {\n items: DetailListItem[]\n}\n\nexport const TwoColumnDescriptionList = ({ items }: TwoColumnDescriptionListProps) => {\n const mid = Math.ceil(items.length / 2)\n const firstColumn = items.slice(0, mid)\n const secondColumn = items.slice(mid)\n\n return (\n <Stack gap=\"6\" className=\"grid grid-cols-2\">\n <DescriptionList alignTerms=\"right\">\n {firstColumn.map(({ label, value }) => (\n <Fragment key={label}>\n <DescriptionTerm>{label}</DescriptionTerm>\n <DescriptionDefinition>{value}</DescriptionDefinition>\n </Fragment>\n ))}\n </DescriptionList>\n\n <DescriptionList alignTerms=\"right\">\n {secondColumn.map(({ label, value }) => (\n <Fragment key={label}>\n <DescriptionTerm>{label}</DescriptionTerm>\n <DescriptionDefinition>{value}</DescriptionDefinition>\n </Fragment>\n ))}\n </DescriptionList>\n </Stack>\n )\n}\n","import { Trans, useLingui } from \"@lingui/react/macro\"\nimport { Stack, ButtonRow, Button, ContentHeading } from \"@cloudoperators/juno-ui-components\"\nimport type { FloatingIp } from \"@/server/Network/types/floatingIp\"\nimport { formatFloatingIpStatus } from \"@/client/utils/formatFloatingIpStatus\"\nimport { DetailListItem, TwoColumnDescriptionList } from \"../TwoColumnDescriptionList\"\nimport { FloatingIpActionModals } from \"../../../-components/-modals/FloatingIpActionModals\"\n\ninterface FloatingIpDetailsViewProps {\n floatingIp: FloatingIp\n}\n\nexport const FloatingIpDetailsView = ({ floatingIp }: FloatingIpDetailsViewProps) => {\n const { t } = useLingui()\n\n const basicInfoItems: DetailListItem[] = [\n { label: t`ID`, value: floatingIp.id },\n { label: t`Description`, value: floatingIp.description || `—` },\n { label: t`Project ID`, value: floatingIp.project_id || `—` },\n { label: t`Status`, value: formatFloatingIpStatus(floatingIp.status) },\n { label: t`Created At`, value: floatingIp.created_at ? new Date(floatingIp.created_at).toLocaleString() : `—` },\n { label: t`Updated At`, value: floatingIp.updated_at ? new Date(floatingIp.updated_at).toLocaleString() : `—` },\n { label: t`Tags`, value: floatingIp.tags?.join(\", \") || `—` },\n ]\n\n const networkRoutingItems: DetailListItem[] = [\n { label: t`Floating IP Address`, value: floatingIp.floating_ip_address || `—` },\n { label: t`Floating Network`, value: floatingIp.floating_network_id || `—` },\n { label: t`Fixed IP Address`, value: floatingIp.fixed_ip_address || `—` },\n { label: t`Port Name`, value: floatingIp.port_details?.name || `—` },\n { label: t`MAC Address`, value: floatingIp.port_details?.mac_address || `—` },\n { label: t`Network ID`, value: floatingIp.port_details?.network_id || `—` },\n { label: t`Device Owner`, value: floatingIp.port_details?.device_owner || `—` },\n { label: t`Device ID`, value: floatingIp.port_details?.device_id || `—` },\n { label: t`Router ID`, value: floatingIp.router_id || `—` },\n { label: t`Port ID`, value: floatingIp.port_id || `—` },\n { label: t`QoS Policy ID`, value: floatingIp.qos_policy_id || `—` },\n { label: t`Port Forwarding`, value: floatingIp.port_forwardings?.map((port) => port.id).join(\", \") || `—` },\n ]\n\n const dnsItems: DetailListItem[] = [\n { label: t`DNS Domain`, value: floatingIp.dns_domain || `—` },\n { label: t`DNS Name`, value: floatingIp.dns_name || `—` },\n ]\n\n return (\n <>\n <p className=\"text-theme-secondary mt-2 text-sm\">\n <Trans>\n Full lifecycle management of Floating IPs, including attachment, port association/disassociation, DNS\n settings, and deletion\n </Trans>\n </p>\n\n <FloatingIpActionModals floatingIp={floatingIp}>\n {({ toggleEditModal, toggleAttachModal, toggleDetachModal, toggleReleaseModal }) => (\n <ButtonRow>\n <Button onClick={toggleEditModal}>{t`Edit Description`}</Button>\n <Button onClick={toggleAttachModal}>{t`Attach`}</Button>\n <Button onClick={toggleDetachModal}>{t`Detach`}</Button>\n <Button onClick={toggleReleaseModal}>{t`Release`}</Button>\n </ButtonRow>\n )}\n </FloatingIpActionModals>\n\n <Stack direction=\"vertical\" gap=\"6\" className=\"my-6\">\n <Stack direction=\"vertical\" gap=\"2\">\n <ContentHeading>\n <Trans>Basic Info</Trans>\n </ContentHeading>\n <TwoColumnDescriptionList items={basicInfoItems} />\n </Stack>\n\n <Stack direction=\"vertical\" gap=\"2\">\n <ContentHeading>\n <Trans>Network & Routing</Trans>\n </ContentHeading>\n <TwoColumnDescriptionList items={networkRoutingItems} />\n </Stack>\n\n <Stack direction=\"vertical\" gap=\"2\">\n <ContentHeading>\n <Trans>DNS</Trans>\n </ContentHeading>\n <TwoColumnDescriptionList items={dnsItems} />\n </Stack>\n </Stack>\n </>\n )\n}\n","import { createFileRoute, redirect, useNavigate } from \"@tanstack/react-router\"\nimport { Button, ContentHeading, Stack, Spinner } from \"@cloudoperators/juno-ui-components\"\nimport { Trans } from \"@lingui/react/macro\"\nimport type { RouteInfo } from \"@/client/routes/routeInfo\"\nimport { getServiceIndex } from \"@/server/Authentication/helpers\"\nimport { useProjectId } from \"@/client/hooks\"\nimport { trpcReact } from \"@/client/trpcClient\"\nimport { FloatingIpDetailsView } from \"./-components/-details/FloatingIpDetailsView\"\n\nexport const Route = createFileRoute(\"/_auth/projects/$projectId/network/floatingips/$floatingIpId/\")({\n staticData: {\n section: \"network\",\n service: \"floatingips\",\n isDetail: true,\n sectionCrumb: { labelKey: \"Network\" },\n crumb: { labelKey: \"Floating IPs\", to: \"/projects/$projectId/network/floatingips\" },\n } satisfies RouteInfo,\n loader: async ({ context, params }) => {\n const floatingIp = await context.trpcClient?.network.floatingIp.getById.query({\n project_id: params.projectId,\n floatingip_id: params.floatingIpId,\n })\n return { floatingIpAddress: floatingIp?.floating_ip_address ?? null }\n },\n head: ({ loaderData }) => ({\n meta: [{ title: loaderData?.floatingIpAddress ?? \"Floating IP\" }],\n }),\n component: RouteComponent,\n beforeLoad: async ({ context, params }) => {\n const { trpcClient } = context\n\n const availableServices = (await trpcClient?.auth.getAvailableServices.query()) || []\n const serviceIndex = getServiceIndex(availableServices)\n\n // Redirect if network service not available\n if (!serviceIndex[\"network\"]) {\n throw redirect({\n to: \"/projects/$projectId/network/floatingips\",\n params: { projectId: params.projectId },\n })\n }\n\n if (!serviceIndex[\"network\"][\"neutron\"]) {\n throw redirect({\n to: \"/projects/$projectId/network/floatingips\",\n params: { projectId: params.projectId },\n })\n }\n },\n})\n\nfunction RouteComponent() {\n const { floatingIpId } = Route.useParams()\n const projectId = useProjectId()\n const navigate = useNavigate()\n\n // Fetch floating IP details\n const {\n data: floatingIp,\n isLoading,\n isError,\n error,\n } = trpcReact.network.floatingIp.getById.useQuery({\n project_id: projectId,\n floatingip_id: floatingIpId,\n })\n\n const handleBack = () => {\n navigate({\n to: \"/projects/$projectId/network/floatingips\",\n params: { projectId },\n })\n }\n\n // Loading state\n if (isLoading) {\n return (\n <Stack className=\"fixed inset-0\" distribution=\"center\" alignment=\"center\" direction=\"vertical\">\n <Spinner variant=\"primary\" size=\"large\" className=\"mb-2\" />\n <Trans>Loading Floating IP Details...</Trans>\n </Stack>\n )\n }\n\n // Error state\n if (isError) {\n const errorMessage = error?.message || \"Unknown error\"\n return (\n <Stack className=\"fixed inset-0\" distribution=\"center\" alignment=\"center\" direction=\"vertical\" gap=\"5\">\n <p className=\"text-theme-error font-semibold\">\n <Trans>Error loading floating IP</Trans>\n </p>\n <p className=\"text-theme-highest\">{errorMessage}</p>\n <Button onClick={handleBack} variant=\"primary\">\n <Trans>Back to Floating IPs</Trans>\n </Button>\n </Stack>\n )\n }\n\n // No data state\n if (!floatingIp) {\n return (\n <Stack className=\"fixed inset-0\" distribution=\"center\" alignment=\"center\" direction=\"vertical\" gap=\"5\">\n <p className=\"text-theme-secondary\">\n <Trans>Floating IP not found</Trans>\n </p>\n <Button onClick={handleBack} variant=\"primary\">\n <Trans>Back to Floating IPs</Trans>\n </Button>\n </Stack>\n )\n }\n\n // Success state\n return (\n <>\n <ContentHeading>{floatingIp.floating_ip_address}</ContentHeading>\n <FloatingIpDetailsView floatingIp={floatingIp} />\n </>\n )\n}\n"],"mappings":"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"}