@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.
- package/dist/client/DeleteVersionsModal-CyYZfB8d.mjs +331 -0
- package/dist/client/DeleteVersionsModal-CyYZfB8d.mjs.map +1 -0
- package/dist/client/{SortInput-VK7IYqQv.mjs → SortInput-DLcusjGZ.mjs} +8 -8
- package/dist/client/SortInput-DLcusjGZ.mjs.map +1 -0
- package/dist/client/_auth-DXJkv9QO.mjs.map +1 -1
- package/dist/client/{_pcaId-BwTvJJgh.mjs → _pcaId-Bo7yHkNW.mjs} +3 -3
- package/dist/client/{_pcaId-BwTvJJgh.mjs.map → _pcaId-Bo7yHkNW.mjs.map} +1 -1
- package/dist/client/{_pcaId-DUHQd0rB.mjs → _pcaId-CKkCVC7b.mjs} +2 -2
- package/dist/client/{_pcaId-DUHQd0rB.mjs.map → _pcaId-CKkCVC7b.mjs.map} +1 -1
- package/dist/client/_projectId-B_2sZKk-.mjs.map +1 -1
- package/dist/client/{_projectId-DR_2U10f.mjs → _projectId-CY8W0IF6.mjs} +2 -2
- package/dist/client/_projectId-CY8W0IF6.mjs.map +1 -0
- package/dist/client/{_projectId-BaqZ4W50.mjs → _projectId-Dbck_MFa.mjs} +43 -49
- package/dist/client/_projectId-Dbck_MFa.mjs.map +1 -0
- package/dist/client/{_securityGroupId-DYxmXUOP.mjs → _securityGroupId-CkN0CGVg.mjs} +3 -3
- package/dist/client/{_securityGroupId-DYxmXUOP.mjs.map → _securityGroupId-CkN0CGVg.mjs.map} +1 -1
- package/dist/client/{_securityGroupId-fhK1CuZh.mjs → _securityGroupId-gSEZbBII.mjs} +2 -2
- package/dist/client/{_securityGroupId-fhK1CuZh.mjs.map → _securityGroupId-gSEZbBII.mjs.map} +1 -1
- package/dist/client/{_storageType-D7-_Xwwl.mjs → _storageType-6k8lUnQo.mjs} +2 -2
- package/dist/client/{_storageType-D7-_Xwwl.mjs.map → _storageType-6k8lUnQo.mjs.map} +1 -1
- package/dist/client/_storageType-CLTxXjQZ.mjs +3048 -0
- package/dist/client/_storageType-CLTxXjQZ.mjs.map +1 -0
- package/dist/client/{constants-J5nm9hbP.mjs → constants-PMXUGI4Q.mjs} +2 -2
- package/dist/client/{constants-J5nm9hbP.mjs.map → constants-PMXUGI4Q.mjs.map} +1 -1
- package/dist/client/{flavors-Dwy1ID_f.mjs → flavors-BclEwobO.mjs} +2 -2
- package/dist/client/{flavors-Dwy1ID_f.mjs.map → flavors-BclEwobO.mjs.map} +1 -1
- package/dist/client/{flavors-C-gY4XeQ.mjs → flavors-p2TKcqP-.mjs} +3 -3
- package/dist/client/{flavors-C-gY4XeQ.mjs.map → flavors-p2TKcqP-.mjs.map} +1 -1
- package/dist/client/{floatingips-Dq4DXQYb.mjs → floatingips-ph0ZxJw8.mjs} +3 -3
- package/dist/client/{floatingips-Dq4DXQYb.mjs.map → floatingips-ph0ZxJw8.mjs.map} +1 -1
- package/dist/client/{images-HG7TneK0.mjs → images-BblnyWJq.mjs} +3 -3
- package/dist/client/{images-HG7TneK0.mjs.map → images-BblnyWJq.mjs.map} +1 -1
- package/dist/client/{images-bG-MZZ7V.mjs → images-CXdghaMW.mjs} +2 -2
- package/dist/client/{images-bG-MZZ7V.mjs.map → images-CXdghaMW.mjs.map} +1 -1
- package/dist/client/index.js +121 -117
- package/dist/client/index.js.map +1 -1
- package/dist/client/{md-CYTrL5dq.mjs → md-CyCflQee.mjs} +10 -28
- package/dist/client/{md-CYTrL5dq.mjs.map → md-CyCflQee.mjs.map} +1 -1
- package/dist/client/{objects-DKWp9RtR.mjs → objects-B9Jh3SMG.mjs} +4 -3
- package/dist/client/objects-B9Jh3SMG.mjs.map +1 -0
- package/dist/client/objects-Bw25cE1m.mjs +5970 -0
- package/dist/client/objects-Bw25cE1m.mjs.map +1 -0
- package/dist/client/objects-o2Cj_ndZ.mjs.map +1 -1
- package/dist/client/{pca-BBxPCAH0.mjs → pca-CiLPHmK7.mjs} +3 -3
- package/dist/client/{pca-BBxPCAH0.mjs.map → pca-CiLPHmK7.mjs.map} +1 -1
- package/dist/client/{pca-D7DF_BZZ.mjs → pca-DUrQ_tIg.mjs} +2 -2
- package/dist/client/{pca-D7DF_BZZ.mjs.map → pca-DUrQ_tIg.mjs.map} +1 -1
- package/dist/client/{securitygroups-CNFLu9zS.mjs → securitygroups-CcA2TpAt.mjs} +2 -2
- package/dist/client/{securitygroups-CNFLu9zS.mjs.map → securitygroups-CcA2TpAt.mjs.map} +1 -1
- package/dist/client/{useListWithFiltering-v2A0-SZb.mjs → useListWithFiltering-CVzhMyEA.mjs} +2 -2
- package/dist/client/{useListWithFiltering-v2A0-SZb.mjs.map → useListWithFiltering-CVzhMyEA.mjs.map} +1 -1
- package/dist/server/index.js +282 -48
- package/package.json +3 -3
- package/dist/client/SortInput-VK7IYqQv.mjs.map +0 -1
- package/dist/client/_projectId-BaqZ4W50.mjs.map +0 -1
- package/dist/client/_projectId-DR_2U10f.mjs.map +0 -1
- package/dist/client/_storageType-B0eJODiQ.mjs +0 -3244
- package/dist/client/_storageType-B0eJODiQ.mjs.map +0 -1
- package/dist/client/objects-DKWp9RtR.mjs.map +0 -1
- package/dist/client/objects-DaCuy_CB.mjs +0 -5708
- package/dist/client/objects-DaCuy_CB.mjs.map +0 -1
package/dist/server/index.js
CHANGED
|
@@ -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
|
-
|
|
27851
|
-
|
|
27852
|
-
|
|
27853
|
-
|
|
27854
|
-
|
|
27855
|
-
|
|
27856
|
-
|
|
27857
|
-
|
|
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
|
-
|
|
33849
|
-
|
|
33850
|
-
|
|
33851
|
-
|
|
33852
|
-
|
|
33853
|
-
|
|
33854
|
-
const
|
|
33855
|
-
|
|
33856
|
-
|
|
33857
|
-
|
|
33858
|
-
|
|
33859
|
-
|
|
33860
|
-
|
|
33861
|
-
|
|
33862
|
-
|
|
33863
|
-
|
|
33864
|
-
|
|
33865
|
-
|
|
33866
|
-
|
|
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
|
-
|
|
33879
|
-
|
|
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.
|
|
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":"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"}
|
|
@@ -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"}
|