Skip to content
Snippets Groups Projects
Commit bc748568 authored by Daniel Göbel's avatar Daniel Göbel
Browse files

Merge branch 'feature/145-add-ui-for-ownership-tranfers' into 'main'

Resolve "Add UI for ownership tranfers"

Closes #145

See merge request !143
parents a20abc63 10e9b968
No related branches found
No related tags found
1 merge request!143Resolve "Add UI for ownership tranfers"
Showing
with 2035 additions and 440 deletions
Source diff could not be displayed: it is too large. Options to address this: view the blob.
...@@ -60,7 +60,7 @@ ...@@ -60,7 +60,7 @@
"rollup-plugin-node-polyfills": "~0.2.1", "rollup-plugin-node-polyfills": "~0.2.1",
"sass": "^1.66.0", "sass": "^1.66.0",
"typescript": "~5.5.0", "typescript": "~5.5.0",
"vite": "~5.3.0", "vite": "~5.4.0",
"vue-tsc": "~2.0.0" "vue-tsc": "~2.0.0"
} }
} }
...@@ -28,6 +28,9 @@ export type { HTTPValidationError } from './models/HTTPValidationError'; ...@@ -28,6 +28,9 @@ export type { HTTPValidationError } from './models/HTTPValidationError';
export type { IconUpdateOut } from './models/IconUpdateOut'; export type { IconUpdateOut } from './models/IconUpdateOut';
export { NextflowVersion } from './models/NextflowVersion'; export { NextflowVersion } from './models/NextflowVersion';
export { OIDCProvider } from './models/OIDCProvider'; export { OIDCProvider } from './models/OIDCProvider';
export type { OwnershipTransferRequestIn } from './models/OwnershipTransferRequestIn';
export type { OwnershipTransferRequestOut } from './models/OwnershipTransferRequestOut';
export { OwnershipTypeEnum } from './models/OwnershipTypeEnum';
export type { ParameterExtension } from './models/ParameterExtension'; export type { ParameterExtension } from './models/ParameterExtension';
export { Permission } from './models/Permission'; export { Permission } from './models/Permission';
export { PermissionStatus } from './models/PermissionStatus'; export { PermissionStatus } from './models/PermissionStatus';
......
...@@ -19,7 +19,7 @@ export type BucketPermissionIn = { ...@@ -19,7 +19,7 @@ export type BucketPermissionIn = {
/** /**
* Permission * Permission
*/ */
permission?: (Permission | string); permission?: (Permission | 'READ' | 'WRITE' | 'READWRITE');
/** /**
* UID of the grantee * UID of the grantee
*/ */
......
...@@ -22,7 +22,7 @@ export type BucketPermissionOut = { ...@@ -22,7 +22,7 @@ export type BucketPermissionOut = {
/** /**
* Permission * Permission
*/ */
permission?: (Permission | string); permission?: (Permission | 'READ' | 'WRITE' | 'READWRITE');
/** /**
* UID of the grantee * UID of the grantee
*/ */
......
...@@ -22,6 +22,6 @@ export type BucketPermissionParameters = { ...@@ -22,6 +22,6 @@ export type BucketPermissionParameters = {
/** /**
* Permission * Permission
*/ */
permission?: (Permission | string); permission?: (Permission | 'READ' | 'WRITE' | 'READWRITE');
}; };
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type OwnershipTransferRequestIn = {
/**
* The new owner that get the request
*/
new_owner_uid: string;
/**
* An optional comment for the transfer request
*/
comment?: (string | null);
};
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { OwnershipTypeEnum } from './OwnershipTypeEnum';
export type OwnershipTransferRequestOut = {
/**
* The new owner that get the request
*/
new_owner_uid: string;
/**
* An optional comment for the transfer request
*/
comment?: string;
/**
* Time when the ownership transfer was requested as UNIX timestamp
*/
created_at: number;
/**
* The current uid of the current owner if he exists
*/
current_owner_uid?: (string | null);
/**
* Id of the target that gets its ownership transferred
*/
target_id: string;
/**
* Name of the target
*/
target_name: string;
/**
* Description of then target
*/
target_description: string;
/**
* Target type of the ownership transfer
*/
target_type: OwnershipTypeEnum;
};
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export enum OwnershipTypeEnum {
BUCKET = 'bucket',
WORKFLOW = 'workflow',
RESOURCE = 'resource',
}
...@@ -24,6 +24,10 @@ export type ResourceVersionOut = { ...@@ -24,6 +24,10 @@ export type ResourceVersionOut = {
* Timestamp when the version was created as UNIX timestamp * Timestamp when the version was created as UNIX timestamp
*/ */
created_at: number; created_at: number;
/**
* Size of the compressed resource in bytes
*/
compressed_size: number;
/** /**
* Path to the resource on the cluster if the resource is synchronized * Path to the resource on the cluster if the resource is synchronized
*/ */
......
...@@ -171,27 +171,30 @@ export class BucketPermissionService { ...@@ -171,27 +171,30 @@ export class BucketPermissionService {
}); });
} }
/** /**
* Delete a bucket permission * Update a bucket permission
* Delete the bucket permissions for the specific combination of bucket and user. * Update a permission for a bucket and user.
* *
* Permission `bucket_permission:delete` required if current user is the target or owner of the bucket permission, * Permission `bucket_permission:update` required.
* otherwise `bucket_permission:delete_any` required.
* @param bucketName Name of bucket * @param bucketName Name of bucket
* @param uid UID of a user * @param uid UID of a user
* @returns void * @param requestBody
* @returns BucketPermissionOut Successful Response
* @throws ApiError * @throws ApiError
*/ */
public static bucketPermissionDeletePermission( public static bucketPermissionUpdatePermission(
bucketName: string, bucketName: string,
uid: string, uid: string,
): CancelablePromise<void> { requestBody: BucketPermissionParameters,
): CancelablePromise<BucketPermissionOut> {
return __request(OpenAPI, { return __request(OpenAPI, {
method: 'DELETE', method: 'PUT',
url: '/permissions/bucket/{bucket_name}/user/{uid}', url: '/permissions/bucket/{bucket_name}/user/{uid}',
path: { path: {
'bucket_name': bucketName, 'bucket_name': bucketName,
'uid': uid, 'uid': uid,
}, },
body: requestBody,
mediaType: 'application/json',
errors: { errors: {
400: `Error decoding JWT Token`, 400: `Error decoding JWT Token`,
401: `Not Authenticated`, 401: `Not Authenticated`,
...@@ -202,30 +205,27 @@ export class BucketPermissionService { ...@@ -202,30 +205,27 @@ export class BucketPermissionService {
}); });
} }
/** /**
* Update a bucket permission * Delete a bucket permission
* Update a permission for a bucket and user. * Delete the bucket permissions for the specific combination of bucket and user.
* *
* Permission `bucket_permission:update` required. * Permission `bucket_permission:delete` required if current user is the target or owner of the bucket permission,
* otherwise `bucket_permission:delete_any` required.
* @param bucketName Name of bucket * @param bucketName Name of bucket
* @param uid UID of a user * @param uid UID of a user
* @param requestBody * @returns void
* @returns BucketPermissionOut Successful Response
* @throws ApiError * @throws ApiError
*/ */
public static bucketPermissionUpdatePermission( public static bucketPermissionDeletePermission(
bucketName: string, bucketName: string,
uid: string, uid: string,
requestBody: BucketPermissionParameters, ): CancelablePromise<void> {
): CancelablePromise<BucketPermissionOut> {
return __request(OpenAPI, { return __request(OpenAPI, {
method: 'PUT', method: 'DELETE',
url: '/permissions/bucket/{bucket_name}/user/{uid}', url: '/permissions/bucket/{bucket_name}/user/{uid}',
path: { path: {
'bucket_name': bucketName, 'bucket_name': bucketName,
'uid': uid, 'uid': uid,
}, },
body: requestBody,
mediaType: 'application/json',
errors: { errors: {
400: `Error decoding JWT Token`, 400: `Error decoding JWT Token`,
401: `Not Authenticated`, 401: `Not Authenticated`,
......
...@@ -7,16 +7,18 @@ import type { BucketIn } from '../models/BucketIn'; ...@@ -7,16 +7,18 @@ import type { BucketIn } from '../models/BucketIn';
import type { BucketOut } from '../models/BucketOut'; import type { BucketOut } from '../models/BucketOut';
import type { BucketSizeLimits } from '../models/BucketSizeLimits'; import type { BucketSizeLimits } from '../models/BucketSizeLimits';
import type { BucketType } from '../models/BucketType'; import type { BucketType } from '../models/BucketType';
import type { OwnershipTransferRequestIn } from '../models/OwnershipTransferRequestIn';
import type { OwnershipTransferRequestOut } from '../models/OwnershipTransferRequestOut';
import type { CancelablePromise } from '../core/CancelablePromise'; import type { CancelablePromise } from '../core/CancelablePromise';
import { OpenAPI } from '../core/OpenAPI'; import { OpenAPI } from '../core/OpenAPI';
import { request as __request } from '../core/request'; import { request as __request } from '../core/request';
export class BucketService { export class BucketService {
/** /**
* List buckets of user * List buckets
* List all the buckets in the system or of the desired user where the user has permissions for. * List all the buckets in the system or of the desired user where the user has permissions for.
* *
* Permission `bucket:list_all` required. See parameter `owner_id` for exception. * Permission `bucket:list_all` required. See parameter `owner_id` for exception.
* @param ownerId UID of the user for whom to fetch the buckets for. Permission 'bucket:read_any' required if current user is not the target. * @param ownerId UID of the user for whom to fetch the buckets for. Permission `bucket:read_any` required if current user is not the target.
* @param bucketType Type of the bucket to get. Ignored when `user` parameter not set * @param bucketType Type of the bucket to get. Ignored when `user` parameter not set
* @returns BucketOut Successful Response * @returns BucketOut Successful Response
* @throws ApiError * @throws ApiError
...@@ -71,6 +73,37 @@ export class BucketService { ...@@ -71,6 +73,37 @@ export class BucketService {
}, },
}); });
} }
/**
* List bucket OTRs
* Get the ownership transfer requests for buckets.
*
* Permission `bucket:list` required if `current_owner_id` or `new_owner_id` is the current users id,
* otherwise `bucket:list_all`
* @param currentOwnerId UID of user who is the current owner.
* @param newOwnerId UID of user who will be the new owner.
* @returns OwnershipTransferRequestOut Successful Response
* @throws ApiError
*/
public static bucketListBucketOtrs(
currentOwnerId?: string,
newOwnerId?: string,
): CancelablePromise<Array<OwnershipTransferRequestOut>> {
return __request(OpenAPI, {
method: 'GET',
url: '/buckets/ownership_transfer_request',
query: {
'current_owner_id': currentOwnerId,
'new_owner_id': newOwnerId,
},
errors: {
400: `Error decoding JWT Token`,
401: `Not Authenticated`,
403: `Not Authorized`,
404: `Entity not Found`,
422: `Validation Error`,
},
});
}
/** /**
* Get a bucket by its name * Get a bucket by its name
* Get a bucket by its name if the current user has READ permissions for the bucket. * Get a bucket by its name if the current user has READ permissions for the bucket.
...@@ -132,6 +165,122 @@ export class BucketService { ...@@ -132,6 +165,122 @@ export class BucketService {
}, },
}); });
} }
/**
* Get a bucket OTR
* Get a specific bucket ownership transfer request.
*
* Permission `bucket:read` required if the current user is the current or new owner of the bucket,
* otherwise `bucket:read_any` required.
* @param bucketName Name of bucket
* @returns OwnershipTransferRequestOut Successful Response
* @throws ApiError
*/
public static bucketGetBucketOtr(
bucketName: string,
): CancelablePromise<OwnershipTransferRequestOut> {
return __request(OpenAPI, {
method: 'GET',
url: '/buckets/{bucket_name}/ownership_transfer_request',
path: {
'bucket_name': bucketName,
},
errors: {
400: `Error decoding JWT Token`,
401: `Not Authenticated`,
403: `Not Authorized`,
404: `Entity not Found`,
422: `Validation Error`,
},
});
}
/**
* Create a bucket OTR
* Create a ownership transfer request for a specific bucket.
*
* Permission `bucket:update` required if the current user is the current owner of the bucket,
* otherwise `bucket:update_any` required.
* @param bucketName Name of bucket
* @param requestBody
* @returns OwnershipTransferRequestOut Successful Response
* @throws ApiError
*/
public static bucketCreateBucketOtr(
bucketName: string,
requestBody: OwnershipTransferRequestIn,
): CancelablePromise<OwnershipTransferRequestOut> {
return __request(OpenAPI, {
method: 'POST',
url: '/buckets/{bucket_name}/ownership_transfer_request',
path: {
'bucket_name': bucketName,
},
body: requestBody,
mediaType: 'application/json',
errors: {
400: `Error decoding JWT Token`,
401: `Not Authenticated`,
403: `Not Authorized`,
404: `Entity not Found`,
422: `Validation Error`,
},
});
}
/**
* Accept a bucket OTR
* Accept an ownership transfer request for a specific workflow.
*
* Permission `bucket:update` required if the current user is the new owner of the workflow,
* otherwise `bucket:update_any` required.
* @param bucketName Name of bucket
* @returns BucketOut Successful Response
* @throws ApiError
*/
public static bucketAcceptBucketOtr(
bucketName: string,
): CancelablePromise<BucketOut> {
return __request(OpenAPI, {
method: 'PATCH',
url: '/buckets/{bucket_name}/ownership_transfer_request',
path: {
'bucket_name': bucketName,
},
errors: {
400: `Error decoding JWT Token`,
401: `Not Authenticated`,
403: `Not Authorized`,
404: `Entity not Found`,
422: `Validation Error`,
},
});
}
/**
* Delete a bucket OTR
* Delete/Reject a bucket ownership transfer request.
*
* Permission `bucket:update` required if the current user is the current or new owner of the bucket,
* otherwise `bucket:update_any` required.
* @param bucketName Name of bucket
* @returns void
* @throws ApiError
*/
public static bucketDeleteBucketOtr(
bucketName: string,
): CancelablePromise<void> {
return __request(OpenAPI, {
method: 'DELETE',
url: '/buckets/{bucket_name}/ownership_transfer_request',
path: {
'bucket_name': bucketName,
},
errors: {
400: `Error decoding JWT Token`,
401: `Not Authenticated`,
403: `Not Authorized`,
404: `Entity not Found`,
422: `Validation Error`,
},
});
}
/** /**
* Update public status * Update public status
* Update the buckets public state. * Update the buckets public state.
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
/* istanbul ignore file */ /* istanbul ignore file */
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */ /* eslint-disable */
import type { OwnershipTransferRequestIn } from '../models/OwnershipTransferRequestIn';
import type { OwnershipTransferRequestOut } from '../models/OwnershipTransferRequestOut';
import type { ResourceIn } from '../models/ResourceIn'; import type { ResourceIn } from '../models/ResourceIn';
import type { ResourceOut } from '../models/ResourceOut'; import type { ResourceOut } from '../models/ResourceOut';
import type { ResourceVersionStatus } from '../models/ResourceVersionStatus'; import type { ResourceVersionStatus } from '../models/ResourceVersionStatus';
...@@ -10,6 +12,32 @@ import type { CancelablePromise } from '../core/CancelablePromise'; ...@@ -10,6 +12,32 @@ import type { CancelablePromise } from '../core/CancelablePromise';
import { OpenAPI } from '../core/OpenAPI'; import { OpenAPI } from '../core/OpenAPI';
import { request as __request } from '../core/request'; import { request as __request } from '../core/request';
export class ResourceService { export class ResourceService {
/**
* Request a new resource
* Request a new resources.
*
* Permission `resource:create` required.
* @param requestBody
* @returns ResourceOut Successful Response
* @throws ApiError
*/
public static resourceCreateResource(
requestBody: ResourceIn,
): CancelablePromise<ResourceOut> {
return __request(OpenAPI, {
method: 'POST',
url: '/resources',
body: requestBody,
mediaType: 'application/json',
errors: {
400: `Error decoding JWT Token`,
401: `Not Authenticated`,
403: `Not Authorized`,
404: `Entity not Found`,
422: `Validation Error`,
},
});
}
/** /**
* List resources * List resources
* List all resources. * List all resources.
...@@ -47,48 +75,53 @@ export class ResourceService { ...@@ -47,48 +75,53 @@ export class ResourceService {
}); });
} }
/** /**
* Request a new resource * List resource sync requests
* Request a new resources. * List all resource sync requests.
* *
* Permission `resource:create` required. * Permission `resource:update_any` required.
* @param requestBody * @returns UserSynchronizationRequestOut Successful Response
* @returns ResourceOut Successful Response
* @throws ApiError * @throws ApiError
*/ */
public static resourceCreateResource( public static resourceListSyncRequests(): CancelablePromise<Array<UserSynchronizationRequestOut>> {
requestBody: ResourceIn,
): CancelablePromise<ResourceOut> {
return __request(OpenAPI, { return __request(OpenAPI, {
method: 'POST', method: 'GET',
url: '/resources', url: '/resources/sync_requests',
body: requestBody,
mediaType: 'application/json',
errors: { errors: {
400: `Error decoding JWT Token`, 400: `Error decoding JWT Token`,
401: `Not Authenticated`, 401: `Not Authenticated`,
403: `Not Authorized`, 403: `Not Authorized`,
404: `Entity not Found`, 404: `Entity not Found`,
422: `Validation Error`,
}, },
}); });
} }
/** /**
* List resource sync requests * List resource OTRs
* List all resource sync requests. * Get the ownership transfer requests for resources.
* *
* Permission `resource:update_any` required. * Permission `resource:list` required if `current_owner_id` or `new_owner_id` is the current users id,
* @returns UserSynchronizationRequestOut Successful Response * otherwise `resource:list_all`
* @param currentOwnerId UID of user who is the current owner.
* @param newOwnerId UID of user who will be the new owner.
* @returns OwnershipTransferRequestOut Successful Response
* @throws ApiError * @throws ApiError
*/ */
public static resourceListSyncRequests(): CancelablePromise<Array<UserSynchronizationRequestOut>> { public static resourceListResourceOtrs(
currentOwnerId?: string,
newOwnerId?: string,
): CancelablePromise<Array<OwnershipTransferRequestOut>> {
return __request(OpenAPI, { return __request(OpenAPI, {
method: 'GET', method: 'GET',
url: '/resources/sync_requests', url: '/resources/ownership_transfer_request',
query: {
'current_owner_id': currentOwnerId,
'new_owner_id': newOwnerId,
},
errors: { errors: {
400: `Error decoding JWT Token`, 400: `Error decoding JWT Token`,
401: `Not Authenticated`, 401: `Not Authenticated`,
403: `Not Authorized`, 403: `Not Authorized`,
404: `Entity not Found`, 404: `Entity not Found`,
422: `Validation Error`,
}, },
}); });
} }
...@@ -151,4 +184,120 @@ export class ResourceService { ...@@ -151,4 +184,120 @@ export class ResourceService {
}, },
}); });
} }
/**
* Get a resource OTR
* Get a specific resource ownership transfer request.
*
* Permission `resource:read` required if the current user is the current or new owner of the resource,
* otherwise `resource:read_any` required.
* @param rid
* @returns OwnershipTransferRequestOut Successful Response
* @throws ApiError
*/
public static resourceGetResourceOtr(
rid: string,
): CancelablePromise<OwnershipTransferRequestOut> {
return __request(OpenAPI, {
method: 'GET',
url: '/resources/{rid}/ownership_transfer_request',
path: {
'rid': rid,
},
errors: {
400: `Error decoding JWT Token`,
401: `Not Authenticated`,
403: `Not Authorized`,
404: `Entity not Found`,
422: `Validation Error`,
},
});
}
/**
* Create a resource OTR
* Create a ownership transfer request for a specific resource.
*
* Permission `resource:update` required if the current user is the current owner of the resource,
* otherwise `resource:update_any` required.
* @param rid
* @param requestBody
* @returns OwnershipTransferRequestOut Successful Response
* @throws ApiError
*/
public static resourceCreateResourceOtr(
rid: string,
requestBody: OwnershipTransferRequestIn,
): CancelablePromise<OwnershipTransferRequestOut> {
return __request(OpenAPI, {
method: 'POST',
url: '/resources/{rid}/ownership_transfer_request',
path: {
'rid': rid,
},
body: requestBody,
mediaType: 'application/json',
errors: {
400: `Error decoding JWT Token`,
401: `Not Authenticated`,
403: `Not Authorized`,
404: `Entity not Found`,
422: `Validation Error`,
},
});
}
/**
* Accept a resource OTR
* Accept an ownership transfer request for a specific resource.
*
* Permission `resource:update` required if the current user is the new owner of the resource,
* otherwise `resource:update_any` required.
* @param rid
* @returns ResourceOut Successful Response
* @throws ApiError
*/
public static resourceAcceptResourceOtr(
rid: string,
): CancelablePromise<ResourceOut> {
return __request(OpenAPI, {
method: 'PATCH',
url: '/resources/{rid}/ownership_transfer_request',
path: {
'rid': rid,
},
errors: {
400: `Error decoding JWT Token`,
401: `Not Authenticated`,
403: `Not Authorized`,
404: `Entity not Found`,
422: `Validation Error`,
},
});
}
/**
* Delete a resource OTR
* Delete/Reject a resource ownership transfer request.
*
* Permission `resource:update` required if the current user is the current or new owner of the resource,
* otherwise `resource:update_any` required.
* @param rid
* @returns void
* @throws ApiError
*/
public static resourceDeleteResourceOtr(
rid: string,
): CancelablePromise<void> {
return __request(OpenAPI, {
method: 'DELETE',
url: '/resources/{rid}/ownership_transfer_request',
path: {
'rid': rid,
},
errors: {
400: `Error decoding JWT Token`,
401: `Not Authenticated`,
403: `Not Authorized`,
404: `Entity not Found`,
422: `Validation Error`,
},
});
}
} }
...@@ -142,7 +142,7 @@ export class UserService { ...@@ -142,7 +142,7 @@ export class UserService {
}); });
} }
/** /**
* Update Roles * Update user roles
* Update the roles of a user. * Update the roles of a user.
* *
* Permission `user:update` required. * Permission `user:update` required.
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */ /* eslint-disable */
import type { AnonymizedWorkflowExecution } from '../models/AnonymizedWorkflowExecution'; import type { AnonymizedWorkflowExecution } from '../models/AnonymizedWorkflowExecution';
import type { OwnershipTransferRequestIn } from '../models/OwnershipTransferRequestIn';
import type { OwnershipTransferRequestOut } from '../models/OwnershipTransferRequestOut';
import type { WorkflowIn } from '../models/WorkflowIn'; import type { WorkflowIn } from '../models/WorkflowIn';
import type { WorkflowOut } from '../models/WorkflowOut'; import type { WorkflowOut } from '../models/WorkflowOut';
import type { WorkflowStatistic } from '../models/WorkflowStatistic'; import type { WorkflowStatistic } from '../models/WorkflowStatistic';
...@@ -13,6 +15,36 @@ import type { CancelablePromise } from '../core/CancelablePromise'; ...@@ -13,6 +15,36 @@ import type { CancelablePromise } from '../core/CancelablePromise';
import { OpenAPI } from '../core/OpenAPI'; import { OpenAPI } from '../core/OpenAPI';
import { request as __request } from '../core/request'; import { request as __request } from '../core/request';
export class WorkflowService { export class WorkflowService {
/**
* Create a new workflow
* Create a new workflow.
*
* For private Gitlab repositories, a Project Access Token with the role Reporter and scope `read_api` is needed.
*
* For private GitHub repositories, a Personal Access Token (classic) with scope `repo` is needed.
*
* Permission `workflow:create` required.
* @param requestBody
* @returns WorkflowOut Successful Response
* @throws ApiError
*/
public static workflowCreateWorkflow(
requestBody: WorkflowIn,
): CancelablePromise<WorkflowOut> {
return __request(OpenAPI, {
method: 'POST',
url: '/workflows',
body: requestBody,
mediaType: 'application/json',
errors: {
400: `Error decoding JWT Token`,
401: `Not Authenticated`,
403: `Not Authorized`,
404: `Entity not Found`,
422: `Validation Error`,
},
});
}
/** /**
* List workflows * List workflows
* List all workflows. * List all workflows.
...@@ -47,26 +79,27 @@ export class WorkflowService { ...@@ -47,26 +79,27 @@ export class WorkflowService {
}); });
} }
/** /**
* Create a new workflow * List workflow OTRs
* Create a new workflow. * Get the ownership transfer requests for workflows.
*
* For private Gitlab repositories, a Project Access Token with the role Reporter and scope `read_api` is needed.
* *
* For private GitHub repositories, a Personal Access Token (classic) with scope `repo` is needed. * Permission `workflow:list` required if `current_owner_id` or `new_owner_id` is the current users id,
* * otherwise `workflow:list_all`
* Permission `workflow:create` required. * @param currentOwnerId UID of user who is the current owner.
* @param requestBody * @param newOwnerId UID of user who will be the new owner.
* @returns WorkflowOut Successful Response * @returns OwnershipTransferRequestOut Successful Response
* @throws ApiError * @throws ApiError
*/ */
public static workflowCreateWorkflow( public static workflowListWorkflowOtrs(
requestBody: WorkflowIn, currentOwnerId?: string,
): CancelablePromise<WorkflowOut> { newOwnerId?: string,
): CancelablePromise<Array<OwnershipTransferRequestOut>> {
return __request(OpenAPI, { return __request(OpenAPI, {
method: 'POST', method: 'GET',
url: '/workflows', url: '/workflows/ownership_transfer_request',
body: requestBody, query: {
mediaType: 'application/json', 'current_owner_id': currentOwnerId,
'new_owner_id': newOwnerId,
},
errors: { errors: {
400: `Error decoding JWT Token`, 400: `Error decoding JWT Token`,
401: `Not Authenticated`, 401: `Not Authenticated`,
...@@ -173,6 +206,122 @@ export class WorkflowService { ...@@ -173,6 +206,122 @@ export class WorkflowService {
}, },
}); });
} }
/**
* Get a workflow OTR
* Get a specific workflow ownership transfer request.
*
* Permission `workflow:read` required if current user is the current or new owner of the workflow,
* otherwise `workflow:read_any` required.
* @param wid ID of a workflow
* @returns OwnershipTransferRequestOut Successful Response
* @throws ApiError
*/
public static workflowGetWorkflowOtr(
wid: string,
): CancelablePromise<OwnershipTransferRequestOut> {
return __request(OpenAPI, {
method: 'GET',
url: '/workflows/{wid}/ownership_transfer_request',
path: {
'wid': wid,
},
errors: {
400: `Error decoding JWT Token`,
401: `Not Authenticated`,
403: `Not Authorized`,
404: `Entity not Found`,
422: `Validation Error`,
},
});
}
/**
* Create a workflow OTR
* Create a ownership transfer request for a specific workflow.
*
* Permission `workflow:update` required if the current user is the current owner of the workflow,
* otherwise `workflow:update_any` required.
* @param wid ID of a workflow
* @param requestBody
* @returns OwnershipTransferRequestOut Successful Response
* @throws ApiError
*/
public static workflowCreateWorkflowOtr(
wid: string,
requestBody: OwnershipTransferRequestIn,
): CancelablePromise<OwnershipTransferRequestOut> {
return __request(OpenAPI, {
method: 'POST',
url: '/workflows/{wid}/ownership_transfer_request',
path: {
'wid': wid,
},
body: requestBody,
mediaType: 'application/json',
errors: {
400: `Error decoding JWT Token`,
401: `Not Authenticated`,
403: `Not Authorized`,
404: `Entity not Found`,
422: `Validation Error`,
},
});
}
/**
* Accept a workflow OTR
* Accept an ownership transfer request for a specific workflow.
*
* Permission `workflow:update` required if the current user is the new owner of the workflow,
* otherwise `workflow:update_any` required.
* @param wid ID of a workflow
* @returns WorkflowOut Successful Response
* @throws ApiError
*/
public static workflowAcceptWorkflowOtr(
wid: string,
): CancelablePromise<WorkflowOut> {
return __request(OpenAPI, {
method: 'PATCH',
url: '/workflows/{wid}/ownership_transfer_request',
path: {
'wid': wid,
},
errors: {
400: `Error decoding JWT Token`,
401: `Not Authenticated`,
403: `Not Authorized`,
404: `Entity not Found`,
422: `Validation Error`,
},
});
}
/**
* Delete a workflow OTR
* Delete/Reject a workflow ownership transfer request.
*
* Permission `workflow:update` required if current user is the current or new owner of the workflow,
* otherwise `workflow:update_any` required.
* @param wid ID of a workflow
* @returns void
* @throws ApiError
*/
public static workflowDeleteWorkflowOtr(
wid: string,
): CancelablePromise<void> {
return __request(OpenAPI, {
method: 'DELETE',
url: '/workflows/{wid}/ownership_transfer_request',
path: {
'wid': wid,
},
errors: {
400: `Error decoding JWT Token`,
401: `Not Authenticated`,
403: `Not Authorized`,
404: `Entity not Found`,
422: `Validation Error`,
},
});
}
/** /**
* Get statistics for a workflow * Get statistics for a workflow
* Get the number of started workflow per day. * Get the number of started workflow per day.
......
...@@ -12,6 +12,13 @@ const props = defineProps({ ...@@ -12,6 +12,13 @@ const props = defineProps({
toastId: { type: String, required: true }, toastId: { type: String, required: true },
}); });
const emit = defineEmits<{
(e: "hide.bs.toast"): void;
(e: "hidden.bs.toast"): void;
(e: "show.bs.toast"): void;
(e: "shown.bs.toast"): void;
}>();
const colorClassName = computed<string>(() => { const colorClassName = computed<string>(() => {
return `text-bg-${props.colorClass}`; return `text-bg-${props.colorClass}`;
}); });
...@@ -27,6 +34,12 @@ const colorClassName = computed<string>(() => { ...@@ -27,6 +34,12 @@ const colorClassName = computed<string>(() => {
:class="colorClassName" :class="colorClassName"
data-bs-autohide="true" data-bs-autohide="true"
:id="props.toastId" :id="props.toastId"
v-on="{
'hidden.bs.toast': emit('hidden.bs.toast'),
'hide.bs.toast': emit('hide.bs.toast'),
'show.bs.toast': emit('show.bs.toast'),
'shown.bs.toast': emit('shown.bs.toast'),
}"
> >
<div class="toast-body"> <div class="toast-body">
<div class="d-flex justify-content-between fs-6"> <div class="d-flex justify-content-between fs-6">
......
<template> <template>
<span <span
class="align-middle" class="align-middle"
ref="iconElement"
:class="icon" :class="icon"
:style="{ :style="{
color: props.fill, color: props.fill,
}" }"
:data-bs-toogle="tooltip.length > 0 ? 'tooltip' : undefined"
:data-bs-title="tooltip.length > 0 ? tooltip : undefined"
></span> ></span>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref } from "vue";
import { Tooltip } from "bootstrap";
const iconElement = ref<HTMLSpanElement | undefined>(undefined);
const props = defineProps({ const props = defineProps({
icon: { type: String, required: true }, icon: { type: String, required: true },
fill: { type: String, default: "currentColor", required: false }, fill: { type: String, default: "currentColor", required: false },
tooltip: { type: String, required: false, default: "" },
});
onMounted(() => {
if (props.tooltip.length > 0 && iconElement.value != undefined) {
new Tooltip(iconElement.value);
}
}); });
</script> </script>
......
<script setup lang="ts">
import {
OwnershipTypeEnum,
type OwnershipTransferRequestIn,
type ResourceOut,
type UserOut,
type WorkflowOut,
type BucketOut,
} from "@/client";
import BootstrapModal from "@/components/modals/BootstrapModal.vue";
import { computed, onMounted, reactive, ref, watch } from "vue";
import SearchUserModal from "@/components/modals/SearchUserModal.vue";
import { Modal, Toast } from "bootstrap";
import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue";
import { useNameStore } from "@/stores/names";
import { useOTRStore } from "@/stores/otrs";
import BootstrapToast from "@/components/BootstrapToast.vue";
type OtrTypes = BucketOut | WorkflowOut | ResourceOut;
const props = defineProps<{
modalId: string;
target: OtrTypes;
backModalId?: string;
}>();
const randomIDSuffix = Math.random().toString(16).substring(2, 8);
let createOtrModal: Modal | null = null;
let successToast: Toast | null = null;
const nameRepository = useNameStore();
const otrRepository = useOTRStore();
const otrCreateForm = ref<HTMLFormElement | undefined>(undefined);
const formState = reactive<{
validated: boolean;
requestIn: OwnershipTransferRequestIn;
}>({
validated: false,
requestIn: {
new_owner_uid: "",
comment: undefined,
},
});
const targetType = computed<OwnershipTypeEnum>(() => {
if (isResource(props.target)) {
return OwnershipTypeEnum.RESOURCE;
} else if (isWorkflow(props.target)) {
return OwnershipTypeEnum.WORKFLOW;
}
return OwnershipTypeEnum.BUCKET;
});
const targetName = computed<string>(() => props.target.name);
const targetId = computed<string>(() => {
if (isResource(props.target)) {
return props.target.resource_id;
} else if (isWorkflow(props.target)) {
return props.target.workflow_id;
} else if (isBucket(props.target)) {
return props.target.name;
}
return "";
});
watch(targetId, (newId, oldId) => {
if (newId !== oldId) {
resetForm();
}
});
function saveOtr() {
formState.validated = true;
formState.requestIn.comment = formState.requestIn.comment?.trim();
if (otrCreateForm.value?.checkValidity()) {
otrRepository
.createOtr(targetId.value, formState.requestIn, targetType.value)
.then(() => {
createOtrModal?.hide();
successToast?.show();
resetForm();
});
}
}
function isBucket(toBeDetermined: OtrTypes): toBeDetermined is BucketOut {
return !!(toBeDetermined as BucketOut).description;
}
function isWorkflow(toBeDetermined: OtrTypes): toBeDetermined is WorkflowOut {
return !!(toBeDetermined as WorkflowOut).workflow_id;
}
function isResource(toBeDetermined: OtrTypes): toBeDetermined is ResourceOut {
return !!(toBeDetermined as ResourceOut).resource_id;
}
function updateUser(user: UserOut) {
formState.requestIn.new_owner_uid = user.uid;
}
function resetForm() {
formState.validated = false;
formState.requestIn.new_owner_uid = "";
formState.requestIn.comment = undefined;
}
onMounted(() => {
createOtrModal = Modal.getOrCreateInstance(`#${props.modalId}`);
successToast = Toast.getOrCreateInstance("#create-otr-success-toast");
});
</script>
<template>
<bootstrap-toast toast-id="create-otr-success-toast" color-class="success">
Successfully created request for {{ targetType }} {{ targetName }}
</bootstrap-toast>
<search-user-modal
:modal-id="`search-user-for-otr-modal-${randomIDSuffix}`"
:back-modal-id="props.modalId"
:filter-user-self="true"
@user-found="updateUser"
/>
<bootstrap-modal
:modalId="props.modalId"
:static-backdrop="true"
size-modifier-modal="lg"
:modal-label="`Create ${targetType} OTR`"
>
<template #header
>Create Ownership transfer request for {{ targetType }}
<b>{{ targetName }}</b></template
>
<template #body>
<form
@submit.prevent="saveOtr"
:class="{ 'was-validated': formState.validated }"
id="create-otr-form"
ref="otrCreateForm"
>
<div class="m-1">
<label for="create-otr-user-search" class="form-label"
>Name of the new owner</label
>
<div class="input-group">
<div class="input-group-text">
<font-awesome-icon icon="fa-solid fa-user" />
</div>
<input
id="create-otr-user-search"
type="text"
class="form-control"
readonly
:value="nameRepository.getName(formState.requestIn.new_owner_uid)"
placeholder="Search for new owner"
data-bs-toggle="modal"
:data-bs-target="`#search-user-for-otr-modal-${randomIDSuffix}`"
/>
</div>
</div>
<div class="m-1">
<label for="create-otr-comment" class="form-label"> Comment </label>
<div class="input-group">
<textarea
class="form-control"
id="create-otr-comment"
rows="2"
maxlength="265"
v-model="formState.requestIn.comment"
placeholder="Optional comment for this request"
></textarea>
</div>
</div>
</form>
</template>
<template #footer>
<button
type="submit"
form="create-otr-form"
class="btn btn-success"
:disabled="formState.requestIn.new_owner_uid.length === 0"
>
Save
</button>
<button
type="button"
class="btn btn-secondary"
:data-bs-target="backModalId ? `#${backModalId}` : undefined"
:data-bs-toggle="backModalId ? 'modal' : undefined"
:data-bs-dismiss="backModalId ? undefined : 'modal'"
>
<template v-if="backModalId">Back</template>
<template v-else>Close</template>
</button>
</template>
</bootstrap-modal>
</template>
<style scoped></style>
<script setup lang="ts">
import BootstrapModal from "@/components/modals/BootstrapModal.vue";
import { type OwnershipTransferRequestOut, OwnershipTypeEnum } from "@/client";
import OtrModal from "@/components/modals/ShowOtrModal.vue";
import { ref } from "vue";
import dayjs from "dayjs";
import { useNameStore } from "@/stores/names";
const nameRepo = useNameStore();
const props = defineProps<{
modalId: string;
otrType: OwnershipTypeEnum;
otrs: OwnershipTransferRequestOut[];
}>();
const otrTargetId = ref<string>("");
const randomIDSuffix = Math.random().toString(16).substring(2, 8);
</script>
<template>
<otr-modal
:otr-target-id="otrTargetId"
:modalId="`view-bucket-otr-modal-${randomIDSuffix}`"
:back-modal-id="modalId"
/>
<bootstrap-modal
:modalId="props.modalId"
:static-backdrop="true"
size-modifier-modal="lg"
:modal-label="`List ${props.otrType} OTRs`"
>
<template #header
>Ownership transfer requests for {{ props.otrType }}s
</template>
<template #body>
<div v-if="otrs.length > 0">
<div class="d-flex justify-content-between w-100 px-3 py-2">
<span>Name</span>
<span>Current Owner</span>
<span>Requested at</span>
</div>
<div class="list-group">
<button
v-for="otr of otrs"
:key="otr.target_id"
type="button"
class="list-group-item list-group-item-action d-flex justify-content-between"
:data-bs-target="`#view-bucket-otr-modal-${randomIDSuffix}`"
data-bs-toggle="modal"
@click="otrTargetId = otr.target_id"
>
<span>{{ otr.target_name }}</span>
<span v-if="otr.current_owner_uid">{{
nameRepo.getName(otr.current_owner_uid)
}}</span>
<span>{{ dayjs.unix(otr.created_at).fromNow() }}</span>
</button>
</div>
</div>
<div v-else>
<h3 class="text-center">
No open {{ props.otrType }} ownership transfer requests
</h3>
</div>
</template>
<template #footer>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
Close
</button>
</template>
</bootstrap-modal>
</template>
<style scoped></style>
<script setup lang="ts">
import BootstrapModal from "@/components/modals/BootstrapModal.vue";
import { useOTRStore } from "@/stores/otrs";
import { computed, onMounted, ref } from "vue";
import type { OwnershipTransferRequestOut } from "@/client";
import { useNameStore } from "@/stores/names";
import dayjs from "dayjs";
import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue";
import DeleteModal from "@/components/modals/DeleteModal.vue";
import { useUserStore } from "@/stores/users";
import BootstrapToast from "@/components/BootstrapToast.vue";
import { Toast } from "bootstrap";
const otrRepository = useOTRStore();
const nameRepository = useNameStore();
const userRepository = useUserStore();
const randomIDSuffix = Math.random().toString(16).substring(2, 8);
let successToast: Toast | null = null;
const props = defineProps<{
modalId: string;
backModalId?: string;
otrTargetId: string;
}>();
const accepted = ref<boolean>(false);
const otr = computed<OwnershipTransferRequestOut | undefined>(
() => otrRepository.otrMapping[props.otrTargetId],
);
const emit = defineEmits<{
(e: "deleted-otr", otr: OwnershipTransferRequestOut): void;
(e: "accepted-otr", otr: OwnershipTransferRequestOut): void;
}>();
function acceptOtr(otr: OwnershipTransferRequestOut) {
accepted.value = true;
otrRepository.acceptOtr(otr).then(() => {
emit("accepted-otr", otr);
successToast?.show();
});
}
function deleteOtr(otr?: OwnershipTransferRequestOut) {
if (otr != undefined) {
accepted.value = false;
otrRepository.deleteOtr(otr).then(() => {
emit("deleted-otr", otr);
successToast?.show();
});
}
}
onMounted(() => {
successToast = Toast.getOrCreateInstance(
`#success-handle-otr-${randomIDSuffix}`,
);
});
</script>
<template>
<bootstrap-toast
:toast-id="`success-handle-otr-${randomIDSuffix}`"
color-class="success"
>
<template #default v-if="accepted">
Successfully accepted request
</template>
<template #default v-else> Successfully deleted request </template>
</bootstrap-toast>
<delete-modal
:modal-id="`delete-otr-modal-${randomIDSuffix}`"
:object-name-delete="`ownership transfer request for ${otr?.target_type} ${otr?.target_name}`"
:back-modal-id="modalId"
@confirm-delete="deleteOtr(otr)"
/>
<bootstrap-modal
:modalId="props.modalId"
:static-backdrop="true"
size-modifier-modal="lg"
:modal-label="`Show ${otr?.target_type} OTR`"
>
<template #header
>Ownership transfer request for {{ otr?.target_type }}
<b>{{ otr?.target_name }}</b></template
>
<template #extra-button>
<font-awesome-icon
v-if="
userRepository.admin ||
otr?.current_owner_uid === userRepository.currentUID
"
icon="fa-solid fa-trash"
class="me-2 cursor-pointer hover-danger"
data-bs-toggle="modal"
:data-bs-target="`#delete-otr-modal-${randomIDSuffix}`"
/>
</template>
<template #body>
<div v-if="otr">
<div class="row g-3">
<div class="col-md-6">
<label for="otr-to-user" class="form-label">To</label>
<input
readonly
class="form-control"
id="otr-to-user"
:value="nameRepository.getName(otr.new_owner_uid)"
/>
</div>
<div class="col-md-6" v-if="otr.current_owner_uid">
<label for="otr-from-user" class="form-label">From</label>
<input
class="form-control"
id="otr-from-user"
:value="nameRepository.getName(otr.current_owner_uid)"
/>
</div>
<div class="col-md-6">
<label for="otr-target-name" class="form-label text-capitalize"
>{{ otr?.target_type }} name</label
>
<input
class="form-control"
id="otr-target-name"
:value="otr.target_name"
/>
</div>
<div class="col-md-6" v-if="otr.current_owner_uid">
<label for="otr-timestamp" class="form-label"
>Request created at</label
>
<input
type="datetime-local"
class="form-control"
id="otr-timestamp"
readonly
:value="
dayjs.unix(otr.created_at ?? 0).format('YYYY-MM-DDTHH:mm')
"
/>
</div>
<div class="col-12">
<label for="otr-description" class="form-label text-capitalize"
>{{ otr?.target_type }} description</label
>
<textarea
class="form-control"
id="otr-description"
readonly
rows="2"
:value="otr.target_description"
/>
</div>
<div v-if="otr.comment?.trim().length !== 0" class="col-12">
<label for="otr-comment" class="form-label">Request comment</label>
<textarea
class="form-control"
id="otr-comment"
readonly
rows="2"
:value="otr.comment"
/>
</div>
</div>
<div
v-if="
userRepository.admin ||
otr.new_owner_uid === userRepository.currentUID
"
class="row mt-3 justify-content-md-center"
>
<button
type="button"
class="btn btn-danger btn-lg col-5 mx-2"
@click="deleteOtr(otr)"
data-bs-dismiss="modal"
>
Reject
</button>
<button
type="button"
class="btn btn-success btn-lg col-5 mx-2"
@click="acceptOtr(otr)"
data-bs-dismiss="modal"
>
Accept
</button>
</div>
</div>
<div v-else>Noting to see</div>
</template>
<template #footer>
<button
type="button"
class="btn btn-secondary"
:data-bs-target="backModalId ? `#${backModalId}` : undefined"
:data-bs-toggle="backModalId ? 'modal' : undefined"
:data-bs-dismiss="backModalId ? undefined : 'modal'"
>
<template v-if="backModalId">Back</template>
<template v-else>Close</template>
</button>
</template>
</bootstrap-modal>
</template>
<style scoped></style>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment