@cobaltcore-dev/aurora 0.9.0 → 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 (61) hide show
  1. package/dist/client/DeleteVersionsModal-CyYZfB8d.mjs +331 -0
  2. package/dist/client/DeleteVersionsModal-CyYZfB8d.mjs.map +1 -0
  3. package/dist/client/{SortInput-VK7IYqQv.mjs → SortInput-DLcusjGZ.mjs} +8 -8
  4. package/dist/client/SortInput-DLcusjGZ.mjs.map +1 -0
  5. package/dist/client/_auth-DXJkv9QO.mjs.map +1 -1
  6. package/dist/client/{_pcaId-BwTvJJgh.mjs → _pcaId-Bo7yHkNW.mjs} +3 -3
  7. package/dist/client/{_pcaId-BwTvJJgh.mjs.map → _pcaId-Bo7yHkNW.mjs.map} +1 -1
  8. package/dist/client/{_pcaId-DUHQd0rB.mjs → _pcaId-CKkCVC7b.mjs} +2 -2
  9. package/dist/client/{_pcaId-DUHQd0rB.mjs.map → _pcaId-CKkCVC7b.mjs.map} +1 -1
  10. package/dist/client/_projectId-B_2sZKk-.mjs.map +1 -1
  11. package/dist/client/{_projectId-DR_2U10f.mjs → _projectId-CY8W0IF6.mjs} +2 -2
  12. package/dist/client/_projectId-CY8W0IF6.mjs.map +1 -0
  13. package/dist/client/{_projectId-BaqZ4W50.mjs → _projectId-Dbck_MFa.mjs} +43 -49
  14. package/dist/client/_projectId-Dbck_MFa.mjs.map +1 -0
  15. package/dist/client/{_securityGroupId-DYxmXUOP.mjs → _securityGroupId-CkN0CGVg.mjs} +3 -3
  16. package/dist/client/{_securityGroupId-DYxmXUOP.mjs.map → _securityGroupId-CkN0CGVg.mjs.map} +1 -1
  17. package/dist/client/{_securityGroupId-fhK1CuZh.mjs → _securityGroupId-gSEZbBII.mjs} +2 -2
  18. package/dist/client/{_securityGroupId-fhK1CuZh.mjs.map → _securityGroupId-gSEZbBII.mjs.map} +1 -1
  19. package/dist/client/{_storageType-D7-_Xwwl.mjs → _storageType-6k8lUnQo.mjs} +2 -2
  20. package/dist/client/{_storageType-D7-_Xwwl.mjs.map → _storageType-6k8lUnQo.mjs.map} +1 -1
  21. package/dist/client/_storageType-CLTxXjQZ.mjs +3048 -0
  22. package/dist/client/_storageType-CLTxXjQZ.mjs.map +1 -0
  23. package/dist/client/{constants-J5nm9hbP.mjs → constants-PMXUGI4Q.mjs} +2 -2
  24. package/dist/client/{constants-J5nm9hbP.mjs.map → constants-PMXUGI4Q.mjs.map} +1 -1
  25. package/dist/client/{flavors-Dwy1ID_f.mjs → flavors-BclEwobO.mjs} +2 -2
  26. package/dist/client/{flavors-Dwy1ID_f.mjs.map → flavors-BclEwobO.mjs.map} +1 -1
  27. package/dist/client/{flavors-C-gY4XeQ.mjs → flavors-p2TKcqP-.mjs} +3 -3
  28. package/dist/client/{flavors-C-gY4XeQ.mjs.map → flavors-p2TKcqP-.mjs.map} +1 -1
  29. package/dist/client/{floatingips-Dq4DXQYb.mjs → floatingips-ph0ZxJw8.mjs} +3 -3
  30. package/dist/client/{floatingips-Dq4DXQYb.mjs.map → floatingips-ph0ZxJw8.mjs.map} +1 -1
  31. package/dist/client/{images-HG7TneK0.mjs → images-BblnyWJq.mjs} +3 -3
  32. package/dist/client/{images-HG7TneK0.mjs.map → images-BblnyWJq.mjs.map} +1 -1
  33. package/dist/client/{images-bG-MZZ7V.mjs → images-CXdghaMW.mjs} +2 -2
  34. package/dist/client/{images-bG-MZZ7V.mjs.map → images-CXdghaMW.mjs.map} +1 -1
  35. package/dist/client/index.js +121 -117
  36. package/dist/client/index.js.map +1 -1
  37. package/dist/client/{md-CYTrL5dq.mjs → md-CyCflQee.mjs} +10 -28
  38. package/dist/client/{md-CYTrL5dq.mjs.map → md-CyCflQee.mjs.map} +1 -1
  39. package/dist/client/{objects-DKWp9RtR.mjs → objects-B9Jh3SMG.mjs} +4 -3
  40. package/dist/client/objects-B9Jh3SMG.mjs.map +1 -0
  41. package/dist/client/objects-Bw25cE1m.mjs +5970 -0
  42. package/dist/client/objects-Bw25cE1m.mjs.map +1 -0
  43. package/dist/client/objects-o2Cj_ndZ.mjs.map +1 -1
  44. package/dist/client/{pca-BBxPCAH0.mjs → pca-CiLPHmK7.mjs} +3 -3
  45. package/dist/client/{pca-BBxPCAH0.mjs.map → pca-CiLPHmK7.mjs.map} +1 -1
  46. package/dist/client/{pca-D7DF_BZZ.mjs → pca-DUrQ_tIg.mjs} +2 -2
  47. package/dist/client/{pca-D7DF_BZZ.mjs.map → pca-DUrQ_tIg.mjs.map} +1 -1
  48. package/dist/client/{securitygroups-CNFLu9zS.mjs → securitygroups-CcA2TpAt.mjs} +2 -2
  49. package/dist/client/{securitygroups-CNFLu9zS.mjs.map → securitygroups-CcA2TpAt.mjs.map} +1 -1
  50. package/dist/client/{useListWithFiltering-v2A0-SZb.mjs → useListWithFiltering-CVzhMyEA.mjs} +2 -2
  51. package/dist/client/{useListWithFiltering-v2A0-SZb.mjs.map → useListWithFiltering-CVzhMyEA.mjs.map} +1 -1
  52. package/dist/server/index.js +282 -48
  53. package/package.json +3 -3
  54. package/dist/client/SortInput-VK7IYqQv.mjs.map +0 -1
  55. package/dist/client/_projectId-BaqZ4W50.mjs.map +0 -1
  56. package/dist/client/_projectId-DR_2U10f.mjs.map +0 -1
  57. package/dist/client/_storageType-B0eJODiQ.mjs +0 -3244
  58. package/dist/client/_storageType-B0eJODiQ.mjs.map +0 -1
  59. package/dist/client/objects-DKWp9RtR.mjs.map +0 -1
  60. package/dist/client/objects-DaCuy_CB.mjs +0 -5708
  61. package/dist/client/objects-DaCuy_CB.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.9.0",
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",
112
111
  "@cobaltcore-dev/policy-engine": "2.0.0",
113
- "@cobaltcore-dev/signal-openstack": "1.0.0"
112
+ "@cobaltcore-dev/signal-openstack": "1.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 +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":"_projectId-BaqZ4W50.mjs","names":["useNavigate","useMatches","useParams","useRouteContext","useState","useEffect","useRef","SideNavigation","SideNavigationList","SideNavigationGroup","SideNavigationItem","Divider","isRouteInfo","Slot","SideNavBar","projectId","projectName","domainName","sections","navigate","matches","provider","strict","slots","navBadge","service","serviceBadge","component","useShadowDOM","currentService","activeMatch","reverse","find","m","staticData","activeRouteInfo","undefined","activeSection","section","activeService","openSections","setOpenSections","Object","fromEntries","map","s","prevSectionRef","mountedRef","prev","current","wasMounted","setTimeout","ariaLabel","onClick","to","params","label","p","className","spacing","services","open","item","isStorageContainers","isSelected","span","selected","sideNavBanner","getServiceIndex","buildNavSections","projectId","availableServices","enabledServices","serviceIndex","isEnabled","service","includes","computeServices","label","t","navigate","nav","to","params","networkServices","storageServices","provider","storageType","pcaServices","clavisServices","section","services","filter","s","length","Breadcrumb","BreadcrumbItem","useMatches","useNavigate","useParams","useMemo","isRouteInfo","ProjectInfoBox","projectInfo","useLingui","navigate","matches","projectId","strict","breadcrumbs","crumbLabels","Compute","t","Network","Storage","Services","Images","Flavors","resolveProviderLabel","provider","items","push","icon","label","onClick","to","domain","name","projectMatches","filter","m","routeId","startsWith","deepest","length","info","staticData","undefined","active","params","sectionCrumb","labelKey","crumb","useParamAsLabel","resolvedLabel","isDetail","intermediateCrumb","iTo","iParam","useParentTitleAsLabel","parentMatch","parentTitle","meta","find","title","iLabel","className","map","item","index","Outlet","useLoaderData","useRouteContext","AppShell","Container","Stack","SideNavBar","buildNavSections","ProjectInfoBox","Route","RouteComponent","availableServices","projectId","crumbProject","crumbDomain","from","id","enabledServices","strict","name","domain","component"],"sources":["../../src/client/routes/_auth/projects/-components/SideNavBar.tsx","../../src/client/routes/_auth/projects/-components/buildNavSections.ts","../../src/client/components/ProjectView/ProjectInfoBox.tsx","../../src/client/routes/_auth/projects/$projectId.tsx?tsr-split=component"],"sourcesContent":["import { useNavigate, useMatches, useParams, useRouteContext } from \"@tanstack/react-router\"\nimport { useState, useEffect, useRef } from \"react\"\nimport {\n SideNavigation,\n SideNavigationList,\n SideNavigationGroup,\n SideNavigationItem,\n Divider,\n} from \"@cloudoperators/juno-ui-components/index\"\nimport { isRouteInfo } from \"@/client/routes/routeInfo\"\nimport { Slot } from \"@/client/components/Slot\"\nimport type { NavSection } from \"./buildNavSections\"\n\ninterface SideNavBarProps {\n projectId: string\n projectName: string\n domainName?: string\n sections: NavSection[]\n}\n\nexport const SideNavBar = ({ projectId, projectName, domainName, sections }: SideNavBarProps) => {\n const navigate = useNavigate()\n const matches = useMatches()\n const { provider } = useParams({ strict: false }) as { provider?: string }\n const { slots } = useRouteContext({ strict: false })\n\n const navBadge = (service: string) => {\n if (!slots?.serviceBadge) return null\n return <Slot component={slots.serviceBadge} useShadowDOM={false} currentService={service} />\n }\n\n // Read active section/service from the deepest match that has valid RouteInfo staticData\n const activeMatch = [...matches].reverse().find((m) => isRouteInfo(m.staticData))\n const activeRouteInfo = activeMatch && isRouteInfo(activeMatch.staticData) ? activeMatch.staticData : undefined\n const activeSection = activeRouteInfo?.section ?? null\n const activeService = activeRouteInfo?.service ?? null\n\n const [openSections, setOpenSections] = useState<Record<string, boolean>>(() =>\n Object.fromEntries(sections.map((s) => [s.section, true]))\n )\n const prevSectionRef = useRef<string | null>(null)\n const mountedRef = useRef(false)\n\n useEffect(() => {\n const prev = prevSectionRef.current\n prevSectionRef.current = activeSection\n const wasMounted = mountedRef.current\n mountedRef.current = true\n // Skip on initial mount: all sections start open, Juno initializes correctly from the open prop.\n // Only re-open when navigating to a section that Juno may have internally collapsed.\n if (!wasMounted) return\n if (activeSection && activeSection !== prev && activeSection in openSections) {\n // Set false first, then true in the next tick so Juno's useEffect([open]) sees the change\n // even if the section was already true in our state (Juno may have internally collapsed it).\n setOpenSections((s) => ({ ...s, [activeSection]: false }))\n const section = activeSection\n setTimeout(() => setOpenSections((s) => ({ ...s, [section]: true })), 0)\n }\n }, [activeSection])\n\n return (\n <SideNavigation ariaLabel=\"Project Side Navigation\">\n <>\n <SideNavigationList>\n <>\n <SideNavigationItem\n onClick={() => navigate({ to: \"/projects/$projectId\", params: { projectId } })}\n label={\n <>\n {domainName && <p className=\"text-theme-light text-xs leading-4 font-bold\">{domainName} /</p>}\n <p className=\"leading-5 font-normal\">{projectName}</p>\n </>\n }\n />\n <Divider spacing=\"1\" />\n {sections.map(({ section, label, services }) => (\n <SideNavigationGroup key={section} label={label} open={openSections[section]}>\n {services.map((item) => {\n const isStorageContainers = activeSection === \"storage\" && activeService === \"containers\"\n const isSelected =\n activeSection === section &&\n (isStorageContainers ? item.params.provider === provider : activeService === item.service)\n\n return (\n <SideNavigationItem\n key={item.service}\n onClick={() => item.navigate(navigate)}\n label={\n <span className=\"flex items-start gap-2\">\n {item.label}\n {navBadge(item.service)}\n </span>\n }\n selected={isSelected}\n />\n )\n })}\n </SideNavigationGroup>\n ))}\n </>\n </SideNavigationList>\n {slots?.sideNavBanner && <Slot component={slots.sideNavBanner} />}\n </>\n </SideNavigation>\n )\n}\n","import { getServiceIndex } from \"@/server/Authentication/helpers\"\nimport { t } from \"@lingui/core/macro\"\nimport type { NavigateFn } from \"@tanstack/react-router\"\n\nexport type NavItem = {\n service: string\n label: string\n navigate: (navigateFn: NavigateFn) => void\n params: Record<string, string>\n}\n\nexport type NavSection = {\n section: string\n label: string\n services: NavItem[]\n}\n\nexport function buildNavSections(\n projectId: string,\n availableServices: { type: string; name: string }[],\n enabledServices?: string[]\n): NavSection[] {\n const serviceIndex = getServiceIndex(availableServices)\n const isEnabled = (service: string) => !enabledServices || enabledServices.includes(service)\n\n const computeServices: NavItem[] = [\n ...(serviceIndex[\"image\"]?.[\"glance\"] && isEnabled(\"images\")\n ? [\n {\n service: \"images\",\n label: t`Images`,\n navigate: (nav: NavigateFn) => nav({ to: \"/projects/$projectId/compute/images\", params: { projectId } }),\n params: { projectId },\n },\n ]\n : []),\n ...(serviceIndex?.[\"compute\"]?.[\"nova\"] && isEnabled(\"flavors\")\n ? [\n {\n service: \"flavors\",\n label: t`Flavors`,\n navigate: (nav: NavigateFn) => nav({ to: \"/projects/$projectId/compute/flavors\", params: { projectId } }),\n params: { projectId },\n },\n ]\n : []),\n ]\n\n const networkServices: NavItem[] = serviceIndex[\"network\"]\n ? [\n ...(isEnabled(\"securitygroups\")\n ? [\n {\n service: \"securitygroups\",\n label: t`Security Groups`,\n navigate: (nav: NavigateFn) =>\n nav({ to: \"/projects/$projectId/network/securitygroups\", params: { projectId } }),\n params: { projectId },\n },\n ]\n : []),\n ...(isEnabled(\"floatingips\")\n ? [\n {\n service: \"floatingips\",\n label: t`Floating IPs`,\n navigate: (nav: NavigateFn) =>\n nav({ to: \"/projects/$projectId/network/floatingips\", params: { projectId } }),\n params: { projectId },\n },\n ]\n : []),\n ]\n : []\n\n const storageServices: NavItem[] = [\n ...(serviceIndex?.[\"object-store\"]?.[\"swift\"] && isEnabled(\"containers\")\n ? [\n {\n service: \"containers\",\n label: t`Object Storage (Swift)`,\n navigate: (nav: NavigateFn) =>\n nav({\n to: \"/projects/$projectId/storage/$provider/$storageType\",\n params: { projectId, provider: \"swift\", storageType: \"containers\" },\n }),\n params: { projectId, provider: \"swift\", storageType: \"containers\" },\n },\n ]\n : []),\n ...(isEnabled(\"ceph-containers\")\n ? [\n {\n service: \"ceph-containers\",\n label: t`Object Storage (Ceph)`,\n navigate: (nav: NavigateFn) =>\n nav({\n to: \"/projects/$projectId/storage/$provider/$storageType\",\n params: { projectId, provider: \"ceph\", storageType: \"buckets\" },\n }),\n params: { projectId, provider: \"ceph\", storageType: \"buckets\" },\n },\n ]\n : []),\n ]\n\n // temporary as clavis is not fully GA, after GA replace with [\"pca\"]?.[\"clavis\"]\n const pcaServices = serviceIndex[\"pca\"]?.[\"clavis-beta\"] || serviceIndex[\"pca\"]?.[\"clavis-dev\"]\n const clavisServices: NavItem[] =\n pcaServices && isEnabled(\"pca\")\n ? [\n {\n service: \"pca\",\n label: t`PCA (Clavis)`,\n navigate: (nav: NavigateFn) => nav({ to: \"/projects/$projectId/services/pca\", params: { projectId } }),\n params: { projectId },\n },\n ]\n : []\n\n return [\n { section: \"compute\", label: t`Compute`, services: computeServices },\n { section: \"network\", label: t`Network`, services: networkServices },\n { section: \"storage\", label: t`Storage`, services: storageServices },\n { section: \"services\", label: t`Services`, services: clavisServices },\n ].filter((s) => s.services.length > 0)\n}\n","import { Breadcrumb, BreadcrumbItem, KnownIcons } from \"@cloudoperators/juno-ui-components\"\nimport { useMatches, useNavigate, useParams } from \"@tanstack/react-router\"\nimport { useMemo } from \"react\"\nimport { useLingui } from \"@lingui/react/macro\"\nimport { isRouteInfo, CrumbLabelKey } from \"@/client/routes/routeInfo\"\n\ninterface ProjectInfoBoxProps {\n projectInfo: {\n id: string\n name: string\n description?: string\n domain?: {\n name?: string\n }\n }\n}\n\nexport function ProjectInfoBox({ projectInfo }: ProjectInfoBoxProps) {\n const { t } = useLingui()\n const navigate = useNavigate()\n const matches = useMatches()\n const { projectId } = useParams({ strict: false }) as { projectId: string }\n\n const breadcrumbs = useMemo(() => {\n const crumbLabels: Record<CrumbLabelKey, string> = {\n Compute: t`Compute`,\n Network: t`Network`,\n Storage: t`Storage`,\n Services: t`Services`,\n Images: t`Images`,\n Flavors: t`Flavors`,\n \"Security Groups\": t`Security Groups`,\n \"Floating IPs\": t`Floating IPs`,\n \"PCA (Clavis)\": t`PCA (Clavis)`,\n }\n\n const resolveProviderLabel = (provider: string | undefined) => {\n if (provider === \"swift\") return t`Object Storage (Swift)`\n if (provider === \"ceph\") return t`Object Storage (Ceph)`\n return t`Storage`\n }\n\n const items: Array<{ label?: string; icon?: KnownIcons; onClick?: () => void; active?: boolean }> = []\n\n items.push({ icon: \"home\", label: t`Home`, onClick: () => navigate({ to: \"/projects\" }) })\n\n if (projectInfo.domain?.name) {\n items.push({ label: projectInfo.domain.name })\n }\n\n const projectMatches = matches.filter(\n (m) => m.routeId !== \"/_auth/projects/$projectId\" && m.routeId.startsWith(\"/_auth/projects/$projectId\")\n )\n const deepest = projectMatches[projectMatches.length - 1]\n\n const info = deepest ? (isRouteInfo(deepest.staticData) ? deepest.staticData : undefined) : undefined\n\n if (!deepest || !info) {\n items.push({ label: projectInfo.name, active: true })\n return items\n }\n\n items.push({\n label: projectInfo.name,\n onClick: () => navigate({ to: \"/projects/$projectId\", params: { projectId } }),\n })\n\n const params = deepest.params as Record<string, string>\n\n if (info.sectionCrumb?.to) {\n const { labelKey, to } = info.sectionCrumb\n const label = labelKey ? crumbLabels[labelKey] : undefined\n items.push({ label, onClick: () => navigate({ to: to as never, params: params as never }) })\n }\n\n if (info.crumb) {\n const { labelKey, to, useParamAsLabel } = info.crumb\n const resolvedLabel = useParamAsLabel\n ? resolveProviderLabel(params[useParamAsLabel])\n : labelKey\n ? crumbLabels[labelKey]\n : undefined\n\n if (info.isDetail) {\n items.push({ label: resolvedLabel, onClick: () => navigate({ to: to as never, params: params as never }) })\n\n if (info.intermediateCrumb) {\n const { to: iTo, useParamAsLabel: iParam, useParentTitleAsLabel } = info.intermediateCrumb\n const parentMatch = projectMatches[projectMatches.length - 2]\n const parentTitle = parentMatch?.meta?.find((m) => m != null && \"title\" in m)?.title as string | undefined\n const iLabel = useParentTitleAsLabel\n ? (parentTitle ?? (iParam ? params[iParam] : undefined))\n : iParam\n ? params[iParam]\n : undefined\n items.push(\n iTo\n ? { label: iLabel, onClick: () => navigate({ to: iTo as never, params: params as never }) }\n : { label: iLabel }\n )\n }\n\n const title = deepest.meta?.find((m) => m != null && \"title\" in m)?.title as string | undefined\n if (title) items.push({ label: title, active: true })\n } else {\n items.push(\n to\n ? { label: resolvedLabel, onClick: () => navigate({ to: to as never, params: params as never }) }\n : { label: resolvedLabel, active: true }\n )\n }\n }\n\n return items\n }, [matches, projectInfo, projectId, navigate, t])\n\n return (\n <Breadcrumb className=\"relative z-1 mt-8 mb-4\">\n {breadcrumbs.map((item, index) => (\n <BreadcrumbItem key={index} label={item.label} icon={item.icon} onClick={item.onClick} active={item.active} />\n ))}\n </Breadcrumb>\n )\n}\n","import { createFileRoute, Outlet, useLoaderData, useRouteContext } from \"@tanstack/react-router\"\nimport { AppShell, Container, Stack } from \"@cloudoperators/juno-ui-components\"\nimport { SideNavBar } from \"@/client/routes/_auth/projects/-components/SideNavBar\"\nimport { buildNavSections } from \"@/client/routes/_auth/projects/-components/buildNavSections\"\nimport { ProjectInfoBox } from \"@/client/components/ProjectView/ProjectInfoBox\"\nimport { RouteError } from \"@/client/components/Error/RouteError\"\n\nexport const Route = createFileRoute(\"/_auth/projects/$projectId\")({\n component: RouteComponent,\n errorComponent: ({ error }) => {\n return <RouteError error={error} />\n },\n loader: async (options) => {\n const { context, params } = options\n const data = await context.trpcClient?.auth.setCurrentScope.mutate({\n type: \"project\",\n projectId: params.projectId || \"\",\n })\n\n const [availableServices, projects] = await Promise.all([\n context.trpcClient?.auth.getAvailableServices.query(),\n context.trpcClient?.project.getAuthProjects.query().catch(() => null),\n ])\n\n const accountId = data?.domain?.id || \"\"\n const description = projects?.find((p) => p.id === params.projectId)?.description ?? null\n\n return {\n trpcClient: context.trpcClient,\n crumbDomain: { path: `/projects`, name: data?.domain?.name },\n crumbProject: data?.project,\n availableServices,\n accountId,\n projectId: params.projectId,\n description,\n }\n },\n})\n\nfunction RouteComponent() {\n const { availableServices, projectId, crumbProject, crumbDomain } = useLoaderData({ from: Route.id })\n const { enabledServices } = useRouteContext({ strict: false })\n\n return (\n <AppShell\n embedded\n sideNavigation={\n <SideNavBar\n sections={buildNavSections(projectId, availableServices!, enabledServices)}\n projectId={projectId}\n projectName={crumbProject?.name || projectId}\n domainName={crumbDomain?.name}\n />\n }\n className=\"h-min-screen\"\n >\n <Container>\n <Stack direction=\"vertical\" distribution=\"start\" alignment=\"stretch\" className=\"xl:flex-row\" gap=\"6\">\n {/* Main content area */}\n <div className=\"min-w-0 flex-1\">\n <ProjectInfoBox\n projectInfo={{\n id: projectId,\n name: crumbProject?.name || projectId,\n domain: crumbProject?.domain,\n }}\n />\n <Outlet />\n </div>\n </Stack>\n </Container>\n </AppShell>\n )\n}\n"],"mappings":";;;;;;;;;;;AAoBA,IAAac,KAAc,EAAEC,cAAWC,gBAAaC,eAAYC,kBAA2B;CAC1F,IAAMC,IAAWnB,EAAAA,GACXoB,IAAUnB,EAAAA,GACV,EAAEoB,gBAAanB,EAAU,EAAEoB,QAAQ,GAAM,CAAA,GACzC,EAAEC,aAAUpB,EAAgB,EAAEmB,QAAQ,GAAM,CAAA,GAE5CE,KAAYC,MACXF,GAAOG,eACL,gBAACb,GAAAA;EAAKc,WAAWJ,EAAMG;EAAcE,cAAc;EAAOC,gBAAgBJ;MADhD,MAK7BK,IAAc,CAAA,GAAIV,CAAAA,EAASW,QAAO,EAAGC,MAAMC,MAAMrB,EAAYqB,EAAEC,UAAU,CAAA,GACzEC,IAAkBL,KAAelB,EAAYkB,EAAYI,UAAU,IAAIJ,EAAYI,aAAaE,KAAAA,GAChGC,IAAgBF,GAAiBG,WAAW,MAC5CC,IAAgBJ,GAAiBV,WAAW,MAE5C,CAACe,GAAcC,KAAmBrC,QACtCsC,OAAOC,YAAYzB,EAAS0B,KAAKC,MAAM,CAACA,EAAEP,SAAS,EAAA,CAAK,CAAA,CAAA,GAEpDQ,IAAiBxC,EAAsB,IAAA,GACvCyC,IAAazC,EAAO,EAAA;CAmB1B,OAjBAD,QAAU;EACR,IAAM2C,IAAOF,EAAeG;EAC5BH,EAAeG,UAAUZ;EACzB,IAAMa,IAAaH,EAAWE;EAC9BF,MAAWE,UAAU,IAGhBC,KACDb,KAAiBA,MAAkBW,KAAQX,KAAiBG,GAAc;GAG5EC,GAAiBI,OAAO;IAAE,GAAGA;KAAIR,IAAgB;GAAM,EAAA;GACvD,IAAMC,IAAUD;GAChBc,iBAAiBV,GAAiBI,OAAO;IAAE,GAAGA;KAAIP,IAAU;GAAK,EAAA,GAAK,CAAA;EACxE;CACF,GAAG,CAACD,CAAAA,CAAc,GAGhB,gBAAC9B,GAAAA;EAAe6C,WAAU;YACxB,gBAAA,GAAA,EAAA,UAAA,CACE,gBAAC5C,GAAAA,EAAAA,UACC,gBAAA,GAAA,EAAA,UAAA;GACE,gBAACE,GAAAA;IACC2C,eAAelC,EAAS;KAAEmC,IAAI;KAAwBC,QAAQ,EAAExC,aAAU;IAAE,CAAA;IAC5EyC,OACE,gBAAA,GAAA,EAAA,UAAA,CACGvC,KAAc,gBAACwC,KAAAA;KAAEC,WAAU;gBAAgDzC,GAAW,IAAA;QACvF,gBAACwC,KAAAA;KAAEC,WAAU;eAAyB1C;;;GAI5C,gBAACL,GAAAA,EAAQgD,SAAQ,IAAA,CAAA;GAChBzC,EAAS0B,KAAK,EAAEN,YAASkB,UAAOI,kBAC/B,gBAACnD,GAAAA;IAAyC+C;IAAOK,MAAMrB,EAAaF;cACjEsB,EAAShB,KAAKkB,MAAAA;KAEb,IAAME,IACJ3B,MAAkBC,MAFQD,MAAkB,aAAaE,MAAkB,eAGpDuB,EAAKP,OAAOlC,aAAaA,IAAWkB,MAAkBuB,EAAKrC;KAEpF,OACE,gBAACf,GAAAA;MAEC2C,eAAeS,EAAK3C,SAASA,CAAAA;MAC7BqC,OACE,gBAACS,QAAAA;OAAKP,WAAU;kBACbI,EAAKN,OACLhC,EAASsC,EAAKrC,OAAO,CAAA;;MAG1ByC,UAAUF;QARLF,EAAKrC,OAAO;IAWvB,CAAA;MApBwBa,CAAAA,CAAAA;WAyB/Bf,GAAO4C,iBAAiB,gBAACtD,GAAAA,EAAKc,WAAWJ,EAAM4C,cAAAA,CAAAA,CAAAA,EAAAA,CAAAA;;AAIxD;;;ACxFA,SAAgBE,EACdC,GACAC,GACAC,GAA0B;CAE1B,IAAMC,IAAeL,EAAgBG,CAAAA,GAC/BG,KAAaC,MAAoB,CAACH,KAAmBA,EAAgBI,SAASD,CAAAA,GAE9EE,IAA6B,CAAA,GAC7BJ,EAAa,OAAW,UAAaC,EAAU,QAAA,IAC/C,CACE;EACEC,SAAS;EACTG,OAAOC,EAAAA,EAAC,EAAA,IAAA,SAAO,CAAA;EACfC,WAAWC,MAAoBA,EAAI;GAAEC,IAAI;GAAuCC,QAAQ,EAAEb,aAAU;EAAE,CAAA;EACtGa,QAAQ,EAAEb,aAAU;CACtB,CAAA,IAEF,CAAA,GAAA,GACAG,GAAe,SAAa,QAAWC,EAAU,SAAA,IACjD,CACE;EACEC,SAAS;EACTG,OAAOC,EAAAA,EAAC,EAAA,IAAA,SAAQ,CAAA;EAChBC,WAAWC,MAAoBA,EAAI;GAAEC,IAAI;GAAwCC,QAAQ,EAAEb,aAAU;EAAE,CAAA;EACvGa,QAAQ,EAAEb,aAAU;CACtB,CAAA,IAEF,CAAA,CAAA,GAGAc,IAA6BX,EAAa,UAC5C,CAAA,GACMC,EAAU,gBAAA,IACV,CACE;EACEC,SAAS;EACTG,OAAOC,EAAAA,EAAC,EAAA,IAAA,SAAgB,CAAA;EACxBC,WAAWC,MACTA,EAAI;GAAEC,IAAI;GAA+CC,QAAQ,EAAEb,aAAU;EAAE,CAAA;EACjFa,QAAQ,EAAEb,aAAU;CACtB,CAAA,IAEF,CAAA,GAAA,GACAI,EAAU,aAAA,IACV,CACE;EACEC,SAAS;EACTG,OAAOC,EAAAA,EAAC,EAAA,IAAA,SAAa,CAAA;EACrBC,WAAWC,MACTA,EAAI;GAAEC,IAAI;GAA4CC,QAAQ,EAAEb,aAAU;EAAE,CAAA;EAC9Ea,QAAQ,EAAEb,aAAU;CACtB,CAAA,IAEF,CAAA,CAAA,IAEN,CAAA,GAEEe,IAA6B,CAAA,GAC7BZ,IAAe,iBAAkB,SAAYC,EAAU,YAAA,IACvD,CACE;EACEC,SAAS;EACTG,OAAOC,EAAAA,EAAC,EAAA,IAAA,SAAuB,CAAA;EAC/BC,WAAWC,MACTA,EAAI;GACFC,IAAI;GACJC,QAAQ;IAAEb;IAAWgB,UAAU;IAASC,aAAa;GAAa;EACpE,CAAA;EACFJ,QAAQ;GAAEb;GAAWgB,UAAU;GAASC,aAAa;EAAa;CACpE,CAAA,IAEF,CAAA,GAAA,GACAb,EAAU,iBAAA,IACV,CACE;EACEC,SAAS;EACTG,OAAOC,EAAAA,EAAC,EAAA,IAAA,SAAsB,CAAA;EAC9BC,WAAWC,MACTA,EAAI;GACFC,IAAI;GACJC,QAAQ;IAAEb;IAAWgB,UAAU;IAAQC,aAAa;GAAU;EAChE,CAAA;EACFJ,QAAQ;GAAEb;GAAWgB,UAAU;GAAQC,aAAa;EAAU;CAChE,CAAA,IAEF,CAAA,CAAA,GAKAE,KADchB,EAAa,MAAS,kBAAkBA,EAAa,MAAS,kBAEjEC,EAAU,KAAA,IACrB,CACE;EACEC,SAAS;EACTG,OAAOC,EAAAA,EAAC,EAAA,IAAA,SAAa,CAAA;EACrBC,WAAWC,MAAoBA,EAAI;GAAEC,IAAI;GAAqCC,QAAQ,EAAEb,aAAU;EAAE,CAAA;EACpGa,QAAQ,EAAEb,aAAU;CACtB,CAAA,IAEF,CAAA;CAEN,OAAO;EACL;GAAEoB,SAAS;GAAWZ,OAAOC,EAAAA,EAAC,EAAA,IAAA,SAAQ,CAAA;GAAGY,UAAUd;EAAgB;EACnE;GAAEa,SAAS;GAAWZ,OAAOC,EAAAA,EAAC,EAAA,IAAA,SAAQ,CAAA;GAAGY,UAAUP;EAAgB;EACnE;GAAEM,SAAS;GAAWZ,OAAOC,EAAAA,EAAC,EAAA,IAAA,SAAQ,CAAA;GAAGY,UAAUN;EAAgB;EACnE;GAAEK,SAAS;GAAYZ,OAAOC,EAAAA,EAAC,EAAA,IAAA,SAAS,CAAA;GAAGY,UAAUF;EAAe;GACpEG,QAAQC,MAAMA,EAAEF,SAASG,SAAS,CAAA;AACtC;;;AC7GA,SAAgBQ,EAAe,EAAEC,kBAAkC;CACjE,IAAM,EAAA,MAAA,GAAA,GAAA,MAAQC,EAAAA,GACRC,IAAWP,EAAAA,GACXQ,IAAUT,EAAAA,GACV,EAAEU,iBAAcR,EAAU,EAAES,QAAQ,GAAM,CAAA;CA+FhD,OACE,gBAACb,GAAAA;EAAW6D,WAAU;YA9FJxD,QAAQ;GAC1B,IAAMU,IAA6C;IACjDC,SAASC,EAAAA,EAAC,EAAA,IAAA,SAAQ,CAAA;IAClBC,SAASD,EAAAA,EAAC,EAAA,IAAA,SAAQ,CAAA;IAClBE,SAASF,EAAAA,EAAC,EAAA,IAAA,SAAQ,CAAA;IAClBG,UAAUH,EAAAA,EAAC,EAAA,IAAA,SAAS,CAAA;IACpBI,QAAQJ,EAAAA,EAAC,EAAA,IAAA,SAAO,CAAA;IAChBK,SAASL,EAAAA,EAAC,EAAA,IAAA,SAAQ,CAAA;IAClB,mBAAmBA,EAAAA,EAAC,EAAA,IAAA,SAAgB,CAAA;IACpC,gBAAgBA,EAAAA,EAAC,EAAA,IAAA,SAAa,CAAA;IAC9B,gBAAgBA,EAAAA,EAAC,EAAA,IAAA,SAAa,CAAA;GAChC,GAEMM,KAAwBC,MACxBA,MAAa,UAAgBP,EAAAA,EAAC,EAAA,IAAA,SAAuB,CAAA,IACrDO,MAAa,SAAeP,EAAAA,EAAC,EAAA,IAAA,SAAsB,CAAA,IAChDA,EAAAA,EAAC,EAAA,IAAA,SAAQ,CAAA,GAGZQ,IAA8F,CAAA;GAIpG,AAFAA,EAAMC,KAAK;IAAEC,MAAM;IAAQC,OAAOX,EAAAA,EAAC,EAAA,IAAA,SAAK,CAAA;IAAGY,eAAenB,EAAS,EAAEoB,IAAI,YAAY,CAAA;GAAG,CAAA,GAEpFtB,EAAYuB,QAAQC,QACtBP,EAAMC,KAAK,EAAEE,OAAOpB,EAAYuB,OAAOC,KAAK,CAAA;GAG9C,IAAMC,IAAiBtB,EAAQuB,QAC5BC,MAAMA,EAAEC,YAAY,gCAAgCD,EAAEC,QAAQC,WAAW,4BAAA,CAAA,GAEtEC,IAAUL,EAAeA,EAAeM,SAAS,IAEjDC,IAAOF,KAAWhC,EAAYgC,EAAQG,UAAU,IAAIH,EAAQG,aAAaC,KAAAA;GAE/E,IAAI,CAACJ,KAAW,CAACE,GAEf,OADAf,EAAMC,KAAK;IAAEE,OAAOpB,EAAYwB;IAAMW,QAAQ;GAAK,CAAA,GAC5ClB;GAGTA,EAAMC,KAAK;IACTE,OAAOpB,EAAYwB;IACnBH,eAAenB,EAAS;KAAEoB,IAAI;KAAwBc,QAAQ,EAAEhC,aAAU;IAAE,CAAA;GAC9E,CAAA;GAEA,IAAMgC,IAASN,EAAQM;GAEvB,IAAIJ,EAAKK,cAAcf,IAAI;IACzB,IAAM,EAAEgB,aAAUhB,UAAOU,EAAKK,cACxBjB,IAAQkB,IAAW/B,EAAY+B,KAAYJ,KAAAA;IACjDjB,EAAMC,KAAK;KAAEE;KAAOC,eAAenB,EAAS;MAAMoB;MAAqBc;KAAgB,CAAA;IAAG,CAAA;GAC5F;GAEA,IAAIJ,EAAKO,OAAO;IACd,IAAM,EAAED,aAAUhB,OAAIkB,uBAAoBR,EAAKO,OACzCE,IAAgBD,IAClBzB,EAAqBqB,EAAOI,EAAgB,IAC5CF,IACE/B,EAAY+B,KACZJ,KAAAA;IAEN,IAAIF,EAAKU,UAAU;KAGjB,IAFAzB,EAAMC,KAAK;MAAEE,OAAOqB;MAAepB,eAAenB,EAAS;OAAMoB;OAAqBc;MAAgB,CAAA;KAAG,CAAA,GAErGJ,EAAKW,mBAAmB;MAC1B,IAAM,EAAErB,IAAIsB,GAAKJ,iBAAiBK,GAAQC,6BAA0Bd,EAAKW,mBAEnEK,IADcvB,EAAeA,EAAeM,SAAS,IAC1BkB,MAAMC,MAAMvB,MAAMA,KAAK,QAAQ,WAAWA,CAAAA,GAAIwB,OACzEC,IAASN,IACVE,MAAgBH,IAAST,EAAOS,KAAUX,KAAAA,KAC3CW,IACET,EAAOS,KACPX,KAAAA;MACNjB,EAAMC,KACJ0B,IACI;OAAExB,OAAOgC;OAAQ/B,eAAenB,EAAS;QAAEoB,IAAIsB;QAAsBR;OAAgB,CAAA;MAAG,IACxF,EAAEhB,OAAOgC,EAAO,CAAA;KAExB;KAEA,IAAMD,IAAQrB,EAAQmB,MAAMC,MAAMvB,MAAMA,KAAK,QAAQ,WAAWA,CAAAA,GAAIwB;KACpE,AAAIA,KAAOlC,EAAMC,KAAK;MAAEE,OAAO+B;MAAOhB,QAAQ;KAAK,CAAA;IACrD,OACElB,EAAMC,KACJI,IACI;KAAEF,OAAOqB;KAAepB,eAAenB,EAAS;MAAMoB;MAAqBc;KAAgB,CAAA;IAAG,IAC9F;KAAEhB,OAAOqB;KAAeN,QAAQ;IAAK,CAAA;GAG/C;GAEA,OAAOlB;EACT,GAAG;GAACd;GAASH;GAAaI;GAAWF;;GAIhCI,EAAYgD,KAAKC,GAAMC,MACtB,gBAAC/D,GAAAA;GAA2B2B,OAAOmC,EAAKnC;GAAOD,MAAMoC,EAAKpC;GAAME,SAASkC,EAAKlC;GAASc,QAAQoB,EAAKpB;KAA/EqB,CAAAA,CAAAA;;AAI7B;;;ACpFA,SAASW,IAAAA;CACP,IAAM,EAAEC,sBAAmBC,cAAWC,iBAAcC,mBAAgBb,EAAc,EAAEc,MAAMN,EAAMO,GAAG,CAAA,GAC7F,EAAEC,uBAAoBf,EAAgB,EAAEgB,QAAQ,GAAM,CAAA;CAE5D,OACE,gBAAC,GAAA;EACC,UAAQ;EACR,gBACE,gBAAC,GAAA;GACC,UAAUX,EAAiBK,GAAWD,GAAoBM,CAAAA;GAC/CL;GACX,aAAaC,GAAcM,QAAQP;GACnC,YAAYE,GAAaK;;EAG7B,WAAU;YAEV,gBAAC,GAAA,EAAA,UACC,gBAAC,GAAA;GAAM,WAAU;GAAW,cAAa;GAAQ,WAAU;GAAU,WAAU;GAAc,KAAI;aAE/F,gBAAC,OAAA;IAAI,WAAU;eACb,gBAAC,GAAA,EACC,aAAa;KACXH,IAAIJ;KACJO,MAAMN,GAAcM,QAAQP;KAC5BQ,QAAQP,GAAcO;IACxB,EAAA,CAAA,GAEF,gBAAC,GAAA,CAAA,CAAA,CAAA;;;;AAMb"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"_projectId-DR_2U10f.mjs","names":["createFileRoute","Route","component","lazyRouteComponent","$$splitComponentImporter","errorComponent","$$splitErrorComponentImporter","loader","options","context","params","data","trpcClient","auth","setCurrentScope","mutate","type","projectId","availableServices","projects","Promise","all","getAvailableServices","query","project","getAuthProjects","catch","accountId","domain","id","description","find","p","crumbDomain","path","name","crumbProject"],"sources":["../../src/client/routes/_auth/projects/$projectId.tsx"],"sourcesContent":["import { createFileRoute, Outlet, useLoaderData, useRouteContext } from \"@tanstack/react-router\"\nimport { AppShell, Container, Stack } from \"@cloudoperators/juno-ui-components\"\nimport { SideNavBar } from \"@/client/routes/_auth/projects/-components/SideNavBar\"\nimport { buildNavSections } from \"@/client/routes/_auth/projects/-components/buildNavSections\"\nimport { ProjectInfoBox } from \"@/client/components/ProjectView/ProjectInfoBox\"\nimport { RouteError } from \"@/client/components/Error/RouteError\"\n\nexport const Route = createFileRoute(\"/_auth/projects/$projectId\")({\n component: RouteComponent,\n errorComponent: ({ error }) => {\n return <RouteError error={error} />\n },\n loader: async (options) => {\n const { context, params } = options\n const data = await context.trpcClient?.auth.setCurrentScope.mutate({\n type: \"project\",\n projectId: params.projectId || \"\",\n })\n\n const [availableServices, projects] = await Promise.all([\n context.trpcClient?.auth.getAvailableServices.query(),\n context.trpcClient?.project.getAuthProjects.query().catch(() => null),\n ])\n\n const accountId = data?.domain?.id || \"\"\n const description = projects?.find((p) => p.id === params.projectId)?.description ?? null\n\n return {\n trpcClient: context.trpcClient,\n crumbDomain: { path: `/projects`, name: data?.domain?.name },\n crumbProject: data?.project,\n availableServices,\n accountId,\n projectId: params.projectId,\n description,\n }\n },\n})\n\nfunction RouteComponent() {\n const { availableServices, projectId, crumbProject, crumbDomain } = useLoaderData({ from: Route.id })\n const { enabledServices } = useRouteContext({ strict: false })\n\n return (\n <AppShell\n embedded\n sideNavigation={\n <SideNavBar\n sections={buildNavSections(projectId, availableServices!, enabledServices)}\n projectId={projectId}\n projectName={crumbProject?.name || projectId}\n domainName={crumbDomain?.name}\n />\n }\n className=\"h-min-screen\"\n >\n <Container>\n <Stack direction=\"vertical\" distribution=\"start\" alignment=\"stretch\" className=\"xl:flex-row\" gap=\"6\">\n {/* Main content area */}\n <div className=\"min-w-0 flex-1\">\n <ProjectInfoBox\n projectInfo={{\n id: projectId,\n name: crumbProject?.name || projectId,\n domain: crumbProject?.domain,\n }}\n />\n <Outlet />\n </div>\n </Stack>\n </Container>\n </AppShell>\n )\n}\n"],"mappings":";AAOA,IAAaC,IAAQD,EAAgB,4BAAA,EAA8B;CACjEE,WAASC,6CAAA,WAAA;CACTE,gBAAcF,6CAAA,gBAAA;CAGdI,QAAQ,OAAOC,MAAAA;EACb,IAAM,EAAEC,YAASC,cAAWF,GACtBG,IAAO,MAAMF,EAAQG,YAAYC,KAAKC,gBAAgBC,OAAO;GACjEC,MAAM;GACNC,WAAWP,EAAOO,aAAa;EACjC,CAAA,GAEM,CAACC,GAAmBC,KAAY,MAAMC,QAAQC,IAAI,CACtDZ,EAAQG,YAAYC,KAAKS,qBAAqBC,MAAAA,GAC9Cd,EAAQG,YAAYY,QAAQC,gBAAgBF,MAAAA,EAAQG,YAAY,IAAA,CAAA,CACjE,GAEKC,IAAYhB,GAAMiB,QAAQC,MAAM,IAChCC,IAAcX,GAAUY,MAAMC,MAAMA,EAAEH,OAAOnB,EAAOO,SAAS,GAAGa,eAAe;EAErF,OAAO;GACLlB,YAAYH,EAAQG;GACpBqB,aAAa;IAAEC,MAAM;IAAaC,MAAMxB,GAAMiB,QAAQO;GAAK;GAC3DC,cAAczB,GAAMa;GACpBN;GACAS;GACAV,WAAWP,EAAOO;GAClBa;EACF;CACF;AACF,CAAA"}