mirror of https://github.com/harness/drone.git
feat: [AH-307]: GC interface Integration, event framework; schema changes; UI changes (#2688)
* fix * Merge branch 'main' into AH-307-plus-url-support-2_no_rbac * resolve PR comments * resolve PR comments * resolve PR comments * feat: [AH-346]: new api changes for version list and digest list (#2726) * feat: [AH-346]: new api changes for version list and digest list * resolve pr comments * resolve pr comments * feat: [AH-346]: new api yaml integration (#2716) * feat: [AH-346]: new api yaml integration * Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/gitness into AH-307-plus-url-support-2_no_rbac * fix wire check * fix lint issues * fix: [AH-357]: migrations * changes for global artifact listing (#2708) * changes for global artifact listing * Merge branch 'main' into AH-307-plus-url-support-2_no_rbac * merged main * Merge branch 'main' into AH-307-plus-url-support-2_no_rbac * [AH-307]: Updated lint * fix comment * add new method to spacestore * feat: [AH-307]: fix after rebase with main * [AH-307]: Removing comments * [AH-307]: linting fixes * feat: [AH-286]: define proto, interface and no-op reporter implementation to publish artifact events (#2657) * feat: [AH-286]: publish artifact event - no row found is not error * feat: [AH-286]: publish artifact event - no row found is not error * feat: [AH-286]: publish artifact event - lint errors, move publishing event outside DB transaction * feat: [AH-286]: publish artifact event - review comments * feat: [AH-286]: publish artifact event - address review comments * feat: [AH-286]: publish artifact event - keep payload generic * feat: [AH-286]: publish artifact event - as sqlite locks DB, perform db operation outside goroutine publishing of events * feat: [AH-286]: publish artifact event - make publishing event async * feat: [AH-286]: publish artifact event - use api types * feat: [AH-286]: Publish event for SSCA to trigger scans - no need to export spacePathStore * feat: [AH-286]: Publish event for SSCA to trigger scans - send spacePath instead of parentID * feat: [AH-286]: Publish event for SSCA to trigger scans - rename scanner as generic reporter * feat: [AH-286]: Publish event for SSCA to trigger scans - rename scanner as generic reporter * feat: [AH-286]: publish artifact event - reuse redis.Send() * feat: [AH-286]: Publish event for SSCA to trigger scans - review comments * feat: [AH-286]: Publish event for SSCA to trigger scans - remove unused interface * feat: [AH-286]: Publish event for SSCA to trigger scans - update msg format * feat: [AH-286]: define proto, interface and no-op reporter implementation to publish artifact events * feat: [AH-286]: Publish event for SSCA to trigger scans - extract acctID/orgID/projectID from spacepathStore * feat: [AH-286]: publish artifact event - remove protobuf reference, fix lint errors * feat: [AH-286]: publish artifact event - fix msg format * feat: [AH-286]: define proto, interface and no-op reporter implementation to publish artifact events * feat: [AH-286]: define proto, interface and no-op reporter implementation to publish artifact events * feat: [AH-321]: make repo form disabled for rbac (#2687) * feat: [AH-321]: make repo form disabled for rbac * fix wire-gen * GC refactoring * feat: [AH-340]: update UI as per the product feedbacks (#2685) * feat: [AH-340]: update UI as per the product feedbacks * feat: [AH-44]: add module data while redirecting to pipeline execution page * feat: [AH-44]: add build pipeline details in overview cards * feat: [AH-44]: update view for prod and non prod tag * feat: [AH-44]: rearrange filters on artifact list apge * feat: [AH-10]: add schema for overview cards, update artifact list, add ai search input, update api for registry artifact list and update mapping for deployments table * feat: [AH-307]: add secretSpacePath in upstream password field while sending to BE (#2631) * feat: [AH-307]: add secretSpacePath in upstream password field while sending to BE * feat: [AH-299]: support new changes for artifact list page (#2630) * feat: update har service api version * feat: [AH-30]: integrate API schema for deployments list content * feat: [AH-300]: update tag colors for prod and non prod tags * feat: [AH-300]: Add Deployments table in artiface version details page * feat: [AH-299]: support new changes for artifact list page * feat: [AH-299]: support new changes for artifact list page * feat: [AH-321]: support artifact registry rbac permission on UI (#2671) * feat: [AH-321]: support artifact registry rbac permission on UI * enable rbac (#2664) * fix scope * enable rbac * feat: [AH-307]: hide code tab from version details page for both docker and helm * feat: [AH-240]: add custom handling for enterprise auth type field * Merge branch 'AH-307-plus-url-support-2_no_rbac' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/gitness into AH-307-plus-url-support-2_no_rbac * feat: [AH-307]: send space_ref in query param while creating registries * lowercase rootRef * [AH-307]: updated route * [AH-307]: Added logs * [AH-307]: Added logs * feat: [AH-317]: add space_ref query param * local * Merge commit * Merge commit * Merge commit * Added comments * Revert changes * Merge commit * Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/gitness into AH-307-plus-url-support-2 * Merge branch 'AH-306d' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/gitness into AH-307-plus-url-support-2 * fix space path handling * Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/gitness into AH-307-plus-url-support-2 * Updated URLs to support slashes with + separator * fix: [AH-306c]: fix anonymous flow * fix: [AH-306c]: fix anonymous flow * feat: [AH-307]: plus url support on UI (cherry picked from commit 3fb6add3ce03498b6668b5f8f6d547e1acedaec4) * [AH-307]: Added examples (cherry picked from commit e83e41303da536f421be333be04aed09fbf75f5f) * [AH-307]: Added Regex request rewrite support (cherry picked from commit ed7b155256bdcd1134bc228b5705556a1233add6) * fix: [AH-306c]: fix anonymous flowCODE-2402
parent
dcc85d5545
commit
479c9b9fe7
|
@ -13,4 +13,4 @@ GITNESS_SSH_HOST=localhost
|
||||||
GITNESS_SSH_PORT=2222
|
GITNESS_SSH_PORT=2222
|
||||||
|
|
||||||
GITNESS_REGISTRY_STORAGE_TYPE=filesystem
|
GITNESS_REGISTRY_STORAGE_TYPE=filesystem
|
||||||
GITNESS_REGISTRY_FILESYSTEM_ROOT_DIRECTORY=/tmp
|
GITNESS_REGISTRY_FILESYSTEM_ROOT_DIRECTORY=/tmp
|
|
@ -24,7 +24,7 @@ import (
|
||||||
"github.com/harness/gitness/types"
|
"github.com/harness/gitness/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// pathCacheGetter is used to hook a SpacePathStore as source of a PathCache.
|
// pathCacheGetter is used to hook a spacePathStore as source of a PathCache.
|
||||||
// IMPORTANT: It assumes that the pathCache already transformed the key.
|
// IMPORTANT: It assumes that the pathCache already transformed the key.
|
||||||
type pathCacheGetter struct {
|
type pathCacheGetter struct {
|
||||||
spacePathStore store.SpacePathStore
|
spacePathStore store.SpacePathStore
|
||||||
|
|
|
@ -94,12 +94,16 @@ type (
|
||||||
DeleteServiceAccount(ctx context.Context, id int64) error
|
DeleteServiceAccount(ctx context.Context, id int64) error
|
||||||
|
|
||||||
// ListServiceAccounts returns a list of service accounts for a specific parent.
|
// ListServiceAccounts returns a list of service accounts for a specific parent.
|
||||||
ListServiceAccounts(ctx context.Context,
|
ListServiceAccounts(
|
||||||
parentType enum.ParentResourceType, parentID int64) ([]*types.ServiceAccount, error)
|
ctx context.Context,
|
||||||
|
parentType enum.ParentResourceType, parentID int64,
|
||||||
|
) ([]*types.ServiceAccount, error)
|
||||||
|
|
||||||
// CountServiceAccounts returns a count of service accounts for a specific parent.
|
// CountServiceAccounts returns a count of service accounts for a specific parent.
|
||||||
CountServiceAccounts(ctx context.Context,
|
CountServiceAccounts(
|
||||||
parentType enum.ParentResourceType, parentID int64) (int64, error)
|
ctx context.Context,
|
||||||
|
parentType enum.ParentResourceType, parentID int64,
|
||||||
|
) (int64, error)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* SERVICE RELATED OPERATIONS.
|
* SERVICE RELATED OPERATIONS.
|
||||||
|
@ -167,6 +171,9 @@ type (
|
||||||
// FindByRef finds the space using the spaceRef as either the id or the space path.
|
// FindByRef finds the space using the spaceRef as either the id or the space path.
|
||||||
FindByRef(ctx context.Context, spaceRef string) (*types.Space, error)
|
FindByRef(ctx context.Context, spaceRef string) (*types.Space, error)
|
||||||
|
|
||||||
|
// FindByRefCaseInsensitive finds the space using the spaceRef.
|
||||||
|
FindByRefCaseInsensitive(ctx context.Context, spaceRef string) (*types.Space, error)
|
||||||
|
|
||||||
// FindByRefAndDeletedAt finds the space using the spaceRef and deleted timestamp.
|
// FindByRefAndDeletedAt finds the space using the spaceRef and deleted timestamp.
|
||||||
FindByRefAndDeletedAt(ctx context.Context, spaceRef string, deletedAt int64) (*types.Space, error)
|
FindByRefAndDeletedAt(ctx context.Context, spaceRef string, deletedAt int64) (*types.Space, error)
|
||||||
|
|
||||||
|
@ -192,8 +199,10 @@ type (
|
||||||
Update(ctx context.Context, space *types.Space) error
|
Update(ctx context.Context, space *types.Space) error
|
||||||
|
|
||||||
// UpdateOptLock updates the space using the optimistic locking mechanism.
|
// UpdateOptLock updates the space using the optimistic locking mechanism.
|
||||||
UpdateOptLock(ctx context.Context, space *types.Space,
|
UpdateOptLock(
|
||||||
mutateFn func(space *types.Space) error) (*types.Space, error)
|
ctx context.Context, space *types.Space,
|
||||||
|
mutateFn func(space *types.Space) error,
|
||||||
|
) (*types.Space, error)
|
||||||
|
|
||||||
// FindForUpdate finds the space and locks it for an update.
|
// FindForUpdate finds the space and locks it for an update.
|
||||||
FindForUpdate(ctx context.Context, id int64) (*types.Space, error)
|
FindForUpdate(ctx context.Context, id int64) (*types.Space, error)
|
||||||
|
@ -205,8 +214,10 @@ type (
|
||||||
Purge(ctx context.Context, id int64, deletedAt *int64) error
|
Purge(ctx context.Context, id int64, deletedAt *int64) error
|
||||||
|
|
||||||
// Restore restores a soft deleted space.
|
// Restore restores a soft deleted space.
|
||||||
Restore(ctx context.Context, space *types.Space,
|
Restore(
|
||||||
newIdentifier *string, newParentID *int64) (*types.Space, error)
|
ctx context.Context, space *types.Space,
|
||||||
|
newIdentifier *string, newParentID *int64,
|
||||||
|
) (*types.Space, error)
|
||||||
|
|
||||||
// Count the child spaces of a space.
|
// Count the child spaces of a space.
|
||||||
Count(ctx context.Context, id int64, opts *types.SpaceFilter) (int64, error)
|
Count(ctx context.Context, id int64, opts *types.SpaceFilter) (int64, error)
|
||||||
|
@ -239,8 +250,10 @@ type (
|
||||||
GetSize(ctx context.Context, id int64) (int64, error)
|
GetSize(ctx context.Context, id int64) (int64, error)
|
||||||
|
|
||||||
// UpdateOptLock the repo details using the optimistic locking mechanism.
|
// UpdateOptLock the repo details using the optimistic locking mechanism.
|
||||||
UpdateOptLock(ctx context.Context, repo *types.Repository,
|
UpdateOptLock(
|
||||||
mutateFn func(repository *types.Repository) error) (*types.Repository, error)
|
ctx context.Context, repo *types.Repository,
|
||||||
|
mutateFn func(repository *types.Repository) error,
|
||||||
|
) (*types.Repository, error)
|
||||||
|
|
||||||
// SoftDelete a repo.
|
// SoftDelete a repo.
|
||||||
SoftDelete(ctx context.Context, repo *types.Repository, deletedAt int64) error
|
SoftDelete(ctx context.Context, repo *types.Repository, deletedAt int64) error
|
||||||
|
@ -249,8 +262,10 @@ type (
|
||||||
Purge(ctx context.Context, id int64, deletedAt *int64) error
|
Purge(ctx context.Context, id int64, deletedAt *int64) error
|
||||||
|
|
||||||
// Restore a deleted repo using the optimistic locking mechanism.
|
// Restore a deleted repo using the optimistic locking mechanism.
|
||||||
Restore(ctx context.Context, repo *types.Repository,
|
Restore(
|
||||||
newIdentifier *string, newParentID *int64) (*types.Repository, error)
|
ctx context.Context, repo *types.Repository,
|
||||||
|
newIdentifier *string, newParentID *int64,
|
||||||
|
) (*types.Repository, error)
|
||||||
|
|
||||||
// Count of active repos in a space. With "DeletedBeforeOrAt" filter, counts deleted repos.
|
// Count of active repos in a space. With "DeletedBeforeOrAt" filter, counts deleted repos.
|
||||||
Count(ctx context.Context, parentID int64, opts *types.RepoFilter) (int64, error)
|
Count(ctx context.Context, parentID int64, opts *types.RepoFilter) (int64, error)
|
||||||
|
@ -308,7 +323,11 @@ type (
|
||||||
CountUsers(ctx context.Context, spaceID int64, filter types.MembershipUserFilter) (int64, error)
|
CountUsers(ctx context.Context, spaceID int64, filter types.MembershipUserFilter) (int64, error)
|
||||||
ListUsers(ctx context.Context, spaceID int64, filter types.MembershipUserFilter) ([]types.MembershipUser, error)
|
ListUsers(ctx context.Context, spaceID int64, filter types.MembershipUserFilter) ([]types.MembershipUser, error)
|
||||||
CountSpaces(ctx context.Context, userID int64, filter types.MembershipSpaceFilter) (int64, error)
|
CountSpaces(ctx context.Context, userID int64, filter types.MembershipSpaceFilter) (int64, error)
|
||||||
ListSpaces(ctx context.Context, userID int64, filter types.MembershipSpaceFilter) ([]types.MembershipSpace, error)
|
ListSpaces(
|
||||||
|
ctx context.Context,
|
||||||
|
userID int64,
|
||||||
|
filter types.MembershipSpaceFilter,
|
||||||
|
) ([]types.MembershipSpace, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PublicAccessStore defines the publicly accessible resources data storage.
|
// PublicAccessStore defines the publicly accessible resources data storage.
|
||||||
|
@ -362,8 +381,10 @@ type (
|
||||||
Update(ctx context.Context, pr *types.PullReq) error
|
Update(ctx context.Context, pr *types.PullReq) error
|
||||||
|
|
||||||
// UpdateOptLock the pull request details using the optimistic locking mechanism.
|
// UpdateOptLock the pull request details using the optimistic locking mechanism.
|
||||||
UpdateOptLock(ctx context.Context, pr *types.PullReq,
|
UpdateOptLock(
|
||||||
mutateFn func(pr *types.PullReq) error) (*types.PullReq, error)
|
ctx context.Context, pr *types.PullReq,
|
||||||
|
mutateFn func(pr *types.PullReq) error,
|
||||||
|
) (*types.PullReq, error)
|
||||||
|
|
||||||
// UpdateActivitySeq the pull request's activity sequence number.
|
// UpdateActivitySeq the pull request's activity sequence number.
|
||||||
// It will set new values to the ActivitySeq, Version and Updated fields.
|
// It will set new values to the ActivitySeq, Version and Updated fields.
|
||||||
|
@ -395,7 +416,8 @@ type (
|
||||||
Create(ctx context.Context, act *types.PullReqActivity) error
|
Create(ctx context.Context, act *types.PullReqActivity) error
|
||||||
|
|
||||||
// CreateWithPayload create a new system activity from the provided payload.
|
// CreateWithPayload create a new system activity from the provided payload.
|
||||||
CreateWithPayload(ctx context.Context,
|
CreateWithPayload(
|
||||||
|
ctx context.Context,
|
||||||
pr *types.PullReq,
|
pr *types.PullReq,
|
||||||
principalID int64,
|
principalID int64,
|
||||||
payload types.PullReqActivityPayload,
|
payload types.PullReqActivityPayload,
|
||||||
|
@ -406,7 +428,8 @@ type (
|
||||||
Update(ctx context.Context, act *types.PullReqActivity) error
|
Update(ctx context.Context, act *types.PullReqActivity) error
|
||||||
|
|
||||||
// UpdateOptLock updates the pull request activity using the optimistic locking mechanism.
|
// UpdateOptLock updates the pull request activity using the optimistic locking mechanism.
|
||||||
UpdateOptLock(ctx context.Context,
|
UpdateOptLock(
|
||||||
|
ctx context.Context,
|
||||||
act *types.PullReqActivity,
|
act *types.PullReqActivity,
|
||||||
mutateFn func(act *types.PullReqActivity) error,
|
mutateFn func(act *types.PullReqActivity) error,
|
||||||
) (*types.PullReqActivity, error)
|
) (*types.PullReqActivity, error)
|
||||||
|
@ -543,8 +566,10 @@ type (
|
||||||
Update(ctx context.Context, hook *types.Webhook) error
|
Update(ctx context.Context, hook *types.Webhook) error
|
||||||
|
|
||||||
// UpdateOptLock updates the webhook using the optimistic locking mechanism.
|
// UpdateOptLock updates the webhook using the optimistic locking mechanism.
|
||||||
UpdateOptLock(ctx context.Context, hook *types.Webhook,
|
UpdateOptLock(
|
||||||
mutateFn func(hook *types.Webhook) error) (*types.Webhook, error)
|
ctx context.Context, hook *types.Webhook,
|
||||||
|
mutateFn func(hook *types.Webhook) error,
|
||||||
|
) (*types.Webhook, error)
|
||||||
|
|
||||||
// Delete deletes the webhook for the given id.
|
// Delete deletes the webhook for the given id.
|
||||||
Delete(ctx context.Context, id int64) error
|
Delete(ctx context.Context, id int64) error
|
||||||
|
@ -553,12 +578,16 @@ type (
|
||||||
DeleteByIdentifier(ctx context.Context, parentType enum.WebhookParent, parentID int64, identifier string) error
|
DeleteByIdentifier(ctx context.Context, parentType enum.WebhookParent, parentID int64, identifier string) error
|
||||||
|
|
||||||
// Count counts the webhooks for a given parent type and id.
|
// Count counts the webhooks for a given parent type and id.
|
||||||
Count(ctx context.Context, parentType enum.WebhookParent, parentID int64,
|
Count(
|
||||||
opts *types.WebhookFilter) (int64, error)
|
ctx context.Context, parentType enum.WebhookParent, parentID int64,
|
||||||
|
opts *types.WebhookFilter,
|
||||||
|
) (int64, error)
|
||||||
|
|
||||||
// List lists the webhooks for a given parent type and id.
|
// List lists the webhooks for a given parent type and id.
|
||||||
List(ctx context.Context, parentType enum.WebhookParent, parentID int64,
|
List(
|
||||||
opts *types.WebhookFilter) ([]*types.Webhook, error)
|
ctx context.Context, parentType enum.WebhookParent, parentID int64,
|
||||||
|
opts *types.WebhookFilter,
|
||||||
|
) ([]*types.Webhook, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebhookExecutionStore defines the webhook execution data storage.
|
// WebhookExecutionStore defines the webhook execution data storage.
|
||||||
|
@ -573,8 +602,10 @@ type (
|
||||||
DeleteOld(ctx context.Context, olderThan time.Time) (int64, error)
|
DeleteOld(ctx context.Context, olderThan time.Time) (int64, error)
|
||||||
|
|
||||||
// ListForWebhook lists the webhook executions for a given webhook id.
|
// ListForWebhook lists the webhook executions for a given webhook id.
|
||||||
ListForWebhook(ctx context.Context, webhookID int64,
|
ListForWebhook(
|
||||||
opts *types.WebhookExecutionFilter) ([]*types.WebhookExecution, error)
|
ctx context.Context, webhookID int64,
|
||||||
|
opts *types.WebhookExecutionFilter,
|
||||||
|
) ([]*types.WebhookExecution, error)
|
||||||
|
|
||||||
// ListForTrigger lists the webhook executions for a given trigger id.
|
// ListForTrigger lists the webhook executions for a given trigger id.
|
||||||
ListForTrigger(ctx context.Context, triggerID string) ([]*types.WebhookExecution, error)
|
ListForTrigger(ctx context.Context, triggerID string) ([]*types.WebhookExecution, error)
|
||||||
|
@ -646,7 +677,10 @@ type (
|
||||||
List(ctx context.Context, filter *types.GitspaceFilter) ([]*types.GitspaceInstance, error)
|
List(ctx context.Context, filter *types.GitspaceFilter) ([]*types.GitspaceInstance, error)
|
||||||
|
|
||||||
// List lists the latest gitspace instance present for the gitspace configs in the datastore.
|
// List lists the latest gitspace instance present for the gitspace configs in the datastore.
|
||||||
FindAllLatestByGitspaceConfigID(ctx context.Context, gitspaceConfigIDs []int64) ([]*types.GitspaceInstance, error)
|
FindAllLatestByGitspaceConfigID(
|
||||||
|
ctx context.Context,
|
||||||
|
gitspaceConfigIDs []int64,
|
||||||
|
) ([]*types.GitspaceInstance, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
InfraProviderConfigStore interface {
|
InfraProviderConfigStore interface {
|
||||||
|
@ -677,7 +711,8 @@ type (
|
||||||
Update(ctx context.Context, infraProviderResource *types.InfraProviderResource) error
|
Update(ctx context.Context, infraProviderResource *types.InfraProviderResource) error
|
||||||
|
|
||||||
// List lists the infra provider resource present for the gitspace config in a parent space ID in the datastore.
|
// List lists the infra provider resource present for the gitspace config in a parent space ID in the datastore.
|
||||||
List(ctx context.Context,
|
List(
|
||||||
|
ctx context.Context,
|
||||||
infraProviderConfigID int64,
|
infraProviderConfigID int64,
|
||||||
filter types.ListQueryFilter,
|
filter types.ListQueryFilter,
|
||||||
) ([]*types.InfraProviderResource, error)
|
) ([]*types.InfraProviderResource, error)
|
||||||
|
@ -707,8 +742,10 @@ type (
|
||||||
ListLatest(ctx context.Context, repoID int64, pagination types.ListQueryFilter) ([]*types.Pipeline, error)
|
ListLatest(ctx context.Context, repoID int64, pagination types.ListQueryFilter) ([]*types.Pipeline, error)
|
||||||
|
|
||||||
// UpdateOptLock updates the pipeline using the optimistic locking mechanism.
|
// UpdateOptLock updates the pipeline using the optimistic locking mechanism.
|
||||||
UpdateOptLock(ctx context.Context, pipeline *types.Pipeline,
|
UpdateOptLock(
|
||||||
mutateFn func(pipeline *types.Pipeline) error) (*types.Pipeline, error)
|
ctx context.Context, pipeline *types.Pipeline,
|
||||||
|
mutateFn func(pipeline *types.Pipeline) error,
|
||||||
|
) (*types.Pipeline, error)
|
||||||
|
|
||||||
// Delete deletes a pipeline ID from the datastore.
|
// Delete deletes a pipeline ID from the datastore.
|
||||||
Delete(ctx context.Context, id int64) error
|
Delete(ctx context.Context, id int64) error
|
||||||
|
@ -743,8 +780,10 @@ type (
|
||||||
Count(ctx context.Context, spaceID int64, pagination types.ListQueryFilter) (int64, error)
|
Count(ctx context.Context, spaceID int64, pagination types.ListQueryFilter) (int64, error)
|
||||||
|
|
||||||
// UpdateOptLock updates the secret using the optimistic locking mechanism.
|
// UpdateOptLock updates the secret using the optimistic locking mechanism.
|
||||||
UpdateOptLock(ctx context.Context, secret *types.Secret,
|
UpdateOptLock(
|
||||||
mutateFn func(secret *types.Secret) error) (*types.Secret, error)
|
ctx context.Context, secret *types.Secret,
|
||||||
|
mutateFn func(secret *types.Secret) error,
|
||||||
|
) (*types.Secret, error)
|
||||||
|
|
||||||
// Update tries to update a secret.
|
// Update tries to update a secret.
|
||||||
Update(ctx context.Context, secret *types.Secret) error
|
Update(ctx context.Context, secret *types.Secret) error
|
||||||
|
@ -837,8 +876,10 @@ type (
|
||||||
Count(ctx context.Context, spaceID int64, pagination types.ListQueryFilter) (int64, error)
|
Count(ctx context.Context, spaceID int64, pagination types.ListQueryFilter) (int64, error)
|
||||||
|
|
||||||
// UpdateOptLock updates the connector using the optimistic locking mechanism.
|
// UpdateOptLock updates the connector using the optimistic locking mechanism.
|
||||||
UpdateOptLock(ctx context.Context, connector *types.Connector,
|
UpdateOptLock(
|
||||||
mutateFn func(connector *types.Connector) error) (*types.Connector, error)
|
ctx context.Context, connector *types.Connector,
|
||||||
|
mutateFn func(connector *types.Connector) error,
|
||||||
|
) (*types.Connector, error)
|
||||||
|
|
||||||
// Update tries to update a connector.
|
// Update tries to update a connector.
|
||||||
Update(ctx context.Context, connector *types.Connector) error
|
Update(ctx context.Context, connector *types.Connector) error
|
||||||
|
@ -858,8 +899,10 @@ type (
|
||||||
Find(ctx context.Context, id int64) (*types.Template, error)
|
Find(ctx context.Context, id int64) (*types.Template, error)
|
||||||
|
|
||||||
// FindByIdentifierAndType returns a template given a space ID, identifier and a type
|
// FindByIdentifierAndType returns a template given a space ID, identifier and a type
|
||||||
FindByIdentifierAndType(ctx context.Context, spaceID int64,
|
FindByIdentifierAndType(
|
||||||
identifier string, resolverType enum.ResolverType) (*types.Template, error)
|
ctx context.Context, spaceID int64,
|
||||||
|
identifier string, resolverType enum.ResolverType,
|
||||||
|
) (*types.Template, error)
|
||||||
|
|
||||||
// Create creates a new template.
|
// Create creates a new template.
|
||||||
Create(ctx context.Context, template *types.Template) error
|
Create(ctx context.Context, template *types.Template) error
|
||||||
|
@ -868,8 +911,10 @@ type (
|
||||||
Count(ctx context.Context, spaceID int64, pagination types.ListQueryFilter) (int64, error)
|
Count(ctx context.Context, spaceID int64, pagination types.ListQueryFilter) (int64, error)
|
||||||
|
|
||||||
// UpdateOptLock updates the template using the optimistic locking mechanism.
|
// UpdateOptLock updates the template using the optimistic locking mechanism.
|
||||||
UpdateOptLock(ctx context.Context, template *types.Template,
|
UpdateOptLock(
|
||||||
mutateFn func(template *types.Template) error) (*types.Template, error)
|
ctx context.Context, template *types.Template,
|
||||||
|
mutateFn func(template *types.Template) error,
|
||||||
|
) (*types.Template, error)
|
||||||
|
|
||||||
// Update tries to update a template.
|
// Update tries to update a template.
|
||||||
Update(ctx context.Context, template *types.Template) error
|
Update(ctx context.Context, template *types.Template) error
|
||||||
|
@ -878,7 +923,12 @@ type (
|
||||||
Delete(ctx context.Context, id int64) error
|
Delete(ctx context.Context, id int64) error
|
||||||
|
|
||||||
// DeleteByIdentifierAndType deletes a template given a space ID, identifier and a type.
|
// DeleteByIdentifierAndType deletes a template given a space ID, identifier and a type.
|
||||||
DeleteByIdentifierAndType(ctx context.Context, spaceID int64, identifier string, resolverType enum.ResolverType) error
|
DeleteByIdentifierAndType(
|
||||||
|
ctx context.Context,
|
||||||
|
spaceID int64,
|
||||||
|
identifier string,
|
||||||
|
resolverType enum.ResolverType,
|
||||||
|
) error
|
||||||
|
|
||||||
// List lists the templates in a given space.
|
// List lists the templates in a given space.
|
||||||
List(ctx context.Context, spaceID int64, filter types.ListQueryFilter) ([]*types.Template, error)
|
List(ctx context.Context, spaceID int64, filter types.ListQueryFilter) ([]*types.Template, error)
|
||||||
|
@ -895,8 +945,10 @@ type (
|
||||||
Update(ctx context.Context, trigger *types.Trigger) error
|
Update(ctx context.Context, trigger *types.Trigger) error
|
||||||
|
|
||||||
// UpdateOptLock updates the trigger using the optimistic locking mechanism.
|
// UpdateOptLock updates the trigger using the optimistic locking mechanism.
|
||||||
UpdateOptLock(ctx context.Context, trigger *types.Trigger,
|
UpdateOptLock(
|
||||||
mutateFn func(trigger *types.Trigger) error) (*types.Trigger, error)
|
ctx context.Context, trigger *types.Trigger,
|
||||||
|
mutateFn func(trigger *types.Trigger) error,
|
||||||
|
) (*types.Trigger, error)
|
||||||
|
|
||||||
// List lists the triggers for a given pipeline ID.
|
// List lists the triggers for a given pipeline ID.
|
||||||
List(ctx context.Context, pipelineID int64, filter types.ListQueryFilter) ([]*types.Trigger, error)
|
List(ctx context.Context, pipelineID int64, filter types.ListQueryFilter) ([]*types.Trigger, error)
|
||||||
|
@ -941,7 +993,11 @@ type (
|
||||||
Map(ctx context.Context, ids []int64) (map[int64]*types.UserGroup, error)
|
Map(ctx context.Context, ids []int64) (map[int64]*types.UserGroup, error)
|
||||||
|
|
||||||
// FindManyByIdentifiersAndSpaceID returns a list of usergroups
|
// FindManyByIdentifiersAndSpaceID returns a list of usergroups
|
||||||
FindManyByIdentifiersAndSpaceID(ctx context.Context, identifiers []string, spaceID int64) ([]*types.UserGroup, error)
|
FindManyByIdentifiersAndSpaceID(
|
||||||
|
ctx context.Context,
|
||||||
|
identifiers []string,
|
||||||
|
spaceID int64,
|
||||||
|
) ([]*types.UserGroup, error)
|
||||||
|
|
||||||
// FindManyByIDs returns a list of usergroups searching them via ids
|
// FindManyByIDs returns a list of usergroups searching them via ids
|
||||||
FindManyByIDs(ctx context.Context, ids []int64) ([]*types.UserGroup, error)
|
FindManyByIDs(ctx context.Context, ids []int64) ([]*types.UserGroup, error)
|
||||||
|
@ -1145,7 +1201,8 @@ type (
|
||||||
spaceID int64,
|
spaceID int64,
|
||||||
gitspaceInstanceID int64,
|
gitspaceInstanceID int64,
|
||||||
) (*types.InfraProvisioned, error)
|
) (*types.InfraProvisioned, error)
|
||||||
FindLatestByGitspaceInstanceIdentifier(ctx context.Context,
|
FindLatestByGitspaceInstanceIdentifier(
|
||||||
|
ctx context.Context,
|
||||||
spaceID int64,
|
spaceID int64,
|
||||||
gitspaceInstanceIdentifier string,
|
gitspaceInstanceIdentifier string,
|
||||||
) (*types.InfraProvisioned, error)
|
) (*types.InfraProvisioned, error)
|
||||||
|
|
|
@ -125,6 +125,40 @@ func (s *SpaceStore) FindByRef(ctx context.Context, spaceRef string) (*types.Spa
|
||||||
return s.findByRef(ctx, spaceRef, nil)
|
return s.findByRef(ctx, spaceRef, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindByRefCaseInsensitive finds the space using the spaceRef.
|
||||||
|
func (s *SpaceStore) FindByRefCaseInsensitive(ctx context.Context, spaceRef string) (*types.Space, error) {
|
||||||
|
segments := paths.Segments(spaceRef)
|
||||||
|
if len(segments) < 1 {
|
||||||
|
return nil, fmt.Errorf("invalid space reference provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
var stmt squirrel.SelectBuilder
|
||||||
|
switch {
|
||||||
|
case len(segments) == 1:
|
||||||
|
stmt = database.Builder.
|
||||||
|
Select("space_id").
|
||||||
|
From("spaces").
|
||||||
|
Where("LOWER(space_uid) = LOWER(?) ", segments[0])
|
||||||
|
|
||||||
|
case len(segments) > 1:
|
||||||
|
stmt = buildRecursiveSelectQueryUsingCaseInsensitivePath(segments)
|
||||||
|
}
|
||||||
|
|
||||||
|
sql, args, err := stmt.ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create sql query: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db := dbtx.GetAccessor(ctx, s.db)
|
||||||
|
|
||||||
|
var spaceID int64
|
||||||
|
if err = db.GetContext(ctx, &spaceID, sql, args...); err != nil {
|
||||||
|
return nil, database.ProcessSQLErrorf(ctx, err, "Failed executing custom select query")
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.find(ctx, spaceID, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// FindByRefAndDeletedAt finds the space using the spaceRef as either the id or the space path and deleted timestamp.
|
// FindByRefAndDeletedAt finds the space using the spaceRef as either the id or the space path and deleted timestamp.
|
||||||
func (s *SpaceStore) FindByRefAndDeletedAt(
|
func (s *SpaceStore) FindByRefAndDeletedAt(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
@ -701,7 +735,8 @@ func (s *SpaceStore) list(
|
||||||
return s.mapToSpaces(ctx, s.db, dst)
|
return s.mapToSpaces(ctx, s.db, dst)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SpaceStore) listAll(ctx context.Context,
|
func (s *SpaceStore) listAll(
|
||||||
|
ctx context.Context,
|
||||||
id int64,
|
id int64,
|
||||||
opts *types.SpaceFilter,
|
opts *types.SpaceFilter,
|
||||||
) ([]*types.Space, error) {
|
) ([]*types.Space, error) {
|
||||||
|
@ -901,7 +936,7 @@ func mapToInternalSpace(s *types.Space) *space {
|
||||||
|
|
||||||
// buildRecursiveSelectQueryUsingPath builds the recursive select query using path among active or soft deleted spaces.
|
// buildRecursiveSelectQueryUsingPath builds the recursive select query using path among active or soft deleted spaces.
|
||||||
func buildRecursiveSelectQueryUsingPath(segments []string, deletedAt int64) squirrel.SelectBuilder {
|
func buildRecursiveSelectQueryUsingPath(segments []string, deletedAt int64) squirrel.SelectBuilder {
|
||||||
leaf := "s" + fmt.Sprint(len(segments)-1)
|
leaf := "s" + strconv.Itoa(len(segments)-1)
|
||||||
|
|
||||||
// add the current space (leaf)
|
// add the current space (leaf)
|
||||||
stmt := database.Builder.
|
stmt := database.Builder.
|
||||||
|
@ -910,10 +945,37 @@ func buildRecursiveSelectQueryUsingPath(segments []string, deletedAt int64) squi
|
||||||
Where(leaf+".space_uid = ? AND "+leaf+".space_deleted = ?", segments[len(segments)-1], deletedAt)
|
Where(leaf+".space_uid = ? AND "+leaf+".space_deleted = ?", segments[len(segments)-1], deletedAt)
|
||||||
|
|
||||||
for i := len(segments) - 2; i >= 0; i-- {
|
for i := len(segments) - 2; i >= 0; i-- {
|
||||||
parentAlias := "s" + fmt.Sprint(i)
|
parentAlias := "s" + strconv.Itoa(i)
|
||||||
alias := "s" + fmt.Sprint(i+1)
|
alias := "s" + strconv.Itoa(i+1)
|
||||||
|
|
||||||
stmt = stmt.InnerJoin(fmt.Sprintf("spaces %s ON %s.space_id = %s.space_parent_id", parentAlias, parentAlias, alias)).
|
stmt = stmt.InnerJoin(fmt.Sprintf("spaces %s ON %s.space_id = %s.space_parent_id", parentAlias, parentAlias,
|
||||||
|
alias)).
|
||||||
|
Where(parentAlias+".space_uid = ?", segments[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
// add parent check for root
|
||||||
|
stmt = stmt.Where("s0.space_parent_id IS NULL")
|
||||||
|
|
||||||
|
return stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildRecursiveSelectQueryUsingCaseInsensitivePath builds the recursive select query using path among active or soft
|
||||||
|
// deleted spaces.
|
||||||
|
func buildRecursiveSelectQueryUsingCaseInsensitivePath(segments []string) squirrel.SelectBuilder {
|
||||||
|
leaf := "s" + strconv.Itoa(len(segments)-1)
|
||||||
|
|
||||||
|
// add the current space (leaf)
|
||||||
|
stmt := database.Builder.
|
||||||
|
Select(leaf+".space_id").
|
||||||
|
From("spaces "+leaf).
|
||||||
|
Where("LOWER("+leaf+".space_uid) = LOWER(?)", segments[len(segments)-1])
|
||||||
|
|
||||||
|
for i := len(segments) - 2; i >= 0; i-- {
|
||||||
|
parentAlias := "s" + strconv.Itoa(i)
|
||||||
|
alias := "s" + strconv.Itoa(i+1)
|
||||||
|
|
||||||
|
stmt = stmt.InnerJoin(fmt.Sprintf("spaces %s ON %s.space_id = %s.space_parent_id", parentAlias, parentAlias,
|
||||||
|
alias)).
|
||||||
Where(parentAlias+".space_uid = ?", segments[i])
|
Where(parentAlias+".space_uid = ?", segments[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -129,6 +129,7 @@ import (
|
||||||
"github.com/harness/gitness/livelog"
|
"github.com/harness/gitness/livelog"
|
||||||
"github.com/harness/gitness/lock"
|
"github.com/harness/gitness/lock"
|
||||||
"github.com/harness/gitness/pubsub"
|
"github.com/harness/gitness/pubsub"
|
||||||
|
"github.com/harness/gitness/registry/app/pkg/docker"
|
||||||
"github.com/harness/gitness/ssh"
|
"github.com/harness/gitness/ssh"
|
||||||
"github.com/harness/gitness/store/database/dbtx"
|
"github.com/harness/gitness/store/database/dbtx"
|
||||||
"github.com/harness/gitness/types"
|
"github.com/harness/gitness/types"
|
||||||
|
@ -270,6 +271,7 @@ func initSystem(ctx context.Context, config *types.Config) (*cliserver.System, e
|
||||||
aiagent.WireSet,
|
aiagent.WireSet,
|
||||||
capabilities.WireSet,
|
capabilities.WireSet,
|
||||||
capabilitiesservice.WireSet,
|
capabilitiesservice.WireSet,
|
||||||
|
docker.ProvideReporter,
|
||||||
secretservice.WireSet,
|
secretservice.WireSet,
|
||||||
containerGit.WireSet,
|
containerGit.WireSet,
|
||||||
containerUser.WireSet,
|
containerUser.WireSet,
|
||||||
|
|
|
@ -432,16 +432,17 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
|
||||||
mediaTypesRepository := database2.ProvideMediaTypeDao(db)
|
mediaTypesRepository := database2.ProvideMediaTypeDao(db)
|
||||||
blobRepository := database2.ProvideBlobDao(db, mediaTypesRepository)
|
blobRepository := database2.ProvideBlobDao(db, mediaTypesRepository)
|
||||||
storageService := docker.StorageServiceProvider(config, storageDriver)
|
storageService := docker.StorageServiceProvider(config, storageDriver)
|
||||||
manifestRepository := database2.ProvideManifestDao(db, mediaTypesRepository)
|
|
||||||
gcService := gc.ServiceProvider()
|
gcService := gc.ServiceProvider()
|
||||||
app := docker.NewApp(ctx, db, storageDeleter, blobRepository, spaceStore, config, storageService, mediaTypesRepository, manifestRepository, gcService)
|
app := docker.NewApp(ctx, db, storageDeleter, blobRepository, spaceStore, config, storageService, gcService)
|
||||||
registryRepository := database2.ProvideRepoDao(db, mediaTypesRepository)
|
registryRepository := database2.ProvideRepoDao(db, mediaTypesRepository)
|
||||||
|
manifestRepository := database2.ProvideManifestDao(db, mediaTypesRepository)
|
||||||
manifestReferenceRepository := database2.ProvideManifestRefDao(db)
|
manifestReferenceRepository := database2.ProvideManifestRefDao(db)
|
||||||
tagRepository := database2.ProvideTagDao(db)
|
tagRepository := database2.ProvideTagDao(db)
|
||||||
imageRepository := database2.ProvideImageDao(db)
|
imageRepository := database2.ProvideImageDao(db)
|
||||||
artifactRepository := database2.ProvideArtifactDao(db)
|
artifactRepository := database2.ProvideArtifactDao(db)
|
||||||
layerRepository := database2.ProvideLayerDao(db, mediaTypesRepository)
|
layerRepository := database2.ProvideLayerDao(db, mediaTypesRepository)
|
||||||
manifestService := docker.ManifestServiceProvider(registryRepository, manifestRepository, blobRepository, mediaTypesRepository, manifestReferenceRepository, tagRepository, imageRepository, artifactRepository, layerRepository, gcService, transactor)
|
eventReporter := docker.ProvideReporter()
|
||||||
|
manifestService := docker.ManifestServiceProvider(registryRepository, manifestRepository, blobRepository, mediaTypesRepository, manifestReferenceRepository, tagRepository, imageRepository, artifactRepository, layerRepository, gcService, transactor, eventReporter, spacePathStore)
|
||||||
registryBlobRepository := database2.ProvideRegistryBlobDao(db)
|
registryBlobRepository := database2.ProvideRegistryBlobDao(db)
|
||||||
bandwidthStatRepository := database2.ProvideBandwidthStatDao(db)
|
bandwidthStatRepository := database2.ProvideBandwidthStatDao(db)
|
||||||
downloadStatRepository := database2.ProvideDownloadStatDao(db)
|
downloadStatRepository := database2.ProvideDownloadStatDao(db)
|
||||||
|
|
|
@ -25,19 +25,53 @@ import (
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetArtifactMetadata(artifacts *[]types.ArtifactMetadata) []artifactapi.ArtifactMetadata {
|
func GetArtifactMetadata(
|
||||||
artifactMetadataList := make([]artifactapi.ArtifactMetadata, 0, len(*artifacts))
|
artifacts []types.ArtifactMetadata,
|
||||||
for _, artifact := range *artifacts {
|
rootIdentifier string,
|
||||||
artifactMetadata := mapToArtifactMetadata(artifact)
|
registryURL string,
|
||||||
|
) []artifactapi.ArtifactMetadata {
|
||||||
|
artifactMetadataList := make([]artifactapi.ArtifactMetadata, 0, len(artifacts))
|
||||||
|
for _, artifact := range artifacts {
|
||||||
|
artifactMetadata := mapToArtifactMetadata(artifact, rootIdentifier, registryURL)
|
||||||
artifactMetadataList = append(artifactMetadataList, *artifactMetadata)
|
artifactMetadataList = append(artifactMetadataList, *artifactMetadata)
|
||||||
}
|
}
|
||||||
return artifactMetadataList
|
return artifactMetadataList
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapToArtifactMetadata(artifact types.ArtifactMetadata) *artifactapi.ArtifactMetadata {
|
func GetRegistryArtifactMetadata(artifacts []types.ArtifactMetadata) []artifactapi.RegistryArtifactMetadata {
|
||||||
|
artifactMetadataList := make([]artifactapi.RegistryArtifactMetadata, 0, len(artifacts))
|
||||||
|
for _, artifact := range artifacts {
|
||||||
|
artifactMetadata := mapToRegistryArtifactMetadata(artifact)
|
||||||
|
artifactMetadataList = append(artifactMetadataList, *artifactMetadata)
|
||||||
|
}
|
||||||
|
return artifactMetadataList
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapToArtifactMetadata(
|
||||||
|
artifact types.ArtifactMetadata,
|
||||||
|
rootIdentifier string,
|
||||||
|
registryURL string,
|
||||||
|
) *artifactapi.ArtifactMetadata {
|
||||||
lastModified := GetTimeInMs(artifact.ModifiedAt)
|
lastModified := GetTimeInMs(artifact.ModifiedAt)
|
||||||
packageType := artifact.PackageType
|
packageType := artifact.PackageType
|
||||||
|
pullCommand := GetPullCommand(rootIdentifier, artifact.RepoName, artifact.Name, artifact.Version,
|
||||||
|
string(packageType), registryURL)
|
||||||
return &artifactapi.ArtifactMetadata{
|
return &artifactapi.ArtifactMetadata{
|
||||||
|
RegistryIdentifier: artifact.RepoName,
|
||||||
|
Name: artifact.Name,
|
||||||
|
Version: &artifact.Version,
|
||||||
|
Labels: &artifact.Labels,
|
||||||
|
LastModified: &lastModified,
|
||||||
|
PackageType: &packageType,
|
||||||
|
DownloadsCount: &artifact.DownloadCount,
|
||||||
|
PullCommand: &pullCommand,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapToRegistryArtifactMetadata(artifact types.ArtifactMetadata) *artifactapi.RegistryArtifactMetadata {
|
||||||
|
lastModified := GetTimeInMs(artifact.ModifiedAt)
|
||||||
|
packageType := artifact.PackageType
|
||||||
|
return &artifactapi.RegistryArtifactMetadata{
|
||||||
RegistryIdentifier: artifact.RepoName,
|
RegistryIdentifier: artifact.RepoName,
|
||||||
Name: artifact.Name,
|
Name: artifact.Name,
|
||||||
LatestVersion: artifact.LatestVersion,
|
LatestVersion: artifact.LatestVersion,
|
||||||
|
@ -103,8 +137,15 @@ func GetAllArtifactResponse(
|
||||||
count int64,
|
count int64,
|
||||||
pageNumber int64,
|
pageNumber int64,
|
||||||
pageSize int,
|
pageSize int,
|
||||||
|
rootIdentifier string,
|
||||||
|
registryURL string,
|
||||||
) *artifactapi.ListArtifactResponseJSONResponse {
|
) *artifactapi.ListArtifactResponseJSONResponse {
|
||||||
artifactMetadataList := GetArtifactMetadata(artifacts)
|
var artifactMetadataList []artifactapi.ArtifactMetadata
|
||||||
|
if artifacts == nil {
|
||||||
|
artifactMetadataList = make([]artifactapi.ArtifactMetadata, 0)
|
||||||
|
} else {
|
||||||
|
artifactMetadataList = GetArtifactMetadata(*artifacts, rootIdentifier, registryURL)
|
||||||
|
}
|
||||||
pageCount := GetPageCount(count, pageSize)
|
pageCount := GetPageCount(count, pageSize)
|
||||||
listArtifact := &artifactapi.ListArtifact{
|
listArtifact := &artifactapi.ListArtifact{
|
||||||
ItemCount: &count,
|
ItemCount: &count,
|
||||||
|
@ -120,6 +161,33 @@ func GetAllArtifactResponse(
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetAllArtifactByRegistryResponse(
|
||||||
|
artifacts *[]types.ArtifactMetadata,
|
||||||
|
count int64,
|
||||||
|
pageNumber int64,
|
||||||
|
pageSize int,
|
||||||
|
) *artifactapi.ListRegistryArtifactResponseJSONResponse {
|
||||||
|
var artifactMetadataList []artifactapi.RegistryArtifactMetadata
|
||||||
|
if artifacts == nil {
|
||||||
|
artifactMetadataList = make([]artifactapi.RegistryArtifactMetadata, 0)
|
||||||
|
} else {
|
||||||
|
artifactMetadataList = GetRegistryArtifactMetadata(*artifacts)
|
||||||
|
}
|
||||||
|
pageCount := GetPageCount(count, pageSize)
|
||||||
|
listArtifact := &artifactapi.ListRegistryArtifact{
|
||||||
|
ItemCount: &count,
|
||||||
|
PageCount: &pageCount,
|
||||||
|
PageIndex: &pageNumber,
|
||||||
|
PageSize: &pageSize,
|
||||||
|
Artifacts: artifactMetadataList,
|
||||||
|
}
|
||||||
|
response := &artifactapi.ListRegistryArtifactResponseJSONResponse{
|
||||||
|
Data: *listArtifact,
|
||||||
|
Status: artifactapi.StatusSUCCESS,
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
func GetAllArtifactLabelsResponse(
|
func GetAllArtifactLabelsResponse(
|
||||||
artifactLabels *[]string,
|
artifactLabels *[]string,
|
||||||
count int64,
|
count int64,
|
||||||
|
|
|
@ -57,6 +57,21 @@ type RegistryRequestInfo struct {
|
||||||
pageNumber int64
|
pageNumber int64
|
||||||
searchTerm string
|
searchTerm string
|
||||||
labels []string
|
labels []string
|
||||||
|
registryIDs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type RegistryRequestParams struct {
|
||||||
|
packageTypesParam *api.PackageTypeParam
|
||||||
|
page *api.PageNumber
|
||||||
|
size *api.PageSize
|
||||||
|
search *api.SearchTerm
|
||||||
|
resource string
|
||||||
|
parentRef string
|
||||||
|
regRef string
|
||||||
|
labelsParam *api.LabelsParam
|
||||||
|
sortOrder *api.SortOrder
|
||||||
|
sortField *api.SortField
|
||||||
|
registryIDsParam *api.RegistryIdentifierParam
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRegistryRequestBaseInfo returns the base info for the registry request
|
// GetRegistryRequestBaseInfo returns the base info for the registry request
|
||||||
|
@ -117,50 +132,45 @@ func (c *APIController) GetRegistryRequestBaseInfo(
|
||||||
|
|
||||||
func (c *APIController) GetRegistryRequestInfo(
|
func (c *APIController) GetRegistryRequestInfo(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
packageTypesParam *api.PackageTypeParam,
|
registryRequestParams RegistryRequestParams,
|
||||||
page *api.PageNumber,
|
|
||||||
size *api.PageSize,
|
|
||||||
search *api.SearchTerm,
|
|
||||||
resource string,
|
|
||||||
parentRef string,
|
|
||||||
regRef string,
|
|
||||||
labelsParam *api.LabelsParam,
|
|
||||||
sortOrder *api.SortOrder,
|
|
||||||
sortField *api.SortField,
|
|
||||||
) (*RegistryRequestInfo, error) {
|
) (*RegistryRequestInfo, error) {
|
||||||
packageTypes := []string{}
|
packageTypes := []string{}
|
||||||
if packageTypesParam != nil {
|
if registryRequestParams.packageTypesParam != nil {
|
||||||
packageTypes = *packageTypesParam
|
packageTypes = *registryRequestParams.packageTypesParam
|
||||||
|
}
|
||||||
|
registryIDs := []string{}
|
||||||
|
if registryRequestParams.registryIDsParam != nil {
|
||||||
|
registryIDs = *registryRequestParams.registryIDsParam
|
||||||
}
|
}
|
||||||
sortByField := ""
|
sortByField := ""
|
||||||
sortByOrder := ""
|
sortByOrder := ""
|
||||||
if sortOrder != nil {
|
if registryRequestParams.sortOrder != nil {
|
||||||
sortByOrder = string(*sortOrder)
|
sortByOrder = string(*registryRequestParams.sortOrder)
|
||||||
}
|
}
|
||||||
|
|
||||||
if sortField != nil {
|
if registryRequestParams.sortField != nil {
|
||||||
sortByField = string(*sortField)
|
sortByField = string(*registryRequestParams.sortField)
|
||||||
}
|
}
|
||||||
|
|
||||||
labels := []string{}
|
labels := []string{}
|
||||||
|
|
||||||
if labelsParam != nil {
|
if registryRequestParams.labelsParam != nil {
|
||||||
labels = *labelsParam
|
labels = *registryRequestParams.labelsParam
|
||||||
}
|
}
|
||||||
|
|
||||||
sortByField = GetSortByField(sortByField, resource)
|
sortByField = GetSortByField(sortByField, registryRequestParams.resource)
|
||||||
sortByOrder = GetSortByOrder(sortByOrder)
|
sortByOrder = GetSortByOrder(sortByOrder)
|
||||||
|
|
||||||
offset := GetOffset(size, page)
|
offset := GetOffset(registryRequestParams.size, registryRequestParams.page)
|
||||||
limit := GetPageLimit(size)
|
limit := GetPageLimit(registryRequestParams.size)
|
||||||
pageNumber := GetPageNumber(page)
|
pageNumber := GetPageNumber(registryRequestParams.page)
|
||||||
|
|
||||||
searchTerm := ""
|
searchTerm := ""
|
||||||
if search != nil {
|
if registryRequestParams.search != nil {
|
||||||
searchTerm = string(*search)
|
searchTerm = string(*registryRequestParams.search)
|
||||||
}
|
}
|
||||||
|
|
||||||
baseInfo, err := c.GetRegistryRequestBaseInfo(ctx, parentRef, regRef)
|
baseInfo, err := c.GetRegistryRequestBaseInfo(ctx, registryRequestParams.parentRef, registryRequestParams.regRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -175,6 +185,7 @@ func (c *APIController) GetRegistryRequestInfo(
|
||||||
pageNumber: pageNumber,
|
pageNumber: pageNumber,
|
||||||
searchTerm: searchTerm,
|
searchTerm: searchTerm,
|
||||||
labels: labels,
|
labels: labels,
|
||||||
|
registryIDs: registryIDs,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,6 @@ import (
|
||||||
apiauth "github.com/harness/gitness/app/api/auth"
|
apiauth "github.com/harness/gitness/app/api/auth"
|
||||||
"github.com/harness/gitness/app/api/request"
|
"github.com/harness/gitness/app/api/request"
|
||||||
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||||
"github.com/harness/gitness/registry/types"
|
|
||||||
"github.com/harness/gitness/types/enum"
|
"github.com/harness/gitness/types/enum"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -29,20 +28,21 @@ func (c *APIController) GetAllArtifacts(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
r artifact.GetAllArtifactsRequestObject,
|
r artifact.GetAllArtifactsRequestObject,
|
||||||
) (artifact.GetAllArtifactsResponseObject, error) {
|
) (artifact.GetAllArtifactsResponseObject, error) {
|
||||||
ref := ""
|
registryRequestParams := &RegistryRequestParams{
|
||||||
if r.Params.RegIdentifier != nil {
|
packageTypesParam: nil,
|
||||||
ref2, err2 := GetRegRef(string(r.SpaceRef), string(*r.Params.RegIdentifier))
|
page: r.Params.Page,
|
||||||
if err2 != nil {
|
size: r.Params.Size,
|
||||||
return c.getAllArtifacts400JsonResponse(err2)
|
search: r.Params.SearchTerm,
|
||||||
}
|
resource: ArtifactResource,
|
||||||
ref = ref2
|
parentRef: string(r.SpaceRef),
|
||||||
|
regRef: "",
|
||||||
|
labelsParam: nil,
|
||||||
|
sortOrder: r.Params.SortOrder,
|
||||||
|
sortField: r.Params.SortField,
|
||||||
|
registryIDsParam: r.Params.RegIdentifier,
|
||||||
}
|
}
|
||||||
|
|
||||||
regInfo, err := c.GetRegistryRequestInfo(
|
regInfo, err := c.GetRegistryRequestInfo(ctx, *registryRequestParams)
|
||||||
ctx, r.Params.PackageType, r.Params.Page, r.Params.Size,
|
|
||||||
r.Params.SearchTerm, ArtifactResource, string(r.SpaceRef), ref, r.Params.Label,
|
|
||||||
r.Params.SortOrder, r.Params.SortField,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.getAllArtifacts400JsonResponse(err)
|
return c.getAllArtifacts400JsonResponse(err)
|
||||||
}
|
}
|
||||||
|
@ -70,28 +70,16 @@ func (c *APIController) GetAllArtifacts(
|
||||||
),
|
),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
latestVersion := false
|
||||||
var artifacts *[]types.ArtifactMetadata
|
if r.Params.LatestVersion != nil {
|
||||||
var count int64
|
latestVersion = bool(*r.Params.LatestVersion)
|
||||||
if len(regInfo.RegistryIdentifier) == 0 {
|
|
||||||
artifacts, err = c.TagStore.GetAllArtifactsByParentID(
|
|
||||||
ctx, regInfo.parentID, ®Info.packageTypes,
|
|
||||||
regInfo.sortByField, regInfo.sortByOrder, regInfo.limit, regInfo.offset, regInfo.searchTerm, regInfo.labels,
|
|
||||||
)
|
|
||||||
count, _ = c.TagStore.CountAllArtifactsByParentID(
|
|
||||||
ctx, regInfo.parentID, ®Info.packageTypes,
|
|
||||||
regInfo.searchTerm, regInfo.labels,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
artifacts, err = c.TagStore.GetAllArtifactsByRepo(
|
|
||||||
ctx, regInfo.parentID, regInfo.RegistryIdentifier,
|
|
||||||
regInfo.sortByField, regInfo.sortByOrder, regInfo.limit, regInfo.offset, regInfo.searchTerm, regInfo.labels,
|
|
||||||
)
|
|
||||||
count, _ = c.TagStore.CountAllArtifactsByRepo(
|
|
||||||
ctx, regInfo.parentID, regInfo.RegistryIdentifier,
|
|
||||||
regInfo.searchTerm, regInfo.labels,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
artifacts, err := c.TagStore.GetAllArtifactsByParentID(
|
||||||
|
ctx, regInfo.parentID, ®Info.registryIDs,
|
||||||
|
regInfo.sortByField, regInfo.sortByOrder, regInfo.limit, regInfo.offset, regInfo.searchTerm, latestVersion)
|
||||||
|
count, _ := c.TagStore.CountAllArtifactsByParentID(
|
||||||
|
ctx, regInfo.parentID, ®Info.registryIDs,
|
||||||
|
regInfo.searchTerm, latestVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return artifact.GetAllArtifacts500JSONResponse{
|
return artifact.GetAllArtifacts500JSONResponse{
|
||||||
InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse(
|
InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse(
|
||||||
|
@ -100,7 +88,8 @@ func (c *APIController) GetAllArtifacts(
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
return artifact.GetAllArtifacts200JSONResponse{
|
return artifact.GetAllArtifacts200JSONResponse{
|
||||||
ListArtifactResponseJSONResponse: *GetAllArtifactResponse(artifacts, count, regInfo.pageNumber, regInfo.limit),
|
ListArtifactResponseJSONResponse: *GetAllArtifactResponse(artifacts, count, regInfo.pageNumber, regInfo.limit,
|
||||||
|
regInfo.RootIdentifier, c.URLProvider.RegistryURL()),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,11 +28,21 @@ func (c *APIController) ListArtifactLabels(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
r artifact.ListArtifactLabelsRequestObject,
|
r artifact.ListArtifactLabelsRequestObject,
|
||||||
) (artifact.ListArtifactLabelsResponseObject, error) {
|
) (artifact.ListArtifactLabelsResponseObject, error) {
|
||||||
|
registryRequestParams := &RegistryRequestParams{
|
||||||
|
packageTypesParam: nil,
|
||||||
|
page: r.Params.Page,
|
||||||
|
size: r.Params.Size,
|
||||||
|
search: r.Params.SearchTerm,
|
||||||
|
resource: ArtifactResource,
|
||||||
|
parentRef: "",
|
||||||
|
regRef: string(r.RegistryRef),
|
||||||
|
labelsParam: nil,
|
||||||
|
sortOrder: nil,
|
||||||
|
sortField: nil,
|
||||||
|
registryIDsParam: nil,
|
||||||
|
}
|
||||||
regInfo, _ := c.GetRegistryRequestInfo(
|
regInfo, _ := c.GetRegistryRequestInfo(
|
||||||
ctx, nil, r.Params.Page, r.Params.Size,
|
ctx, *registryRequestParams)
|
||||||
r.Params.SearchTerm, ArtifactResource, "", string(r.RegistryRef),
|
|
||||||
nil, nil, nil,
|
|
||||||
)
|
|
||||||
|
|
||||||
space, err := c.SpaceStore.FindByRef(ctx, regInfo.ParentRef)
|
space, err := c.SpaceStore.FindByRef(ctx, regInfo.ParentRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -36,11 +36,20 @@ func (c *APIController) GetAllArtifactVersions(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
r artifact.GetAllArtifactVersionsRequestObject,
|
r artifact.GetAllArtifactVersionsRequestObject,
|
||||||
) (artifact.GetAllArtifactVersionsResponseObject, error) {
|
) (artifact.GetAllArtifactVersionsResponseObject, error) {
|
||||||
regInfo, _ := c.GetRegistryRequestInfo(
|
registryRequestParams := &RegistryRequestParams{
|
||||||
ctx, nil, r.Params.Page, r.Params.Size,
|
packageTypesParam: nil,
|
||||||
r.Params.SearchTerm, ArtifactVersionResource, "", string(r.RegistryRef),
|
page: r.Params.Page,
|
||||||
nil, r.Params.SortOrder, r.Params.SortField,
|
size: r.Params.Size,
|
||||||
)
|
search: r.Params.SearchTerm,
|
||||||
|
resource: ArtifactVersionResource,
|
||||||
|
parentRef: "",
|
||||||
|
regRef: string(r.RegistryRef),
|
||||||
|
labelsParam: nil,
|
||||||
|
sortOrder: r.Params.SortOrder,
|
||||||
|
sortField: r.Params.SortField,
|
||||||
|
registryIDsParam: nil,
|
||||||
|
}
|
||||||
|
regInfo, _ := c.GetRegistryRequestInfo(ctx, *registryRequestParams)
|
||||||
|
|
||||||
space, err := c.SpaceStore.FindByRef(ctx, regInfo.ParentRef)
|
space, err := c.SpaceStore.FindByRef(ctx, regInfo.ParentRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -32,11 +32,20 @@ func (c *APIController) GetAllRegistries(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
r artifact.GetAllRegistriesRequestObject,
|
r artifact.GetAllRegistriesRequestObject,
|
||||||
) (artifact.GetAllRegistriesResponseObject, error) {
|
) (artifact.GetAllRegistriesResponseObject, error) {
|
||||||
regInfo, _ := c.GetRegistryRequestInfo(
|
registryRequestParams := &RegistryRequestParams{
|
||||||
ctx, r.Params.PackageType, r.Params.Page, r.Params.Size,
|
packageTypesParam: nil,
|
||||||
r.Params.SearchTerm, RepositoryResource, string(r.SpaceRef), "", nil,
|
page: r.Params.Page,
|
||||||
r.Params.SortOrder, r.Params.SortField,
|
size: r.Params.Size,
|
||||||
)
|
search: r.Params.SearchTerm,
|
||||||
|
resource: RepositoryResource,
|
||||||
|
parentRef: string(r.SpaceRef),
|
||||||
|
regRef: "",
|
||||||
|
labelsParam: nil,
|
||||||
|
sortOrder: r.Params.SortOrder,
|
||||||
|
sortField: r.Params.SortField,
|
||||||
|
registryIDsParam: nil,
|
||||||
|
}
|
||||||
|
regInfo, _ := c.GetRegistryRequestInfo(ctx, *registryRequestParams)
|
||||||
|
|
||||||
space, err := c.SpaceStore.FindByRef(ctx, regInfo.ParentRef)
|
space, err := c.SpaceStore.FindByRef(ctx, regInfo.ParentRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
// Copyright 2023 Harness, Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package metadata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
apiauth "github.com/harness/gitness/app/api/auth"
|
||||||
|
"github.com/harness/gitness/app/api/request"
|
||||||
|
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||||
|
"github.com/harness/gitness/types/enum"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *APIController) GetAllArtifactsByRegistry(
|
||||||
|
ctx context.Context,
|
||||||
|
r artifact.GetAllArtifactsByRegistryRequestObject,
|
||||||
|
) (artifact.GetAllArtifactsByRegistryResponseObject, error) {
|
||||||
|
registryRequestParams := &RegistryRequestParams{
|
||||||
|
packageTypesParam: nil,
|
||||||
|
page: r.Params.Page,
|
||||||
|
size: r.Params.Size,
|
||||||
|
search: r.Params.SearchTerm,
|
||||||
|
resource: ArtifactResource,
|
||||||
|
parentRef: "",
|
||||||
|
regRef: string(r.RegistryRef),
|
||||||
|
labelsParam: nil,
|
||||||
|
sortOrder: r.Params.SortOrder,
|
||||||
|
sortField: r.Params.SortField,
|
||||||
|
registryIDsParam: nil,
|
||||||
|
}
|
||||||
|
regInfo, err := c.GetRegistryRequestInfo(ctx, *registryRequestParams)
|
||||||
|
if err != nil {
|
||||||
|
return c.getAllArtifactsByRegistry400JsonResponse(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
space, err := c.SpaceStore.FindByRef(ctx, regInfo.ParentRef)
|
||||||
|
if err != nil {
|
||||||
|
return artifact.GetAllArtifactsByRegistry400JSONResponse{
|
||||||
|
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||||
|
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||||
|
),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
session, _ := request.AuthSessionFrom(ctx)
|
||||||
|
permissionChecks := GetPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
|
||||||
|
if err = apiauth.CheckRegistry(
|
||||||
|
ctx,
|
||||||
|
c.Authorizer,
|
||||||
|
session,
|
||||||
|
permissionChecks...,
|
||||||
|
); err != nil {
|
||||||
|
return artifact.GetAllArtifactsByRegistry403JSONResponse{
|
||||||
|
UnauthorizedJSONResponse: artifact.UnauthorizedJSONResponse(
|
||||||
|
*GetErrorResponse(http.StatusForbidden, err.Error()),
|
||||||
|
),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
artifacts, err := c.TagStore.GetAllArtifactsByRepo(
|
||||||
|
ctx, regInfo.parentID, regInfo.RegistryIdentifier,
|
||||||
|
regInfo.sortByField, regInfo.sortByOrder, regInfo.limit, regInfo.offset, regInfo.searchTerm, regInfo.labels,
|
||||||
|
)
|
||||||
|
count, _ := c.TagStore.CountAllArtifactsByRepo(
|
||||||
|
ctx, regInfo.parentID, regInfo.RegistryIdentifier,
|
||||||
|
regInfo.searchTerm, regInfo.labels,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return artifact.GetAllArtifactsByRegistry500JSONResponse{
|
||||||
|
InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse(
|
||||||
|
*GetErrorResponse(http.StatusInternalServerError, err.Error()),
|
||||||
|
),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return artifact.GetAllArtifactsByRegistry200JSONResponse{
|
||||||
|
ListRegistryArtifactResponseJSONResponse: *GetAllArtifactByRegistryResponse(
|
||||||
|
artifacts, count, regInfo.pageNumber, regInfo.limit,
|
||||||
|
),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *APIController) getAllArtifactsByRegistry400JsonResponse(err error) (
|
||||||
|
artifact.GetAllArtifactsByRegistryResponseObject, error,
|
||||||
|
) {
|
||||||
|
return artifact.GetAllArtifactsByRegistry400JSONResponse{
|
||||||
|
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||||
|
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||||
|
),
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -29,10 +29,20 @@ func (c *APIController) UpdateArtifactLabels(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
r artifact.UpdateArtifactLabelsRequestObject,
|
r artifact.UpdateArtifactLabelsRequestObject,
|
||||||
) (artifact.UpdateArtifactLabelsResponseObject, error) {
|
) (artifact.UpdateArtifactLabelsResponseObject, error) {
|
||||||
regInfo, _ := c.GetRegistryRequestInfo(
|
registryRequestParams := &RegistryRequestParams{
|
||||||
ctx, nil, nil, nil, nil,
|
packageTypesParam: nil,
|
||||||
ArtifactVersionResource, "", string(r.RegistryRef), nil, nil, nil,
|
page: nil,
|
||||||
)
|
size: nil,
|
||||||
|
search: nil,
|
||||||
|
resource: ArtifactVersionResource,
|
||||||
|
parentRef: "",
|
||||||
|
regRef: string(r.RegistryRef),
|
||||||
|
labelsParam: nil,
|
||||||
|
sortOrder: nil,
|
||||||
|
sortField: nil,
|
||||||
|
registryIDsParam: nil,
|
||||||
|
}
|
||||||
|
regInfo, _ := c.GetRegistryRequestInfo(ctx, *registryRequestParams)
|
||||||
|
|
||||||
space, err := c.SpaceStore.FindByRef(ctx, regInfo.ParentRef)
|
space, err := c.SpaceStore.FindByRef(ctx, regInfo.ParentRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -400,7 +400,7 @@ func GetPermissionChecks(
|
||||||
) []types.PermissionCheck {
|
) []types.PermissionCheck {
|
||||||
var permissionChecks []types.PermissionCheck
|
var permissionChecks []types.PermissionCheck
|
||||||
permissionCheck := &types.PermissionCheck{
|
permissionCheck := &types.PermissionCheck{
|
||||||
Scope: types.Scope{SpacePath: space.Identifier},
|
Scope: types.Scope{SpacePath: space.Path},
|
||||||
Resource: types.Resource{Type: enum.ResourceTypeRegistry, Identifier: registryIdentifier},
|
Resource: types.Resource{Type: enum.ResourceTypeRegistry, Identifier: registryIdentifier},
|
||||||
Permission: permission,
|
Permission: permission,
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ package oci
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -34,6 +35,10 @@ import (
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type TokenResponseOCI struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Handler) GetToken(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) GetToken(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
session, ok := request.AuthSessionFrom(ctx)
|
session, ok := request.AuthSessionFrom(ctx)
|
||||||
|
@ -79,8 +84,12 @@ func (h *Handler) GetToken(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
if jwtToken != "" {
|
if jwtToken != "" {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
_, err := w.Write([]byte(fmt.Sprintf("{\"token\":\"%s\"}", jwtToken)))
|
enc := json.NewEncoder(w)
|
||||||
if err != nil {
|
if err := enc.Encode(
|
||||||
|
TokenResponseOCI{
|
||||||
|
Token: jwtToken,
|
||||||
|
},
|
||||||
|
); err != nil {
|
||||||
log.Error().Msgf("failed to write token response: %v", err)
|
log.Error().Msgf("failed to write token response: %v", err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
|
@ -173,17 +173,44 @@ paths:
|
||||||
- Spaces
|
- Spaces
|
||||||
parameters:
|
parameters:
|
||||||
- $ref: "#/components/parameters/spaceRefPathParam"
|
- $ref: "#/components/parameters/spaceRefPathParam"
|
||||||
- $ref: "#/components/parameters/LabelsParam"
|
|
||||||
- $ref: "#/components/parameters/packageTypeParam"
|
|
||||||
- $ref: "#/components/parameters/RegistryIdentifierParam"
|
- $ref: "#/components/parameters/RegistryIdentifierParam"
|
||||||
- $ref: "#/components/parameters/pageNumber"
|
- $ref: "#/components/parameters/pageNumber"
|
||||||
- $ref: "#/components/parameters/pageSize"
|
- $ref: "#/components/parameters/pageSize"
|
||||||
- $ref: "#/components/parameters/sortOrder"
|
- $ref: "#/components/parameters/sortOrder"
|
||||||
- $ref: "#/components/parameters/sortField"
|
- $ref: "#/components/parameters/sortField"
|
||||||
- $ref: "#/components/parameters/searchTerm"
|
- $ref: "#/components/parameters/searchTerm"
|
||||||
|
- $ref: "#/components/parameters/latestVersion"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
$ref: "#/components/responses/ListArtifactResponse"
|
||||||
|
400:
|
||||||
|
$ref: "#/components/responses/BadRequest"
|
||||||
|
401:
|
||||||
|
$ref: "#/components/responses/Unauthenticated"
|
||||||
|
403:
|
||||||
|
$ref: "#/components/responses/Unauthorized"
|
||||||
|
404:
|
||||||
|
$ref: "#/components/responses/NotFound"
|
||||||
|
500:
|
||||||
|
$ref: "#/components/responses/InternalServerError"
|
||||||
|
/registry/{registry_ref}/artifacts:
|
||||||
|
get:
|
||||||
|
summary: List Artifacts for Registry
|
||||||
|
description: Lists all the Artifacts for Registry
|
||||||
|
operationId: GetAllArtifactsByRegistry
|
||||||
|
tags:
|
||||||
|
- Registries
|
||||||
|
parameters:
|
||||||
|
- $ref: "#/components/parameters/registryRefPathParam"
|
||||||
|
- $ref: "#/components/parameters/LabelsParam"
|
||||||
|
- $ref: "#/components/parameters/pageNumber"
|
||||||
|
- $ref: "#/components/parameters/pageSize"
|
||||||
|
- $ref: "#/components/parameters/sortOrder"
|
||||||
|
- $ref: "#/components/parameters/sortField"
|
||||||
|
- $ref: "#/components/parameters/searchTerm"
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
$ref: "#/components/responses/ListArtifactResponse"
|
$ref: "#/components/responses/ListRegistryArtifactResponse"
|
||||||
400:
|
400:
|
||||||
$ref: "#/components/responses/BadRequest"
|
$ref: "#/components/responses/BadRequest"
|
||||||
401:
|
401:
|
||||||
|
@ -764,6 +791,20 @@ components:
|
||||||
required:
|
required:
|
||||||
- status
|
- status
|
||||||
- data
|
- data
|
||||||
|
ListRegistryArtifactResponse:
|
||||||
|
description: response for list artifact
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
$ref: "#/components/schemas/Status"
|
||||||
|
data:
|
||||||
|
$ref: "#/components/schemas/ListRegistryArtifact"
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
- data
|
||||||
ListArtifactVersionResponse:
|
ListArtifactVersionResponse:
|
||||||
description: response for list versions of artifact
|
description: response for list versions of artifact
|
||||||
content:
|
content:
|
||||||
|
@ -896,6 +937,36 @@ components:
|
||||||
$ref: "#/components/schemas/ArtifactMetadata"
|
$ref: "#/components/schemas/ArtifactMetadata"
|
||||||
required:
|
required:
|
||||||
- artifacts
|
- artifacts
|
||||||
|
ListRegistryArtifact:
|
||||||
|
type: object
|
||||||
|
description: A list of Artifacts
|
||||||
|
properties:
|
||||||
|
pageCount:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: The total number of pages
|
||||||
|
example: 100
|
||||||
|
itemCount:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: The total number of items
|
||||||
|
example: 1
|
||||||
|
pageSize:
|
||||||
|
type: integer
|
||||||
|
description: The number of items per page
|
||||||
|
example: 1
|
||||||
|
pageIndex:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: The current page
|
||||||
|
example: 0
|
||||||
|
artifacts:
|
||||||
|
type: array
|
||||||
|
description: A list of Artifact
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/RegistryArtifactMetadata"
|
||||||
|
required:
|
||||||
|
- artifacts
|
||||||
ListArtifactVersion:
|
ListArtifactVersion:
|
||||||
type: object
|
type: object
|
||||||
description: A list of Artifact versions
|
description: A list of Artifact versions
|
||||||
|
@ -1029,6 +1100,36 @@ components:
|
||||||
type:
|
type:
|
||||||
$ref: "#/components/schemas/ClientSetupStepType"
|
$ref: "#/components/schemas/ClientSetupStepType"
|
||||||
ArtifactMetadata:
|
ArtifactMetadata:
|
||||||
|
type: object
|
||||||
|
description: Artifact Metadata
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
version:
|
||||||
|
type: string
|
||||||
|
registryIdentifier:
|
||||||
|
type: string
|
||||||
|
registryPath:
|
||||||
|
type: string
|
||||||
|
labels:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
downloadsCount:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
lastModified:
|
||||||
|
type: string
|
||||||
|
pullCommand:
|
||||||
|
type: string
|
||||||
|
packageType:
|
||||||
|
$ref: "#/components/schemas/PackageType"
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
- registryIdentifier
|
||||||
|
- latestVersion
|
||||||
|
- registryPath
|
||||||
|
RegistryArtifactMetadata:
|
||||||
type: object
|
type: object
|
||||||
description: Artifact Metadata
|
description: Artifact Metadata
|
||||||
properties:
|
properties:
|
||||||
|
@ -1397,7 +1498,7 @@ components:
|
||||||
description: "ClientSetupStepType type"
|
description: "ClientSetupStepType type"
|
||||||
enum:
|
enum:
|
||||||
- Static
|
- Static
|
||||||
- GenerateToken
|
- GenerateToken
|
||||||
Error:
|
Error:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -1503,7 +1604,9 @@ components:
|
||||||
required: false
|
required: false
|
||||||
description: Registry Identifier
|
description: Registry Identifier
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
spaceRefPathParam:
|
spaceRefPathParam:
|
||||||
name: space_ref
|
name: space_ref
|
||||||
in: path
|
in: path
|
||||||
|
@ -1601,6 +1704,13 @@ components:
|
||||||
description: sortField
|
description: sortField
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
latestVersion:
|
||||||
|
name: latest_version
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: Latest Version Filter.
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
fromDateParam:
|
fromDateParam:
|
||||||
name: from
|
name: from
|
||||||
in: query
|
in: query
|
||||||
|
@ -1614,4 +1724,4 @@ components:
|
||||||
required: false
|
required: false
|
||||||
description: Date. Format - MM/DD/YYYY
|
description: Date. Format - MM/DD/YYYY
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
|
@ -74,6 +74,9 @@ type ServerInterface interface {
|
||||||
// List Artifact Versions
|
// List Artifact Versions
|
||||||
// (GET /registry/{registry_ref}/artifact/{artifact}/versions)
|
// (GET /registry/{registry_ref}/artifact/{artifact}/versions)
|
||||||
GetAllArtifactVersions(w http.ResponseWriter, r *http.Request, registryRef RegistryRefPathParam, artifact ArtifactPathParam, params GetAllArtifactVersionsParams)
|
GetAllArtifactVersions(w http.ResponseWriter, r *http.Request, registryRef RegistryRefPathParam, artifact ArtifactPathParam, params GetAllArtifactVersionsParams)
|
||||||
|
// List Artifacts for Registry
|
||||||
|
// (GET /registry/{registry_ref}/artifacts)
|
||||||
|
GetAllArtifactsByRegistry(w http.ResponseWriter, r *http.Request, registryRef RegistryRefPathParam, params GetAllArtifactsByRegistryParams)
|
||||||
// Returns CLI Client Setup Details
|
// Returns CLI Client Setup Details
|
||||||
// (GET /registry/{registry_ref}/client-setup-details)
|
// (GET /registry/{registry_ref}/client-setup-details)
|
||||||
GetClientSetupDetails(w http.ResponseWriter, r *http.Request, registryRef RegistryRefPathParam, params GetClientSetupDetailsParams)
|
GetClientSetupDetails(w http.ResponseWriter, r *http.Request, registryRef RegistryRefPathParam, params GetClientSetupDetailsParams)
|
||||||
|
@ -194,6 +197,12 @@ func (_ Unimplemented) GetAllArtifactVersions(w http.ResponseWriter, r *http.Req
|
||||||
w.WriteHeader(http.StatusNotImplemented)
|
w.WriteHeader(http.StatusNotImplemented)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// List Artifacts for Registry
|
||||||
|
// (GET /registry/{registry_ref}/artifacts)
|
||||||
|
func (_ Unimplemented) GetAllArtifactsByRegistry(w http.ResponseWriter, r *http.Request, registryRef RegistryRefPathParam, params GetAllArtifactsByRegistryParams) {
|
||||||
|
w.WriteHeader(http.StatusNotImplemented)
|
||||||
|
}
|
||||||
|
|
||||||
// Returns CLI Client Setup Details
|
// Returns CLI Client Setup Details
|
||||||
// (GET /registry/{registry_ref}/client-setup-details)
|
// (GET /registry/{registry_ref}/client-setup-details)
|
||||||
func (_ Unimplemented) GetClientSetupDetails(w http.ResponseWriter, r *http.Request, registryRef RegistryRefPathParam, params GetClientSetupDetailsParams) {
|
func (_ Unimplemented) GetClientSetupDetails(w http.ResponseWriter, r *http.Request, registryRef RegistryRefPathParam, params GetClientSetupDetailsParams) {
|
||||||
|
@ -995,6 +1004,83 @@ func (siw *ServerInterfaceWrapper) GetAllArtifactVersions(w http.ResponseWriter,
|
||||||
handler.ServeHTTP(w, r.WithContext(ctx))
|
handler.ServeHTTP(w, r.WithContext(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAllArtifactsByRegistry operation middleware
|
||||||
|
func (siw *ServerInterfaceWrapper) GetAllArtifactsByRegistry(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// ------------- Path parameter "registry_ref" -------------
|
||||||
|
var registryRef RegistryRefPathParam
|
||||||
|
|
||||||
|
err = runtime.BindStyledParameterWithOptions("simple", "registry_ref", chi.URLParam(r, "registry_ref"), ®istryRef, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true})
|
||||||
|
if err != nil {
|
||||||
|
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "registry_ref", Err: err})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parameter object where we will unmarshal all parameters from the context
|
||||||
|
var params GetAllArtifactsByRegistryParams
|
||||||
|
|
||||||
|
// ------------- Optional query parameter "label" -------------
|
||||||
|
|
||||||
|
err = runtime.BindQueryParameter("form", true, false, "label", r.URL.Query(), ¶ms.Label)
|
||||||
|
if err != nil {
|
||||||
|
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "label", Err: err})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------- Optional query parameter "page" -------------
|
||||||
|
|
||||||
|
err = runtime.BindQueryParameter("form", true, false, "page", r.URL.Query(), ¶ms.Page)
|
||||||
|
if err != nil {
|
||||||
|
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "page", Err: err})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------- Optional query parameter "size" -------------
|
||||||
|
|
||||||
|
err = runtime.BindQueryParameter("form", true, false, "size", r.URL.Query(), ¶ms.Size)
|
||||||
|
if err != nil {
|
||||||
|
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "size", Err: err})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------- Optional query parameter "sort_order" -------------
|
||||||
|
|
||||||
|
err = runtime.BindQueryParameter("form", true, false, "sort_order", r.URL.Query(), ¶ms.SortOrder)
|
||||||
|
if err != nil {
|
||||||
|
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "sort_order", Err: err})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------- Optional query parameter "sort_field" -------------
|
||||||
|
|
||||||
|
err = runtime.BindQueryParameter("form", true, false, "sort_field", r.URL.Query(), ¶ms.SortField)
|
||||||
|
if err != nil {
|
||||||
|
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "sort_field", Err: err})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------- Optional query parameter "search_term" -------------
|
||||||
|
|
||||||
|
err = runtime.BindQueryParameter("form", true, false, "search_term", r.URL.Query(), ¶ms.SearchTerm)
|
||||||
|
if err != nil {
|
||||||
|
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "search_term", Err: err})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
siw.Handler.GetAllArtifactsByRegistry(w, r, registryRef, params)
|
||||||
|
}))
|
||||||
|
|
||||||
|
for _, middleware := range siw.HandlerMiddlewares {
|
||||||
|
handler = middleware(handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.ServeHTTP(w, r.WithContext(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
// GetClientSetupDetails operation middleware
|
// GetClientSetupDetails operation middleware
|
||||||
func (siw *ServerInterfaceWrapper) GetClientSetupDetails(w http.ResponseWriter, r *http.Request) {
|
func (siw *ServerInterfaceWrapper) GetClientSetupDetails(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
@ -1103,22 +1189,6 @@ func (siw *ServerInterfaceWrapper) GetAllArtifacts(w http.ResponseWriter, r *htt
|
||||||
// Parameter object where we will unmarshal all parameters from the context
|
// Parameter object where we will unmarshal all parameters from the context
|
||||||
var params GetAllArtifactsParams
|
var params GetAllArtifactsParams
|
||||||
|
|
||||||
// ------------- Optional query parameter "label" -------------
|
|
||||||
|
|
||||||
err = runtime.BindQueryParameter("form", true, false, "label", r.URL.Query(), ¶ms.Label)
|
|
||||||
if err != nil {
|
|
||||||
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "label", Err: err})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------- Optional query parameter "package_type" -------------
|
|
||||||
|
|
||||||
err = runtime.BindQueryParameter("form", true, false, "package_type", r.URL.Query(), ¶ms.PackageType)
|
|
||||||
if err != nil {
|
|
||||||
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "package_type", Err: err})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------- Optional query parameter "reg_identifier" -------------
|
// ------------- Optional query parameter "reg_identifier" -------------
|
||||||
|
|
||||||
err = runtime.BindQueryParameter("form", true, false, "reg_identifier", r.URL.Query(), ¶ms.RegIdentifier)
|
err = runtime.BindQueryParameter("form", true, false, "reg_identifier", r.URL.Query(), ¶ms.RegIdentifier)
|
||||||
|
@ -1167,6 +1237,14 @@ func (siw *ServerInterfaceWrapper) GetAllArtifacts(w http.ResponseWriter, r *htt
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------- Optional query parameter "latest_version" -------------
|
||||||
|
|
||||||
|
err = runtime.BindQueryParameter("form", true, false, "latest_version", r.URL.Query(), ¶ms.LatestVersion)
|
||||||
|
if err != nil {
|
||||||
|
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "latest_version", Err: err})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
siw.Handler.GetAllArtifacts(w, r, spaceRef, params)
|
siw.Handler.GetAllArtifacts(w, r, spaceRef, params)
|
||||||
}))
|
}))
|
||||||
|
@ -1427,6 +1505,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
r.Get(options.BaseURL+"/registry/{registry_ref}/artifact/{artifact}/versions", wrapper.GetAllArtifactVersions)
|
r.Get(options.BaseURL+"/registry/{registry_ref}/artifact/{artifact}/versions", wrapper.GetAllArtifactVersions)
|
||||||
})
|
})
|
||||||
|
r.Group(func(r chi.Router) {
|
||||||
|
r.Get(options.BaseURL+"/registry/{registry_ref}/artifacts", wrapper.GetAllArtifactsByRegistry)
|
||||||
|
})
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
r.Get(options.BaseURL+"/registry/{registry_ref}/client-setup-details", wrapper.GetClientSetupDetails)
|
r.Get(options.BaseURL+"/registry/{registry_ref}/client-setup-details", wrapper.GetClientSetupDetails)
|
||||||
})
|
})
|
||||||
|
@ -1559,6 +1640,14 @@ type ListArtifactVersionResponseJSONResponse struct {
|
||||||
Status Status `json:"status"`
|
Status Status `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ListRegistryArtifactResponseJSONResponse struct {
|
||||||
|
// Data A list of Artifacts
|
||||||
|
Data ListRegistryArtifact `json:"data"`
|
||||||
|
|
||||||
|
// Status Indicates if the request was successful or not
|
||||||
|
Status Status `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
type ListRegistryResponseJSONResponse struct {
|
type ListRegistryResponseJSONResponse struct {
|
||||||
// Data A list of Harness Artifact Registries
|
// Data A list of Harness Artifact Registries
|
||||||
Data ListRegistry `json:"data"`
|
Data ListRegistry `json:"data"`
|
||||||
|
@ -2719,6 +2808,73 @@ func (response GetAllArtifactVersions500JSONResponse) VisitGetAllArtifactVersion
|
||||||
return json.NewEncoder(w).Encode(response)
|
return json.NewEncoder(w).Encode(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GetAllArtifactsByRegistryRequestObject struct {
|
||||||
|
RegistryRef RegistryRefPathParam `json:"registry_ref"`
|
||||||
|
Params GetAllArtifactsByRegistryParams
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetAllArtifactsByRegistryResponseObject interface {
|
||||||
|
VisitGetAllArtifactsByRegistryResponse(w http.ResponseWriter) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetAllArtifactsByRegistry200JSONResponse struct {
|
||||||
|
ListRegistryArtifactResponseJSONResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
func (response GetAllArtifactsByRegistry200JSONResponse) VisitGetAllArtifactsByRegistryResponse(w http.ResponseWriter) error {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(200)
|
||||||
|
|
||||||
|
return json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetAllArtifactsByRegistry400JSONResponse struct{ BadRequestJSONResponse }
|
||||||
|
|
||||||
|
func (response GetAllArtifactsByRegistry400JSONResponse) VisitGetAllArtifactsByRegistryResponse(w http.ResponseWriter) error {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(400)
|
||||||
|
|
||||||
|
return json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetAllArtifactsByRegistry401JSONResponse struct{ UnauthenticatedJSONResponse }
|
||||||
|
|
||||||
|
func (response GetAllArtifactsByRegistry401JSONResponse) VisitGetAllArtifactsByRegistryResponse(w http.ResponseWriter) error {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(401)
|
||||||
|
|
||||||
|
return json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetAllArtifactsByRegistry403JSONResponse struct{ UnauthorizedJSONResponse }
|
||||||
|
|
||||||
|
func (response GetAllArtifactsByRegistry403JSONResponse) VisitGetAllArtifactsByRegistryResponse(w http.ResponseWriter) error {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(403)
|
||||||
|
|
||||||
|
return json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetAllArtifactsByRegistry404JSONResponse struct{ NotFoundJSONResponse }
|
||||||
|
|
||||||
|
func (response GetAllArtifactsByRegistry404JSONResponse) VisitGetAllArtifactsByRegistryResponse(w http.ResponseWriter) error {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(404)
|
||||||
|
|
||||||
|
return json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetAllArtifactsByRegistry500JSONResponse struct {
|
||||||
|
InternalServerErrorJSONResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
func (response GetAllArtifactsByRegistry500JSONResponse) VisitGetAllArtifactsByRegistryResponse(w http.ResponseWriter) error {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(500)
|
||||||
|
|
||||||
|
return json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
type GetClientSetupDetailsRequestObject struct {
|
type GetClientSetupDetailsRequestObject struct {
|
||||||
RegistryRef RegistryRefPathParam `json:"registry_ref"`
|
RegistryRef RegistryRefPathParam `json:"registry_ref"`
|
||||||
Params GetClientSetupDetailsParams
|
Params GetClientSetupDetailsParams
|
||||||
|
@ -3040,6 +3196,9 @@ type StrictServerInterface interface {
|
||||||
// List Artifact Versions
|
// List Artifact Versions
|
||||||
// (GET /registry/{registry_ref}/artifact/{artifact}/versions)
|
// (GET /registry/{registry_ref}/artifact/{artifact}/versions)
|
||||||
GetAllArtifactVersions(ctx context.Context, request GetAllArtifactVersionsRequestObject) (GetAllArtifactVersionsResponseObject, error)
|
GetAllArtifactVersions(ctx context.Context, request GetAllArtifactVersionsRequestObject) (GetAllArtifactVersionsResponseObject, error)
|
||||||
|
// List Artifacts for Registry
|
||||||
|
// (GET /registry/{registry_ref}/artifacts)
|
||||||
|
GetAllArtifactsByRegistry(ctx context.Context, request GetAllArtifactsByRegistryRequestObject) (GetAllArtifactsByRegistryResponseObject, error)
|
||||||
// Returns CLI Client Setup Details
|
// Returns CLI Client Setup Details
|
||||||
// (GET /registry/{registry_ref}/client-setup-details)
|
// (GET /registry/{registry_ref}/client-setup-details)
|
||||||
GetClientSetupDetails(ctx context.Context, request GetClientSetupDetailsRequestObject) (GetClientSetupDetailsResponseObject, error)
|
GetClientSetupDetails(ctx context.Context, request GetClientSetupDetailsRequestObject) (GetClientSetupDetailsResponseObject, error)
|
||||||
|
@ -3571,6 +3730,33 @@ func (sh *strictHandler) GetAllArtifactVersions(w http.ResponseWriter, r *http.R
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAllArtifactsByRegistry operation middleware
|
||||||
|
func (sh *strictHandler) GetAllArtifactsByRegistry(w http.ResponseWriter, r *http.Request, registryRef RegistryRefPathParam, params GetAllArtifactsByRegistryParams) {
|
||||||
|
var request GetAllArtifactsByRegistryRequestObject
|
||||||
|
|
||||||
|
request.RegistryRef = registryRef
|
||||||
|
request.Params = params
|
||||||
|
|
||||||
|
handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) {
|
||||||
|
return sh.ssi.GetAllArtifactsByRegistry(ctx, request.(GetAllArtifactsByRegistryRequestObject))
|
||||||
|
}
|
||||||
|
for _, middleware := range sh.middlewares {
|
||||||
|
handler = middleware(handler, "GetAllArtifactsByRegistry")
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := handler(r.Context(), w, r, request)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
sh.options.ResponseErrorHandlerFunc(w, r, err)
|
||||||
|
} else if validResponse, ok := response.(GetAllArtifactsByRegistryResponseObject); ok {
|
||||||
|
if err := validResponse.VisitGetAllArtifactsByRegistryResponse(w); err != nil {
|
||||||
|
sh.options.ResponseErrorHandlerFunc(w, r, err)
|
||||||
|
}
|
||||||
|
} else if response != nil {
|
||||||
|
sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetClientSetupDetails operation middleware
|
// GetClientSetupDetails operation middleware
|
||||||
func (sh *strictHandler) GetClientSetupDetails(w http.ResponseWriter, r *http.Request, registryRef RegistryRefPathParam, params GetClientSetupDetailsParams) {
|
func (sh *strictHandler) GetClientSetupDetails(w http.ResponseWriter, r *http.Request, registryRef RegistryRefPathParam, params GetClientSetupDetailsParams) {
|
||||||
var request GetClientSetupDetailsRequestObject
|
var request GetClientSetupDetailsRequestObject
|
||||||
|
@ -3682,69 +3868,70 @@ func (sh *strictHandler) GetAllRegistries(w http.ResponseWriter, r *http.Request
|
||||||
// Base64 encoded, gzipped, json marshaled Swagger object
|
// Base64 encoded, gzipped, json marshaled Swagger object
|
||||||
var swaggerSpec = []string{
|
var swaggerSpec = []string{
|
||||||
|
|
||||||
"H4sIAAAAAAAC/+xdXW/buNL+KwLf91KN092ec+G7bJK2wUnaHLspUCyKgpHGtrb6WpJKmg383w9IihIt",
|
"H4sIAAAAAAAC/+xd33LUuNJ/FZe+79Jkwi7nXOQuJAFSJ4GchFBFbVGUYvfMePG/leSELDXvfkqSZcu2",
|
||||||
"kRLl+Kuo7xJryBnOPDMckUPqGQVZkmcppIyi8TPKMcEJMCDiv2t8DzG95b/xf0OgAYlyFmUpGsuHJ8hH",
|
"ZMuTycyw+Iowbqlb6l+32lKr/QMFWZJnKaSMoqMfKMcEJ8CAiP9d4DuI6RX/jf83BBqQKGdRlqIj+fAA",
|
||||||
"Ef/v7wLIE/JRihNAYxTzh8hHNFhAgnnjiEEiOmVPOaegjETpHC199QMmBD+h5dJHE5hHlJGnqxBSFs0i",
|
"+Sji//urAPKIfJTiBNARivlD5CMaLCHBvHHEIBGdssecU1BGonSBVr76AROCH9Fq5aNrWESUkcfzEFIW",
|
||||||
"IBYRFKFXU1rkITD/FulEtWANeTT+n55y6OPMaSw8mXxUc4K0SND4T/T5avLp7uwa+ejudvppcnl2g776",
|
"zSMgFhEUoVdTWuQhsPga6URPEuzjYw5DInEaizBMPqpFgLRI0NEf6NP59cfb4wvko9urm4/XZ8eX6Ivf",
|
||||||
"BjkwYdEMB8wiw5l4zCzcVeOesdY82MLC5wNOwMtmniKtbJ5jtjAyJPB3EREI0ZiRAroFCKM5UNsQL8RD",
|
"lmvlI0xYNMcBs8hwLB4zC3fVuCFBHw+2tPB5jxPwsrmnSCsw5JgtjQwJ/FVEBEJ0xEgB/QKE0QKobYin",
|
||||||
"G8hk04H8ZiRLLjCzGZY/OvHeZiTBzHvl3dyMLi5GX758+WKRgXfXo+IcB9/xHFzQdCtJu1BV9vatha4B",
|
"4qENfbLpSH5zkiWnmNkUyx8deG8ykmDmvfAuL2enp7PPnz9/tsjAuxuY4hgzoOwTECpYdA2MP/bK596b",
|
||||||
"DpbjOXwoknsgbVnOC0IgZR6n8VJJZJNkvipBCDNcxAyNX/toJjSIxihK2b/foEqIKGUwB1KJMY3+AQPk",
|
"KGZA7AbHib/el50ZGN9lWQw4FZxzHHzDC3DB8ZUk7cNz2dvXDq5HmFaOF/C+SO6AdGU5KQiBlHmcxksl",
|
||||||
"BF8OOjEqLwfilexMklDeiVGS307dRCGlBSYw6/CEuzT6uwBPEXvcASzeoGi+EZgNRCgFTILFJyAGCeQz",
|
"kU2SRVOCEOa4iBk6eumjudAdOkJRyv79ClVCRCmDBZBKjJvobzCAXfDlcBej8nIgXsnOJAnlnRgl+e3Q",
|
||||||
"jz+0eYUk+cZ4+x5GGWFvI4hDA5/qkYVJRti3WUnQx+MjCU1Iqx918MhKgk4eOQ7AyXKCsstsgmAdm5Ui",
|
"TRRSauAa5j02eJtGfxXgKWKPm57FDhXNVwLzkbZBAZNg+RGIQQL5zOMPbeCUJF8Zbz/AKCPsTQRxaOBT",
|
||||||
"/JcPwVUG27g1Gbp4smyDcYxlPdwegNAoSy3sPsunls4fqqdOHHpnorNyqvFKthZj1mzdTbmUxEDZH1kY",
|
"PbIwyQj7Oi8Jhnh8IKEJafWjHh5ZSdDLI8cBOGlOUPapTRCso7NShP/yIbjKYBu3JkMfT5Zt0IOybIBb",
|
||||||
"gQioip3IeCbyKf89yFIGqfgT53kcBZjLOfqLcmGfNSb/z405Rv83qpOtkXxKR8bOhRyrYy+l8ljmFXmI",
|
"6ews7D5VrtDUeY+jNHEYXAOPy0VOuWuLMmu27qpcSWKg7HUWRiAcqmIngrBr+ZT/HmQpg1T8ifM8jgLM",
|
||||||
"GVTzsSeSLYq0zGXTQjb77ZBvlhEvICAETEMlqwqHZbCleZZSo3Llk0GC5yTLgbDSWCFmzjqfFkmCuVA+",
|
"5Zz9SeUCUzP5f67MI/R/szr+m8mndGbsXMjRHHsplccyr8hDzKCKBDwR/1GkxUybFrLdb49884x4AQEh",
|
||||||
"ogyzgvY1nEoqhRIJqT9VY18yr3O57P4vCCzakgPl5pwDa9jSU4+5ZJWwDDO6awVxnoekHt4VNatH2vKI",
|
"YBoqWZU7LJ0tzbOUGidXPhkleE6yHAgrlRVi5jznN0WSYC6UjyjDrKBDDW8klUKJhNQfqrEvmddRZHb3",
|
||||||
"IFqLpKQsw+R+VLTK/AA0FWbBdyC1wsppQlfcHzjcdAi9JCQjJvH+wKFHVFz10XkcQcqmwIr8AhiO4l35",
|
"JwSW2ZID5epcAGvp0lOPuWSVsAwzuu0J4jz3aXp4V9Q8PVKXE4JoLZKSsnSTu5miJvM9mKkwC74BqSes",
|
||||||
"fJvxPm0lphEhkUe5SF5Yy3QhDKjwJYXdkZJMrA8Q0mEl2KrANziNZkDZXrSlmB+gvhJNNCn0NX4CQneq",
|
"XCb0iXuNw0270DNCMmIS7zUOPaL8qo9O4ghSdgOsyE+B4Sjels13Ge9SV2IZERJ5lIvkhbVMp0KBCl9S",
|
||||||
"J8nyIHMSLlitG2XI3aqn4nqYqnkPcbKXkNRmfAAKWkCcmMKRLuyOg5GJ9cFpSg9EVykDkuJ4CuQBiMwf",
|
"2C1Nkon1HkI6rARrCnyJ02gOlO1kthTzPZyvRBNNCn2BH4HQrc6TZLmXMQkXrJ4bpcjtTk/FdT+n5h3E",
|
||||||
"tp6NKKYeFVw9kIQ+uo4o28e7WovvvrOSOKLM9O6tC7oH3RyUWpr6KN8B9qAWtTB1CNopXzSovpmiNFWv",
|
"yU5cUpfxHkzQEuLE5I50YbfsjEys926mdEd0njIgKY5vgNwDkfHDs0cjiqlHBVcPJKGPLiLKdvGu1uG7",
|
||||||
"sOxMRZNqTeYAdEM0YT5k7G1WpOH2o92nBXg0hyCaRcDfwmhWkAC8R0y9NGPeTEixsq62E+scimXkOp4v",
|
"66gkjigzvXvrgu5gbvZqWtrzUb4D7GBa1MbUPsxO+aJB9WMcNVNqh2UHCGqz3ksk1TtQW5+XvZgPognz",
|
||||||
"Ux7TYp6PpkUQAKUvUMgmBugyslJSb6ItHt2luGALSBkXFnYAuCbDSoaMRP/sToCSm9i5kC3EUmyapU9J",
|
"PmNvsiINn381+LgEj+YQRPMI+FsqzQoSgPeAqZdmzJsLKRr7jlvRzr5oRu5z+jIkNG12+uimCAKg9AkT",
|
||||||
"JoyhrR41l71XzVdOgMM2IHUTlh20TVhLcAMMK78x7YEHzKtI/Ka/ZY9pnOGQnmeF1GrvfqC/xqB4G8pu",
|
"sokBuoyslNS71jbXblNcsCWkjAsLWwBcm2ElQ0aiv7cnQMlNnOzIFmKrOs3SxyQTytB219rHAk31lQHC",
|
||||||
"slCEEmODGDOg1YRkopD7FoYH2hZyn+VvNVJtY1OrkDAxUGS3mC3MuzO6yYScxr6bo2z03GVkudbcsvB7",
|
"uANaXYVlB10V1hJcAsPKbkzZCQHzKhK/bW/ZQxpnOKQnWSFndfC81F9jULwNZZdZKFyJsYE8tTE80A7Q",
|
||||||
"TFLutpWlJZ3NzEOsrNqoPWiHJixjOJ6yjGhb1w7NinwQn2WXmsr1AQdFlZRNVcmoHp4xIxLW8pcowXP4",
|
"h/R6pZHylkUcn2RJglMzS9JJfuklu8JsaSS4r9MKuudaujLFGI182xkKLa596pe79B3dv8Mk5QZdYUDS",
|
||||||
"YEPvOt6UlJ5kkXJdh2gAuZZ7tcsunJbQdohJJWVHbBIFLJWi7QgdZgxqizT3WRYDTp2i1RZiUV7E8XmW",
|
"2QAwRv+qjTq9d2jCMobjG5YR7dDfoVmRj+Kz6pumcmfFYaJKyvZUSX8fHjMjCNaypCjBC3hvQ/46dpaU",
|
||||||
"JDgNO4PQy2KVL8tB9hPEGpsd7W15ufDYgojNV7sdK6LX/ZZe314P1qmqw43qTfdehyrYQknV8KA6R+LK",
|
"NmaRcl1jagG5lrvZZR9OS2g7eCuVhmP3WiLpqJpoO0LHKYN2soTa+Tz/bD9GSzvbgRNrHRN1Exrklm0H",
|
||||||
"KUudVPncHQVyiyl9zEiIfC1raRfR+eicq6HIb7M4Cgz2KB978rlIeltxtMzGuUWa5oEfeUTgAj9Rs//2",
|
"IjZb7TesiF4Ma3p9fTmvB7oZ1ekKgwZVsKWSqmVBdfTEJ6dMElMpj7cUyBWm9CEjIfK1eKab+OijEz4N",
|
||||||
"OdEtgVn0Y1hkVGUSg5uaZhXDpo9BR2IbRhB5iqqpiQRH6XvAocVnKQTdTzmv1SnCca9qKtv2ppmagLo4",
|
"RX6VxVFg0Ef52JPPRTjc8aNlnM410lYPfM8jAqf4kZrtd8iIrgjMo+/jPKNKMBnd1LSqGI7LDHMkDrAE",
|
||||||
"GvOv3fpRjLr1o6ia+ll0jJ5Bvt7QGeSDzSwa9YyBk7SyBhm4B07jHcNmDgGpIXc1h/cN0BxWDETN2MKT",
|
"kaeo2jOR4Ch9Bzi02CyFoP8p59VcIhxP+W5k28EAVBNQF0dj/qV/fhSj/vlRVO35WfaMnkG+3tAZ5KPV",
|
||||||
"yyhAPnoHKRDM4FP2HVJjZDFuBfYG/JJu7zmZ09SxpSTMPRMYOsX7qCCx8fcXzmUrAkku/ROcZfuzFyMV",
|
"LBoNjIGTdKIG6bhHLuM9w2YODqkld7WGDw3Q7FYMRG3fwoPLKEA+egspEMzgY/YNUqNnMR6iDjr8km7n",
|
||||||
"ZTvI1l10j6KitMslthsvU+b0LiH3Jm1B4QVZmOqhR07a+9ojyayJVFkrbn49eSrPOzgF4Jb2DGEvo2ck",
|
"MZnT0vFMQZh7JDB2ifdRQeKnvduY17KGQJLL8AJnOTgexEhF2XWydRf9o6go7XKJg9qzlDm9S8hTXZtT",
|
||||||
"cHiRLqWyD15BwTo5O1uqO8DYtWMdiquFqyr9WElWdtk/6o7x1iRbSJkTnf8AUDStZc/e1otDJo1V+4JN",
|
"eEIUpnoYkJMOvvZIMmsgVeb3m19PHsvLK04OuDN7BreX0WMSLIdHX0plH7yCgnVxdtZUv4Oxz451KK4a",
|
||||||
"zwwNM+CnBXgLxnK5recJIh/BD5zkMaDxm9M3hokutGHvLAwj/ieOVXWOh++zgnlsAeXGoUHgBCjFc4tw",
|
"rm5WxEqyssvhUfeMtyZ5hpA50fmPAEVbW/bobT0/ZJqx6kS1bZmhYQX8uARvyVguD0Q9QeQj+I6TPAZ0",
|
||||||
"BDDlGb/4sywoxVEMIfJ7A4kYi+rdpCrDTnkbXxAnvbN1tXljQtg2pvLjZL31ydpYHtADj21P1Cv7u233",
|
"9OrwlWGhC23YOw7DiP+JY5XX5OG7rGAeW0J55GoQOAFK8cIiHAFMecQv/ixTcXEUQ4j8QUcixqJ6N02V",
|
||||||
"kztnWj08tSKVujRHvluway3LG+Ic76iCeNvPxTpqebyoOuajhyKXI0TyBNEALpx8lcvpqTOfqzSEH2Y+",
|
"Icegiy+Ik8HVujrWMSHsOZbyabF+9sXamFgxAI/nXqgbJ+Nd85NnatpNAmpFKnVpjnw3Z9fZsDf4Od5R",
|
||||||
"gXZmSu/evXPzMSjed2o/CqUry3imqYZZjYM+nMkaiw60GDIOsbzbmod3goB11paPqHFETcfWnKnGwiHE",
|
"BfGunYt91PJiVnVBSndFLpev5N2rEVw4eZPL4aEzn/M0hO9mPoF220zv3r1z8wUy3ndqv0SmT5bxNlgN",
|
||||||
"VDUQ1kj1WREM7G1Q5Gqu4R8D2M8fwKqiiSGxq2Nt9wiA/QKgyuPUG/xaNnUKCwo69njQQKMmmQmOt6u5",
|
"sxoHQziT2Sk9aDFEHGJ7t7MObwUB6+wtT6hxRE3PoZ0pO8XBxVTZI1ZP9UkRjOxtlOdq7+FPDuznd2BV",
|
||||||
"drPCZgaEeizzypRUW368+Hj+n8sJ8tHN2efLD8hH7y4/XE6uzpGP3l9e3xjXIe24t2mmvTqC4zh7hPAW",
|
"OsUY39WztzsBYLcAqOI49Qa/lk6d3IKCjt0ftNCoSTYExz2M39qiTW7wH+IGr5rveO2crzkQ6rHMK1+F",
|
||||||
"MwYkHTab3sf83Xe9tkFzd8ZxCVxvZeo2S2fR3NXq55K6/9VNV65paaF72/KAdsFtb17mHdKVezPK9XK3",
|
"tG3v0w8n/zm7Rj66PP509h756O3Z+7Pr8xPko3dnF5fG/W+7v7VZZHdXDsdx9gDhFWYMSDouiruLs+Db",
|
||||||
"d6qGhlsAnRb38pEqwgvEDtzniLACx15GvLucMgI40aEbRryPJEoxkysfCc5zLv/4ub5Qw6IG1V8pkV/d",
|
"mm2D9qmg49GL3srUbZbOo4Wr+Z1I6uEtA31yTVta/cfle5R9YXvjN5/MN4rvlOc0bu/yVgf3U2eW2bI6",
|
||||||
"xWGhL0WpIVL6zdMH7Y6PpY+yFD7O0PjPbiM0e+umbsi6/Nr0WpcdFP0uk5aBWZ/h7EUO1thir3aopvBB",
|
"njVnYzM5Gc+ZetEyp46Kb4o7+UjlAAfimP9TRFiBYy8j3m1OGQGc6H4qjHgfSZRiJrdXE5znfCxHP+pK",
|
||||||
"NTo9TrfeesrmPbW3nuIF6y+2dRW1zjG1ra8MB4hjZCijgD6mRpzg3XQhy1pPeJyK9jXZvAShPHmccPre",
|
"S5YpVP2VEvlVkSYLfSlK7Q9KBD6+14o/rXyUpfBhjo7+6Fdgu7d+6pasqy9t/Lsc0+pFrjrKZkNWardO",
|
||||||
"FTvDZOI6jfTlU7whzwir+1miE/Ae6smkKAOqaQ6xxPVBVzb5aFrVMjdP1YSi4Jd60WxlcfsRU4/KwuRZ",
|
"60JiN9dqgRyVCDjgYdfbtN28Wx50EU/Y5LVt3ir7u7Ft4o4HiOMyULp8fUytRYF304csazrzFHfsKrJ4",
|
||||||
"IYRMM6bvR9+dn19Op8hHb8+uru8ml8hHl5PJx4mRfWPGaG+Ai98LIstqjMUuqotbkv0wvRXhQgYjtwlv",
|
"CkJ5bH7N6QePBQyRg2vMMBQ884Y8iq/KZ0UH4N3Xi0lROlTTGmLx66Nq+fnoprpK0b70GIr7BtSL5o0T",
|
||||||
"pVSnb76rC3mWX5e+4OSCxaqMSNw2U5AA9Lu25MbMorhHPjovKMsSo+acol4lUQukPvrxagVSrx5wXHCC",
|
"tAdMPSrvRcwLIWSaMT3p5fbk5OzmBvnozfH5xe31GfLR2fX1h2sj+9aK0c2yEb8XRObuGTPqVBdXJPtu",
|
||||||
"Ci7cPLo22nXwEBBgPbVvkmia4wCuQnPNkUZiXZEvKBDLFlljzBUld9NSdG6d1VRmANJUcufyCl40wPii",
|
"2nrBhXRGbgteIx9waL2rswVXX1a+4OSCxSpXURQDK0gAehFGefq7LO6Qj04KyrLEOHNOXq+SqANSH31/",
|
||||||
"Yif+U5TOMlVjX66pl3fF2POZV14IDxBzuWg5sY3RgrGcjkejx8fHk4VsehJlQoyIxd0dnt1eadsVY/T6",
|
"0YDUi3scF5ygggtXjz4b3Ws4EBBgA8GcJLrJcQDnoTmxUSOxHvsVFIjlHL415oqSm2kpOtdOM5QZgTQV",
|
||||||
"5PTkVCSROaQ4j9AY/S5+klO/GO2IaG95eWbamjgvL12pGJ0g0aW0AUdLSaK/BWrX/VlcuSYZGS454klp",
|
"3Lns8xUtMD4po5L/FKXzTF3xKTd+ylJe9njmhRfCPcRcLloubEdoyVhOj2azh4eHg6VsehBlQoyIxf0d",
|
||||||
"fV/Ok81XV67Uad8m07gS5rfT1/aOSrpR60jO0kdvTk/7G2p3O4gmDrwMpzbenP7u2k4dtvDRv1zkMx37",
|
"Hl+da2eiR+jlweHBoQgic0hxHqEj9Lv4SS79YrQzor3S55np/POkrIlVMTpAokupA46WkkR/5dcKxFpM",
|
||||||
"FAc1VAWEsrRuZ4bn3IRIc6avvFGFm9GzfiPZUsInBmaYUC/E7xqQvEjuyOIg4Em2cGf+/zx6gNT7Dk8t",
|
"uSaZGWrQ8aC0Lmf2aLPVRsWzbrGvVsWu3w5f2jsq6WadG4ErH706PBxuqJXeEU0ceBkujb06/N21nbrr",
|
||||||
"oMku1gaa8TY2CbUVmDhoU51P+gnQ8eb0TX+j6mzc5uDUsrcNTz6aAzPdXsgKktIaLmX5w3DYvAN2CJj5",
|
"5aN/uchnupUv7ompNCulaV3PDC+4CpFmTF94owo3sx96wciVhE8MzLCgnorfNSB5kUz7wEHAg2xhzvz/",
|
||||||
"GUPLvsBjM74dQ3lhwNCdOFlIXxR0xOv30zYAtPH57QjCjYKwjZ41psSRWp8a1S/Pxnh3HVHW3P1u51qt",
|
"i+geUu8bPHaAJrtYG2jGYpkSag2YOMymuh75E6Dj1eGr4UbV1dzNwamjbxuefLQAZiouywqS0houZY7V",
|
||||||
"PXW6IUT6ve20610dqcUKkgOtdjXpeqHVfovEEd5WeJsApwG8rv5xxDdVJy2N8H4HrHHY8sQ0Ua8c23yb",
|
"eNi8BbYPmPkZXcuuwGNTvh1DeWHA0K242Eyf5HTE6/fjcwBo4+vbBMKNgrCLnjWWxJnan5rVL89Gf3cR",
|
||||||
"kQ3H3X4srl7k7NBAvy91PfSar2I8IteK3DaWXoLbZ/XXUgvRHelEf5CWdHsK0+2r19fKNswXyL4E3sfA",
|
"UdZOsenGWp3EHbohRPqD7bTq247UYgfJgVarHL2ea7UX+ZngbYW3CXAawOsjakd8U3Wd2wjvt8BaN7oP",
|
||||||
"PCjv2GRo1iC++Si9T2Qf4/kxnneBvT7e4wB3SdwN+Poc0F6D+dqQbNzVewSlIygru28CluUi/ei5/GM5",
|
"TAt14274m4xs2O8OY7FZ4d+hgV7Oej30mivlTsi1IreLpafg9of6a6W56J5wYthJS7oduenuNznWijbM",
|
||||||
"kjeHjrQzMVa8mo9+UhNqTWdJDzxct27rd2ijf+tlPffovPr36CMda7wcnffgWc8jK3dpEGzUa+qTls5O",
|
"9b2fAu/JMY+KOzbpmjWIb95L7xLZkz+f/Hkf2Os7hA5wl8T9gK8vG+7Uma8NyVYp9QmUjqCs9L4JWJab",
|
||||||
"Ux1n7PGZ+tjj0WVMLtO4z/joKsNdpYLYLlxFP0Hm7CzaebQed9FPrh0dpmuOad1SfHSd4a6jwW2XzkPX",
|
"9LMf5R+rmSzsPNMu3lnxar5fTk2oNV1Y33N33fmYikMb/SNg65lHb2X2yUZ69ng5Ou/AsxY9UObSItio",
|
||||||
"8h7q7j4/3YTzAodo3/p+9IT1PWHr88gC4sTpNcV04t3oAu3j878G/Dtu9j96QL8HWG5UUOBfebxB6Dsl",
|
"1dTXuZ2NprozPWAz9d3qyWRMJtMqNz+ZynhTqSC2DVPRr6k6G4t26XXAXPTrsZPB9K0xnSLyk+mMNx0N",
|
||||||
"UNbj/J3g/1mTpxej/5gLvRj/hkxoCx4waDm1cQdl57Jq437LX8EBej6idnQBt4XZ9k2nG1yg7S7WoR6O",
|
"bts0HrqW9VB38/npFpwnGET3oxyTJaxvCc++jiwhTpxeU0xlNYwm0K3R8WvAv+fDK5MFDFuApWyLAn/j",
|
||||||
"Y1E81pTGsmkWx2fN2wgOGulbLPipvtHrSCw/GrzrUqLm90WOTulYTKThe6g7yq/zvRJf53vV96KhykTP",
|
"8Qah7xRAWWuG9IL/Zw2enoz+KRZ6Mv4NkdAzWMCo7dRWodvebdVWEd1fwQAGvnE5mYDbxmy3nPIGN2j7",
|
||||||
"r688032y3j2mEHpZqg7Fq4s5W85puLF29745dAZaf/bp+BzkEeS9Vck2uHWVh4pTLnT0XH2Ge9uVc+Ko",
|
"k3Woh+NYJI+1pbEcmsXxcbvkyV4j/RkTfqpPqDsSy2+6bzuVqP35p8koHZOJNHyva45jbY+KVE4tXajP",
|
||||||
"1tqHcY41Fr9yjUUHWIfmRL25EN0NRmUR1YDcpzo87Nxm0rrz/5hquaZaRxcemGPp7itCfYfvrt545OC8",
|
"/ujrx60nFslT/Mn4hozP+kWxyfrcrK9jCaNTVuUHjF+IDxi/GHrZV6naJxfnnqlwvHeHKYRelqoqJKoC",
|
||||||
"9exl897V47Xbd9+X+OOgRr+sJx5PBg30xBUXaLuiODPPO5BO0XxxqY41yJPfI5xHo4fXwn5lX627yW6v",
|
"d8dADaXpt78+jo0C148Ae76YPUF98GaADW59eBc3zejsh/h3G9mr4rrk2hfipjynXznPqQeso2OjofcR",
|
||||||
"xH0U6vN58rN5/sqHDqUw5eFzTUAOInNvc2D+6lc2tR7qYNPZQXVPczZrfgNb66y1H+Xc58rHbLUeG0uc",
|
"uh2MXnfKv/wy4dAwdbMMztNfXiaDHBs3acYoHHePJTYLFTqYYr0W2WyxeWH9+Y1RK48x2oBHNfrl32Qm",
|
||||||
"y6/L/wUAAP//B0v/XHmOAAA=",
|
"S3S0xIYJdE1RVKHgHUijaL+GVO87spbCDOfR7P6l0F/ZV6cW3NW5qPCivocrv4PrN75cLIUpyzloAnIQ",
|
||||||
|
"mXtbAPObH4PWeqidTW8H1ecVsrknsw9MnXVOeJ37bHy9X+uxdWiw+rL6XwAAAP//+v5kDP2XAAA=",
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSwagger returns the content of the embedded swagger specification file
|
// GetSwagger returns the content of the embedded swagger specification file
|
||||||
|
|
|
@ -75,13 +75,14 @@ type ArtifactMetadata struct {
|
||||||
DownloadsCount *int64 `json:"downloadsCount,omitempty"`
|
DownloadsCount *int64 `json:"downloadsCount,omitempty"`
|
||||||
Labels *[]string `json:"labels,omitempty"`
|
Labels *[]string `json:"labels,omitempty"`
|
||||||
LastModified *string `json:"lastModified,omitempty"`
|
LastModified *string `json:"lastModified,omitempty"`
|
||||||
LatestVersion string `json:"latestVersion"`
|
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
||||||
// PackageType refers to package
|
// PackageType refers to package
|
||||||
PackageType *PackageType `json:"packageType,omitempty"`
|
PackageType *PackageType `json:"packageType,omitempty"`
|
||||||
|
PullCommand *string `json:"pullCommand,omitempty"`
|
||||||
RegistryIdentifier string `json:"registryIdentifier"`
|
RegistryIdentifier string `json:"registryIdentifier"`
|
||||||
RegistryPath string `json:"registryPath"`
|
RegistryPath string `json:"registryPath"`
|
||||||
|
Version *string `json:"version,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ArtifactStats Harness Artifact Stats
|
// ArtifactStats Harness Artifact Stats
|
||||||
|
@ -321,6 +322,24 @@ type ListRegistry struct {
|
||||||
Registries []RegistryMetadata `json:"registries"`
|
Registries []RegistryMetadata `json:"registries"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListRegistryArtifact A list of Artifacts
|
||||||
|
type ListRegistryArtifact struct {
|
||||||
|
// Artifacts A list of Artifact
|
||||||
|
Artifacts []RegistryArtifactMetadata `json:"artifacts"`
|
||||||
|
|
||||||
|
// ItemCount The total number of items
|
||||||
|
ItemCount *int64 `json:"itemCount,omitempty"`
|
||||||
|
|
||||||
|
// PageCount The total number of pages
|
||||||
|
PageCount *int64 `json:"pageCount,omitempty"`
|
||||||
|
|
||||||
|
// PageIndex The current page
|
||||||
|
PageIndex *int64 `json:"pageIndex,omitempty"`
|
||||||
|
|
||||||
|
// PageSize The number of items per page
|
||||||
|
PageSize *int `json:"pageSize,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// PackageType refers to package
|
// PackageType refers to package
|
||||||
type PackageType string
|
type PackageType string
|
||||||
|
|
||||||
|
@ -343,6 +362,20 @@ type Registry struct {
|
||||||
Url string `json:"url"`
|
Url string `json:"url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegistryArtifactMetadata Artifact Metadata
|
||||||
|
type RegistryArtifactMetadata struct {
|
||||||
|
DownloadsCount *int64 `json:"downloadsCount,omitempty"`
|
||||||
|
Labels *[]string `json:"labels,omitempty"`
|
||||||
|
LastModified *string `json:"lastModified,omitempty"`
|
||||||
|
LatestVersion string `json:"latestVersion"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// PackageType refers to package
|
||||||
|
PackageType *PackageType `json:"packageType,omitempty"`
|
||||||
|
RegistryIdentifier string `json:"registryIdentifier"`
|
||||||
|
RegistryPath string `json:"registryPath"`
|
||||||
|
}
|
||||||
|
|
||||||
// RegistryConfig SubConfig specific for Virtual or Upstream Registry
|
// RegistryConfig SubConfig specific for Virtual or Upstream Registry
|
||||||
type RegistryConfig struct {
|
type RegistryConfig struct {
|
||||||
// Type refers to type of registry i.e virtual or upstream
|
// Type refers to type of registry i.e virtual or upstream
|
||||||
|
@ -427,7 +460,7 @@ type VirtualConfig struct {
|
||||||
type LabelsParam []string
|
type LabelsParam []string
|
||||||
|
|
||||||
// RegistryIdentifierParam defines model for RegistryIdentifierParam.
|
// RegistryIdentifierParam defines model for RegistryIdentifierParam.
|
||||||
type RegistryIdentifierParam string
|
type RegistryIdentifierParam []string
|
||||||
|
|
||||||
// RegistryTypeParam defines model for RegistryTypeParam.
|
// RegistryTypeParam defines model for RegistryTypeParam.
|
||||||
type RegistryTypeParam string
|
type RegistryTypeParam string
|
||||||
|
@ -444,6 +477,9 @@ type DigestParam string
|
||||||
// FromDateParam defines model for fromDateParam.
|
// FromDateParam defines model for fromDateParam.
|
||||||
type FromDateParam string
|
type FromDateParam string
|
||||||
|
|
||||||
|
// LatestVersion defines model for latestVersion.
|
||||||
|
type LatestVersion bool
|
||||||
|
|
||||||
// PackageTypeParam defines model for packageTypeParam.
|
// PackageTypeParam defines model for packageTypeParam.
|
||||||
type PackageTypeParam []string
|
type PackageTypeParam []string
|
||||||
|
|
||||||
|
@ -612,6 +648,15 @@ type ListArtifactVersionResponse struct {
|
||||||
Status Status `json:"status"`
|
Status Status `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListRegistryArtifactResponse defines model for ListRegistryArtifactResponse.
|
||||||
|
type ListRegistryArtifactResponse struct {
|
||||||
|
// Data A list of Artifacts
|
||||||
|
Data ListRegistryArtifact `json:"data"`
|
||||||
|
|
||||||
|
// Status Indicates if the request was successful or not
|
||||||
|
Status Status `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
// ListRegistryResponse defines model for ListRegistryResponse.
|
// ListRegistryResponse defines model for ListRegistryResponse.
|
||||||
type ListRegistryResponse struct {
|
type ListRegistryResponse struct {
|
||||||
// Data A list of Harness Artifact Registries
|
// Data A list of Harness Artifact Registries
|
||||||
|
@ -717,6 +762,27 @@ type GetAllArtifactVersionsParams struct {
|
||||||
SearchTerm *SearchTerm `form:"search_term,omitempty" json:"search_term,omitempty"`
|
SearchTerm *SearchTerm `form:"search_term,omitempty" json:"search_term,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAllArtifactsByRegistryParams defines parameters for GetAllArtifactsByRegistry.
|
||||||
|
type GetAllArtifactsByRegistryParams struct {
|
||||||
|
// Label Label.
|
||||||
|
Label *LabelsParam `form:"label,omitempty" json:"label,omitempty"`
|
||||||
|
|
||||||
|
// Page Current page number
|
||||||
|
Page *PageNumber `form:"page,omitempty" json:"page,omitempty"`
|
||||||
|
|
||||||
|
// Size Number of items per page
|
||||||
|
Size *PageSize `form:"size,omitempty" json:"size,omitempty"`
|
||||||
|
|
||||||
|
// SortOrder sortOrder
|
||||||
|
SortOrder *SortOrder `form:"sort_order,omitempty" json:"sort_order,omitempty"`
|
||||||
|
|
||||||
|
// SortField sortField
|
||||||
|
SortField *SortField `form:"sort_field,omitempty" json:"sort_field,omitempty"`
|
||||||
|
|
||||||
|
// SearchTerm search Term.
|
||||||
|
SearchTerm *SearchTerm `form:"search_term,omitempty" json:"search_term,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// GetClientSetupDetailsParams defines parameters for GetClientSetupDetails.
|
// GetClientSetupDetailsParams defines parameters for GetClientSetupDetails.
|
||||||
type GetClientSetupDetailsParams struct {
|
type GetClientSetupDetailsParams struct {
|
||||||
// Artifact Artifat
|
// Artifact Artifat
|
||||||
|
@ -737,12 +803,6 @@ type GetArtifactStatsForSpaceParams struct {
|
||||||
|
|
||||||
// GetAllArtifactsParams defines parameters for GetAllArtifacts.
|
// GetAllArtifactsParams defines parameters for GetAllArtifacts.
|
||||||
type GetAllArtifactsParams struct {
|
type GetAllArtifactsParams struct {
|
||||||
// Label Label.
|
|
||||||
Label *LabelsParam `form:"label,omitempty" json:"label,omitempty"`
|
|
||||||
|
|
||||||
// PackageType Registry Package Type
|
|
||||||
PackageType *PackageTypeParam `form:"package_type,omitempty" json:"package_type,omitempty"`
|
|
||||||
|
|
||||||
// RegIdentifier Registry Identifier
|
// RegIdentifier Registry Identifier
|
||||||
RegIdentifier *RegistryIdentifierParam `form:"reg_identifier,omitempty" json:"reg_identifier,omitempty"`
|
RegIdentifier *RegistryIdentifierParam `form:"reg_identifier,omitempty" json:"reg_identifier,omitempty"`
|
||||||
|
|
||||||
|
@ -760,6 +820,9 @@ type GetAllArtifactsParams struct {
|
||||||
|
|
||||||
// SearchTerm search Term.
|
// SearchTerm search Term.
|
||||||
SearchTerm *SearchTerm `form:"search_term,omitempty" json:"search_term,omitempty"`
|
SearchTerm *SearchTerm `form:"search_term,omitempty" json:"search_term,omitempty"`
|
||||||
|
|
||||||
|
// LatestVersion Latest Version Filter.
|
||||||
|
LatestVersion *LatestVersion `form:"latest_version,omitempty" json:"latest_version,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAllRegistriesParams defines parameters for GetAllRegistries.
|
// GetAllRegistriesParams defines parameters for GetAllRegistries.
|
||||||
|
|
|
@ -30,6 +30,7 @@ import (
|
||||||
"github.com/harness/gitness/registry/app/pkg/docker"
|
"github.com/harness/gitness/registry/app/pkg/docker"
|
||||||
"github.com/harness/gitness/registry/app/store/database"
|
"github.com/harness/gitness/registry/app/store/database"
|
||||||
"github.com/harness/gitness/registry/config"
|
"github.com/harness/gitness/registry/config"
|
||||||
|
"github.com/harness/gitness/registry/gc"
|
||||||
"github.com/harness/gitness/types"
|
"github.com/harness/gitness/types"
|
||||||
|
|
||||||
"github.com/google/wire"
|
"github.com/google/wire"
|
||||||
|
@ -64,9 +65,11 @@ func BlobStorageProvider(c *types.Config) (storagedriver.StorageDriver, error) {
|
||||||
return d, err
|
return d, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHandlerProvider(controller *docker.Controller, spaceStore corestore.SpaceStore,
|
func NewHandlerProvider(
|
||||||
|
controller *docker.Controller, spaceStore corestore.SpaceStore,
|
||||||
tokenStore corestore.TokenStore, userCtrl *usercontroller.Controller, authenticator authn.Authenticator,
|
tokenStore corestore.TokenStore, userCtrl *usercontroller.Controller, authenticator authn.Authenticator,
|
||||||
urlProvider urlprovider.Provider, authorizer authz.Authorizer) *ocihandler.Handler {
|
urlProvider urlprovider.Provider, authorizer authz.Authorizer,
|
||||||
|
) *ocihandler.Handler {
|
||||||
return ocihandler.NewHandler(controller, spaceStore, tokenStore, userCtrl, authenticator, urlProvider, authorizer)
|
return ocihandler.NewHandler(controller, spaceStore, tokenStore, userCtrl, authenticator, urlProvider, authorizer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,6 +80,7 @@ var WireSet = wire.NewSet(
|
||||||
pkg.WireSet,
|
pkg.WireSet,
|
||||||
docker.WireSet,
|
docker.WireSet,
|
||||||
router.WireSet,
|
router.WireSet,
|
||||||
|
gc.WireSet,
|
||||||
)
|
)
|
||||||
|
|
||||||
func Wire(_ *types.Config) (RegistryApp, error) {
|
func Wire(_ *types.Config) (RegistryApp, error) {
|
||||||
|
|
|
@ -177,8 +177,9 @@ func (d *driver) PutContent(ctx context.Context, subPath string, contents []byte
|
||||||
|
|
||||||
// Reader retrieves an io.ReadCloser for the content stored at "path" with a
|
// Reader retrieves an io.ReadCloser for the content stored at "path" with a
|
||||||
// given byte offset.
|
// given byte offset.
|
||||||
func (d *driver) Reader(_ context.Context, path string, offset int64) (io.ReadCloser, error) {
|
func (d *driver) Reader(ctx context.Context, path string, offset int64) (io.ReadCloser, error) {
|
||||||
file, err := os.OpenFile(d.fullPath(path), os.O_RDONLY, 0o644)
|
file, err := os.OpenFile(d.fullPath(path), os.O_RDONLY, 0o644)
|
||||||
|
log.Ctx(ctx).Info().Msgf("Opening file %s %s", d.fullPath(path), d.rootDirectory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return nil, storagedriver.PathNotFoundError{Path: path}
|
return nil, storagedriver.PathNotFoundError{Path: path}
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
// Copyright 2023 Harness, Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package event
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PackageType int32
|
||||||
|
|
||||||
|
type ArtifactDetails struct {
|
||||||
|
RegistryID string `json:"registry_id,omitempty"`
|
||||||
|
ImagePath string `json:"image_path,omitempty"` // format = image:tag
|
||||||
|
PackageType PackageType `json:"package_type,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PackageType constants using iota.
|
||||||
|
const (
|
||||||
|
PackageTypeDOCKER = iota
|
||||||
|
PackageTypeGENERIC
|
||||||
|
PackageTypeHELM
|
||||||
|
PackageTypeMAVEN
|
||||||
|
)
|
||||||
|
|
||||||
|
var PackageTypeValue = map[string]PackageType{
|
||||||
|
string(artifact.PackageTypeDOCKER): PackageTypeDOCKER,
|
||||||
|
string(artifact.PackageTypeGENERIC): PackageTypeGENERIC,
|
||||||
|
string(artifact.PackageTypeHELM): PackageTypeHELM,
|
||||||
|
string(artifact.PackageTypeMAVEN): PackageTypeMAVEN,
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPackageTypeFromString returns the PackageType constant corresponding to the given string value.
|
||||||
|
func GetPackageTypeFromString(value string) (PackageType, error) {
|
||||||
|
if val, ok := PackageTypeValue[value]; ok {
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("invalid PackageType string value: %s", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Reporter interface {
|
||||||
|
ReportEvent(
|
||||||
|
ctx context.Context, payload interface{}, spacePath string,
|
||||||
|
) // format of spacePath = acctID/orgID/projectID
|
||||||
|
}
|
||||||
|
|
||||||
|
type Noop struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Noop) ReportEvent(_ context.Context, _ interface{}, _ string) {
|
||||||
|
// no implementation
|
||||||
|
}
|
|
@ -55,7 +55,6 @@ func NewApp(
|
||||||
ctx context.Context, sqlDB *sqlx.DB, storageDeleter storagedriver.StorageDeleter,
|
ctx context.Context, sqlDB *sqlx.DB, storageDeleter storagedriver.StorageDeleter,
|
||||||
blobRepo store.BlobRepository, spaceStore corestore.SpaceStore,
|
blobRepo store.BlobRepository, spaceStore corestore.SpaceStore,
|
||||||
cfg *types.Config, storageService *registrystorage.Service,
|
cfg *types.Config, storageService *registrystorage.Service,
|
||||||
mtRepository store.MediaTypesRepository, manifestRepository store.ManifestRepository,
|
|
||||||
gcService gc.Service,
|
gcService gc.Service,
|
||||||
) *App {
|
) *App {
|
||||||
app := &App{
|
app := &App{
|
||||||
|
@ -64,7 +63,7 @@ func NewApp(
|
||||||
storageService: storageService,
|
storageService: storageService,
|
||||||
}
|
}
|
||||||
app.configureSecret(cfg)
|
app.configureSecret(cfg)
|
||||||
gcService.Start(ctx, sqlDB, spaceStore, blobRepo, storageDeleter, cfg, mtRepository, manifestRepository)
|
gcService.Start(ctx, sqlDB, spaceStore, blobRepo, storageDeleter, cfg)
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ func GetRegistryCheckAccess(
|
||||||
for i := range reqPermissions {
|
for i := range reqPermissions {
|
||||||
permissionCheck := types.PermissionCheck{
|
permissionCheck := types.PermissionCheck{
|
||||||
Permission: reqPermissions[i],
|
Permission: reqPermissions[i],
|
||||||
Scope: types.Scope{SpacePath: space.Identifier},
|
Scope: types.Scope{SpacePath: space.Path},
|
||||||
Resource: types.Resource{
|
Resource: types.Resource{
|
||||||
Type: enum.ResourceTypeRegistry,
|
Type: enum.ResourceTypeRegistry,
|
||||||
Identifier: registry.Name,
|
Identifier: registry.Name,
|
||||||
|
|
|
@ -23,6 +23,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
gitnessappstore "github.com/harness/gitness/app/store"
|
||||||
|
"github.com/harness/gitness/registry/app/event"
|
||||||
"github.com/harness/gitness/registry/app/manifest"
|
"github.com/harness/gitness/registry/app/manifest"
|
||||||
"github.com/harness/gitness/registry/app/manifest/manifestlist"
|
"github.com/harness/gitness/registry/app/manifest/manifestlist"
|
||||||
"github.com/harness/gitness/registry/app/manifest/ocischema"
|
"github.com/harness/gitness/registry/app/manifest/ocischema"
|
||||||
|
@ -33,7 +35,8 @@ import (
|
||||||
"github.com/harness/gitness/registry/app/store/database/util"
|
"github.com/harness/gitness/registry/app/store/database/util"
|
||||||
"github.com/harness/gitness/registry/gc"
|
"github.com/harness/gitness/registry/gc"
|
||||||
"github.com/harness/gitness/registry/types"
|
"github.com/harness/gitness/registry/types"
|
||||||
store2 "github.com/harness/gitness/store"
|
gitnessstore "github.com/harness/gitness/store"
|
||||||
|
db "github.com/harness/gitness/store/database"
|
||||||
"github.com/harness/gitness/store/database/dbtx"
|
"github.com/harness/gitness/store/database/dbtx"
|
||||||
|
|
||||||
"github.com/distribution/distribution/v3"
|
"github.com/distribution/distribution/v3"
|
||||||
|
@ -53,8 +56,10 @@ type manifestService struct {
|
||||||
imageDao store.ImageRepository
|
imageDao store.ImageRepository
|
||||||
artifactDao store.ArtifactRepository
|
artifactDao store.ArtifactRepository
|
||||||
manifestRefDao store.ManifestReferenceRepository
|
manifestRefDao store.ManifestReferenceRepository
|
||||||
|
spacePathStore gitnessappstore.SpacePathStore
|
||||||
gcService gc.Service
|
gcService gc.Service
|
||||||
tx dbtx.Transactor
|
tx dbtx.Transactor
|
||||||
|
reporter event.Reporter
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManifestService(
|
func NewManifestService(
|
||||||
|
@ -62,7 +67,7 @@ func NewManifestService(
|
||||||
blobRepo store.BlobRepository, mtRepository store.MediaTypesRepository, tagDao store.TagRepository,
|
blobRepo store.BlobRepository, mtRepository store.MediaTypesRepository, tagDao store.TagRepository,
|
||||||
imageDao store.ImageRepository, artifactDao store.ArtifactRepository,
|
imageDao store.ImageRepository, artifactDao store.ArtifactRepository,
|
||||||
layerDao store.LayerRepository, manifestRefDao store.ManifestReferenceRepository,
|
layerDao store.LayerRepository, manifestRefDao store.ManifestReferenceRepository,
|
||||||
tx dbtx.Transactor, gcService gc.Service,
|
tx dbtx.Transactor, gcService gc.Service, reporter event.Reporter, spacePathStore gitnessappstore.SpacePathStore,
|
||||||
) ManifestService {
|
) ManifestService {
|
||||||
return &manifestService{
|
return &manifestService{
|
||||||
registryDao: registryDao,
|
registryDao: registryDao,
|
||||||
|
@ -76,6 +81,8 @@ func NewManifestService(
|
||||||
manifestRefDao: manifestRefDao,
|
manifestRefDao: manifestRefDao,
|
||||||
gcService: gcService,
|
gcService: gcService,
|
||||||
tx: tx,
|
tx: tx,
|
||||||
|
reporter: reporter,
|
||||||
|
spacePathStore: spacePathStore,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,78 +173,154 @@ func (l *manifestService) dbTagManifest(
|
||||||
tagName, imageName string,
|
tagName, imageName string,
|
||||||
info pkg.RegistryInfo,
|
info pkg.RegistryInfo,
|
||||||
) error {
|
) error {
|
||||||
dbRepo, err := l.registryDao.GetByParentIDAndName(ctx, info.ParentID, info.RegIdentifier)
|
dbRegistry, err := l.registryDao.GetByParentIDAndName(ctx, info.ParentID, info.RegIdentifier)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return formatFailedToTagErr(err)
|
||||||
}
|
}
|
||||||
newDigest, err := types.NewDigest(dgst)
|
newDigest, err := types.NewDigest(dgst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return formatFailedToTagErr(err)
|
||||||
|
}
|
||||||
|
dbManifest, err := l.manifestDao.FindManifestByDigest(ctx, dbRegistry.ID, info.Image, newDigest)
|
||||||
|
if errors.Is(err, gitnessstore.ErrResourceNotFound) {
|
||||||
|
return fmt.Errorf("manifest %s not found in database", dgst)
|
||||||
}
|
}
|
||||||
dbManifest, err := l.manifestDao.FindManifestByDigest(ctx, dbRepo.ID, info.Image, newDigest)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, store2.ErrResourceNotFound) {
|
return formatFailedToTagErr(err)
|
||||||
return fmt.Errorf("manifest %s not found in database", dgst)
|
}
|
||||||
|
|
||||||
|
err = l.tx.WithTx(ctx, func(ctx context.Context) error {
|
||||||
|
// Prevent long running transactions by setting an upper limit of manifestTagGCLockTimeout. If the GC is holding
|
||||||
|
// the lock of a related review record, the processing there should be fast enough to avoid this. Regardless, we
|
||||||
|
// should not let transactions open (and clients waiting) for too long. If this sensible timeout is exceeded, abort
|
||||||
|
// the tag creation and let the client retry. This will bubble up and lead to a 503 Service Unavailable response.
|
||||||
|
// Set timeout for the transaction to prevent long-running operations
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, manifestTagGCLockTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Attempt to find and lock the manifest for GC review
|
||||||
|
if err := l.lockManifestForGC(ctx, dbRegistry.ID, dbManifest.ID); err != nil {
|
||||||
|
return formatFailedToTagErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create or update artifact and tag records
|
||||||
|
if err := l.createOrUpdateArtifactAndTag(ctx, dbRegistry.ID, dbManifest.ID, imageName, tagName, dgst); err != nil {
|
||||||
|
return formatFailedToTagErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return formatFailedToTagErr(err)
|
||||||
|
}
|
||||||
|
spacePath, packageType, err := l.getSpacePathAndPackageType(ctx, dbRegistry)
|
||||||
|
if err == nil {
|
||||||
|
l.reportEventAsync(ctx, info.RegIdentifier, imageName, tagName, packageType, spacePath)
|
||||||
|
} else {
|
||||||
|
log.Ctx(ctx).Err(err).Msg("Failed to find spacePath, not publishing event")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatFailedToTagErr(err error) error {
|
||||||
|
return fmt.Errorf("failed to tag manifest: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locks the manifest for GC review.
|
||||||
|
func (l *manifestService) lockManifestForGC(ctx context.Context, repoID, manifestID int64) error {
|
||||||
|
_, err := l.gcService.ManifestFindAndLockBefore(
|
||||||
|
ctx, repoID, manifestID,
|
||||||
|
time.Now().Add(manifestTagGCReviewWindow),
|
||||||
|
)
|
||||||
|
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||||
|
// Use ProcessSQLErrorf for handling the SQL error abstraction
|
||||||
|
return db.ProcessSQLErrorf(
|
||||||
|
ctx,
|
||||||
|
err,
|
||||||
|
"failed to lock manifest for GC review [repoID: %d, manifestID: %d]", repoID, manifestID,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates or updates artifact and tag records.
|
||||||
|
func (l *manifestService) createOrUpdateArtifactAndTag(
|
||||||
|
ctx context.Context,
|
||||||
|
registryID,
|
||||||
|
manifestID int64,
|
||||||
|
imageName,
|
||||||
|
tagName string,
|
||||||
|
dgst digest.Digest,
|
||||||
|
) error {
|
||||||
|
image := &types.Image{
|
||||||
|
Name: imageName,
|
||||||
|
RegistryID: registryID,
|
||||||
|
Enabled: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := l.imageDao.CreateOrUpdate(ctx, image); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to find and lock a GC manifest task that is related with the manifest that we're about to tag. This
|
digest, err := types.NewDigest(dgst)
|
||||||
// is needed to ensure we lock any related online GC tasks to prevent race conditions around the tag creation. See:
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
artifact := &types.Artifact{
|
||||||
|
ImageID: image.ID,
|
||||||
|
Version: digest.String(),
|
||||||
|
}
|
||||||
|
|
||||||
return l.tx.WithTx(
|
if err := l.artifactDao.CreateOrUpdate(ctx, artifact); err != nil {
|
||||||
ctx, func(ctx context.Context) error {
|
return err
|
||||||
// Prevent long running transactions by setting an upper limit of manifestTagGCLockTimeout. If the GC is holding
|
}
|
||||||
// the lock of a related review record, the processing there should be fast enough to avoid this. Regardless, we
|
|
||||||
// should not let transactions open (and clients waiting) for too long. If this sensible timeout is exceeded, abort
|
|
||||||
// the tag creation and let the client retry. This will bubble up and lead to a 503 Service Unavailable response.
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, manifestTagGCLockTimeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if _, err := l.gcService.ManifestFindAndLockBefore(
|
tag := &types.Tag{
|
||||||
ctx, dbRepo.ID, dbManifest.ID,
|
Name: tagName,
|
||||||
time.Now().Add(manifestTagGCReviewWindow),
|
ImageName: imageName,
|
||||||
); err != nil && !errors.Is(err, sql.ErrNoRows) {
|
RegistryID: registryID,
|
||||||
return err
|
ManifestID: manifestID,
|
||||||
}
|
}
|
||||||
|
|
||||||
image := &types.Image{
|
return l.tagDao.CreateOrUpdate(ctx, tag)
|
||||||
Name: imageName,
|
}
|
||||||
RegistryID: dbRepo.ID,
|
|
||||||
Enabled: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := l.imageDao.CreateOrUpdate(ctx, image); err != nil {
|
// Retrieves the spacePath and packageType.
|
||||||
return err
|
func (l *manifestService) getSpacePathAndPackageType(
|
||||||
}
|
ctx context.Context,
|
||||||
|
dbRepo *types.Registry,
|
||||||
|
) (string, event.PackageType, error) {
|
||||||
|
spacePath, err := l.spacePathStore.FindPrimaryBySpaceID(ctx, dbRepo.ParentID)
|
||||||
|
if err != nil {
|
||||||
|
log.Ctx(ctx).Err(err).Msg("Failed to find spacePath")
|
||||||
|
return "", event.PackageType(0), err
|
||||||
|
}
|
||||||
|
|
||||||
digest, err := types.NewDigest(dgst)
|
packageType, err := event.GetPackageTypeFromString(string(dbRepo.PackageType))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
log.Ctx(ctx).Err(err).Msg("Failed to find packageType")
|
||||||
}
|
return "", event.PackageType(0), err
|
||||||
artifact := &types.Artifact{
|
}
|
||||||
ImageID: image.ID,
|
|
||||||
Version: digest.String(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := l.artifactDao.CreateOrUpdate(ctx, artifact); err != nil {
|
return spacePath.Value, packageType, nil
|
||||||
return err
|
}
|
||||||
}
|
|
||||||
|
|
||||||
tag := &types.Tag{
|
// Reports event asynchronously.
|
||||||
Name: tagName,
|
func (l *manifestService) reportEventAsync(
|
||||||
ImageName: imageName,
|
ctx context.Context,
|
||||||
RegistryID: dbRepo.ID,
|
regID,
|
||||||
ManifestID: dbManifest.ID,
|
imageName,
|
||||||
}
|
tagName string,
|
||||||
|
packageType event.PackageType,
|
||||||
if err := l.tagDao.CreateOrUpdate(ctx, tag); err != nil {
|
spacePath string,
|
||||||
return err
|
) {
|
||||||
}
|
go l.reporter.ReportEvent(ctx, &event.ArtifactDetails{
|
||||||
|
RegistryID: regID,
|
||||||
return nil
|
ImagePath: imageName + ":" + tagName,
|
||||||
},
|
PackageType: packageType,
|
||||||
)
|
}, spacePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *manifestService) DBPut(
|
func (l *manifestService) DBPut(
|
||||||
|
@ -327,7 +410,7 @@ func (l *manifestService) dbPutManifestV2(
|
||||||
}
|
}
|
||||||
|
|
||||||
dbManifest, err := l.manifestDao.FindManifestByDigest(ctx, dbRepo.ID, info.Image, dgst)
|
dbManifest, err := l.manifestDao.FindManifestByDigest(ctx, dbRepo.ID, info.Image, dgst)
|
||||||
if err != nil && !errors.Is(err, store2.ErrResourceNotFound) {
|
if err != nil && !errors.Is(err, gitnessstore.ErrResourceNotFound) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -433,7 +516,7 @@ func (l *manifestService) DBFindRepositoryBlob(
|
||||||
image := info.Image
|
image := info.Image
|
||||||
b, err := l.blobRepo.FindByDigestAndRepoID(ctx, desc.Digest, repoID, image)
|
b, err := l.blobRepo.FindByDigestAndRepoID(ctx, desc.Digest, repoID, image)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, store2.ErrResourceNotFound) {
|
if errors.Is(err, gitnessstore.ErrResourceNotFound) {
|
||||||
return nil, fmt.Errorf("blob not found in database")
|
return nil, fmt.Errorf("blob not found in database")
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -453,11 +536,11 @@ func (l *manifestService) handleSubject(
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dbSubject, err := l.manifestDao.FindManifestByDigest(ctx, dbRepo.ID, info.Image, subjectDigest)
|
dbSubject, err := l.manifestDao.FindManifestByDigest(ctx, dbRepo.ID, info.Image, subjectDigest)
|
||||||
if err != nil && !errors.Is(err, store2.ErrResourceNotFound) {
|
if err != nil && !errors.Is(err, gitnessstore.ErrResourceNotFound) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if errors.Is(err, store2.ErrResourceNotFound) {
|
if errors.Is(err, gitnessstore.ErrResourceNotFound) {
|
||||||
// in case something happened to the referenced manifest after validation
|
// in case something happened to the referenced manifest after validation
|
||||||
// return distribution.ManifestBlobUnknownError{Digest: subject.Digest}
|
// return distribution.ManifestBlobUnknownError{Digest: subject.Digest}
|
||||||
log.Ctx(ctx).Warn().Msgf("subject manifest not found in database")
|
log.Ctx(ctx).Warn().Msgf("subject manifest not found in database")
|
||||||
|
@ -516,7 +599,7 @@ func (l *manifestService) dbPutManifestList(
|
||||||
}
|
}
|
||||||
|
|
||||||
ml, err := l.manifestDao.FindManifestByDigest(ctx, r.ID, info.Image, dgst)
|
ml, err := l.manifestDao.FindManifestByDigest(ctx, r.ID, info.Image, dgst)
|
||||||
if err != nil && !errors.Is(err, store2.ErrResourceNotFound) {
|
if err != nil && !errors.Is(err, gitnessstore.ErrResourceNotFound) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -625,7 +708,7 @@ func (l *manifestService) dbPutImageIndex(
|
||||||
}
|
}
|
||||||
|
|
||||||
mi, err := l.manifestDao.FindManifestByDigest(ctx, r.ID, info.Image, dgst)
|
mi, err := l.manifestDao.FindManifestByDigest(ctx, r.ID, info.Image, dgst)
|
||||||
if err != nil && !errors.Is(err, store2.ErrResourceNotFound) {
|
if err != nil && !errors.Is(err, gitnessstore.ErrResourceNotFound) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -758,7 +841,7 @@ func (l *manifestService) dbFindManifestListManifest(
|
||||||
imageName, dgst,
|
imageName, dgst,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, store2.ErrResourceNotFound) {
|
if errors.Is(err, gitnessstore.ErrResourceNotFound) {
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"manifest %s not found for %s/%s", digest.String(),
|
"manifest %s not found for %s/%s", digest.String(),
|
||||||
repository.Name, imageName,
|
repository.Name, imageName,
|
||||||
|
@ -849,7 +932,7 @@ func (l *manifestService) DeleteManifest(
|
||||||
}
|
}
|
||||||
m, err := l.manifestDao.FindManifestByDigest(ctx, registry.ID, imageName, newDigest)
|
m, err := l.manifestDao.FindManifestByDigest(ctx, registry.ID, imageName, newDigest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, store2.ErrResourceNotFound) {
|
if errors.Is(err, gitnessstore.ErrResourceNotFound) {
|
||||||
return util.ErrManifestNotFound
|
return util.ErrManifestNotFound
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -16,8 +16,9 @@ package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/harness/gitness/app/auth/authz"
|
"github.com/harness/gitness/app/auth/authz"
|
||||||
corestore "github.com/harness/gitness/app/store"
|
gitnessstore "github.com/harness/gitness/app/store"
|
||||||
storagedriver "github.com/harness/gitness/registry/app/driver"
|
storagedriver "github.com/harness/gitness/registry/app/driver"
|
||||||
|
"github.com/harness/gitness/registry/app/event"
|
||||||
"github.com/harness/gitness/registry/app/pkg"
|
"github.com/harness/gitness/registry/app/pkg"
|
||||||
"github.com/harness/gitness/registry/app/storage"
|
"github.com/harness/gitness/registry/app/storage"
|
||||||
"github.com/harness/gitness/registry/app/store"
|
"github.com/harness/gitness/registry/app/store"
|
||||||
|
@ -49,17 +50,17 @@ func ManifestServiceProvider(
|
||||||
manifestDao store.ManifestRepository, blobRepo store.BlobRepository, mtRepository store.MediaTypesRepository,
|
manifestDao store.ManifestRepository, blobRepo store.BlobRepository, mtRepository store.MediaTypesRepository,
|
||||||
manifestRefDao store.ManifestReferenceRepository, tagDao store.TagRepository, imageDao store.ImageRepository,
|
manifestRefDao store.ManifestReferenceRepository, tagDao store.TagRepository, imageDao store.ImageRepository,
|
||||||
artifactDao store.ArtifactRepository, layerDao store.LayerRepository,
|
artifactDao store.ArtifactRepository, layerDao store.LayerRepository,
|
||||||
gcService gc.Service, tx dbtx.Transactor,
|
gcService gc.Service, tx dbtx.Transactor, reporter event.Reporter, spacePathStore gitnessstore.SpacePathStore,
|
||||||
) ManifestService {
|
) ManifestService {
|
||||||
return NewManifestService(
|
return NewManifestService(
|
||||||
registryDao, manifestDao, blobRepo, mtRepository, tagDao, imageDao,
|
registryDao, manifestDao, blobRepo, mtRepository, tagDao, imageDao,
|
||||||
artifactDao, layerDao, manifestRefDao, tx, gcService,
|
artifactDao, layerDao, manifestRefDao, tx, gcService, reporter, spacePathStore,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RemoteRegistryProvider(
|
func RemoteRegistryProvider(
|
||||||
local *LocalRegistry, app *App, upstreamProxyConfigRepo store.UpstreamProxyConfigRepository,
|
local *LocalRegistry, app *App, upstreamProxyConfigRepo store.UpstreamProxyConfigRepository,
|
||||||
spacePathStore corestore.SpacePathStore, secretService secret.Service,
|
spacePathStore gitnessstore.SpacePathStore, secretService secret.Service,
|
||||||
) *RemoteRegistry {
|
) *RemoteRegistry {
|
||||||
return NewRemoteRegistry(local, app, upstreamProxyConfigRepo, spacePathStore, secretService).(*RemoteRegistry)
|
return NewRemoteRegistry(local, app, upstreamProxyConfigRepo, spacePathStore, secretService).(*RemoteRegistry)
|
||||||
}
|
}
|
||||||
|
@ -68,7 +69,7 @@ func ControllerProvider(
|
||||||
local *LocalRegistry,
|
local *LocalRegistry,
|
||||||
remote *RemoteRegistry,
|
remote *RemoteRegistry,
|
||||||
controller *pkg.CoreController,
|
controller *pkg.CoreController,
|
||||||
spaceStore corestore.SpaceStore,
|
spaceStore gitnessstore.SpaceStore,
|
||||||
authorizer authz.Authorizer,
|
authorizer authz.Authorizer,
|
||||||
) *Controller {
|
) *Controller {
|
||||||
return NewController(local, remote, controller, spaceStore, authorizer)
|
return NewController(local, remote, controller, spaceStore, authorizer)
|
||||||
|
@ -78,8 +79,12 @@ func StorageServiceProvider(cfg *types.Config, driver storagedriver.StorageDrive
|
||||||
return GetStorageService(cfg, driver)
|
return GetStorageService(cfg, driver)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ProvideReporter() event.Reporter {
|
||||||
|
return &event.Noop{}
|
||||||
|
}
|
||||||
|
|
||||||
var ControllerSet = wire.NewSet(ControllerProvider)
|
var ControllerSet = wire.NewSet(ControllerProvider)
|
||||||
var RegistrySet = wire.NewSet(LocalRegistryProvider, ManifestServiceProvider, RemoteRegistryProvider)
|
var RegistrySet = wire.NewSet(LocalRegistryProvider, ManifestServiceProvider, RemoteRegistryProvider)
|
||||||
var StorageServiceSet = wire.NewSet(StorageServiceProvider)
|
var StorageServiceSet = wire.NewSet(StorageServiceProvider)
|
||||||
var AppSet = wire.NewSet(NewApp)
|
var AppSet = wire.NewSet(NewApp)
|
||||||
var WireSet = wire.NewSet(ControllerSet, RegistrySet, StorageServiceSet, AppSet, gc.WireSet)
|
var WireSet = wire.NewSet(ControllerSet, RegistrySet, StorageServiceSet, AppSet)
|
||||||
|
|
|
@ -178,15 +178,15 @@ type TagRepository interface {
|
||||||
|
|
||||||
GetAllArtifactsByParentID(
|
GetAllArtifactsByParentID(
|
||||||
ctx context.Context, parentID int64,
|
ctx context.Context, parentID int64,
|
||||||
packageTypes *[]string, sortByField string,
|
registryIDs *[]string, sortByField string,
|
||||||
sortByOrder string, limit int, offset int, search string,
|
sortByOrder string, limit int, offset int, search string,
|
||||||
labels []string,
|
latestVersion bool,
|
||||||
) (*[]types.ArtifactMetadata, error)
|
) (*[]types.ArtifactMetadata, error)
|
||||||
|
|
||||||
CountAllArtifactsByParentID(
|
CountAllArtifactsByParentID(
|
||||||
ctx context.Context, parentID int64,
|
ctx context.Context, parentID int64,
|
||||||
packageTypes *[]string, search string,
|
registryIDs *[]string, search string,
|
||||||
labels []string,
|
latestVersion bool,
|
||||||
) (int64, error)
|
) (int64, error)
|
||||||
|
|
||||||
GetAllArtifactsByRepo(
|
GetAllArtifactsByRepo(
|
||||||
|
@ -460,4 +460,5 @@ type GCManifestTaskRepository interface {
|
||||||
Postpone(ctx context.Context, b *types.GCManifestTask, d time.Duration) error
|
Postpone(ctx context.Context, b *types.GCManifestTask, d time.Duration) error
|
||||||
IsDangling(ctx context.Context, b *types.GCManifestTask) (bool, error)
|
IsDangling(ctx context.Context, b *types.GCManifestTask) (bool, error)
|
||||||
Delete(ctx context.Context, b *types.GCManifestTask) error
|
Delete(ctx context.Context, b *types.GCManifestTask) error
|
||||||
|
DeleteManifest(ctx context.Context, registryID, id int64) (*digest.Digest, error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,6 +77,7 @@ type artifactMetadataDB struct {
|
||||||
LatestVersion string `db:"latest_version"`
|
LatestVersion string `db:"latest_version"`
|
||||||
CreatedAt int64 `db:"created_at"`
|
CreatedAt int64 `db:"created_at"`
|
||||||
ModifiedAt int64 `db:"modified_at"`
|
ModifiedAt int64 `db:"modified_at"`
|
||||||
|
Version string `db:"version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type tagMetadataDB struct {
|
type tagMetadataDB struct {
|
||||||
|
@ -337,54 +338,54 @@ func sqlPartialMatch(value string) string {
|
||||||
func (t tagDao) GetAllArtifactsByParentID(
|
func (t tagDao) GetAllArtifactsByParentID(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
parentID int64,
|
parentID int64,
|
||||||
packageTypes *[]string,
|
registryIDs *[]string,
|
||||||
sortByField string,
|
sortByField string,
|
||||||
sortByOrder string,
|
sortByOrder string,
|
||||||
limit int,
|
limit int,
|
||||||
offset int,
|
offset int,
|
||||||
search string,
|
search string,
|
||||||
labels []string,
|
latestVersion bool,
|
||||||
) (*[]types.ArtifactMetadata, error) {
|
) (*[]types.ArtifactMetadata, error) {
|
||||||
q := databaseg.Builder.Select(
|
q := databaseg.Builder.Select(
|
||||||
"r.registry_name as repo_name, t.tag_image_name as name,"+
|
`r.registry_name as repo_name,
|
||||||
" r.registry_package_type as package_type, t.tag_name as latest_version,"+
|
t.tag_image_name as name,
|
||||||
" t.tag_updated_at as modified_at, ar.image_labels as labels, t2.download_count as download_count ",
|
r.registry_package_type as package_type,
|
||||||
|
t.tag_name as version,
|
||||||
|
t.tag_updated_at as modified_at,
|
||||||
|
ar.image_labels as labels,
|
||||||
|
COALESCE(t2.download_count,0) as download_count `,
|
||||||
).
|
).
|
||||||
From("tags t").
|
From("tags t").
|
||||||
Join(
|
|
||||||
"(SELECT t.tag_id as id, ROW_NUMBER() OVER "+
|
|
||||||
" (PARTITION BY t.tag_registry_id, t.tag_image_name ORDER BY t.tag_updated_at DESC) AS rank "+
|
|
||||||
" FROM tags t JOIN registries r ON t.tag_registry_id = r.registry_id "+
|
|
||||||
" WHERE r.registry_parent_id = ? ) AS a ON t.tag_id = a.id", parentID,
|
|
||||||
).
|
|
||||||
Join("registries r ON t.tag_registry_id = r.registry_id").
|
Join("registries r ON t.tag_registry_id = r.registry_id").
|
||||||
|
Where("r.registry_parent_id = ?", parentID).
|
||||||
Join(
|
Join(
|
||||||
"images ar ON ar.image_registry_id = t.tag_registry_id AND"+
|
"images ar ON ar.image_registry_id = t.tag_registry_id AND"+
|
||||||
" ar.image_name = t.tag_image_name",
|
" ar.image_name = t.tag_image_name",
|
||||||
).
|
).
|
||||||
LeftJoin(
|
LeftJoin(
|
||||||
"(SELECT i.image_name, t1.download_count FROM"+
|
`( SELECT i.image_name, SUM(COALESCE(t1.download_count, 0)) as download_count FROM
|
||||||
" ( SELECT a.artifact_image_id, COUNT(d.download_stat_id) as download_count"+
|
( SELECT a.artifact_image_id, COUNT(d.download_stat_id) as download_count
|
||||||
" FROM artifacts a "+
|
FROM artifacts a JOIN download_stats d ON d.download_stat_artifact_id = a.artifact_id
|
||||||
" LEFT JOIN download_stats d ON d.download_stat_artifact_id = a.artifact_id GROUP BY"+
|
GROUP BY a.artifact_image_id ) as t1
|
||||||
" a.artifact_image_id ) as t1 "+
|
JOIN images i ON i.image_id = t1.artifact_image_id
|
||||||
" JOIN images ON i.image_id = t1.artifact_image_id "+
|
JOIN registries r ON r.registry_id = i.image_registry_id
|
||||||
" JOIN registries r ON r.registry_id = i.image_registry_id "+
|
WHERE r.registry_parent_id = ? GROUP BY i.image_name) as t2
|
||||||
" WHERE r.registry_parent_id = ? GROUP BY i.image_name) as t2"+
|
ON t.tag_image_name = t2.image_name`, parentID,
|
||||||
" ON t.tag_image_name = t2.image_name", parentID,
|
)
|
||||||
).
|
|
||||||
Where("a.rank = 1")
|
|
||||||
|
|
||||||
if len(*packageTypes) > 0 {
|
if latestVersion {
|
||||||
q = q.Where(sq.Eq{"r.registry_package_type": packageTypes})
|
q = q.Join(
|
||||||
|
`(SELECT t.tag_id as id, ROW_NUMBER() OVER (PARTITION BY t.tag_registry_id, t.tag_image_name
|
||||||
|
ORDER BY t.tag_updated_at DESC) AS rank FROM tags t
|
||||||
|
JOIN registries r ON t.tag_registry_id = r.registry_id
|
||||||
|
WHERE r.registry_parent_id = ? ) AS a
|
||||||
|
ON t.tag_id = a.id`, parentID, // nolint:goconst
|
||||||
|
).
|
||||||
|
Where("a.rank = 1")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(labels) > 0 {
|
if len(*registryIDs) > 0 {
|
||||||
sort.Strings(labels)
|
q = q.Where(sq.Eq{"r.registry_name": registryIDs})
|
||||||
labelsVal := util.GetEmptySQLString(util.ArrToString(labels))
|
|
||||||
|
|
||||||
labelsVal.String = labelSeparatorStart + labelsVal.String + labelSeparatorEnd
|
|
||||||
q = q.Where("'^_' || ar.image_labels || '^_' LIKE ?", labelsVal)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if search != "" {
|
if search != "" {
|
||||||
|
@ -409,39 +410,35 @@ func (t tagDao) GetAllArtifactsByParentID(
|
||||||
|
|
||||||
func (t tagDao) CountAllArtifactsByParentID(
|
func (t tagDao) CountAllArtifactsByParentID(
|
||||||
ctx context.Context, parentID int64,
|
ctx context.Context, parentID int64,
|
||||||
packageTypes *[]string, search string, labels []string,
|
registryIDs *[]string, search string, latestVersion bool,
|
||||||
) (int64, error) {
|
) (int64, error) {
|
||||||
// nolint:goconst
|
// nolint:goconst
|
||||||
q := databaseg.Builder.Select("COUNT(*)").
|
q := databaseg.Builder.Select("COUNT(*)").
|
||||||
From("tags t").
|
From("tags t").
|
||||||
Join(
|
Join("registries r ON t.tag_registry_id = r.registry_id"). // nolint:goconst
|
||||||
"(SELECT t.tag_id as id, ROW_NUMBER() OVER "+
|
Where("r.registry_parent_id = ?", parentID).
|
||||||
" (PARTITION BY t.tag_registry_id, t.tag_image_name ORDER BY t.tag_updated_at DESC) AS rank FROM tags t "+
|
|
||||||
" JOIN registries r ON t.tag_registry_id = r.registry_id "+
|
|
||||||
" WHERE r.registry_parent_id = ?) AS a ON t.tag_id = a.id", parentID,
|
|
||||||
).
|
|
||||||
Join("registries r ON t.tag_registry_id = r.registry_id").
|
|
||||||
Join(
|
Join(
|
||||||
"images ar ON ar.image_registry_id = t.tag_registry_id" +
|
"images ar ON ar.image_registry_id = t.tag_registry_id" +
|
||||||
" AND ar.image_name = t.tag_image_name",
|
" AND ar.image_name = t.tag_image_name",
|
||||||
).
|
)
|
||||||
Where("a.rank = 1 ")
|
|
||||||
|
|
||||||
if len(*packageTypes) > 0 {
|
if latestVersion {
|
||||||
q = q.Where(sq.Eq{"r.registry_package_type": packageTypes})
|
q = q.Join(
|
||||||
|
`(SELECT t.tag_id as id, ROW_NUMBER() OVER (PARTITION BY t.tag_registry_id, t.tag_image_name
|
||||||
|
ORDER BY t.tag_updated_at DESC) AS rank FROM tags t
|
||||||
|
JOIN registries r ON t.tag_registry_id = r.registry_id
|
||||||
|
WHERE r.registry_parent_id = ? ) AS a
|
||||||
|
ON t.tag_id = a.id`, parentID, // nolint:goconst
|
||||||
|
).Where("a.rank = 1")
|
||||||
|
}
|
||||||
|
if len(*registryIDs) > 0 {
|
||||||
|
q = q.Where(sq.Eq{"r.registry_name": registryIDs})
|
||||||
}
|
}
|
||||||
|
|
||||||
if search != "" {
|
if search != "" {
|
||||||
q = q.Where("tag_image_name LIKE ?", sqlPartialMatch(search))
|
q = q.Where("tag_image_name LIKE ?", sqlPartialMatch(search))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(labels) > 0 {
|
|
||||||
sort.Strings(labels)
|
|
||||||
labelsVal := util.GetEmptySQLString(util.ArrToString(labels))
|
|
||||||
labelsVal.String = labelSeparatorStart + labelsVal.String + labelSeparatorEnd
|
|
||||||
q = q.Where("'^_' || ar.image_labels || '^_' LIKE ?", labelsVal)
|
|
||||||
}
|
|
||||||
|
|
||||||
sql, args, err := q.ToSql()
|
sql, args, err := q.ToSql()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, errors.Wrap(err, "Failed to convert query to sql")
|
return -1, errors.Wrap(err, "Failed to convert query to sql")
|
||||||
|
@ -461,9 +458,9 @@ func (t tagDao) GetTagDetail(
|
||||||
name string,
|
name string,
|
||||||
) (*types.TagDetail, error) {
|
) (*types.TagDetail, error) {
|
||||||
q := databaseg.Builder.Select(
|
q := databaseg.Builder.Select(
|
||||||
"tag_id as id, tag_name as name ,"+
|
`tag_id as id, tag_name as name,
|
||||||
" tag_image_name as image_name, tag_created_at as created_at, "+
|
tag_image_name as image_name, tag_created_at as created_at,
|
||||||
" tag_updated_at as updated_at, manifest_total_size as size",
|
tag_updated_at as updated_at, manifest_total_size as size`,
|
||||||
).
|
).
|
||||||
From("tags").
|
From("tags").
|
||||||
Join("manifests ON manifest_id = tag_manifest_id").
|
Join("manifests ON manifest_id = tag_manifest_id").
|
||||||
|
@ -494,13 +491,13 @@ func (t tagDao) GetLatestTagMetadata(
|
||||||
imageName string,
|
imageName string,
|
||||||
) (*types.ArtifactMetadata, error) {
|
) (*types.ArtifactMetadata, error) {
|
||||||
q := databaseg.Builder.Select(
|
q := databaseg.Builder.Select(
|
||||||
"r.registry_name as repo_name,"+
|
`r.registry_name as repo_name,
|
||||||
" r.registry_package_type as package_type, t.tag_image_name as name, "+
|
r.registry_package_type as package_type, t.tag_image_name as name,
|
||||||
"t.tag_name as latest_version, t.tag_created_at as created_at,"+
|
t.tag_name as latest_version, t.tag_created_at as created_at,
|
||||||
" t.tag_updated_at as modified_at, ar.image_labels as labels",
|
t.tag_updated_at as modified_at, ar.image_labels as labels`,
|
||||||
).
|
).
|
||||||
From("tags t").
|
From("tags t").
|
||||||
Join("registries r ON t.tag_registry_id = r.registry_id").
|
Join("registries r ON t.tag_registry_id = r.registry_id"). // nolint:goconst
|
||||||
Join(
|
Join(
|
||||||
"images ar ON ar.image_registry_id = t.tag_registry_id "+
|
"images ar ON ar.image_registry_id = t.tag_registry_id "+
|
||||||
"AND ar.image_name = t.tag_image_name",
|
"AND ar.image_name = t.tag_image_name",
|
||||||
|
@ -618,18 +615,18 @@ func (t tagDao) GetAllArtifactsByRepo(
|
||||||
labels []string,
|
labels []string,
|
||||||
) (*[]types.ArtifactMetadata, error) {
|
) (*[]types.ArtifactMetadata, error) {
|
||||||
q := databaseg.Builder.Select(
|
q := databaseg.Builder.Select(
|
||||||
"r.registry_name as repo_name, t.tag_image_name as name,"+
|
`r.registry_name as repo_name, t.tag_image_name as name,
|
||||||
" r.registry_package_type as package_type, t.tag_name as latest_version,"+
|
r.registry_package_type as package_type, t.tag_name as latest_version,
|
||||||
" t.tag_updated_at as modified_at, ar.image_labels as labels, "+
|
t.tag_updated_at as modified_at, ar.image_labels as labels,
|
||||||
" COALESCE(t2.download_count, 0) as download_count ",
|
COALESCE(t2.download_count, 0) as download_count `,
|
||||||
).
|
).
|
||||||
From("tags t").
|
From("tags t").
|
||||||
Join(
|
Join(
|
||||||
"(SELECT t.tag_id as id, ROW_NUMBER() OVER (PARTITION BY t.tag_registry_id, t.tag_image_name"+
|
`(SELECT t.tag_id as id, ROW_NUMBER() OVER (PARTITION BY t.tag_registry_id, t.tag_image_name
|
||||||
" ORDER BY t.tag_updated_at DESC) AS rank FROM tags t "+
|
ORDER BY t.tag_updated_at DESC) AS rank FROM tags t
|
||||||
" JOIN registries r ON t.tag_registry_id = r.registry_id "+
|
JOIN registries r ON t.tag_registry_id = r.registry_id
|
||||||
" WHERE r.registry_parent_id = ? AND r.registry_name = ? ) AS a"+
|
WHERE r.registry_parent_id = ? AND r.registry_name = ? ) AS a
|
||||||
" ON t.tag_id = a.id", parentID, repoKey,
|
ON t.tag_id = a.id`, parentID, repoKey, // nolint:goconst
|
||||||
).
|
).
|
||||||
Join("registries r ON t.tag_registry_id = r.registry_id").
|
Join("registries r ON t.tag_registry_id = r.registry_id").
|
||||||
Join(
|
Join(
|
||||||
|
@ -637,15 +634,15 @@ func (t tagDao) GetAllArtifactsByRepo(
|
||||||
" AND ar.image_name = t.tag_image_name",
|
" AND ar.image_name = t.tag_image_name",
|
||||||
).
|
).
|
||||||
LeftJoin(
|
LeftJoin(
|
||||||
"( SELECT i.image_name, SUM(COALESCE(t1.download_count, 0)) as download_count FROM"+
|
`( SELECT i.image_name, SUM(COALESCE(t1.download_count, 0)) as download_count FROM
|
||||||
" ( SELECT a.artifact_image_id, COUNT(d.download_stat_id) as download_count"+
|
( SELECT a.artifact_image_id, COUNT(d.download_stat_id) as download_count
|
||||||
" FROM artifacts a "+
|
FROM artifacts a
|
||||||
" JOIN download_stats d ON d.download_stat_artifact_id = a.artifact_id GROUP BY"+
|
JOIN download_stats d ON d.download_stat_artifact_id = a.artifact_id GROUP BY
|
||||||
" a.artifact_image_id ) as t1 "+
|
a.artifact_image_id ) as t1
|
||||||
" JOIN images i ON i.image_id = t1.artifact_image_id "+
|
JOIN images i ON i.image_id = t1.artifact_image_id
|
||||||
" JOIN registries r ON r.registry_id = i.image_registry_id "+
|
JOIN registries r ON r.registry_id = i.image_registry_id
|
||||||
" WHERE r.registry_parent_id = ? AND r.registry_name = ? GROUP BY i.image_name) as t2"+
|
WHERE r.registry_parent_id = ? AND r.registry_name = ? GROUP BY i.image_name) as t2
|
||||||
" ON t.tag_image_name = t2.image_name", parentID, repoKey,
|
ON t.tag_image_name = t2.image_name`, parentID, repoKey,
|
||||||
).
|
).
|
||||||
Where("a.rank = 1 ")
|
Where("a.rank = 1 ")
|
||||||
|
|
||||||
|
@ -676,6 +673,7 @@ func (t tagDao) GetAllArtifactsByRepo(
|
||||||
return t.mapToArtifactMetadataList(ctx, dst)
|
return t.mapToArtifactMetadataList(ctx, dst)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nolint:goconst
|
||||||
func (t tagDao) CountAllArtifactsByRepo(
|
func (t tagDao) CountAllArtifactsByRepo(
|
||||||
ctx context.Context, parentID int64, repoKey string,
|
ctx context.Context, parentID int64, repoKey string,
|
||||||
search string, labels []string,
|
search string, labels []string,
|
||||||
|
@ -683,10 +681,10 @@ func (t tagDao) CountAllArtifactsByRepo(
|
||||||
q := databaseg.Builder.Select("COUNT(*)").
|
q := databaseg.Builder.Select("COUNT(*)").
|
||||||
From("tags t").
|
From("tags t").
|
||||||
Join(
|
Join(
|
||||||
"(SELECT t.tag_id as id, ROW_NUMBER() OVER (PARTITION BY t.tag_registry_id, t.tag_image_name"+
|
`(SELECT t.tag_id as id, ROW_NUMBER() OVER (PARTITION BY t.tag_registry_id, t.tag_image_name
|
||||||
" ORDER BY t.tag_updated_at DESC) AS rank FROM tags t "+
|
ORDER BY t.tag_updated_at DESC) AS rank FROM tags t
|
||||||
" JOIN registries r ON t.tag_registry_id = r.registry_id "+
|
JOIN registries r ON t.tag_registry_id = r.registry_id
|
||||||
" WHERE r.registry_parent_id = ? AND r.registry_name = ? ) AS a ON t.tag_id = a.id", parentID, repoKey,
|
WHERE r.registry_parent_id = ? AND r.registry_name = ? ) AS a ON t.tag_id = a.id`, parentID, repoKey,
|
||||||
).
|
).
|
||||||
Join("registries r ON t.tag_registry_id = r.registry_id").
|
Join("registries r ON t.tag_registry_id = r.registry_id").
|
||||||
Join(
|
Join(
|
||||||
|
@ -726,10 +724,10 @@ func (t tagDao) GetAllTagsByRepoAndImage(
|
||||||
search string,
|
search string,
|
||||||
) (*[]types.TagMetadata, error) {
|
) (*[]types.TagMetadata, error) {
|
||||||
q := databaseg.Builder.Select(
|
q := databaseg.Builder.Select(
|
||||||
"t.tag_name as name, m.manifest_total_size as size,"+
|
`t.tag_name as name, m.manifest_total_size as size,
|
||||||
" r.registry_package_type as package_type, t.tag_updated_at as modified_at, "+
|
r.registry_package_type as package_type, t.tag_updated_at as modified_at,
|
||||||
" m.manifest_schema_version, m.manifest_non_conformant, m.manifest_payload, "+
|
m.manifest_schema_version, m.manifest_non_conformant, m.manifest_payload,
|
||||||
" mt.mt_media_type ",
|
mt.mt_media_type `,
|
||||||
).
|
).
|
||||||
From("tags t").
|
From("tags t").
|
||||||
Join("registries r ON t.tag_registry_id = r.registry_id").
|
Join("registries r ON t.tag_registry_id = r.registry_id").
|
||||||
|
@ -903,6 +901,7 @@ func (t tagDao) mapToArtifactMetadata(
|
||||||
Labels: util.StringToArr(dst.Labels.String),
|
Labels: util.StringToArr(dst.Labels.String),
|
||||||
CreatedAt: time.UnixMilli(dst.CreatedAt),
|
CreatedAt: time.UnixMilli(dst.CreatedAt),
|
||||||
ModifiedAt: time.UnixMilli(dst.ModifiedAt),
|
ModifiedAt: time.UnixMilli(dst.ModifiedAt),
|
||||||
|
Version: dst.Version,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ func New() Service {
|
||||||
func (s *Noop) Start(
|
func (s *Noop) Start(
|
||||||
_ context.Context, _ *sqlx.DB, _ corestore.SpaceStore,
|
_ context.Context, _ *sqlx.DB, _ corestore.SpaceStore,
|
||||||
_ store.BlobRepository, _ storagedriver.StorageDeleter,
|
_ store.BlobRepository, _ storagedriver.StorageDeleter,
|
||||||
_ *types.Config, _ store.MediaTypesRepository, _ store.ManifestRepository,
|
_ *types.Config,
|
||||||
) {
|
) {
|
||||||
// NOOP
|
// NOOP
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ type Service interface {
|
||||||
Start(
|
Start(
|
||||||
ctx context.Context, sqlDB *sqlx.DB, spaceStore corestore.SpaceStore,
|
ctx context.Context, sqlDB *sqlx.DB, spaceStore corestore.SpaceStore,
|
||||||
blobRepo store.BlobRepository, storageDeleter storagedriver.StorageDeleter,
|
blobRepo store.BlobRepository, storageDeleter storagedriver.StorageDeleter,
|
||||||
config *types.Config, mtRepository store.MediaTypesRepository, manifestRepository store.ManifestRepository,
|
config *types.Config,
|
||||||
)
|
)
|
||||||
BlobFindAndLockBefore(ctx context.Context, blobID int64, date time.Time) (*registrytypes.GCBlobTask, error)
|
BlobFindAndLockBefore(ctx context.Context, blobID int64, date time.Time) (*registrytypes.GCBlobTask, error)
|
||||||
BlobReschedule(ctx context.Context, b *registrytypes.GCBlobTask, d time.Duration) error
|
BlobReschedule(ctx context.Context, b *registrytypes.GCBlobTask, d time.Duration) error
|
||||||
|
|
|
@ -14,15 +14,11 @@
|
||||||
|
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type GCBlobTask struct {
|
type GCBlobTask struct {
|
||||||
BlobID int64
|
BlobID int64
|
||||||
ReviewAfter time.Time
|
ReviewAfter int64
|
||||||
ReviewCount int
|
ReviewCount int
|
||||||
CreatedAt time.Time
|
CreatedAt int64
|
||||||
Event string
|
Event string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,14 +26,8 @@ type GCBlobTask struct {
|
||||||
type GCManifestTask struct {
|
type GCManifestTask struct {
|
||||||
RegistryID int64
|
RegistryID int64
|
||||||
ManifestID int64
|
ManifestID int64
|
||||||
ReviewAfter time.Time
|
ReviewAfter int64
|
||||||
ReviewCount int
|
ReviewCount int
|
||||||
CreatedAt time.Time
|
CreatedAt int64
|
||||||
Event string
|
Event string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GCReviewAfterDefault represents a row in the gc_review_after_defaults table.
|
|
||||||
type GCReviewAfterDefault struct {
|
|
||||||
Event string
|
|
||||||
Value time.Duration
|
|
||||||
}
|
|
||||||
|
|
|
@ -42,6 +42,7 @@ type ArtifactMetadata struct {
|
||||||
LatestVersion string
|
LatestVersion string
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
ModifiedAt time.Time
|
ModifiedAt time.Time
|
||||||
|
Version string
|
||||||
}
|
}
|
||||||
|
|
||||||
type TagMetadata struct {
|
type TagMetadata struct {
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
"@codemirror/view": "^6.9.6",
|
"@codemirror/view": "^6.9.6",
|
||||||
"@harnessio/design-system": "^2.1.1",
|
"@harnessio/design-system": "^2.1.1",
|
||||||
"@harnessio/icons": "^2.1.7",
|
"@harnessio/icons": "^2.1.7",
|
||||||
"@harnessio/react-har-service-client": "^0.0.15",
|
"@harnessio/react-har-service-client": "^0.0.21",
|
||||||
"@harnessio/uicore": "^4.1.2",
|
"@harnessio/uicore": "^4.1.2",
|
||||||
"@tanstack/react-query": "4.20.4",
|
"@tanstack/react-query": "4.20.4",
|
||||||
"@types/dompurify": "^3.0.2",
|
"@types/dompurify": "^3.0.2",
|
||||||
|
|
|
@ -15,9 +15,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type React from 'react'
|
import type React from 'react'
|
||||||
import type { Button } from '@harnessio/uicore'
|
|
||||||
|
|
||||||
import type { PermissionIdentifier, ResourceType } from '@ar/common/permissionTypes'
|
import type { PermissionIdentifier, ResourceType } from '@ar/common/permissionTypes'
|
||||||
|
import type RbacButton from '@ar/__mocks__/components/RbacButton'
|
||||||
import type RbacMenuItem from '@ar/__mocks__/components/RbacMenuItem'
|
import type RbacMenuItem from '@ar/__mocks__/components/RbacMenuItem'
|
||||||
import type NGBreadcrumbs from '@ar/__mocks__/components/NGBreadcrumbs'
|
import type NGBreadcrumbs from '@ar/__mocks__/components/NGBreadcrumbs'
|
||||||
import type DependencyView from '@ar/__mocks__/components/DependencyView'
|
import type DependencyView from '@ar/__mocks__/components/DependencyView'
|
||||||
|
@ -43,6 +43,16 @@ export interface Scope {
|
||||||
space?: string
|
space?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PipelineExecutionPathProps {
|
||||||
|
executionIdentifier: string
|
||||||
|
pipelineIdentifier: string
|
||||||
|
module: 'ci' | 'cd'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServiceDetailsPathProps {
|
||||||
|
serviceId: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface PermissionsRequest {
|
export interface PermissionsRequest {
|
||||||
resource: { resourceType: ResourceType; resourceIdentifier?: string }
|
resource: { resourceType: ResourceType; resourceIdentifier?: string }
|
||||||
permissions: PermissionIdentifier[]
|
permissions: PermissionIdentifier[]
|
||||||
|
@ -63,7 +73,7 @@ export interface ParentContextObj {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Components {
|
export interface Components {
|
||||||
RbacButton: typeof Button
|
RbacButton: typeof RbacButton
|
||||||
NGBreadcrumbs: typeof NGBreadcrumbs
|
NGBreadcrumbs: typeof NGBreadcrumbs
|
||||||
RbacMenuItem: typeof RbacMenuItem
|
RbacMenuItem: typeof RbacMenuItem
|
||||||
}
|
}
|
||||||
|
@ -96,6 +106,8 @@ export interface CustomUtils {
|
||||||
getCustomHeaders: () => Record<string, string>
|
getCustomHeaders: () => Record<string, string>
|
||||||
getApiBaseUrl: (url: string) => string
|
getApiBaseUrl: (url: string) => string
|
||||||
getRouteDefinitions?: (routeParams: Record<string, string>) => ARRouteDefinitionsReturn
|
getRouteDefinitions?: (routeParams: Record<string, string>) => ARRouteDefinitionsReturn
|
||||||
|
getRouteToPipelineExecutionView?: (params: Scope & PipelineExecutionPathProps) => string
|
||||||
|
getRouteToServiceDetailsView?: (params: Scope & ServiceDetailsPathProps) => string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MFEAppProps {
|
export interface MFEAppProps {
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Harness, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import { Button, ButtonProps } from '@harnessio/uicore'
|
||||||
|
|
||||||
|
import type { PermissionsRequest } from '@ar/MFEAppTypes'
|
||||||
|
import type { PermissionIdentifier } from '@ar/common/permissionTypes'
|
||||||
|
|
||||||
|
export interface RbacButtonProps extends ButtonProps {
|
||||||
|
permission?: Omit<PermissionsRequest, 'permissions'> & { permission: PermissionIdentifier }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RbacButton(props: RbacButtonProps) {
|
||||||
|
return <Button {...props} />
|
||||||
|
}
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
import React, { createContext } from 'react'
|
import React, { createContext } from 'react'
|
||||||
import { defaultTo, noop } from 'lodash-es'
|
import { defaultTo, noop } from 'lodash-es'
|
||||||
import { Button, Container } from '@harnessio/uicore'
|
import { Container } from '@harnessio/uicore'
|
||||||
|
|
||||||
import type { MFEAppProps } from '@ar/MFEAppTypes'
|
import type { MFEAppProps } from '@ar/MFEAppTypes'
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ import {
|
||||||
useQueryParamsOptions,
|
useQueryParamsOptions,
|
||||||
useUpdateQueryParams
|
useUpdateQueryParams
|
||||||
} from '@ar/__mocks__/hooks'
|
} from '@ar/__mocks__/hooks'
|
||||||
|
import RbacButton from '@ar/__mocks__/components/RbacButton'
|
||||||
import RbacMenuItem from '@ar/__mocks__/components/RbacMenuItem'
|
import RbacMenuItem from '@ar/__mocks__/components/RbacMenuItem'
|
||||||
import NGBreadcrumbs from '@ar/__mocks__/components/NGBreadcrumbs'
|
import NGBreadcrumbs from '@ar/__mocks__/components/NGBreadcrumbs'
|
||||||
import DependencyView from '@ar/__mocks__/components/DependencyView'
|
import DependencyView from '@ar/__mocks__/components/DependencyView'
|
||||||
|
@ -76,7 +77,7 @@ const GitnessApp = (props: Partial<MFEAppProps>): JSX.Element => {
|
||||||
scope={defaultTo(scope, {})}
|
scope={defaultTo(scope, {})}
|
||||||
customScope={defaultTo(customScope, {})}
|
customScope={defaultTo(customScope, {})}
|
||||||
components={Object.assign(
|
components={Object.assign(
|
||||||
{ RbacButton: Button, NGBreadcrumbs, RbacMenuItem, SecretFormInput, VulnerabilityView, DependencyView },
|
{ RbacButton, NGBreadcrumbs, RbacMenuItem, SecretFormInput, VulnerabilityView, DependencyView },
|
||||||
components
|
components
|
||||||
)}
|
)}
|
||||||
NavComponent={NavComponent}
|
NavComponent={NavComponent}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
import type { IconName } from '@harnessio/icons'
|
import type { IconName } from '@harnessio/icons'
|
||||||
import type { StringsMap } from '@ar/frameworks/strings'
|
import type { StringsMap } from '@ar/frameworks/strings'
|
||||||
import { RepositoryPackageType } from './types'
|
import { EnvironmentType, RepositoryPackageType } from './types'
|
||||||
|
|
||||||
export interface RepositoryTypeListItem {
|
export interface RepositoryTypeListItem {
|
||||||
label: keyof StringsMap
|
label: keyof StringsMap
|
||||||
|
@ -80,3 +80,20 @@ export const RepositoryTypes: RepositoryTypeListItem[] = [
|
||||||
disabled: true
|
disabled: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
interface EnvironmentTypeListItem {
|
||||||
|
label: keyof StringsMap
|
||||||
|
value: EnvironmentType
|
||||||
|
disabled?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EnvironmentTypeList: EnvironmentTypeListItem[] = [
|
||||||
|
{
|
||||||
|
label: 'prod',
|
||||||
|
value: EnvironmentType.Prod
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'nonProd',
|
||||||
|
value: EnvironmentType.NonProd
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
|
@ -15,10 +15,14 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export enum PermissionIdentifier {
|
export enum PermissionIdentifier {
|
||||||
DELETE_SERVICE = 'core_service_delete',
|
VIEW_ARTIFACT_REGISTRY = 'artifact_artregistry_view',
|
||||||
EDIT_SERVICE = 'core_service_edit'
|
EDIT_ARTIFACT_REGISTRY = 'artifact_artregistry_edit',
|
||||||
|
DELETE_ARTIFACT_REGISTRY = 'artifact_artregistry_delete',
|
||||||
|
DOWNLOAD_ARTIFACT = 'artifact_artregistry_downloadartifact',
|
||||||
|
UPLOAD_ARTIFACT = 'artifact_artregistry_uploadartifact',
|
||||||
|
DELETE_ARTIFACT = 'artifact_artregistry_deleteartifact'
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ResourceType {
|
export enum ResourceType {
|
||||||
SERVICE = 'SERVICE'
|
ARTIFACT_REGISTRY = 'ARTIFACT_REGISTRY'
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,8 +29,8 @@ export type FormikFowardRef<T = unknown> =
|
||||||
| null
|
| null
|
||||||
|
|
||||||
export enum EnvironmentType {
|
export enum EnvironmentType {
|
||||||
Production = 'Production',
|
Prod = 'Production',
|
||||||
PreProduction = 'PreProduction'
|
NonProd = 'PreProduction'
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum RepositoryPackageType {
|
export enum RepositoryPackageType {
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Harness, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.textInputWrapper {
|
||||||
|
margin-bottom: 0;
|
||||||
|
width: 600px;
|
||||||
|
|
||||||
|
.iconClass {
|
||||||
|
margin-top: 11px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rightIconClass {
|
||||||
|
width: 130px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.popoverContainer {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.popoverContent {
|
||||||
|
position: absolute;
|
||||||
|
left: 0px;
|
||||||
|
top: 40px;
|
||||||
|
border: 1px solid var(--grey-200);
|
||||||
|
border-radius: 0 0 5px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aiButton {
|
||||||
|
position: absolute;
|
||||||
|
top: var(--spacing-xsmall);
|
||||||
|
right: var(--spacing-xsmall);
|
||||||
|
--background-color: var(--white) !important;
|
||||||
|
--background-color-hover: var(--white) !important;
|
||||||
|
--background-color-active: var(--white) !important;
|
||||||
|
--text-color: var(--ai-purple-800) !important;
|
||||||
|
--border: 1px solid var(--ai-purple-800) !important;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 Harness, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable */
|
||||||
|
// This is an auto-generated file
|
||||||
|
export declare const aiButton: string
|
||||||
|
export declare const iconClass: string
|
||||||
|
export declare const popoverContainer: string
|
||||||
|
export declare const popoverContent: string
|
||||||
|
export declare const rightIconClass: string
|
||||||
|
export declare const textInputWrapper: string
|
|
@ -0,0 +1,103 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Harness, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { debounce, isEmpty } from 'lodash-es'
|
||||||
|
import { Menu, MenuItem } from '@blueprintjs/core'
|
||||||
|
import { Button, ButtonSize, ButtonVariation, Container, TextInput } from '@harnessio/uicore'
|
||||||
|
|
||||||
|
import { useStrings } from '@ar/frameworks/strings'
|
||||||
|
|
||||||
|
import type { AIOption } from './types'
|
||||||
|
import css from './AISearchInput.module.scss'
|
||||||
|
|
||||||
|
interface AISearchInputProps {
|
||||||
|
searchTerm: string
|
||||||
|
onChange: (val: string) => void
|
||||||
|
placeholder?: string
|
||||||
|
options: AIOption[]
|
||||||
|
}
|
||||||
|
|
||||||
|
function AISearchInput({ searchTerm, onChange, placeholder, options }: AISearchInputProps) {
|
||||||
|
const [query, setQuery] = useState(searchTerm)
|
||||||
|
const [focus, setFocus] = useState(false)
|
||||||
|
const { getString } = useStrings()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isEmpty(searchTerm)) {
|
||||||
|
setQuery('')
|
||||||
|
}
|
||||||
|
}, [searchTerm])
|
||||||
|
|
||||||
|
const deboucedOnChange = debounce(onChange, 500)
|
||||||
|
const deboucedSetFocus = debounce(setFocus, 400)
|
||||||
|
|
||||||
|
const handleChange = (value: string) => {
|
||||||
|
setQuery(value)
|
||||||
|
deboucedOnChange(value)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Container className={css.popoverContainer}>
|
||||||
|
<TextInput
|
||||||
|
value={query}
|
||||||
|
wrapperClassName={css.textInputWrapper}
|
||||||
|
onChange={(evt: React.ChangeEvent<HTMLInputElement>) => handleChange(evt.currentTarget.value as string)}
|
||||||
|
placeholder={placeholder ?? getString('search')}
|
||||||
|
leftIcon="search"
|
||||||
|
onFocus={() => {
|
||||||
|
setFocus(true)
|
||||||
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
deboucedSetFocus(false)
|
||||||
|
}}
|
||||||
|
leftIconProps={{
|
||||||
|
name: 'search',
|
||||||
|
className: css.iconClass
|
||||||
|
}}
|
||||||
|
rightElement="harness-copilot"
|
||||||
|
rightElementProps={{
|
||||||
|
className: css.rightIconClass
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
className={css.aiButton}
|
||||||
|
icon="harness-copilot"
|
||||||
|
variation={ButtonVariation.AI}
|
||||||
|
text={getString('harnessAI')}
|
||||||
|
size={ButtonSize.SMALL}
|
||||||
|
/>
|
||||||
|
{focus && (
|
||||||
|
<Container width={600} className={css.popoverContent}>
|
||||||
|
<Menu>
|
||||||
|
{options.map(each => (
|
||||||
|
<MenuItem
|
||||||
|
key={each.value}
|
||||||
|
text={each.label}
|
||||||
|
disabled={each.disabled}
|
||||||
|
onClick={() => {
|
||||||
|
handleChange(each.value)
|
||||||
|
setFocus(false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
</Container>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AISearchInput
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Harness, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface AIOption {
|
||||||
|
label: string
|
||||||
|
value: string
|
||||||
|
disabled?: boolean
|
||||||
|
}
|
|
@ -40,13 +40,14 @@ interface ButtonTabsProps<T> {
|
||||||
children: React.ReactElement<ButtonTabProps<T>, typeof ButtonTab>[]
|
children: React.ReactElement<ButtonTabProps<T>, typeof ButtonTab>[]
|
||||||
small?: boolean
|
small?: boolean
|
||||||
bold?: boolean
|
bold?: boolean
|
||||||
|
className?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ButtonTabs<T>(props: ButtonTabsProps<T>): JSX.Element {
|
export function ButtonTabs<T>(props: ButtonTabsProps<T>): JSX.Element {
|
||||||
const { children: tabs, small, id, selectedTabId, onChange, bold } = props
|
const { children: tabs, small, id, selectedTabId, onChange, bold, className } = props
|
||||||
const selectedTabPannel = tabs.find(each => each.props.id === selectedTabId)
|
const selectedTabPannel = tabs.find(each => each.props.id === selectedTabId)
|
||||||
return (
|
return (
|
||||||
<Container data-testid={id}>
|
<Container className={className} data-testid={id}>
|
||||||
<ButtonGroup className={css.btnGroup}>
|
<ButtonGroup className={css.btnGroup}>
|
||||||
{tabs.map(each => (
|
{tabs.map(each => (
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Harness, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { useStrings } from '@ar/frameworks/strings'
|
||||||
|
import type { EnvironmentType } from '@ar/common/types'
|
||||||
|
import { EnvironmentTypeList } from '@ar/common/constants'
|
||||||
|
import MultiSelectDropdownList from '@ar/components/MultiDropdownSelect/MultiDropdownSelect'
|
||||||
|
|
||||||
|
interface EnvironmentTypeSelectorProps {
|
||||||
|
value: EnvironmentType[]
|
||||||
|
onChange: (val: EnvironmentType[]) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function EnvironmentTypeSelector(props: EnvironmentTypeSelectorProps): JSX.Element {
|
||||||
|
const { value, onChange } = props
|
||||||
|
const { getString } = useStrings()
|
||||||
|
return (
|
||||||
|
<MultiSelectDropdownList
|
||||||
|
buttonTestId="environment-type-select"
|
||||||
|
items={EnvironmentTypeList.filter(each => !each.disabled).map(each => ({
|
||||||
|
...each,
|
||||||
|
label: getString(each.label)
|
||||||
|
}))}
|
||||||
|
value={value}
|
||||||
|
onSelect={onChange}
|
||||||
|
placeholder={getString('repositoryList.selectEnvironments')}
|
||||||
|
allowSearch
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
|
@ -31,7 +31,7 @@ interface PatternInputProps extends StyledProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function PatternInput<T>(props: PatternInputProps & { formik: FormikContextType<T> }): JSX.Element {
|
function PatternInput<T>(props: PatternInputProps & { formik: FormikContextType<T> }): JSX.Element {
|
||||||
const { label, name, placeholder, formik } = props
|
const { label, name, placeholder, formik, disabled } = props
|
||||||
|
|
||||||
const formValue = get(formik.values, name, []) as string[]
|
const formValue = get(formik.values, name, []) as string[]
|
||||||
|
|
||||||
|
@ -53,6 +53,7 @@ function PatternInput<T>(props: PatternInputProps & { formik: FormikContextType<
|
||||||
onChange={selectedItems => {
|
onChange={selectedItems => {
|
||||||
formik.setFieldValue(name, selectedItems)
|
formik.setFieldValue(name, selectedItems)
|
||||||
}}
|
}}
|
||||||
|
readonly={disabled}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)
|
)
|
||||||
|
|
|
@ -83,11 +83,13 @@ export default function IncludeExcludePatterns<T>(props: IncludeExcludePatternsP
|
||||||
label={includePatternListProps.label}
|
label={includePatternListProps.label}
|
||||||
name={includePatternListProps.name}
|
name={includePatternListProps.name}
|
||||||
placeholder={includePatternListProps.placeholder}
|
placeholder={includePatternListProps.placeholder}
|
||||||
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
<PatternInput
|
<PatternInput
|
||||||
label={excludePatternListProps.label}
|
label={excludePatternListProps.label}
|
||||||
name={excludePatternListProps.name}
|
name={excludePatternListProps.name}
|
||||||
placeholder={excludePatternListProps.placeholder}
|
placeholder={excludePatternListProps.placeholder}
|
||||||
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
|
|
|
@ -70,7 +70,7 @@ export const Description = (props: DescriptionComponentProps): JSX.Element => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Tags = (props: TagsComponentProps): JSX.Element => {
|
export const Tags = (props: TagsComponentProps): JSX.Element => {
|
||||||
const { tagsProps, hasValue, isOptional = true, disabled, name } = props
|
const { tagsProps = {}, hasValue, isOptional = true, disabled, name } = props
|
||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
const [isTagsOpen, setTagsOpen] = useState<boolean>(hasValue || false)
|
const [isTagsOpen, setTagsOpen] = useState<boolean>(hasValue || false)
|
||||||
|
|
||||||
|
@ -96,7 +96,17 @@ export const Tags = (props: TagsComponentProps): JSX.Element => {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Label>
|
</Label>
|
||||||
{isTagsOpen && <FormInput.KVTagInput name={name} isArray tagsProps={tagsProps} disabled={disabled} />}
|
{isTagsOpen && (
|
||||||
|
<FormInput.KVTagInput
|
||||||
|
name={name}
|
||||||
|
isArray
|
||||||
|
tagsProps={{
|
||||||
|
...tagsProps,
|
||||||
|
disabled
|
||||||
|
}}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,9 @@ import React, { useState } from 'react'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { Button, ButtonVariation, Layout, useToggleOpen } from '@harnessio/uicore'
|
import { Button, ButtonVariation, Layout, useToggleOpen } from '@harnessio/uicore'
|
||||||
|
|
||||||
|
import { useParentComponents } from '@ar/hooks'
|
||||||
import TagIcon from '@ar/components/MultiTagsInput/TagIcon'
|
import TagIcon from '@ar/components/MultiTagsInput/TagIcon'
|
||||||
|
import type { RbacButtonProps } from '@ar/__mocks__/components/RbacButton'
|
||||||
import MultiTagsInput from '@ar/components/MultiTagsInput/MultiTagsInput'
|
import MultiTagsInput from '@ar/components/MultiTagsInput/MultiTagsInput'
|
||||||
|
|
||||||
import css from './PageTitle.module.scss'
|
import css from './PageTitle.module.scss'
|
||||||
|
@ -27,14 +29,16 @@ interface ArtifactTagsProps {
|
||||||
labels: string[]
|
labels: string[]
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
onChange: (items: string[]) => Promise<boolean>
|
onChange: (items: string[]) => Promise<boolean>
|
||||||
|
permission?: RbacButtonProps['permission']
|
||||||
}
|
}
|
||||||
|
|
||||||
const EMPTY_TAG_VALUE = '+ Labels'
|
const EMPTY_TAG_VALUE = '+ Labels'
|
||||||
|
|
||||||
export default function ArtifactTags(props: ArtifactTagsProps): JSX.Element | null {
|
export default function ArtifactTags(props: ArtifactTagsProps): JSX.Element | null {
|
||||||
const { labels, onChange, placeholder } = props
|
const { labels, onChange, placeholder, permission } = props
|
||||||
const [selectedItems, setSelectedItems] = useState(labels)
|
const [selectedItems, setSelectedItems] = useState(labels)
|
||||||
const [query, setQuery] = useState('')
|
const [query, setQuery] = useState('')
|
||||||
|
const { RbacButton } = useParentComponents()
|
||||||
const { isOpen: isEdit, open, close } = useToggleOpen(false)
|
const { isOpen: isEdit, open, close } = useToggleOpen(false)
|
||||||
|
|
||||||
const handleOnSubmit = async () => {
|
const handleOnSubmit = async () => {
|
||||||
|
@ -83,13 +87,14 @@ export default function ArtifactTags(props: ArtifactTagsProps): JSX.Element | nu
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{!isEdit && !!selectedItems.length && (
|
{!isEdit && !!selectedItems.length && (
|
||||||
<Button
|
<RbacButton
|
||||||
className={css.iconBtn}
|
className={css.iconBtn}
|
||||||
minimal
|
minimal
|
||||||
iconProps={{ size: 20 }}
|
iconProps={{ size: 20 }}
|
||||||
variation={ButtonVariation.ICON}
|
variation={ButtonVariation.ICON}
|
||||||
icon="code-edit"
|
icon="code-edit"
|
||||||
onClick={() => open()}
|
onClick={() => open()}
|
||||||
|
permission={permission}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isEdit && (
|
{isEdit && (
|
||||||
|
@ -107,13 +112,14 @@ export default function ArtifactTags(props: ArtifactTagsProps): JSX.Element | nu
|
||||||
setQuery('')
|
setQuery('')
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button
|
<RbacButton
|
||||||
className={css.iconBtn}
|
className={css.iconBtn}
|
||||||
minimal
|
minimal
|
||||||
variation={ButtonVariation.ICON}
|
variation={ButtonVariation.ICON}
|
||||||
iconProps={{ size: 20 }}
|
iconProps={{ size: 20 }}
|
||||||
icon="small-tick"
|
icon="small-tick"
|
||||||
onClick={handleOnSubmit}
|
onClick={handleOnSubmit}
|
||||||
|
permission={permission}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -20,16 +20,35 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.nameCellContainer {
|
.nameCellContainer {
|
||||||
align-items: center;
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: var(--spacing-small);
|
||||||
}
|
}
|
||||||
|
|
||||||
.copyButton {
|
.copyUrlBtn {
|
||||||
& :global(.bp3-button-text) {
|
& :global(.bp3-button-text) {
|
||||||
margin-left: var(--spacing-small) !important;
|
padding-left: var(--spacing-xsmall);
|
||||||
font-size: 12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
& .copyUrlIcon {
|
& .copyUrlIcon {
|
||||||
|
--icon-padding: 0px !important;
|
||||||
rotate: 45deg;
|
rotate: 45deg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.copyButton {
|
||||||
|
--font-size: 12px !important;
|
||||||
|
--font-weight: 500 !important;
|
||||||
|
--padding: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deploymentsRow {
|
||||||
|
--typography-size: 11px;
|
||||||
|
--typography-weight: 700;
|
||||||
|
&.prod {
|
||||||
|
--intent-color: #2e2d96;
|
||||||
|
}
|
||||||
|
&.nonProd {
|
||||||
|
--intent-color: #07a0ab;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -17,6 +17,10 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// This is an auto-generated file
|
// This is an auto-generated file
|
||||||
export declare const copyButton: string
|
export declare const copyButton: string
|
||||||
|
export declare const copyUrlBtn: string
|
||||||
export declare const copyUrlIcon: string
|
export declare const copyUrlIcon: string
|
||||||
|
export declare const deploymentsRow: string
|
||||||
export declare const nameCellContainer: string
|
export declare const nameCellContainer: string
|
||||||
|
export declare const nonProd: string
|
||||||
|
export declare const prod: string
|
||||||
export declare const toggleAccordion: string
|
export declare const toggleAccordion: string
|
||||||
|
|
|
@ -15,13 +15,14 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { FC, PropsWithChildren, useState } from 'react'
|
import React, { FC, PropsWithChildren, useState } from 'react'
|
||||||
|
import classNames from 'classnames'
|
||||||
import { defaultTo } from 'lodash-es'
|
import { defaultTo } from 'lodash-es'
|
||||||
import copy from 'clipboard-copy'
|
import copy from 'clipboard-copy'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import type { TableExpandedToggleProps } from 'react-table'
|
import type { TableExpandedToggleProps } from 'react-table'
|
||||||
import { Button, ButtonVariation, Layout, Text } from '@harnessio/uicore'
|
import { Button, ButtonProps, ButtonVariation, Layout, Text } from '@harnessio/uicore'
|
||||||
import type { IconName, IconProps } from '@harnessio/icons'
|
import type { IconName, IconProps } from '@harnessio/icons'
|
||||||
import { Color } from '@harnessio/design-system'
|
import { Color, FontVariation } from '@harnessio/design-system'
|
||||||
|
|
||||||
import { killEvent } from '@ar/common/utils'
|
import { killEvent } from '@ar/common/utils'
|
||||||
import { useStrings } from '@ar/frameworks/strings/String'
|
import { useStrings } from '@ar/frameworks/strings/String'
|
||||||
|
@ -31,6 +32,8 @@ import RepositoryLocationBadge from '@ar/components/Badge/RepositoryLocationBadg
|
||||||
|
|
||||||
import { DefaultIconProps } from './constants'
|
import { DefaultIconProps } from './constants'
|
||||||
import { handleToggleExpandableRow } from './utils'
|
import { handleToggleExpandableRow } from './utils'
|
||||||
|
import CommandBlock from '../CommandBlock/CommandBlock'
|
||||||
|
import { NonProdTag, ProdTag } from '../Tag/Tags'
|
||||||
|
|
||||||
import css from './TableCells.module.scss'
|
import css from './TableCells.module.scss'
|
||||||
|
|
||||||
|
@ -67,10 +70,11 @@ export const CopyUrlCell: FC<PropsWithChildren<CopyUrlCellProps>> = ({ value, ch
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
className={css.copyButton}
|
className={classNames(css.copyButton, css.copyUrlBtn)}
|
||||||
intent="primary"
|
intent="primary"
|
||||||
minimal
|
minimal
|
||||||
icon="link"
|
icon="link"
|
||||||
|
variation={ButtonVariation.LINK}
|
||||||
iconProps={{ size: 12, className: css.copyUrlIcon }}
|
iconProps={{ size: 12, className: css.copyUrlIcon }}
|
||||||
onClick={evt => {
|
onClick={evt => {
|
||||||
killEvent(evt)
|
killEvent(evt)
|
||||||
|
@ -84,6 +88,46 @@ export const CopyUrlCell: FC<PropsWithChildren<CopyUrlCellProps>> = ({ value, ch
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CopyTextCellProps {
|
||||||
|
value: string
|
||||||
|
icon?: ButtonProps['rightIcon']
|
||||||
|
iconProps?: ButtonProps['iconProps']
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CopyTextCell: FC<PropsWithChildren<CopyTextCellProps>> = ({
|
||||||
|
value,
|
||||||
|
icon,
|
||||||
|
iconProps,
|
||||||
|
children
|
||||||
|
}): JSX.Element => {
|
||||||
|
const { getString } = useStrings()
|
||||||
|
const [openTooltip, setOpenTooltip] = useState(false)
|
||||||
|
const showCopySuccess = () => {
|
||||||
|
setOpenTooltip(true)
|
||||||
|
setTimeout(() => {
|
||||||
|
setOpenTooltip(false)
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
className={css.copyButton}
|
||||||
|
intent="primary"
|
||||||
|
minimal
|
||||||
|
variation={ButtonVariation.LINK}
|
||||||
|
rightIcon={defaultTo(icon, 'code-copy')}
|
||||||
|
iconProps={iconProps}
|
||||||
|
onClick={evt => {
|
||||||
|
killEvent(evt)
|
||||||
|
copy(value)
|
||||||
|
showCopySuccess()
|
||||||
|
}}
|
||||||
|
tooltip={getString('copied')}
|
||||||
|
tooltipProps={{ isOpen: openTooltip, isDark: true }}>
|
||||||
|
{children}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
interface RepositoryLocationBadgeProps {
|
interface RepositoryLocationBadgeProps {
|
||||||
value: RepositoryConfigType
|
value: RepositoryConfigType
|
||||||
}
|
}
|
||||||
|
@ -158,26 +202,58 @@ const ToggleAccordionCell = (props: ToggleAccordionCellProps): JSX.Element => {
|
||||||
|
|
||||||
interface LinkCellProps {
|
interface LinkCellProps {
|
||||||
label: string
|
label: string
|
||||||
|
subLabel?: string
|
||||||
prefix?: React.ReactElement
|
prefix?: React.ReactElement
|
||||||
postfix?: React.ReactElement
|
postfix?: React.ReactElement
|
||||||
linkTo: string
|
linkTo: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const LinkCell = (props: LinkCellProps): JSX.Element => {
|
const LinkCell = (props: LinkCellProps): JSX.Element => {
|
||||||
const { prefix, postfix, label, linkTo } = props
|
const { prefix, postfix, label, linkTo, subLabel } = props
|
||||||
return (
|
return (
|
||||||
<Layout.Horizontal className={css.nameCellContainer} spacing="small">
|
<Layout.Horizontal
|
||||||
|
className={css.nameCellContainer}
|
||||||
|
flex={{ justifyContent: 'flex-start', alignItems: 'flex-start' }}>
|
||||||
{prefix}
|
{prefix}
|
||||||
<Link to={linkTo}>
|
<Layout.Vertical>
|
||||||
<Text color={Color.PRIMARY_7} lineClamp={1}>
|
<Link to={linkTo}>
|
||||||
{label}
|
<Text color={Color.PRIMARY_7} lineClamp={1}>
|
||||||
</Text>
|
{label}
|
||||||
</Link>
|
</Text>
|
||||||
|
</Link>
|
||||||
|
{subLabel && <Text lineClamp={1}>{subLabel}</Text>}
|
||||||
|
</Layout.Vertical>
|
||||||
{postfix}
|
{postfix}
|
||||||
</Layout.Horizontal>
|
</Layout.Horizontal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DeploymentsCellProps {
|
||||||
|
prodCount?: number
|
||||||
|
nonProdCount?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DeploymentsCell = ({ prodCount, nonProdCount }: DeploymentsCellProps) => {
|
||||||
|
return (
|
||||||
|
<Layout.Horizontal spacing="xsmall">
|
||||||
|
<Layout.Horizontal spacing="small">
|
||||||
|
<Text font={{ variation: FontVariation.BODY }}>{defaultTo(prodCount, 0)}</Text>
|
||||||
|
<ProdTag />
|
||||||
|
</Layout.Horizontal>
|
||||||
|
<Layout.Horizontal spacing="small">
|
||||||
|
<Text font={{ variation: FontVariation.BODY }}>{defaultTo(nonProdCount, 0)}</Text>
|
||||||
|
<NonProdTag />
|
||||||
|
</Layout.Horizontal>
|
||||||
|
</Layout.Horizontal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PullCommandCell = ({ value }: CommonCellProps) => {
|
||||||
|
const { getString } = useStrings()
|
||||||
|
if (!value) return <>{getString('na')}</>
|
||||||
|
return <CommandBlock noWrap commandSnippet={value as string} allowCopy onCopy={killEvent} />
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
UrlCell,
|
UrlCell,
|
||||||
SizeCell,
|
SizeCell,
|
||||||
|
@ -185,6 +261,9 @@ export default {
|
||||||
LinkCell,
|
LinkCell,
|
||||||
TextCell,
|
TextCell,
|
||||||
CopyUrlCell,
|
CopyUrlCell,
|
||||||
|
CopyTextCell,
|
||||||
|
DeploymentsCell,
|
||||||
|
PullCommandCell,
|
||||||
LastModifiedCell,
|
LastModifiedCell,
|
||||||
ToggleAccordionCell,
|
ToggleAccordionCell,
|
||||||
RepositoryLocationBadgeCell
|
RepositoryLocationBadgeCell
|
||||||
|
|
|
@ -31,3 +31,19 @@
|
||||||
color: var(--grey-0) !important;
|
color: var(--grey-0) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prodTag {
|
||||||
|
background-color: var(--blue-50) !important;
|
||||||
|
color: var(--blue-900) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nonProdTag {
|
||||||
|
background-color: var(--teal-50) !important;
|
||||||
|
color: var(--team-900) !important;
|
||||||
|
}
|
||||||
|
|
|
@ -17,4 +17,7 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// This is an auto-generated file
|
// This is an auto-generated file
|
||||||
export declare const artifactTag: string
|
export declare const artifactTag: string
|
||||||
|
export declare const nonProdTag: string
|
||||||
|
export declare const prodTag: string
|
||||||
|
export declare const tag: string
|
||||||
export declare const versionTag: string
|
export declare const versionTag: string
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Harness, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import { Tag } from '@blueprintjs/core'
|
||||||
|
|
||||||
|
import { useStrings } from '@ar/frameworks/strings'
|
||||||
|
|
||||||
|
import css from './Tag.module.scss'
|
||||||
|
|
||||||
|
export function ProdTag() {
|
||||||
|
const { getString } = useStrings()
|
||||||
|
return (
|
||||||
|
<Tag className={classNames(css.tag, css.prodTag)} round>
|
||||||
|
{getString('prod')}
|
||||||
|
</Tag>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NonProdTag() {
|
||||||
|
const { getString } = useStrings()
|
||||||
|
return (
|
||||||
|
<Tag className={classNames(css.tag, css.nonProdTag)} round>
|
||||||
|
{getString('nonProd')}
|
||||||
|
</Tag>
|
||||||
|
)
|
||||||
|
}
|
|
@ -50,10 +50,10 @@ export default function DeleteRepositoryMenuItem({ repoKey }: ArtifactActionProp
|
||||||
onClick={handleDeleteService}
|
onClick={handleDeleteService}
|
||||||
permission={{
|
permission={{
|
||||||
resource: {
|
resource: {
|
||||||
resourceType: ResourceType.SERVICE,
|
resourceType: ResourceType.ARTIFACT_REGISTRY,
|
||||||
resourceIdentifier: repoKey
|
resourceIdentifier: repoKey
|
||||||
},
|
},
|
||||||
permission: PermissionIdentifier.DELETE_SERVICE
|
permission: PermissionIdentifier.DELETE_ARTIFACT_REGISTRY
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
@ -47,10 +47,10 @@ export default function EditRepositoryMenuItem({ repoKey }: ArtifactActionProps)
|
||||||
onClick={handleOpenRepository}
|
onClick={handleOpenRepository}
|
||||||
permission={{
|
permission={{
|
||||||
resource: {
|
resource: {
|
||||||
resourceType: ResourceType.SERVICE,
|
resourceType: ResourceType.ARTIFACT_REGISTRY,
|
||||||
resourceIdentifier: repoKey
|
resourceIdentifier: repoKey
|
||||||
},
|
},
|
||||||
permission: PermissionIdentifier.EDIT_SERVICE
|
permission: PermissionIdentifier.EDIT_ARTIFACT_REGISTRY
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -43,10 +43,10 @@ export default function SetupClientMenuItem({ data, repoKey }: ArtifactActionPro
|
||||||
onClick={showSetupClientModal}
|
onClick={showSetupClientModal}
|
||||||
permission={{
|
permission={{
|
||||||
resource: {
|
resource: {
|
||||||
resourceType: ResourceType.SERVICE,
|
resourceType: ResourceType.ARTIFACT_REGISTRY,
|
||||||
resourceIdentifier: defaultTo(repoKey, '')
|
resourceIdentifier: defaultTo(repoKey, '')
|
||||||
},
|
},
|
||||||
permission: PermissionIdentifier.DELETE_SERVICE
|
permission: PermissionIdentifier.VIEW_ARTIFACT_REGISTRY
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -30,6 +30,7 @@ import WeeklyDownloads from '@ar/components/PageTitle/WeeklyDownloads'
|
||||||
import CreatedAndModifiedAt from '@ar/components/PageTitle/CreatedAndModifiedAt'
|
import CreatedAndModifiedAt from '@ar/components/PageTitle/CreatedAndModifiedAt'
|
||||||
import ArtifactTags from '@ar/components/PageTitle/ArtifactTags'
|
import ArtifactTags from '@ar/components/PageTitle/ArtifactTags'
|
||||||
import NameAndDescription from '@ar/components/PageTitle/NameAndDescription'
|
import NameAndDescription from '@ar/components/PageTitle/NameAndDescription'
|
||||||
|
import { PermissionIdentifier, ResourceType } from '@ar/common/permissionTypes'
|
||||||
import { useSetupClientModal } from '@ar/pages/repository-details/hooks/useSetupClientModal/useSetupClientModal'
|
import { useSetupClientModal } from '@ar/pages/repository-details/hooks/useSetupClientModal/useSetupClientModal'
|
||||||
|
|
||||||
import RepositoryIcon from '@ar/frameworks/RepositoryStep/RepositoryIcon'
|
import RepositoryIcon from '@ar/frameworks/RepositoryStep/RepositoryIcon'
|
||||||
|
@ -109,6 +110,13 @@ function ArtifactDetailsHeaderContent(props: ArtifactDetailsHeaderContentProps):
|
||||||
onChange={handleUpdateArtifactLabels}
|
onChange={handleUpdateArtifactLabels}
|
||||||
labels={defaultTo(labels, [])}
|
labels={defaultTo(labels, [])}
|
||||||
placeholder={getString('artifactDetails.artifactLabelInputPlaceholder')}
|
placeholder={getString('artifactDetails.artifactLabelInputPlaceholder')}
|
||||||
|
permission={{
|
||||||
|
permission: PermissionIdentifier.EDIT_ARTIFACT_REGISTRY,
|
||||||
|
resource: {
|
||||||
|
resourceType: ResourceType.ARTIFACT_REGISTRY,
|
||||||
|
resourceIdentifier: repositoryIdentifier
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Layout.Vertical>
|
</Layout.Vertical>
|
||||||
</Layout.Horizontal>
|
</Layout.Horizontal>
|
||||||
|
|
|
@ -35,3 +35,12 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filterTabContainer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
& button {
|
||||||
|
--button-height: 32px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// This is an auto-generated file
|
// This is an auto-generated file
|
||||||
|
export declare const filterTabContainer: string
|
||||||
export declare const pageBody: string
|
export declare const pageBody: string
|
||||||
export declare const subHeader: string
|
export declare const subHeader: string
|
||||||
export declare const subHeaderItems: string
|
export declare const subHeaderItems: string
|
||||||
|
|
|
@ -14,47 +14,38 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useCallback, useMemo, useRef } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { flushSync } from 'react-dom'
|
|
||||||
import { defaultTo } from 'lodash-es'
|
|
||||||
import { Expander } from '@blueprintjs/core'
|
import { Expander } from '@blueprintjs/core'
|
||||||
|
import { HarnessDocTooltip, Page, Button, ButtonVariation } from '@harnessio/uicore'
|
||||||
import {
|
import {
|
||||||
ExpandingSearchInput,
|
GetAllHarnessArtifactsQueryQueryParams,
|
||||||
HarnessDocTooltip,
|
useGetAllHarnessArtifactsQuery
|
||||||
Page,
|
} from '@harnessio/react-har-service-client'
|
||||||
type ExpandingSearchInputHandle,
|
|
||||||
Button,
|
|
||||||
ButtonVariation
|
|
||||||
} from '@harnessio/uicore'
|
|
||||||
import { useGetAllArtifactsQuery } from '@harnessio/react-har-service-client'
|
|
||||||
|
|
||||||
import { useStrings } from '@ar/frameworks/strings'
|
import { useStrings } from '@ar/frameworks/strings'
|
||||||
import { DEFAULT_PAGE_INDEX, PreferenceScope } from '@ar/constants'
|
import { DEFAULT_PAGE_INDEX, PreferenceScope } from '@ar/constants'
|
||||||
|
import { ButtonTab, ButtonTabs } from '@ar/components/ButtonTabs/ButtonTabs'
|
||||||
import { useGetSpaceRef, useParentComponents, useParentHooks } from '@ar/hooks'
|
import { useGetSpaceRef, useParentComponents, useParentHooks } from '@ar/hooks'
|
||||||
import PackageTypeSelector from '@ar/components/PackageTypeSelector/PackageTypeSelector'
|
import PackageTypeSelector from '@ar/components/PackageTypeSelector/PackageTypeSelector'
|
||||||
import RepositorySelector from './components/RepositorySelector/RepositorySelector'
|
|
||||||
import ArtifactListTable from './components/ArtifactListTable/ArtifactListTable'
|
import { ArtifactListVersionFilter } from './constants'
|
||||||
import LabelsSelector from './components/LabelsSelector/LabelsSelector'
|
import LabelsSelector from './components/LabelsSelector/LabelsSelector'
|
||||||
|
import ArtifactListTable from './components/ArtifactListTable/ArtifactListTable'
|
||||||
|
import RepositorySelector from './components/RepositorySelector/RepositorySelector'
|
||||||
|
import ArtifactSearchInput from './components/ArtifactSearchInput/ArtifactSearchInput'
|
||||||
import { useArtifactListQueryParamOptions, type ArtifactListPageQueryParams } from './utils'
|
import { useArtifactListQueryParamOptions, type ArtifactListPageQueryParams } from './utils'
|
||||||
|
|
||||||
import css from './ArtifactListPage.module.scss'
|
import css from './ArtifactListPage.module.scss'
|
||||||
|
|
||||||
interface ArtifactListPageProps {
|
function ArtifactListPage(): JSX.Element {
|
||||||
withHeader?: boolean
|
|
||||||
parentRepoKey?: string
|
|
||||||
pageBodyClassName?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
function ArtifactListPage({ withHeader = true, parentRepoKey, pageBodyClassName }: ArtifactListPageProps): JSX.Element {
|
|
||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
const { NGBreadcrumbs } = useParentComponents()
|
const { NGBreadcrumbs } = useParentComponents()
|
||||||
const { useQueryParams, useUpdateQueryParams, usePreferenceStore } = useParentHooks()
|
const { useQueryParams, useUpdateQueryParams, usePreferenceStore } = useParentHooks()
|
||||||
const searchRef = useRef({} as ExpandingSearchInputHandle)
|
|
||||||
const { updateQueryParams } = useUpdateQueryParams<Partial<ArtifactListPageQueryParams>>()
|
const { updateQueryParams } = useUpdateQueryParams<Partial<ArtifactListPageQueryParams>>()
|
||||||
const queryParams = useQueryParams<ArtifactListPageQueryParams>(useArtifactListQueryParamOptions())
|
const queryParams = useQueryParams<ArtifactListPageQueryParams>(useArtifactListQueryParamOptions())
|
||||||
const { searchTerm, isDeployedArtifacts, packageTypes, repositoryKey, page, size, labels } = queryParams
|
const { searchTerm, isDeployedArtifacts, repositoryKey, page, size, latestVersion, packageTypes, labels } =
|
||||||
const shouldRenderRepositorySelectFilter = !parentRepoKey
|
queryParams
|
||||||
const shouldRenderPackageTypeSelectFilter = !parentRepoKey
|
|
||||||
const spaceRef = useGetSpaceRef('')
|
const spaceRef = useGetSpaceRef('')
|
||||||
|
|
||||||
const { preference: sortingPreference, setPreference: setSortingPreference } = usePreferenceStore<string | undefined>(
|
const { preference: sortingPreference, setPreference: setSortingPreference } = usePreferenceStore<string | undefined>(
|
||||||
|
@ -73,108 +64,108 @@ function ArtifactListPage({ withHeader = true, parentRepoKey, pageBodyClassName
|
||||||
refetch,
|
refetch,
|
||||||
isLoading: loading,
|
isLoading: loading,
|
||||||
error
|
error
|
||||||
} = useGetAllArtifactsQuery({
|
} = useGetAllHarnessArtifactsQuery({
|
||||||
space_ref: spaceRef,
|
space_ref: spaceRef,
|
||||||
queryParams: {
|
queryParams: {
|
||||||
page,
|
page,
|
||||||
size,
|
size,
|
||||||
label: labels,
|
|
||||||
package_type: packageTypes,
|
|
||||||
search_term: searchTerm,
|
search_term: searchTerm,
|
||||||
sort_field: sortField,
|
sort_field: sortField,
|
||||||
sort_order: sortOrder,
|
sort_order: sortOrder,
|
||||||
reg_identifier: defaultTo(parentRepoKey, repositoryKey)
|
reg_identifier: repositoryKey ? [repositoryKey] : undefined,
|
||||||
},
|
latest_version: latestVersion,
|
||||||
|
deployed_artifact: isDeployedArtifacts,
|
||||||
|
package_type: packageTypes,
|
||||||
|
label: labels
|
||||||
|
} as GetAllHarnessArtifactsQueryQueryParams,
|
||||||
stringifyQueryParamsOptions: {
|
stringifyQueryParamsOptions: {
|
||||||
arrayFormat: 'repeat'
|
arrayFormat: 'repeat'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleClearAllFilters = (): void => {
|
const handleClearAllFilters = (): void => {
|
||||||
flushSync(searchRef.current.clear)
|
|
||||||
updateQueryParams({
|
updateQueryParams({
|
||||||
page: 0,
|
page: 0,
|
||||||
searchTerm: '',
|
searchTerm: '',
|
||||||
packageTypes: [],
|
isDeployedArtifacts: false,
|
||||||
isDeployedArtifacts: false
|
latestVersion: false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleClickLabel = useCallback(
|
const hasFilter = !!searchTerm || isDeployedArtifacts || latestVersion
|
||||||
(val: string) => {
|
|
||||||
if (labels.includes(val)) return
|
|
||||||
updateQueryParams({
|
|
||||||
labels: [...labels, val],
|
|
||||||
page: DEFAULT_PAGE_INDEX
|
|
||||||
})
|
|
||||||
},
|
|
||||||
[labels]
|
|
||||||
)
|
|
||||||
|
|
||||||
const hasFilter = !!searchTerm || packageTypes.length || isDeployedArtifacts
|
|
||||||
const responseData = data?.content?.data
|
const responseData = data?.content?.data
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{withHeader && (
|
<Page.Header
|
||||||
<Page.Header
|
title={
|
||||||
title={
|
<div className="ng-tooltip-native">
|
||||||
<div className="ng-tooltip-native">
|
<h2 data-tooltip-id="artifactsPageHeading">{getString('artifactList.pageHeading')}</h2>
|
||||||
<h2 data-tooltip-id="artifactsPageHeading">{getString('artifactList.pageHeading')}</h2>
|
<HarnessDocTooltip tooltipId="artifactsPageHeading" useStandAlone={true} />
|
||||||
<HarnessDocTooltip tooltipId="artifactsPageHeading" useStandAlone={true} />
|
</div>
|
||||||
</div>
|
}
|
||||||
}
|
breadcrumbs={<NGBreadcrumbs links={[]} />}
|
||||||
breadcrumbs={<NGBreadcrumbs links={[]} />}
|
/>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Page.SubHeader className={css.subHeader}>
|
<Page.SubHeader className={css.subHeader}>
|
||||||
<div className={css.subHeaderItems}>
|
<div className={css.subHeaderItems}>
|
||||||
{shouldRenderRepositorySelectFilter && (
|
<ArtifactSearchInput
|
||||||
<RepositorySelector
|
searchTerm={searchTerm || ''}
|
||||||
value={repositoryKey}
|
onChange={text => {
|
||||||
onChange={val => {
|
updateQueryParams({ searchTerm: text || undefined, page: DEFAULT_PAGE_INDEX })
|
||||||
updateQueryParams({ repositoryKey: val, page: DEFAULT_PAGE_INDEX })
|
}}
|
||||||
}}
|
placeholder={getString('search')}
|
||||||
/>
|
/>
|
||||||
)}
|
<RepositorySelector
|
||||||
{shouldRenderPackageTypeSelectFilter && (
|
value={repositoryKey}
|
||||||
<PackageTypeSelector
|
onChange={val => {
|
||||||
value={packageTypes}
|
updateQueryParams({ repositoryKey: val, page: DEFAULT_PAGE_INDEX })
|
||||||
onChange={val => {
|
}}
|
||||||
updateQueryParams({ packageTypes: val, page: DEFAULT_PAGE_INDEX })
|
/>
|
||||||
}}
|
<PackageTypeSelector
|
||||||
/>
|
value={packageTypes}
|
||||||
)}
|
onChange={val => {
|
||||||
|
updateQueryParams({ packageTypes: val, page: DEFAULT_PAGE_INDEX })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<LabelsSelector
|
<LabelsSelector
|
||||||
value={labels}
|
value={labels}
|
||||||
onChange={val => {
|
onChange={val => {
|
||||||
updateQueryParams({ labels: val, page: DEFAULT_PAGE_INDEX })
|
updateQueryParams({ labels: val, page: DEFAULT_PAGE_INDEX })
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/* TODO: removed till BE support this filter */}
|
|
||||||
{/* <TableFilterCheckbox
|
|
||||||
value={isDeployedArtifacts}
|
|
||||||
label={getString('artifactList.deployedArtifacts')}
|
|
||||||
disabled={false}
|
|
||||||
onChange={val => {
|
|
||||||
updateQueryParams({ isDeployedArtifacts: val, page: DEFAULT_PAGE_INDEX })
|
|
||||||
}}
|
|
||||||
/> */}
|
|
||||||
<Expander />
|
<Expander />
|
||||||
<ExpandingSearchInput
|
<ButtonTabs
|
||||||
alwaysExpanded
|
className={css.filterTabContainer}
|
||||||
width={200}
|
small
|
||||||
placeholder={getString('search')}
|
bold
|
||||||
onChange={text => {
|
selectedTabId={
|
||||||
updateQueryParams({ searchTerm: text || undefined, page: DEFAULT_PAGE_INDEX })
|
latestVersion ? ArtifactListVersionFilter.LATEST_VERSION : ArtifactListVersionFilter.ALL_VERSION
|
||||||
}}
|
}
|
||||||
defaultValue={searchTerm}
|
onChange={newTab => {
|
||||||
ref={searchRef}
|
updateQueryParams({
|
||||||
/>
|
latestVersion: newTab === ArtifactListVersionFilter.LATEST_VERSION,
|
||||||
|
page: DEFAULT_PAGE_INDEX
|
||||||
|
})
|
||||||
|
}}>
|
||||||
|
<ButtonTab
|
||||||
|
id={ArtifactListVersionFilter.LATEST_VERSION}
|
||||||
|
icon="layers"
|
||||||
|
iconProps={{ size: 12 }}
|
||||||
|
panel={<></>}
|
||||||
|
title={getString('artifactList.table.latestVersions')}
|
||||||
|
/>
|
||||||
|
<ButtonTab
|
||||||
|
id={ArtifactListVersionFilter.ALL_VERSION}
|
||||||
|
icon="document"
|
||||||
|
iconProps={{ size: 12 }}
|
||||||
|
panel={<></>}
|
||||||
|
title={getString('artifactList.table.allVersions')}
|
||||||
|
/>
|
||||||
|
</ButtonTabs>
|
||||||
</div>
|
</div>
|
||||||
</Page.SubHeader>
|
</Page.SubHeader>
|
||||||
<Page.Body
|
<Page.Body
|
||||||
className={classNames(css.pageBody, pageBodyClassName)}
|
className={classNames(css.pageBody)}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
error={error?.message}
|
error={error?.message}
|
||||||
retryOnError={() => refetch()}
|
retryOnError={() => refetch()}
|
||||||
|
@ -199,7 +190,6 @@ function ArtifactListPage({ withHeader = true, parentRepoKey, pageBodyClassName
|
||||||
setSortingPreference(JSON.stringify(sortArray))
|
setSortingPreference(JSON.stringify(sortArray))
|
||||||
updateQueryParams({ sort: sortArray })
|
updateQueryParams({ sort: sortArray })
|
||||||
}}
|
}}
|
||||||
onClickLabel={handleClickLabel}
|
|
||||||
sortBy={sort}
|
sortBy={sort}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -0,0 +1,161 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Harness, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useCallback, useMemo, useRef } from 'react'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import { flushSync } from 'react-dom'
|
||||||
|
import { Expander } from '@blueprintjs/core'
|
||||||
|
import { ExpandingSearchInput, Page, type ExpandingSearchInputHandle, Button, ButtonVariation } from '@harnessio/uicore'
|
||||||
|
import { useGetAllArtifactsByRegistryQuery } from '@harnessio/react-har-service-client'
|
||||||
|
|
||||||
|
import { useStrings } from '@ar/frameworks/strings'
|
||||||
|
import { DEFAULT_PAGE_INDEX, PreferenceScope } from '@ar/constants'
|
||||||
|
import { useGetSpaceRef, useParentHooks } from '@ar/hooks'
|
||||||
|
import LabelsSelector from './components/LabelsSelector/LabelsSelector'
|
||||||
|
import {
|
||||||
|
useRegistryArtifactListQueryParamOptions,
|
||||||
|
type RegistryArtifactListPageQueryParams
|
||||||
|
} from './components/RegistryArtifactListTable/utils'
|
||||||
|
import RegistryArtifactListTable from './components/RegistryArtifactListTable/RegistryArtifactListTable'
|
||||||
|
|
||||||
|
import css from './ArtifactListPage.module.scss'
|
||||||
|
|
||||||
|
interface RegistryArtifactListPageProps {
|
||||||
|
pageBodyClassName?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function RegistryArtifactListPage({ pageBodyClassName }: RegistryArtifactListPageProps): JSX.Element {
|
||||||
|
const { getString } = useStrings()
|
||||||
|
const { useQueryParams, useUpdateQueryParams, usePreferenceStore } = useParentHooks()
|
||||||
|
const searchRef = useRef({} as ExpandingSearchInputHandle)
|
||||||
|
const { updateQueryParams } = useUpdateQueryParams<Partial<RegistryArtifactListPageQueryParams>>()
|
||||||
|
const queryParams = useQueryParams<RegistryArtifactListPageQueryParams>(useRegistryArtifactListQueryParamOptions())
|
||||||
|
const { searchTerm, isDeployedArtifacts, packageTypes, page, size, labels } = queryParams
|
||||||
|
const registryRef = useGetSpaceRef()
|
||||||
|
|
||||||
|
const { preference: sortingPreference, setPreference: setSortingPreference } = usePreferenceStore<string | undefined>(
|
||||||
|
PreferenceScope.USER,
|
||||||
|
'ArtifactRepositorySortingPreference'
|
||||||
|
)
|
||||||
|
const sort = useMemo(
|
||||||
|
() => (sortingPreference ? JSON.parse(sortingPreference) : queryParams.sort),
|
||||||
|
[queryParams.sort, sortingPreference]
|
||||||
|
)
|
||||||
|
|
||||||
|
const [sortField, sortOrder] = sort || []
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
refetch,
|
||||||
|
isLoading: loading,
|
||||||
|
error
|
||||||
|
} = useGetAllArtifactsByRegistryQuery({
|
||||||
|
registry_ref: registryRef,
|
||||||
|
queryParams: {
|
||||||
|
page,
|
||||||
|
size,
|
||||||
|
search_term: searchTerm,
|
||||||
|
sort_field: sortField,
|
||||||
|
sort_order: sortOrder
|
||||||
|
},
|
||||||
|
stringifyQueryParamsOptions: {
|
||||||
|
arrayFormat: 'repeat'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleClearAllFilters = (): void => {
|
||||||
|
flushSync(searchRef.current.clear)
|
||||||
|
updateQueryParams({
|
||||||
|
page: 0,
|
||||||
|
searchTerm: '',
|
||||||
|
packageTypes: [],
|
||||||
|
isDeployedArtifacts: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClickLabel = useCallback(
|
||||||
|
(val: string) => {
|
||||||
|
if (labels.includes(val)) return
|
||||||
|
updateQueryParams({
|
||||||
|
labels: [...labels, val],
|
||||||
|
page: DEFAULT_PAGE_INDEX
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[labels]
|
||||||
|
)
|
||||||
|
|
||||||
|
const hasFilter = !!searchTerm || packageTypes.length || isDeployedArtifacts
|
||||||
|
const responseData = data?.content?.data
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Page.SubHeader className={css.subHeader}>
|
||||||
|
<div className={css.subHeaderItems}>
|
||||||
|
<LabelsSelector
|
||||||
|
value={labels}
|
||||||
|
onChange={val => {
|
||||||
|
updateQueryParams({ labels: val, page: DEFAULT_PAGE_INDEX })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Expander />
|
||||||
|
<ExpandingSearchInput
|
||||||
|
alwaysExpanded
|
||||||
|
width={200}
|
||||||
|
placeholder={getString('search')}
|
||||||
|
onChange={text => {
|
||||||
|
updateQueryParams({ searchTerm: text || undefined, page: DEFAULT_PAGE_INDEX })
|
||||||
|
}}
|
||||||
|
defaultValue={searchTerm}
|
||||||
|
ref={searchRef}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Page.SubHeader>
|
||||||
|
<Page.Body
|
||||||
|
className={classNames(css.pageBody, pageBodyClassName)}
|
||||||
|
loading={loading}
|
||||||
|
error={error?.message}
|
||||||
|
retryOnError={() => refetch()}
|
||||||
|
noData={{
|
||||||
|
when: () => !responseData?.artifacts?.length,
|
||||||
|
// image: getEmptyStateIllustration(hasFilter, module),
|
||||||
|
icon: 'container',
|
||||||
|
messageTitle: hasFilter ? getString('noResultsFound') : getString('artifactList.table.noArtifactsTitle'),
|
||||||
|
button: hasFilter ? (
|
||||||
|
<Button text={getString('clearFilters')} variation={ButtonVariation.LINK} onClick={handleClearAllFilters} />
|
||||||
|
) : undefined
|
||||||
|
}}>
|
||||||
|
{responseData && (
|
||||||
|
<RegistryArtifactListTable
|
||||||
|
data={responseData}
|
||||||
|
gotoPage={pageNumber => updateQueryParams({ page: pageNumber })}
|
||||||
|
onPageSizeChange={newSize => updateQueryParams({ size: newSize, page: DEFAULT_PAGE_INDEX })}
|
||||||
|
refetchList={() => {
|
||||||
|
refetch()
|
||||||
|
}}
|
||||||
|
setSortBy={sortArray => {
|
||||||
|
setSortingPreference(JSON.stringify(sortArray))
|
||||||
|
updateQueryParams({ sort: sortArray })
|
||||||
|
}}
|
||||||
|
onClickLabel={handleClickLabel}
|
||||||
|
sortBy={sort}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Page.Body>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RegistryArtifactListPage
|
|
@ -34,7 +34,7 @@
|
||||||
div[class*='TableV2--cells'],
|
div[class*='TableV2--cells'],
|
||||||
div[class*='TableV2--header'] {
|
div[class*='TableV2--header'] {
|
||||||
display: grid !important;
|
display: grid !important;
|
||||||
grid-template-columns: 1fr 15rem 20rem 15rem;
|
grid-template-columns: 1fr 10rem 10rem 15rem 15rem 15rem 50px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,3 +45,11 @@
|
||||||
.nameCellContainer {
|
.nameCellContainer {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cellBtn {
|
||||||
|
--padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.optionsMenu {
|
||||||
|
min-width: unset;
|
||||||
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// This is an auto-generated file
|
// This is an auto-generated file
|
||||||
|
export declare const cellBtn: string
|
||||||
export declare const nameCellContainer: string
|
export declare const nameCellContainer: string
|
||||||
|
export declare const optionsMenu: string
|
||||||
export declare const table: string
|
export declare const table: string
|
||||||
export declare const tableRow: string
|
export declare const tableRow: string
|
||||||
|
|
|
@ -23,10 +23,12 @@ import type { ArtifactMetadata, ListArtifact } from '@harnessio/react-har-servic
|
||||||
import { useStrings } from '@ar/frameworks/strings'
|
import { useStrings } from '@ar/frameworks/strings'
|
||||||
import { useParentHooks } from '@ar/hooks'
|
import { useParentHooks } from '@ar/hooks'
|
||||||
import {
|
import {
|
||||||
|
ArtifactDeploymentsCell,
|
||||||
ArtifactDownloadsCell,
|
ArtifactDownloadsCell,
|
||||||
|
ArtifactListPullCommandCell,
|
||||||
|
ArtifactListVulnerabilitiesCell,
|
||||||
ArtifactNameCell,
|
ArtifactNameCell,
|
||||||
LatestArtifactCell,
|
LatestArtifactCell
|
||||||
RepositoryNameCell
|
|
||||||
} from './ArtifactListTableCell'
|
} from './ArtifactListTableCell'
|
||||||
import css from './ArtifactListTable.module.scss'
|
import css from './ArtifactListTable.module.scss'
|
||||||
|
|
||||||
|
@ -40,11 +42,10 @@ export interface ArtifactListTableProps extends ArtifactListColumnActions {
|
||||||
setSortBy: (sortBy: string[]) => void
|
setSortBy: (sortBy: string[]) => void
|
||||||
sortBy: string[]
|
sortBy: string[]
|
||||||
minimal?: boolean
|
minimal?: boolean
|
||||||
onClickLabel: (val: string) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ArtifactListTable(props: ArtifactListTableProps): JSX.Element {
|
export default function ArtifactListTable(props: ArtifactListTableProps): JSX.Element {
|
||||||
const { data, gotoPage, onPageSizeChange, sortBy, setSortBy, onClickLabel } = props
|
const { data, gotoPage, onPageSizeChange, sortBy, setSortBy } = props
|
||||||
const { useDefaultPaginationProps } = useParentHooks()
|
const { useDefaultPaginationProps } = useParentHooks()
|
||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
|
|
||||||
|
@ -72,17 +73,16 @@ export default function ArtifactListTable(props: ArtifactListTableProps): JSX.El
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
Header: getString('artifactList.table.columns.name'),
|
Header: getString('artifactList.table.columns.artifactName'),
|
||||||
accessor: 'name',
|
accessor: 'name',
|
||||||
Cell: ArtifactNameCell,
|
Cell: ArtifactNameCell,
|
||||||
serverSortProps: getServerSortProps('name'),
|
serverSortProps: getServerSortProps('name')
|
||||||
onClickLabel
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: getString('artifactList.table.columns.repository'),
|
Header: getString('artifactList.table.columns.pullCommand'),
|
||||||
accessor: 'registryIdentifier',
|
accessor: 'pullCommand',
|
||||||
Cell: RepositoryNameCell,
|
Cell: ArtifactListPullCommandCell,
|
||||||
serverSortProps: getServerSortProps('registryIdentifier')
|
disableSortBy: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: getString('artifactList.table.columns.downloads'),
|
Header: getString('artifactList.table.columns.downloads'),
|
||||||
|
@ -91,13 +91,25 @@ export default function ArtifactListTable(props: ArtifactListTableProps): JSX.El
|
||||||
serverSortProps: getServerSortProps('downloadsCount')
|
serverSortProps: getServerSortProps('downloadsCount')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: getString('artifactList.table.columns.latestVersion'),
|
Header: getString('artifactList.table.columns.environments'),
|
||||||
accessor: 'latestVersion',
|
accessor: 'environments',
|
||||||
|
Cell: ArtifactDeploymentsCell,
|
||||||
|
disableSortBy: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: getString('artifactList.table.columns.sto'),
|
||||||
|
accessor: 'sto',
|
||||||
|
Cell: ArtifactListVulnerabilitiesCell,
|
||||||
|
disableSortBy: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: getString('artifactList.table.columns.lastUpdated'),
|
||||||
|
accessor: 'lastUpdated',
|
||||||
Cell: LatestArtifactCell,
|
Cell: LatestArtifactCell,
|
||||||
serverSortProps: getServerSortProps('latestVersion')
|
serverSortProps: getServerSortProps('lastUpdated')
|
||||||
}
|
}
|
||||||
].filter(Boolean) as unknown as Column<ArtifactMetadata>[]
|
].filter(Boolean) as unknown as Column<ArtifactMetadata>[]
|
||||||
}, [currentOrder, currentSort, getString, onClickLabel])
|
}, [currentOrder, currentSort, getString])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableV2<ArtifactMetadata>
|
<TableV2<ArtifactMetadata>
|
||||||
|
|
|
@ -14,22 +14,25 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react'
|
import React, { useState } from 'react'
|
||||||
import { defaultTo } from 'lodash-es'
|
import { defaultTo } from 'lodash-es'
|
||||||
import { Position } from '@blueprintjs/core'
|
import { Link, useHistory } from 'react-router-dom'
|
||||||
|
import { Menu, Position } from '@blueprintjs/core'
|
||||||
|
import { Color, FontVariation } from '@harnessio/design-system'
|
||||||
|
import { Button, ButtonVariation, Layout, Text } from '@harnessio/uicore'
|
||||||
import type { Cell, CellValue, ColumnInstance, Renderer, Row, TableInstance } from 'react-table'
|
import type { Cell, CellValue, ColumnInstance, Renderer, Row, TableInstance } from 'react-table'
|
||||||
import { Layout, Text } from '@harnessio/uicore'
|
import type { ArtifactMetadata, StoDigestMetadata } from '@harnessio/react-har-service-client'
|
||||||
import { Color } from '@harnessio/design-system'
|
|
||||||
import type { ArtifactMetadata } from '@harnessio/react-har-service-client'
|
|
||||||
|
|
||||||
import { useRoutes } from '@ar/hooks'
|
import { useParentComponents, useRoutes } from '@ar/hooks'
|
||||||
import Tag from '@ar/components/Tag/Tag'
|
|
||||||
import { useStrings } from '@ar/frameworks/strings'
|
|
||||||
import TableCells from '@ar/components/TableCells/TableCells'
|
import TableCells from '@ar/components/TableCells/TableCells'
|
||||||
|
import type { RepositoryPackageType } from '@ar/common/types'
|
||||||
import LabelsPopover from '@ar/components/LabelsPopover/LabelsPopover'
|
import LabelsPopover from '@ar/components/LabelsPopover/LabelsPopover'
|
||||||
import RepositoryIcon from '@ar/frameworks/RepositoryStep/RepositoryIcon'
|
import RepositoryIcon from '@ar/frameworks/RepositoryStep/RepositoryIcon'
|
||||||
import type { RepositoryPackageType } from '@ar/common/types'
|
import { useStrings } from '@ar/frameworks/strings'
|
||||||
import { RepositoryDetailsTab } from '@ar/pages/repository-details/constants'
|
import { getShortDigest } from '@ar/pages/digest-list/utils'
|
||||||
|
import { VersionDetailsTab } from '@ar/pages/version-details/components/VersionDetailsTabs/constants'
|
||||||
|
|
||||||
|
import css from './ArtifactListTable.module.scss'
|
||||||
|
|
||||||
type CellTypeWithActions<D extends Record<string, any>, V = any> = TableInstance<D> & {
|
type CellTypeWithActions<D extends Record<string, any>, V = any> = TableInstance<D> & {
|
||||||
column: ColumnInstance<D>
|
column: ColumnInstance<D>
|
||||||
|
@ -51,62 +54,37 @@ export const ArtifactNameCell: Renderer<{
|
||||||
const { original } = row
|
const { original } = row
|
||||||
const { onClickLabel } = column
|
const { onClickLabel } = column
|
||||||
const routes = useRoutes()
|
const routes = useRoutes()
|
||||||
const value = original.name
|
const { name: value, version } = original
|
||||||
return (
|
return (
|
||||||
<TableCells.LinkCell
|
<Layout.Vertical>
|
||||||
prefix={<RepositoryIcon packageType={original.packageType as RepositoryPackageType} iconProps={{ size: 24 }} />}
|
<TableCells.LinkCell
|
||||||
linkTo={routes.toARArtifactDetails({
|
prefix={<RepositoryIcon packageType={original.packageType as RepositoryPackageType} iconProps={{ size: 24 }} />}
|
||||||
repositoryIdentifier: original.registryIdentifier,
|
linkTo={routes.toARVersionDetailsTab({
|
||||||
artifactIdentifier: value
|
repositoryIdentifier: original.registryIdentifier,
|
||||||
})}
|
artifactIdentifier: value,
|
||||||
label={value}
|
versionIdentifier: defaultTo(version, ''),
|
||||||
postfix={
|
versionTab: VersionDetailsTab.OVERVIEW
|
||||||
<LabelsPopover
|
})}
|
||||||
popoverProps={{
|
label={value}
|
||||||
position: Position.RIGHT
|
subLabel={version}
|
||||||
}}
|
postfix={
|
||||||
labels={defaultTo(original.labels, [])}
|
<LabelsPopover
|
||||||
tagProps={{
|
popoverProps={{
|
||||||
interactive: true,
|
position: Position.RIGHT
|
||||||
onClick: e => onClickLabel(e.currentTarget.ariaValueText as string)
|
}}
|
||||||
}}
|
labels={defaultTo(original.labels, [])}
|
||||||
/>
|
tagProps={{
|
||||||
}
|
interactive: true,
|
||||||
/>
|
onClick: e => {
|
||||||
)
|
if (typeof onClickLabel === 'function') {
|
||||||
}
|
onClickLabel(e.currentTarget.ariaValueText as string)
|
||||||
|
}
|
||||||
export const ArtifactTagsCell: CellType = ({ value }) => {
|
}
|
||||||
const { getString } = useStrings()
|
}}
|
||||||
if (!Array.isArray(value) || !value.length) {
|
/>
|
||||||
return (
|
}
|
||||||
<Text color={Color.GREY_900} font={{ size: 'small' }}>
|
/>
|
||||||
{getString('na')}
|
</Layout.Vertical>
|
||||||
</Text>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Layout.Horizontal spacing="small">
|
|
||||||
{Array.isArray(value) &&
|
|
||||||
value.map(each => (
|
|
||||||
<Tag key={each} isArtifactTag>
|
|
||||||
{each}
|
|
||||||
</Tag>
|
|
||||||
))}
|
|
||||||
</Layout.Horizontal>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const RepositoryNameCell: CellType = ({ value }) => {
|
|
||||||
const routes = useRoutes()
|
|
||||||
return (
|
|
||||||
<TableCells.LinkCell
|
|
||||||
linkTo={routes.toARRepositoryDetails({
|
|
||||||
repositoryIdentifier: value,
|
|
||||||
tab: RepositoryDetailsTab.PACKAGES
|
|
||||||
})}
|
|
||||||
label={value}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,23 +92,107 @@ export const ArtifactDownloadsCell: CellType = ({ value }) => {
|
||||||
return <TableCells.CountCell value={value} icon="download-box" iconProps={{ size: 12 }} />
|
return <TableCells.CountCell value={value} icon="download-box" iconProps={{ size: 12 }} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LatestArtifactCell: CellType = ({ row }) => {
|
export const ArtifactDeploymentsCell: CellType = ({ row }) => {
|
||||||
const { getString } = useStrings()
|
|
||||||
const { original } = row
|
const { original } = row
|
||||||
const { latestVersion, lastModified } = original || {}
|
const { deploymentMetadata } = original
|
||||||
if (!latestVersion) {
|
const { nonProdEnvCount, prodEnvCount } = deploymentMetadata || {}
|
||||||
|
return <TableCells.DeploymentsCell prodCount={prodEnvCount} nonProdCount={nonProdEnvCount} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ArtifactListPullCommandCell: CellType = ({ value }) => {
|
||||||
|
const { getString } = useStrings()
|
||||||
|
return <TableCells.CopyTextCell value={value}>{getString('copy')}</TableCells.CopyTextCell>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ArtifactListVulnerabilitiesCell: CellType = ({ row }) => {
|
||||||
|
const { original } = row
|
||||||
|
const { stoMetadata, registryIdentifier, name, version } = original
|
||||||
|
const { scannedCount, totalCount, digestMetadata } = stoMetadata || {}
|
||||||
|
const [isOptionsOpen, setIsOptionsOpen] = useState(false)
|
||||||
|
const { getString } = useStrings()
|
||||||
|
const { RbacMenuItem } = useParentComponents()
|
||||||
|
const routes = useRoutes()
|
||||||
|
const history = useHistory()
|
||||||
|
|
||||||
|
const handleRenderDigestMenuItem = (digest: StoDigestMetadata) => {
|
||||||
return (
|
return (
|
||||||
<Text color={Color.GREY_900} font={{ size: 'small' }}>
|
<RbacMenuItem
|
||||||
{getString('na')}
|
text={getString('artifactList.table.actions.VulnerabilityStatus.digestMenuItemText', {
|
||||||
</Text>
|
archName: digest.osArch,
|
||||||
|
digest: getShortDigest(digest.digest || '')
|
||||||
|
})}
|
||||||
|
onClick={() => {
|
||||||
|
const url = routes.toARVersionDetailsTab({
|
||||||
|
repositoryIdentifier: registryIdentifier,
|
||||||
|
artifactIdentifier: name,
|
||||||
|
versionIdentifier: version as string,
|
||||||
|
versionTab: VersionDetailsTab.SECURITY_TESTS,
|
||||||
|
pipelineIdentifier: digest.stoPipelineId,
|
||||||
|
executionIdentifier: digest.stoExecutionId
|
||||||
|
})
|
||||||
|
history.push(`${url}?digest=${digest.digest}`)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!scannedCount) {
|
||||||
|
return <Text>{getString('artifactList.table.actions.VulnerabilityStatus.nonScanned')}</Text>
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Vertical spacing="small">
|
<Button
|
||||||
<Text color={Color.PRIMARY_7} font={{ size: 'small' }}>
|
className={css.cellBtn}
|
||||||
{latestVersion}
|
tooltip={
|
||||||
|
<Menu
|
||||||
|
className={css.optionsMenu}
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation()
|
||||||
|
setIsOptionsOpen(false)
|
||||||
|
}}>
|
||||||
|
{digestMetadata?.map(handleRenderDigestMenuItem)}
|
||||||
|
</Menu>
|
||||||
|
}
|
||||||
|
tooltipProps={{
|
||||||
|
interactionKind: 'click',
|
||||||
|
onInteraction: nextOpenState => {
|
||||||
|
setIsOptionsOpen(nextOpenState)
|
||||||
|
},
|
||||||
|
isOpen: isOptionsOpen,
|
||||||
|
position: Position.BOTTOM
|
||||||
|
}}
|
||||||
|
variation={ButtonVariation.LINK}>
|
||||||
|
<Text font={{ variation: FontVariation.BODY }} color={Color.PRIMARY_7}>
|
||||||
|
{getString('artifactList.table.actions.VulnerabilityStatus.partiallyScanned', {
|
||||||
|
total: defaultTo(totalCount, 0),
|
||||||
|
scanned: defaultTo(scannedCount, 0)
|
||||||
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
<TableCells.LastModifiedCell value={defaultTo(lastModified, 0)} />
|
</Button>
|
||||||
</Layout.Vertical>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ScanStatusCell: CellType = ({ row }) => {
|
||||||
|
const { original } = row
|
||||||
|
const router = useRoutes()
|
||||||
|
const { version = '', name, registryIdentifier } = original
|
||||||
|
const { getString } = useStrings()
|
||||||
|
const linkTo = router.toARVersionDetailsTab({
|
||||||
|
repositoryIdentifier: registryIdentifier,
|
||||||
|
artifactIdentifier: name,
|
||||||
|
versionIdentifier: version,
|
||||||
|
versionTab: VersionDetailsTab.OVERVIEW
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<Link to={linkTo} target="_blank">
|
||||||
|
<Text color={Color.PRIMARY_7} rightIcon="main-share" rightIconProps={{ size: 12, color: Color.PRIMARY_7 }}>
|
||||||
|
{getString('artifactList.table.actions.VulnerabilityStatus.scanStatus')}
|
||||||
|
</Text>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LatestArtifactCell: CellType = ({ row }) => {
|
||||||
|
const { original } = row
|
||||||
|
return <TableCells.LastModifiedCell value={defaultTo(original.lastModified, 0)} />
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Harness, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import AISearchInput from '@ar/components/AISearchInput/AISearchInput'
|
||||||
|
import { ArtifactListAIOptions } from './constants'
|
||||||
|
|
||||||
|
interface ArtifactSearchInputProps {
|
||||||
|
searchTerm: string
|
||||||
|
onChange: (val: string) => void
|
||||||
|
placeholder?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ArtifactSearchInput(props: ArtifactSearchInputProps) {
|
||||||
|
return <AISearchInput {...props} options={ArtifactListAIOptions} />
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Harness, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { AIOption } from '@ar/components/AISearchInput/types'
|
||||||
|
|
||||||
|
export const ArtifactListAIOptions: AIOption[] = [
|
||||||
|
{
|
||||||
|
label: 'Need help? Here are some prompts example:',
|
||||||
|
value: '-1',
|
||||||
|
disabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Show me artifacts not downloaded in last 30 days',
|
||||||
|
value: 'Show me artifacts not downloaded in last 30 days'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Find out artifacts in prod but not deployed in lower environments',
|
||||||
|
value: 'Find out artifacts in prod but not deployed in lower environments'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'List artifacts in prod but with critical vulnerabilities',
|
||||||
|
value: 'List artifacts in prod but with critical vulnerabilities'
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Harness, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.table {
|
||||||
|
--typography-size: 12px;
|
||||||
|
--typography-weight: 400;
|
||||||
|
--line-height: 20px;
|
||||||
|
padding: var(--spacing-large);
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: max-content 1fr max-content;
|
||||||
|
row-gap: var(--spacing-small);
|
||||||
|
min-height: inherit;
|
||||||
|
|
||||||
|
[role='cell'],
|
||||||
|
[role='columnheader'] {
|
||||||
|
width: auto !important;
|
||||||
|
padding-right: var(--spacing-small);
|
||||||
|
}
|
||||||
|
|
||||||
|
div[class*='TableV2--cells'],
|
||||||
|
div[class*='TableV2--header'] {
|
||||||
|
display: grid !important;
|
||||||
|
grid-template-columns: 1fr 15rem 20rem 15rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tableRow {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nameCellContainer {
|
||||||
|
align-items: center;
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 Harness, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable */
|
||||||
|
// This is an auto-generated file
|
||||||
|
export declare const nameCellContainer: string
|
||||||
|
export declare const table: string
|
||||||
|
export declare const tableRow: string
|
|
@ -0,0 +1,112 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Harness, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import type { Column } from 'react-table'
|
||||||
|
import { PaginationProps, TableV2 } from '@harnessio/uicore'
|
||||||
|
import type { ArtifactMetadata, ListArtifact } from '@harnessio/react-har-service-client'
|
||||||
|
|
||||||
|
import { useStrings } from '@ar/frameworks/strings'
|
||||||
|
import { useParentHooks } from '@ar/hooks'
|
||||||
|
import {
|
||||||
|
RegistryArtifactDownloadsCell,
|
||||||
|
RegistryArtifactLatestUpdatedCell,
|
||||||
|
RegistryArtifactNameCell,
|
||||||
|
RepositoryNameCell
|
||||||
|
} from './RegistryArtifactListTableCell'
|
||||||
|
import css from './RegistryArtifactListTable.module.scss'
|
||||||
|
|
||||||
|
export interface RegistryArtifactListColumnActions {
|
||||||
|
refetchList?: () => void
|
||||||
|
}
|
||||||
|
export interface RegistryArtifactListTableProps extends RegistryArtifactListColumnActions {
|
||||||
|
data: ListArtifact
|
||||||
|
gotoPage: (pageNumber: number) => void
|
||||||
|
onPageSizeChange?: PaginationProps['onPageSizeChange']
|
||||||
|
setSortBy: (sortBy: string[]) => void
|
||||||
|
sortBy: string[]
|
||||||
|
minimal?: boolean
|
||||||
|
onClickLabel: (val: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RegistryArtifactListTable(props: RegistryArtifactListTableProps): JSX.Element {
|
||||||
|
const { data, gotoPage, onPageSizeChange, sortBy, setSortBy, onClickLabel } = props
|
||||||
|
const { useDefaultPaginationProps } = useParentHooks()
|
||||||
|
const { getString } = useStrings()
|
||||||
|
|
||||||
|
const { artifacts = [], itemCount = 0, pageCount = 0, pageIndex, pageSize = 0 } = data || {}
|
||||||
|
const paginationProps = useDefaultPaginationProps({
|
||||||
|
itemCount,
|
||||||
|
pageSize,
|
||||||
|
pageCount,
|
||||||
|
pageIndex,
|
||||||
|
gotoPage,
|
||||||
|
onPageSizeChange
|
||||||
|
})
|
||||||
|
const [currentSort, currentOrder] = sortBy
|
||||||
|
|
||||||
|
const columns: Column<ArtifactMetadata>[] = React.useMemo(() => {
|
||||||
|
const getServerSortProps = (id: string) => {
|
||||||
|
return {
|
||||||
|
enableServerSort: true,
|
||||||
|
isServerSorted: currentSort === id,
|
||||||
|
isServerSortedDesc: currentOrder === 'DESC',
|
||||||
|
getSortedColumn: ({ sort }: any) => {
|
||||||
|
setSortBy([sort, currentOrder === 'DESC' ? 'ASC' : 'DESC'])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
Header: getString('artifactList.table.columns.name'),
|
||||||
|
accessor: 'name',
|
||||||
|
Cell: RegistryArtifactNameCell,
|
||||||
|
serverSortProps: getServerSortProps('name'),
|
||||||
|
onClickLabel
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: getString('artifactList.table.columns.repository'),
|
||||||
|
accessor: 'registryIdentifier',
|
||||||
|
Cell: RepositoryNameCell,
|
||||||
|
serverSortProps: getServerSortProps('registryIdentifier')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: getString('artifactList.table.columns.downloads'),
|
||||||
|
accessor: 'downloadsCount',
|
||||||
|
Cell: RegistryArtifactDownloadsCell,
|
||||||
|
serverSortProps: getServerSortProps('downloadsCount')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: getString('artifactList.table.columns.latestVersion'),
|
||||||
|
accessor: 'latestVersion',
|
||||||
|
Cell: RegistryArtifactLatestUpdatedCell,
|
||||||
|
serverSortProps: getServerSortProps('latestVersion')
|
||||||
|
}
|
||||||
|
].filter(Boolean) as unknown as Column<ArtifactMetadata>[]
|
||||||
|
}, [currentOrder, currentSort, getString, onClickLabel])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableV2<ArtifactMetadata>
|
||||||
|
className={classNames(css.table)}
|
||||||
|
columns={columns}
|
||||||
|
data={artifacts}
|
||||||
|
pagination={paginationProps}
|
||||||
|
sortable
|
||||||
|
getRowClassName={() => css.tableRow}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Harness, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import { defaultTo } from 'lodash-es'
|
||||||
|
import { Position } from '@blueprintjs/core'
|
||||||
|
import type { Cell, CellValue, ColumnInstance, Renderer, Row, TableInstance } from 'react-table'
|
||||||
|
import { Layout, Text } from '@harnessio/uicore'
|
||||||
|
import { Color } from '@harnessio/design-system'
|
||||||
|
import type { RegistryArtifactMetadata } from '@harnessio/react-har-service-client'
|
||||||
|
|
||||||
|
import { useRoutes } from '@ar/hooks'
|
||||||
|
import Tag from '@ar/components/Tag/Tag'
|
||||||
|
import { useStrings } from '@ar/frameworks/strings'
|
||||||
|
import TableCells from '@ar/components/TableCells/TableCells'
|
||||||
|
import LabelsPopover from '@ar/components/LabelsPopover/LabelsPopover'
|
||||||
|
import RepositoryIcon from '@ar/frameworks/RepositoryStep/RepositoryIcon'
|
||||||
|
import type { RepositoryPackageType } from '@ar/common/types'
|
||||||
|
import { RepositoryDetailsTab } from '@ar/pages/repository-details/constants'
|
||||||
|
|
||||||
|
type CellTypeWithActions<D extends Record<string, any>, V = any> = TableInstance<D> & {
|
||||||
|
column: ColumnInstance<D>
|
||||||
|
row: Row<D>
|
||||||
|
cell: Cell<D, V>
|
||||||
|
value: CellValue<V>
|
||||||
|
}
|
||||||
|
|
||||||
|
type CellType = Renderer<CellTypeWithActions<RegistryArtifactMetadata>>
|
||||||
|
|
||||||
|
type RegistryArtifactNameCellActionProps = {
|
||||||
|
onClickLabel: (val: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RegistryArtifactNameCell: Renderer<{
|
||||||
|
row: Row<RegistryArtifactMetadata>
|
||||||
|
column: ColumnInstance<RegistryArtifactMetadata> & RegistryArtifactNameCellActionProps
|
||||||
|
}> = ({ row, column }) => {
|
||||||
|
const { original } = row
|
||||||
|
const { onClickLabel } = column
|
||||||
|
const routes = useRoutes()
|
||||||
|
const value = original.name
|
||||||
|
return (
|
||||||
|
<TableCells.LinkCell
|
||||||
|
prefix={<RepositoryIcon packageType={original.packageType as RepositoryPackageType} iconProps={{ size: 24 }} />}
|
||||||
|
linkTo={routes.toARArtifactDetails({
|
||||||
|
repositoryIdentifier: original.registryIdentifier,
|
||||||
|
artifactIdentifier: value
|
||||||
|
})}
|
||||||
|
label={value}
|
||||||
|
postfix={
|
||||||
|
<LabelsPopover
|
||||||
|
popoverProps={{
|
||||||
|
position: Position.RIGHT
|
||||||
|
}}
|
||||||
|
labels={defaultTo(original.labels, [])}
|
||||||
|
tagProps={{
|
||||||
|
interactive: true,
|
||||||
|
onClick: e => onClickLabel(e.currentTarget.ariaValueText as string)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RegistryArtifactTagsCell: CellType = ({ value }) => {
|
||||||
|
const { getString } = useStrings()
|
||||||
|
if (!Array.isArray(value) || !value.length) {
|
||||||
|
return (
|
||||||
|
<Text color={Color.GREY_900} font={{ size: 'small' }}>
|
||||||
|
{getString('na')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Layout.Horizontal spacing="small">
|
||||||
|
{Array.isArray(value) &&
|
||||||
|
value.map(each => (
|
||||||
|
<Tag key={each} isArtifactTag>
|
||||||
|
{each}
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
</Layout.Horizontal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RepositoryNameCell: CellType = ({ value }) => {
|
||||||
|
const routes = useRoutes()
|
||||||
|
return (
|
||||||
|
<TableCells.LinkCell
|
||||||
|
linkTo={routes.toARRepositoryDetails({
|
||||||
|
repositoryIdentifier: value,
|
||||||
|
tab: RepositoryDetailsTab.PACKAGES
|
||||||
|
})}
|
||||||
|
label={value}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RegistryArtifactDownloadsCell: CellType = ({ value }) => {
|
||||||
|
return <TableCells.CountCell value={value} icon="download-box" iconProps={{ size: 12 }} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RegistryArtifactLatestUpdatedCell: CellType = ({ row }) => {
|
||||||
|
const { getString } = useStrings()
|
||||||
|
const { original } = row
|
||||||
|
const { latestVersion, lastModified } = original || {}
|
||||||
|
if (!latestVersion) {
|
||||||
|
return (
|
||||||
|
<Text color={Color.GREY_900} font={{ size: 'small' }}>
|
||||||
|
{getString('na')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Layout.Vertical spacing="small">
|
||||||
|
<Text color={Color.PRIMARY_7} font={{ size: 'small' }}>
|
||||||
|
{latestVersion}
|
||||||
|
</Text>
|
||||||
|
<TableCells.LastModifiedCell value={defaultTo(lastModified, 0)} />
|
||||||
|
</Layout.Vertical>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Harness, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
|
import { useParentHooks } from '@ar/hooks'
|
||||||
|
import type { RepositoryPackageType } from '@ar/common/types'
|
||||||
|
import { DEFAULT_PAGE_INDEX, DEFAULT_PAGE_SIZE, DEFAULT_PIPELINE_LIST_TABLE_SORT } from '@ar/constants'
|
||||||
|
import type { UseQueryParamsOptions } from '@ar/__mocks__/hooks'
|
||||||
|
|
||||||
|
export type RegistryArtifactListPageQueryParams = {
|
||||||
|
page: number
|
||||||
|
size: number
|
||||||
|
sort: string[]
|
||||||
|
searchTerm?: string
|
||||||
|
isDeployedArtifacts: boolean
|
||||||
|
packageTypes: RepositoryPackageType[]
|
||||||
|
repositoryKey?: string
|
||||||
|
labels: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useRegistryArtifactListQueryParamOptions =
|
||||||
|
(): UseQueryParamsOptions<RegistryArtifactListPageQueryParams> => {
|
||||||
|
const { useQueryParamsOptions } = useParentHooks()
|
||||||
|
const _options = useQueryParamsOptions(
|
||||||
|
{
|
||||||
|
page: DEFAULT_PAGE_INDEX,
|
||||||
|
size: DEFAULT_PAGE_SIZE,
|
||||||
|
sort: DEFAULT_PIPELINE_LIST_TABLE_SORT,
|
||||||
|
isDeployedArtifacts: false,
|
||||||
|
packageTypes: [],
|
||||||
|
labels: []
|
||||||
|
},
|
||||||
|
{ ignoreEmptyString: false }
|
||||||
|
)
|
||||||
|
const options = useMemo(() => ({ ..._options, strictNullHandling: true }), [_options])
|
||||||
|
|
||||||
|
return options
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Harness, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export enum ArtifactListVersionFilter {
|
||||||
|
LATEST_VERSION = 'latest_version',
|
||||||
|
ALL_VERSION = 'all_version'
|
||||||
|
}
|
|
@ -3,13 +3,26 @@ pageHeading: Artifacts
|
||||||
deployedArtifacts: Deployed Artifacts
|
deployedArtifacts: Deployed Artifacts
|
||||||
table:
|
table:
|
||||||
noArtifactsTitle: There are no artifact available.
|
noArtifactsTitle: There are no artifact available.
|
||||||
allRepositories: All Registries
|
allRepositories: Registries
|
||||||
|
latestVersions: Latest Versions
|
||||||
|
allVersions: All Versions
|
||||||
columns:
|
columns:
|
||||||
name: Name
|
name: Name
|
||||||
|
artifactName: Artifact Name
|
||||||
repository: Registry
|
repository: Registry
|
||||||
tags: Labels
|
tags: Labels
|
||||||
downloads: Downloads (In the last week)
|
downloads: Downloads
|
||||||
latestVersion: Latest Version
|
latestVersion: Latest Version
|
||||||
|
pullCommand: Pull Command
|
||||||
|
environments: Environments
|
||||||
|
sto: Security Vulnerabilities
|
||||||
|
lastUpdated: Uploaded At
|
||||||
actions:
|
actions:
|
||||||
editRepository: Edit Registry
|
editRepository: Edit Registry
|
||||||
deleteRepository: Delete Registry
|
deleteRepository: Delete Registry
|
||||||
|
VulnerabilityStatus:
|
||||||
|
scanned: Scanned
|
||||||
|
nonScanned: Not Scanned
|
||||||
|
scanStatus: Scan Status
|
||||||
|
partiallyScanned: '{{ scanned }}/{{ total }} Scanned'
|
||||||
|
digestMenuItemText: '{{archName}} (digest: {{digest}})'
|
||||||
|
|
|
@ -17,29 +17,22 @@
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
import { useParentHooks } from '@ar/hooks'
|
import { useParentHooks } from '@ar/hooks'
|
||||||
import type { RepositoryPackageType } from '@ar/common/types'
|
|
||||||
import { DEFAULT_PAGE_INDEX, DEFAULT_PAGE_SIZE, DEFAULT_PIPELINE_LIST_TABLE_SORT } from '@ar/constants'
|
import { DEFAULT_PAGE_INDEX, DEFAULT_PAGE_SIZE, DEFAULT_PIPELINE_LIST_TABLE_SORT } from '@ar/constants'
|
||||||
|
import type { RepositoryPackageType } from '@ar/common/types'
|
||||||
import type { UseQueryParamsOptions } from '@ar/__mocks__/hooks'
|
import type { UseQueryParamsOptions } from '@ar/__mocks__/hooks'
|
||||||
|
|
||||||
type GetArtifactListQueryParams = {
|
export type ArtifactListPageQueryParams = {
|
||||||
accountIdentifier: string
|
|
||||||
orgIdentifier: string
|
|
||||||
projectIdentifier: string
|
|
||||||
page: number
|
page: number
|
||||||
size: number
|
size: number
|
||||||
sort: string[]
|
sort: string[]
|
||||||
searchTerm?: string
|
|
||||||
isDeployedArtifacts: boolean
|
|
||||||
packageTypes: RepositoryPackageType[]
|
packageTypes: RepositoryPackageType[]
|
||||||
repositoryKey?: string
|
|
||||||
labels: string[]
|
labels: string[]
|
||||||
|
latestVersion: boolean
|
||||||
|
isDeployedArtifacts: boolean
|
||||||
|
searchTerm?: string
|
||||||
|
repositoryKey?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ArtifactListPageQueryParams = Omit<
|
|
||||||
GetArtifactListQueryParams,
|
|
||||||
'accountIdentifier' | 'orgIdentifier' | 'projectIdentifier'
|
|
||||||
>
|
|
||||||
|
|
||||||
export const useArtifactListQueryParamOptions = (): UseQueryParamsOptions<ArtifactListPageQueryParams> => {
|
export const useArtifactListQueryParamOptions = (): UseQueryParamsOptions<ArtifactListPageQueryParams> => {
|
||||||
const { useQueryParamsOptions } = useParentHooks()
|
const { useQueryParamsOptions } = useParentHooks()
|
||||||
const _options = useQueryParamsOptions(
|
const _options = useQueryParamsOptions(
|
||||||
|
@ -48,10 +41,11 @@ export const useArtifactListQueryParamOptions = (): UseQueryParamsOptions<Artifa
|
||||||
size: DEFAULT_PAGE_SIZE,
|
size: DEFAULT_PAGE_SIZE,
|
||||||
sort: DEFAULT_PIPELINE_LIST_TABLE_SORT,
|
sort: DEFAULT_PIPELINE_LIST_TABLE_SORT,
|
||||||
isDeployedArtifacts: false,
|
isDeployedArtifacts: false,
|
||||||
|
latestVersion: false,
|
||||||
packageTypes: [],
|
packageTypes: [],
|
||||||
labels: []
|
labels: []
|
||||||
},
|
},
|
||||||
{ ignoreEmptyString: false }
|
{ ignoreEmptyString: true }
|
||||||
)
|
)
|
||||||
const options = useMemo(() => ({ ..._options, strictNullHandling: true }), [_options])
|
const options = useMemo(() => ({ ..._options, strictNullHandling: true }), [_options])
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
div[class*='TableV2--cells'],
|
div[class*='TableV2--cells'],
|
||||||
div[class*='TableV2--header'] {
|
div[class*='TableV2--header'] {
|
||||||
display: grid !important;
|
display: grid !important;
|
||||||
grid-template-columns: 1fr 10rem 10rem 10rem 20rem 50px !important;
|
grid-template-columns: 1fr 10rem 10rem 10rem 20rem 10rem 50px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
div[class*='TableV2--header'] {
|
div[class*='TableV2--header'] {
|
||||||
|
|
|
@ -26,6 +26,7 @@ import {
|
||||||
DigestNameCell,
|
DigestNameCell,
|
||||||
DownloadsCell,
|
DownloadsCell,
|
||||||
OsArchCell,
|
OsArchCell,
|
||||||
|
ScanStatusCell,
|
||||||
SizeCell,
|
SizeCell,
|
||||||
UploadedByCell
|
UploadedByCell
|
||||||
} from './DigestTableCells'
|
} from './DigestTableCells'
|
||||||
|
@ -69,6 +70,12 @@ export default function DigestListTable(props: DigestListTableProps): JSX.Elemen
|
||||||
accessor: 'downloadsCount',
|
accessor: 'downloadsCount',
|
||||||
Cell: DownloadsCell
|
Cell: DownloadsCell
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Header: getString('digestList.table.columns.scanStatus'),
|
||||||
|
accessor: 'scanStatus',
|
||||||
|
Cell: ScanStatusCell,
|
||||||
|
version
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Header: '',
|
Header: '',
|
||||||
accessor: 'menu',
|
accessor: 'menu',
|
||||||
|
|
|
@ -15,13 +15,18 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
import type { Cell, CellValue, ColumnInstance, Renderer, Row, TableInstance } from 'react-table'
|
import type { Cell, CellValue, ColumnInstance, Renderer, Row, TableInstance } from 'react-table'
|
||||||
|
|
||||||
|
import { Text } from '@harnessio/uicore'
|
||||||
|
import { Color } from '@harnessio/design-system'
|
||||||
import type { DockerManifestDetails } from '@harnessio/react-har-service-client'
|
import type { DockerManifestDetails } from '@harnessio/react-har-service-client'
|
||||||
|
|
||||||
|
import { useStrings } from '@ar/frameworks/strings'
|
||||||
import { useDecodedParams, useRoutes } from '@ar/hooks'
|
import { useDecodedParams, useRoutes } from '@ar/hooks'
|
||||||
import type { ArtifactDetailsPathParams } from '@ar/routes/types'
|
|
||||||
import TableCells from '@ar/components/TableCells/TableCells'
|
|
||||||
import { getShortDigest } from '@ar/pages/digest-list/utils'
|
import { getShortDigest } from '@ar/pages/digest-list/utils'
|
||||||
|
import TableCells from '@ar/components/TableCells/TableCells'
|
||||||
|
import type { ArtifactDetailsPathParams } from '@ar/routes/types'
|
||||||
import { VersionDetailsTab } from '@ar/pages/version-details/components/VersionDetailsTabs/constants'
|
import { VersionDetailsTab } from '@ar/pages/version-details/components/VersionDetailsTabs/constants'
|
||||||
|
|
||||||
type CellTypeWithActions<D extends Record<string, any>, V = any> = TableInstance<D> & {
|
type CellTypeWithActions<D extends Record<string, any>, V = any> = TableInstance<D> & {
|
||||||
|
@ -72,6 +77,42 @@ export const DownloadsCell: CellType = ({ value }) => {
|
||||||
return <TableCells.CountCell value={value} icon="download-box" iconProps={{ size: 12 }} />
|
return <TableCells.CountCell value={value} icon="download-box" iconProps={{ size: 12 }} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ScanStatusCell: Renderer<{
|
||||||
|
row: Row<DockerManifestDetails>
|
||||||
|
column: ColumnInstance<DockerManifestDetails> & DigestNameColumnProps
|
||||||
|
}> = ({ row, column }) => {
|
||||||
|
const { original } = row
|
||||||
|
const { version } = column
|
||||||
|
const router = useRoutes()
|
||||||
|
const { stoExecutionId, stoPipelineId, digest } = original
|
||||||
|
const pathParams = useDecodedParams<ArtifactDetailsPathParams>()
|
||||||
|
const { getString } = useStrings()
|
||||||
|
if (!stoExecutionId || !stoPipelineId)
|
||||||
|
return <TableCells.TextCell value={getString('artifactList.table.actions.VulnerabilityStatus.nonScanned')} />
|
||||||
|
|
||||||
|
const linkTo = router.toARVersionDetailsTab({
|
||||||
|
repositoryIdentifier: pathParams.repositoryIdentifier,
|
||||||
|
artifactIdentifier: pathParams.artifactIdentifier,
|
||||||
|
versionIdentifier: version,
|
||||||
|
versionTab: VersionDetailsTab.SECURITY_TESTS,
|
||||||
|
pipelineIdentifier: stoPipelineId,
|
||||||
|
executionIdentifier: stoExecutionId
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<Link to={`${linkTo}?digest=${digest}`}>
|
||||||
|
<Text
|
||||||
|
color={Color.PRIMARY_7}
|
||||||
|
rightIcon="main-share"
|
||||||
|
rightIconProps={{
|
||||||
|
size: 12
|
||||||
|
}}
|
||||||
|
lineClamp={1}>
|
||||||
|
{getString('artifactList.table.actions.VulnerabilityStatus.scanned')}
|
||||||
|
</Text>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export const DigestActionsCell: CellType = () => {
|
export const DigestActionsCell: CellType = () => {
|
||||||
return <></>
|
return <></>
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,5 +6,6 @@ table:
|
||||||
digest: Digest
|
digest: Digest
|
||||||
osArch: OS/Arch
|
osArch: OS/Arch
|
||||||
size: Size
|
size: Size
|
||||||
uploadedBy: Uploaded By
|
uploadedBy: Uploaded At
|
||||||
downloads: Downloads/Last Download
|
downloads: Downloads/Last Download
|
||||||
|
scanStatus: Scan Status
|
||||||
|
|
|
@ -16,31 +16,33 @@
|
||||||
|
|
||||||
import React, { useCallback, useContext } from 'react'
|
import React, { useCallback, useContext } from 'react'
|
||||||
import type { FormikProps } from 'formik'
|
import type { FormikProps } from 'formik'
|
||||||
import { useParams } from 'react-router-dom'
|
|
||||||
import { Expander } from '@blueprintjs/core'
|
import { Expander } from '@blueprintjs/core'
|
||||||
import { Button, ButtonVariation, Container, Layout, Tab, Tabs } from '@harnessio/uicore'
|
import { Button, ButtonVariation, Container, Layout, Tab, Tabs } from '@harnessio/uicore'
|
||||||
|
|
||||||
import { useParentHooks } from '@ar/hooks'
|
|
||||||
import { useStrings } from '@ar/frameworks/strings'
|
|
||||||
import type { RepositoryDetailsPathParams } from '@ar/routes/types'
|
import type { RepositoryDetailsPathParams } from '@ar/routes/types'
|
||||||
import ArtifactListPage from '@ar/pages/artifact-list/ArtifactListPage'
|
import { useDecodedParams, useParentComponents, useParentHooks } from '@ar/hooks'
|
||||||
|
import { PermissionIdentifier, ResourceType } from '@ar/common/permissionTypes'
|
||||||
|
import { useStrings } from '@ar/frameworks/strings'
|
||||||
import type { RepositoryConfigType, RepositoryPackageType } from '@ar/common/types'
|
import type { RepositoryConfigType, RepositoryPackageType } from '@ar/common/types'
|
||||||
import RepositoryDetailsHeaderWidget from '@ar/frameworks/RepositoryStep/RepositoryDetailsHeaderWidget'
|
import RepositoryDetailsHeaderWidget from '@ar/frameworks/RepositoryStep/RepositoryDetailsHeaderWidget'
|
||||||
import RepositoryConfigurationFormWidget from '@ar/frameworks/RepositoryStep/RepositoryConfigurationFormWidget'
|
import RepositoryConfigurationFormWidget from '@ar/frameworks/RepositoryStep/RepositoryConfigurationFormWidget'
|
||||||
import { RepositoryProviderContext } from './context/RepositoryProvider'
|
|
||||||
import type { Repository } from './types'
|
import type { Repository } from './types'
|
||||||
import { RepositoryDetailsTab } from './constants'
|
import { RepositoryDetailsTab } from './constants'
|
||||||
|
import { RepositoryProviderContext } from './context/RepositoryProvider'
|
||||||
|
import RegistryArtifactListPage from '../artifact-list/RegistryArtifactListPage'
|
||||||
import css from './RepositoryDetailsPage.module.scss'
|
import css from './RepositoryDetailsPage.module.scss'
|
||||||
|
|
||||||
export default function RepositoryDetails(): JSX.Element | null {
|
export default function RepositoryDetails(): JSX.Element | null {
|
||||||
const pathParams = useParams<RepositoryDetailsPathParams>()
|
|
||||||
const { useUpdateQueryParams, useQueryParams } = useParentHooks()
|
const { useUpdateQueryParams, useQueryParams } = useParentHooks()
|
||||||
const { updateQueryParams } = useUpdateQueryParams()
|
const { updateQueryParams } = useUpdateQueryParams()
|
||||||
|
const { RbacButton } = useParentComponents()
|
||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
|
const { repositoryIdentifier } = useDecodedParams<RepositoryDetailsPathParams>()
|
||||||
const { tab: selectedTabId = RepositoryDetailsTab.PACKAGES } = useQueryParams<{ tab: RepositoryDetailsTab }>()
|
const { tab: selectedTabId = RepositoryDetailsTab.PACKAGES } = useQueryParams<{ tab: RepositoryDetailsTab }>()
|
||||||
const stepRef = React.useRef<FormikProps<unknown> | null>(null)
|
const stepRef = React.useRef<FormikProps<unknown> | null>(null)
|
||||||
|
|
||||||
const { isDirty, data } = useContext(RepositoryProviderContext)
|
const { isDirty, data, isReadonly, isUpdating } = useContext(RepositoryProviderContext)
|
||||||
|
|
||||||
const { config } = data as Repository
|
const { config } = data as Repository
|
||||||
const { type } = config
|
const { type } = config
|
||||||
|
@ -62,12 +64,19 @@ export default function RepositoryDetails(): JSX.Element | null {
|
||||||
|
|
||||||
const renderActionBtns = (): JSX.Element => (
|
const renderActionBtns = (): JSX.Element => (
|
||||||
<Layout.Horizontal className={css.btnContainer}>
|
<Layout.Horizontal className={css.btnContainer}>
|
||||||
<Button
|
<RbacButton
|
||||||
variation={ButtonVariation.PRIMARY}
|
|
||||||
text={getString('save')}
|
text={getString('save')}
|
||||||
className={css.saveButton}
|
className={css.saveButton}
|
||||||
|
variation={ButtonVariation.PRIMARY}
|
||||||
onClick={handleSubmitForm}
|
onClick={handleSubmitForm}
|
||||||
disabled={!isDirty}
|
disabled={!isDirty || isUpdating}
|
||||||
|
permission={{
|
||||||
|
permission: PermissionIdentifier.EDIT_ARTIFACT_REGISTRY,
|
||||||
|
resource: {
|
||||||
|
resourceType: ResourceType.ARTIFACT_REGISTRY,
|
||||||
|
resourceIdentifier: repositoryIdentifier
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
className={css.discardBtn}
|
className={css.discardBtn}
|
||||||
|
@ -95,11 +104,7 @@ export default function RepositoryDetails(): JSX.Element | null {
|
||||||
title={getString('repositoryDetails.tabs.packages')}
|
title={getString('repositoryDetails.tabs.packages')}
|
||||||
panel={
|
panel={
|
||||||
<Container>
|
<Container>
|
||||||
<ArtifactListPage
|
<RegistryArtifactListPage pageBodyClassName={css.packagesPageBody} />
|
||||||
withHeader={false}
|
|
||||||
parentRepoKey={pathParams.repositoryIdentifier}
|
|
||||||
pageBodyClassName={css.packagesPageBody}
|
|
||||||
/>
|
|
||||||
</Container>
|
</Container>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -111,7 +116,7 @@ export default function RepositoryDetails(): JSX.Element | null {
|
||||||
packageType={data.packageType as RepositoryPackageType}
|
packageType={data.packageType as RepositoryPackageType}
|
||||||
type={type as RepositoryConfigType}
|
type={type as RepositoryConfigType}
|
||||||
ref={stepRef}
|
ref={stepRef}
|
||||||
readonly={false}
|
readonly={isReadonly}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -53,10 +53,10 @@ export default function DeleteRepositoryMenuItem({ data }: RepositoryActionsProp
|
||||||
onClick={handleDeleteService}
|
onClick={handleDeleteService}
|
||||||
permission={{
|
permission={{
|
||||||
resource: {
|
resource: {
|
||||||
resourceType: ResourceType.SERVICE,
|
resourceType: ResourceType.ARTIFACT_REGISTRY,
|
||||||
resourceIdentifier: defaultTo(data?.identifier, '')
|
resourceIdentifier: defaultTo(data?.identifier, '')
|
||||||
},
|
},
|
||||||
permission: PermissionIdentifier.DELETE_SERVICE
|
permission: PermissionIdentifier.DELETE_ARTIFACT_REGISTRY
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
@ -32,10 +32,10 @@ export default function QuarantineRepositoryMenuItem({ data }: RepositoryActions
|
||||||
onClick={noop}
|
onClick={noop}
|
||||||
permission={{
|
permission={{
|
||||||
resource: {
|
resource: {
|
||||||
resourceType: ResourceType.SERVICE,
|
resourceType: ResourceType.ARTIFACT_REGISTRY,
|
||||||
resourceIdentifier: defaultTo(data?.identifier, '')
|
resourceIdentifier: defaultTo(data?.identifier, '')
|
||||||
},
|
},
|
||||||
permission: PermissionIdentifier.DELETE_SERVICE
|
permission: PermissionIdentifier.DELETE_ARTIFACT_REGISTRY
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
@ -32,10 +32,10 @@ export default function RestoreRepositoryMenuItem({ data }: RepositoryActionsPro
|
||||||
onClick={noop}
|
onClick={noop}
|
||||||
permission={{
|
permission={{
|
||||||
resource: {
|
resource: {
|
||||||
resourceType: ResourceType.SERVICE,
|
resourceType: ResourceType.ARTIFACT_REGISTRY,
|
||||||
resourceIdentifier: defaultTo(data?.identifier, '')
|
resourceIdentifier: defaultTo(data?.identifier, '')
|
||||||
},
|
},
|
||||||
permission: PermissionIdentifier.DELETE_SERVICE
|
permission: PermissionIdentifier.DELETE_ARTIFACT_REGISTRY
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
@ -32,10 +32,10 @@ export default function ScanRepositoryMenuItem({ data }: RepositoryActionsProps)
|
||||||
onClick={noop}
|
onClick={noop}
|
||||||
permission={{
|
permission={{
|
||||||
resource: {
|
resource: {
|
||||||
resourceType: ResourceType.SERVICE,
|
resourceType: ResourceType.ARTIFACT_REGISTRY,
|
||||||
resourceIdentifier: defaultTo(data?.identifier, '')
|
resourceIdentifier: defaultTo(data?.identifier, '')
|
||||||
},
|
},
|
||||||
permission: PermissionIdentifier.DELETE_SERVICE
|
permission: PermissionIdentifier.EDIT_ARTIFACT_REGISTRY
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
@ -39,10 +39,10 @@ export default function SetupClientMenuItem({ data }: RepositoryActionsProps): J
|
||||||
onClick={showSetupClientModal}
|
onClick={showSetupClientModal}
|
||||||
permission={{
|
permission={{
|
||||||
resource: {
|
resource: {
|
||||||
resourceType: ResourceType.SERVICE,
|
resourceType: ResourceType.ARTIFACT_REGISTRY,
|
||||||
resourceIdentifier: defaultTo(data?.identifier, '')
|
resourceIdentifier: defaultTo(data?.identifier, '')
|
||||||
},
|
},
|
||||||
permission: PermissionIdentifier.DELETE_SERVICE
|
permission: PermissionIdentifier.VIEW_ARTIFACT_REGISTRY
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -44,7 +44,7 @@ interface RepositoryConfigurationFormProps {
|
||||||
|
|
||||||
function RepositoryConfigurationForm(props: RepositoryConfigurationFormProps, formikRef: FormikFowardRef): JSX.Element {
|
function RepositoryConfigurationForm(props: RepositoryConfigurationFormProps, formikRef: FormikFowardRef): JSX.Element {
|
||||||
const { readonly, factory = repositoryFactory } = props
|
const { readonly, factory = repositoryFactory } = props
|
||||||
const { data } = useContext(RepositoryProviderContext)
|
const { data, setIsUpdating } = useContext(RepositoryProviderContext)
|
||||||
const { showSuccess, showError, clear } = useToaster()
|
const { showSuccess, showError, clear } = useToaster()
|
||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
const spaceRef = useGetSpaceRef()
|
const spaceRef = useGetSpaceRef()
|
||||||
|
@ -54,6 +54,7 @@ function RepositoryConfigurationForm(props: RepositoryConfigurationFormProps, fo
|
||||||
|
|
||||||
const handleModifyRepository = async (values: VirtualRegistryRequest): Promise<void> => {
|
const handleModifyRepository = async (values: VirtualRegistryRequest): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
|
setIsUpdating(true)
|
||||||
const response = await modifyRepository({
|
const response = await modifyRepository({
|
||||||
registry_ref: spaceRef,
|
registry_ref: spaceRef,
|
||||||
body: values as unknown as RegistryRequestRequestBody
|
body: values as unknown as RegistryRequestRequestBody
|
||||||
|
@ -65,6 +66,8 @@ function RepositoryConfigurationForm(props: RepositoryConfigurationFormProps, fo
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
showError(getErrorInfoFromErrorObject(e, true))
|
showError(getErrorInfoFromErrorObject(e, true))
|
||||||
|
} finally {
|
||||||
|
setIsUpdating(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -136,6 +136,9 @@ function RepositoryCreateForm(props: RepositoryCreateFormProps, formikRef: Formi
|
||||||
const formattedValues = repositoryType.processRepositoryFormData(values) as VirtualRegistryRequest
|
const formattedValues = repositoryType.processRepositoryFormData(values) as VirtualRegistryRequest
|
||||||
const formattedValuesForCleanupPolicy = getFormattedFormDataForCleanupPolicy(formattedValues)
|
const formattedValuesForCleanupPolicy = getFormattedFormDataForCleanupPolicy(formattedValues)
|
||||||
const response = await createRepository({
|
const response = await createRepository({
|
||||||
|
queryParams: {
|
||||||
|
space_ref: decodeRef(parentRef)
|
||||||
|
},
|
||||||
body: {
|
body: {
|
||||||
...formattedValuesForCleanupPolicy,
|
...formattedValuesForCleanupPolicy,
|
||||||
parentRef: decodeRef(parentRef)
|
parentRef: decodeRef(parentRef)
|
||||||
|
|
|
@ -29,8 +29,10 @@ import type { Repository } from '@ar/pages/repository-details/types'
|
||||||
export interface RepositoryProviderProps {
|
export interface RepositoryProviderProps {
|
||||||
data: Repository | undefined
|
data: Repository | undefined
|
||||||
isDirty: boolean
|
isDirty: boolean
|
||||||
|
isUpdating: boolean
|
||||||
setIsDirty: (val: boolean) => void
|
setIsDirty: (val: boolean) => void
|
||||||
setIsLoading: (val: boolean) => void
|
setIsLoading: (val: boolean) => void
|
||||||
|
setIsUpdating: (val: boolean) => void
|
||||||
isReadonly: boolean
|
isReadonly: boolean
|
||||||
refetch: () => void
|
refetch: () => void
|
||||||
}
|
}
|
||||||
|
@ -38,8 +40,10 @@ export interface RepositoryProviderProps {
|
||||||
export const RepositoryProviderContext = createContext<RepositoryProviderProps>({
|
export const RepositoryProviderContext = createContext<RepositoryProviderProps>({
|
||||||
data: undefined,
|
data: undefined,
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
|
isUpdating: false,
|
||||||
setIsDirty: noop,
|
setIsDirty: noop,
|
||||||
setIsLoading: noop,
|
setIsLoading: noop,
|
||||||
|
setIsUpdating: noop,
|
||||||
isReadonly: false,
|
isReadonly: false,
|
||||||
refetch: noop
|
refetch: noop
|
||||||
})
|
})
|
||||||
|
@ -48,6 +52,7 @@ const RepositoryProvider: FC<PropsWithChildren<{ className?: string }>> = ({ chi
|
||||||
const { repositoryIdentifier } = useDecodedParams<RepositoryDetailsPathParams>()
|
const { repositoryIdentifier } = useDecodedParams<RepositoryDetailsPathParams>()
|
||||||
const [isDirty, setIsDirty] = useState(false)
|
const [isDirty, setIsDirty] = useState(false)
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
const [isUpdating, setIsUpdating] = useState(false)
|
||||||
const { usePermission } = useParentHooks()
|
const { usePermission } = useParentHooks()
|
||||||
const spaceRef = useGetSpaceRef()
|
const spaceRef = useGetSpaceRef()
|
||||||
const { scope } = useAppStore()
|
const { scope } = useAppStore()
|
||||||
|
@ -62,10 +67,10 @@ const RepositoryProvider: FC<PropsWithChildren<{ className?: string }>> = ({ chi
|
||||||
projectIdentifier
|
projectIdentifier
|
||||||
},
|
},
|
||||||
resource: {
|
resource: {
|
||||||
resourceType: ResourceType.SERVICE,
|
resourceType: ResourceType.ARTIFACT_REGISTRY,
|
||||||
resourceIdentifier: repositoryIdentifier
|
resourceIdentifier: repositoryIdentifier
|
||||||
},
|
},
|
||||||
permissions: [PermissionIdentifier.EDIT_SERVICE]
|
permissions: [PermissionIdentifier.EDIT_ARTIFACT_REGISTRY]
|
||||||
},
|
},
|
||||||
[accountId, projectIdentifier, orgIdentifier, repositoryIdentifier]
|
[accountId, projectIdentifier, orgIdentifier, repositoryIdentifier]
|
||||||
)
|
)
|
||||||
|
@ -85,8 +90,10 @@ const RepositoryProvider: FC<PropsWithChildren<{ className?: string }>> = ({ chi
|
||||||
value={{
|
value={{
|
||||||
data: repositoryData?.content?.data as RepositoryProviderProps['data'],
|
data: repositoryData?.content?.data as RepositoryProviderProps['data'],
|
||||||
isDirty,
|
isDirty,
|
||||||
|
isUpdating,
|
||||||
setIsDirty,
|
setIsDirty,
|
||||||
setIsLoading,
|
setIsLoading,
|
||||||
|
setIsUpdating,
|
||||||
isReadonly: !isEdit,
|
isReadonly: !isEdit,
|
||||||
refetch
|
refetch
|
||||||
}}>
|
}}>
|
||||||
|
|
|
@ -40,43 +40,47 @@ export function useCreateRepositoryModal(props: useCreateRepositoryModalProps) {
|
||||||
stepRef.current?.submitForm()
|
stepRef.current?.submitForm()
|
||||||
}
|
}
|
||||||
|
|
||||||
const [showModal, hideModal] = useModalHook(() => (
|
const [showModal, hideModal] = useModalHook(
|
||||||
<ModalDialog
|
() => (
|
||||||
isOpen={true}
|
<ModalDialog
|
||||||
enforceFocus={false}
|
isOpen={true}
|
||||||
canEscapeKeyClose
|
enforceFocus={false}
|
||||||
canOutsideClickClose
|
canEscapeKeyClose
|
||||||
onClose={() => {
|
canOutsideClickClose
|
||||||
hideModal()
|
onClose={() => {
|
||||||
}}
|
hideModal()
|
||||||
title={
|
}}
|
||||||
<>
|
title={
|
||||||
<Text font={{ variation: FontVariation.H3 }} margin={{ bottom: 'small' }}>
|
<>
|
||||||
{getString('repositoryDetails.repositoryForm.modalTitle')}
|
<Text font={{ variation: FontVariation.H3 }} margin={{ bottom: 'small' }}>
|
||||||
</Text>
|
{getString('repositoryDetails.repositoryForm.modalTitle')}
|
||||||
<Text font={{ variation: FontVariation.SMALL }} color={Color.GREY_500}>
|
</Text>
|
||||||
{getString('repositoryDetails.repositoryForm.modalSubTitle')}
|
<Text font={{ variation: FontVariation.SMALL }} color={Color.GREY_500}>
|
||||||
</Text>
|
{getString('repositoryDetails.repositoryForm.modalSubTitle')}
|
||||||
</>
|
</Text>
|
||||||
}
|
</>
|
||||||
isCloseButtonShown
|
}
|
||||||
width={800}
|
isCloseButtonShown
|
||||||
showOverlay={showOverlay}
|
width={800}
|
||||||
footer={
|
showOverlay={showOverlay}
|
||||||
<Layout.Horizontal spacing="small">
|
footer={
|
||||||
<Button
|
<Layout.Horizontal spacing="small">
|
||||||
variation={ButtonVariation.PRIMARY}
|
<Button
|
||||||
type={'submit'}
|
variation={ButtonVariation.PRIMARY}
|
||||||
text={getString('repositoryDetails.repositoryForm.create')}
|
type={'submit'}
|
||||||
data-id="service-save"
|
text={getString('repositoryDetails.repositoryForm.create')}
|
||||||
onClick={handleSubmitForm}
|
data-id="service-save"
|
||||||
/>
|
onClick={handleSubmitForm}
|
||||||
<Button variation={ButtonVariation.TERTIARY} text={getString('cancel')} onClick={hideModal} />
|
disabled={showOverlay}
|
||||||
</Layout.Horizontal>
|
/>
|
||||||
}>
|
<Button variation={ButtonVariation.TERTIARY} text={getString('cancel')} onClick={hideModal} />
|
||||||
<RepositoryCreateForm onSuccess={onSuccess} setShowOverlay={setShowOverlay} ref={stepRef} />
|
</Layout.Horizontal>
|
||||||
</ModalDialog>
|
}>
|
||||||
))
|
<RepositoryCreateForm onSuccess={onSuccess} setShowOverlay={setShowOverlay} ref={stepRef} />
|
||||||
|
</ModalDialog>
|
||||||
|
),
|
||||||
|
[showOverlay]
|
||||||
|
)
|
||||||
|
|
||||||
return [showModal, hideModal]
|
return [showModal, hideModal]
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ function RepositoryListPage(): JSX.Element {
|
||||||
const spaceRef = useGetSpaceRef()
|
const spaceRef = useGetSpaceRef()
|
||||||
const queryParamOptions = useArtifactRepositoriesQueryParamOptions()
|
const queryParamOptions = useArtifactRepositoriesQueryParamOptions()
|
||||||
const queryParams = useQueryParams<ArtifactRepositoryListPageQueryParams>(queryParamOptions)
|
const queryParams = useQueryParams<ArtifactRepositoryListPageQueryParams>(queryParamOptions)
|
||||||
const { searchTerm, page, size, environmentTypes, repositoryTypes } = queryParams
|
const { searchTerm, page, size, repositoryTypes } = queryParams
|
||||||
|
|
||||||
const { preference: sortingPreference, setPreference: setSortingPreference } = usePreferenceStore<string | undefined>(
|
const { preference: sortingPreference, setPreference: setSortingPreference } = usePreferenceStore<string | undefined>(
|
||||||
PreferenceScope.USER,
|
PreferenceScope.USER,
|
||||||
|
@ -85,7 +85,7 @@ function RepositoryListPage(): JSX.Element {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasFilter = !!searchTerm || repositoryTypes.length || environmentTypes.length
|
const hasFilter = !!searchTerm || repositoryTypes.length
|
||||||
|
|
||||||
const responseData = data?.content.data
|
const responseData = data?.content.data
|
||||||
|
|
||||||
|
|
|
@ -39,14 +39,14 @@ export default function CreateRepositoryButton(props: CreateRepositoryButtonProp
|
||||||
|
|
||||||
// CHANGE_ME: update permissions once we get actual premission for AR module
|
// CHANGE_ME: update permissions once we get actual premission for AR module
|
||||||
const [canDoAction] = usePermission({
|
const [canDoAction] = usePermission({
|
||||||
permissions: [PermissionIdentifier.EDIT_SERVICE],
|
permissions: [PermissionIdentifier.EDIT_ARTIFACT_REGISTRY],
|
||||||
resourceScope: {
|
resourceScope: {
|
||||||
accountIdentifier: scope.accountId,
|
accountIdentifier: scope.accountId,
|
||||||
orgIdentifier: scope.orgIdentifier,
|
orgIdentifier: scope.orgIdentifier,
|
||||||
projectIdentifier: scope.projectIdentifier
|
projectIdentifier: scope.projectIdentifier
|
||||||
},
|
},
|
||||||
resource: {
|
resource: {
|
||||||
resourceType: ResourceType.SERVICE
|
resourceType: ResourceType.ARTIFACT_REGISTRY
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { useMemo } from 'react'
|
||||||
|
|
||||||
import { useParentHooks } from '@ar/hooks'
|
import { useParentHooks } from '@ar/hooks'
|
||||||
import { DEFAULT_PAGE_INDEX, DEFAULT_PAGE_SIZE, DEFAULT_PIPELINE_LIST_TABLE_SORT } from '@ar/constants'
|
import { DEFAULT_PAGE_INDEX, DEFAULT_PAGE_SIZE, DEFAULT_PIPELINE_LIST_TABLE_SORT } from '@ar/constants'
|
||||||
import type { EnvironmentType, RepositoryPackageType } from '@ar/common/types'
|
import type { RepositoryPackageType } from '@ar/common/types'
|
||||||
import type { UseQueryParamsOptions } from '@ar/__mocks__/hooks'
|
import type { UseQueryParamsOptions } from '@ar/__mocks__/hooks'
|
||||||
|
|
||||||
type GetArtifactRepositoryQueryParams = {
|
type GetArtifactRepositoryQueryParams = {
|
||||||
|
@ -29,7 +29,6 @@ type GetArtifactRepositoryQueryParams = {
|
||||||
size: number
|
size: number
|
||||||
sort: string[]
|
sort: string[]
|
||||||
searchTerm?: string
|
searchTerm?: string
|
||||||
environmentTypes: EnvironmentType[]
|
|
||||||
repositoryTypes: RepositoryPackageType[]
|
repositoryTypes: RepositoryPackageType[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +45,6 @@ export const useArtifactRepositoriesQueryParamOptions =
|
||||||
page: DEFAULT_PAGE_INDEX,
|
page: DEFAULT_PAGE_INDEX,
|
||||||
size: DEFAULT_PAGE_SIZE,
|
size: DEFAULT_PAGE_SIZE,
|
||||||
sort: DEFAULT_PIPELINE_LIST_TABLE_SORT,
|
sort: DEFAULT_PIPELINE_LIST_TABLE_SORT,
|
||||||
environmentTypes: [],
|
|
||||||
repositoryTypes: []
|
repositoryTypes: []
|
||||||
},
|
},
|
||||||
{ ignoreEmptyString: false }
|
{ ignoreEmptyString: false }
|
||||||
|
|
|
@ -38,6 +38,7 @@ function DockerRepositoryUrlInput(
|
||||||
<FormInput.RadioGroup
|
<FormInput.RadioGroup
|
||||||
name="config.source"
|
name="config.source"
|
||||||
radioGroup={{ inline: true }}
|
radioGroup={{ inline: true }}
|
||||||
|
disabled={readonly}
|
||||||
label={getString('upstreamProxyDetails.createForm.source.title')}
|
label={getString('upstreamProxyDetails.createForm.source.title')}
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
|
|
|
@ -71,6 +71,7 @@ export default function UpstreamProxyAuthenticationFormContent({
|
||||||
value: UpstreamProxyAuthenticationMode.ANONYMOUS
|
value: UpstreamProxyAuthenticationMode.ANONYMOUS
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
|
disabled={readonly}
|
||||||
/>
|
/>
|
||||||
{selectedRadioValue === UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD && (
|
{selectedRadioValue === UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD && (
|
||||||
<Container className={css.authContainer}>
|
<Container className={css.authContainer}>
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { Formik, FormikForm, getErrorInfoFromErrorObject, useToaster } from '@ha
|
||||||
import { Anonymous, UserPassword, useModifyRegistryMutation } from '@harnessio/react-har-service-client'
|
import { Anonymous, UserPassword, useModifyRegistryMutation } from '@harnessio/react-har-service-client'
|
||||||
|
|
||||||
import { URL_REGEX } from '@ar/constants'
|
import { URL_REGEX } from '@ar/constants'
|
||||||
import { useGetSpaceRef } from '@ar/hooks'
|
import { useAppStore, useGetSpaceRef } from '@ar/hooks'
|
||||||
import { useStrings } from '@ar/frameworks/strings'
|
import { useStrings } from '@ar/frameworks/strings'
|
||||||
import { queryClient } from '@ar/utils/queryClient'
|
import { queryClient } from '@ar/utils/queryClient'
|
||||||
import type { FormikFowardRef } from '@ar/common/types'
|
import type { FormikFowardRef } from '@ar/common/types'
|
||||||
|
@ -41,6 +41,7 @@ import {
|
||||||
type UpstreamRegistry,
|
type UpstreamRegistry,
|
||||||
type UpstreamRegistryRequest
|
type UpstreamRegistryRequest
|
||||||
} from '../../types'
|
} from '../../types'
|
||||||
|
import { getFormattedFormDataForAuthType } from './utils'
|
||||||
|
|
||||||
import css from './Forms.module.scss'
|
import css from './Forms.module.scss'
|
||||||
|
|
||||||
|
@ -54,9 +55,10 @@ function UpstreamProxyConfigurationForm(
|
||||||
formikRef: FormikFowardRef
|
formikRef: FormikFowardRef
|
||||||
): JSX.Element {
|
): JSX.Element {
|
||||||
const { readonly, factory = repositoryFactory } = props
|
const { readonly, factory = repositoryFactory } = props
|
||||||
const { data } = useContext(RepositoryProviderContext)
|
const { data, setIsUpdating } = useContext(RepositoryProviderContext)
|
||||||
const { showSuccess, showError, clear } = useToaster()
|
const { showSuccess, showError, clear } = useToaster()
|
||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
|
const { parent } = useAppStore()
|
||||||
const spaceRef = useGetSpaceRef()
|
const spaceRef = useGetSpaceRef()
|
||||||
|
|
||||||
const { mutateAsync: modifyUpstreamProxy } = useModifyRegistryMutation()
|
const { mutateAsync: modifyUpstreamProxy } = useModifyRegistryMutation()
|
||||||
|
@ -74,6 +76,7 @@ function UpstreamProxyConfigurationForm(
|
||||||
|
|
||||||
const handleModifyUpstreamProxy = async (values: UpstreamRegistryRequest): Promise<void> => {
|
const handleModifyUpstreamProxy = async (values: UpstreamRegistryRequest): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
|
setIsUpdating(true)
|
||||||
const response = await modifyUpstreamProxy({
|
const response = await modifyUpstreamProxy({
|
||||||
registry_ref: spaceRef,
|
registry_ref: spaceRef,
|
||||||
body: values
|
body: values
|
||||||
|
@ -85,13 +88,16 @@ function UpstreamProxyConfigurationForm(
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
showError(getErrorInfoFromErrorObject(e, true))
|
showError(getErrorInfoFromErrorObject(e, true))
|
||||||
|
} finally {
|
||||||
|
setIsUpdating(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSubmit = async (values: UpstreamRegistryRequest): Promise<void> => {
|
const handleSubmit = async (values: UpstreamRegistryRequest): Promise<void> => {
|
||||||
const repositoryType = factory.getRepositoryType(values.packageType)
|
const repositoryType = factory.getRepositoryType(values.packageType)
|
||||||
if (repositoryType) {
|
if (repositoryType) {
|
||||||
const transformedCleanupPolicy = getFormattedFormDataForCleanupPolicy(values)
|
const transfomedAuthType = getFormattedFormDataForAuthType(values, parent)
|
||||||
|
const transformedCleanupPolicy = getFormattedFormDataForCleanupPolicy(transfomedAuthType)
|
||||||
const transformedValues = repositoryType.processUpstreamProxyFormData(
|
const transformedValues = repositoryType.processUpstreamProxyFormData(
|
||||||
transformedCleanupPolicy
|
transformedCleanupPolicy
|
||||||
) as UpstreamRegistryRequest
|
) as UpstreamRegistryRequest
|
||||||
|
@ -111,16 +117,18 @@ function UpstreamProxyConfigurationForm(
|
||||||
authType: Yup.string()
|
authType: Yup.string()
|
||||||
.required()
|
.required()
|
||||||
.oneOf([UpstreamProxyAuthenticationMode.ANONYMOUS, UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD]),
|
.oneOf([UpstreamProxyAuthenticationMode.ANONYMOUS, UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD]),
|
||||||
auth: Yup.object().when(['authType'], {
|
auth: Yup.object()
|
||||||
is: (authType: UpstreamProxyAuthenticationMode) =>
|
.when(['authType'], {
|
||||||
authType === UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD,
|
is: (authType: UpstreamProxyAuthenticationMode) =>
|
||||||
then: (schema: Yup.ObjectSchema<UserPassword | Anonymous>) =>
|
authType === UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD,
|
||||||
schema.shape({
|
then: (schema: Yup.ObjectSchema<UserPassword | Anonymous>) =>
|
||||||
userName: Yup.string().trim().required(getString('validationMessages.userNameRequired')),
|
schema.shape({
|
||||||
secretIdentifier: Yup.string().trim().required(getString('validationMessages.passwordRequired'))
|
userName: Yup.string().trim().required(getString('validationMessages.userNameRequired')),
|
||||||
}),
|
secretIdentifier: Yup.string().trim().required(getString('validationMessages.passwordRequired'))
|
||||||
otherwise: Yup.object().optional().nullable()
|
}),
|
||||||
}),
|
otherwise: Yup.object().optional().nullable()
|
||||||
|
})
|
||||||
|
.nullable(),
|
||||||
url: Yup.string().when(['source'], {
|
url: Yup.string().when(['source'], {
|
||||||
is: (source: DockerRepositoryURLInputSource) => source === DockerRepositoryURLInputSource.Custom,
|
is: (source: DockerRepositoryURLInputSource) => source === DockerRepositoryURLInputSource.Custom,
|
||||||
then: (schema: Yup.StringSchema) =>
|
then: (schema: Yup.StringSchema) =>
|
||||||
|
|
|
@ -29,7 +29,7 @@ import {
|
||||||
} from '@harnessio/uicore'
|
} from '@harnessio/uicore'
|
||||||
import { Anonymous, UserPassword, useCreateRegistryMutation } from '@harnessio/react-har-service-client'
|
import { Anonymous, UserPassword, useCreateRegistryMutation } from '@harnessio/react-har-service-client'
|
||||||
|
|
||||||
import { useGetSpaceRef } from '@ar/hooks'
|
import { useAppStore, useGetSpaceRef } from '@ar/hooks'
|
||||||
import { useStrings } from '@ar/frameworks/strings'
|
import { useStrings } from '@ar/frameworks/strings'
|
||||||
import { decodeRef } from '@ar/hooks/useGetSpaceRef'
|
import { decodeRef } from '@ar/hooks/useGetSpaceRef'
|
||||||
import { setFormikRef } from '@ar/common/utils'
|
import { setFormikRef } from '@ar/common/utils'
|
||||||
|
@ -127,6 +127,7 @@ function UpstreamProxyCreateForm(props: UpstreamProxyCreateFormProps, formikRef:
|
||||||
const { showSuccess, showError, clear } = useToaster()
|
const { showSuccess, showError, clear } = useToaster()
|
||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
const spaceRef = useGetSpaceRef('')
|
const spaceRef = useGetSpaceRef('')
|
||||||
|
const { parent, scope } = useAppStore()
|
||||||
|
|
||||||
const { isLoading: loading, mutateAsync: createUpstreamProxy } = useCreateRegistryMutation()
|
const { isLoading: loading, mutateAsync: createUpstreamProxy } = useCreateRegistryMutation()
|
||||||
|
|
||||||
|
@ -137,6 +138,9 @@ function UpstreamProxyCreateForm(props: UpstreamProxyCreateFormProps, formikRef:
|
||||||
const handleCreateUpstreamProxy = async (values: UpstreamRegistryRequest): Promise<void> => {
|
const handleCreateUpstreamProxy = async (values: UpstreamRegistryRequest): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const response = await createUpstreamProxy({
|
const response = await createUpstreamProxy({
|
||||||
|
queryParams: {
|
||||||
|
space_ref: decodeRef(spaceRef)
|
||||||
|
},
|
||||||
body: {
|
body: {
|
||||||
...values,
|
...values,
|
||||||
parentRef: decodeRef(spaceRef)
|
parentRef: decodeRef(spaceRef)
|
||||||
|
@ -155,7 +159,7 @@ function UpstreamProxyCreateForm(props: UpstreamProxyCreateFormProps, formikRef:
|
||||||
const handleSubmit = (values: UpstreamRegistryRequest): void => {
|
const handleSubmit = (values: UpstreamRegistryRequest): void => {
|
||||||
const repositoryType = factory.getRepositoryType(values.packageType)
|
const repositoryType = factory.getRepositoryType(values.packageType)
|
||||||
if (repositoryType) {
|
if (repositoryType) {
|
||||||
const transfomedAuthType = getFormattedFormDataForAuthType(values)
|
const transfomedAuthType = getFormattedFormDataForAuthType(values, parent, scope)
|
||||||
const transformedCleanupPolicy = getFormattedFormDataForCleanupPolicy(transfomedAuthType)
|
const transformedCleanupPolicy = getFormattedFormDataForCleanupPolicy(transfomedAuthType)
|
||||||
const transformedValues = repositoryType.processUpstreamProxyFormData(
|
const transformedValues = repositoryType.processUpstreamProxyFormData(
|
||||||
transformedCleanupPolicy
|
transformedCleanupPolicy
|
||||||
|
@ -192,15 +196,17 @@ function UpstreamProxyCreateForm(props: UpstreamProxyCreateFormProps, formikRef:
|
||||||
authType: Yup.string()
|
authType: Yup.string()
|
||||||
.required()
|
.required()
|
||||||
.oneOf([UpstreamProxyAuthenticationMode.ANONYMOUS, UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD]),
|
.oneOf([UpstreamProxyAuthenticationMode.ANONYMOUS, UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD]),
|
||||||
auth: Yup.object().when(['authType'], {
|
auth: Yup.object()
|
||||||
is: (authType: UpstreamProxyAuthenticationMode) =>
|
.when(['authType'], {
|
||||||
authType === UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD,
|
is: (authType: UpstreamProxyAuthenticationMode) =>
|
||||||
then: (schema: Yup.ObjectSchema<UserPassword | Anonymous>) =>
|
authType === UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD,
|
||||||
schema.shape({
|
then: (schema: Yup.ObjectSchema<UserPassword | Anonymous>) =>
|
||||||
userName: Yup.string().trim().required(getString('validationMessages.userNameRequired')),
|
schema.shape({
|
||||||
secretIdentifier: Yup.string().trim().required(getString('validationMessages.passwordRequired'))
|
userName: Yup.string().trim().required(getString('validationMessages.userNameRequired')),
|
||||||
})
|
secretIdentifier: Yup.string().trim().required(getString('validationMessages.passwordRequired'))
|
||||||
}),
|
})
|
||||||
|
})
|
||||||
|
.nullable(),
|
||||||
url: Yup.string().when(['source'], {
|
url: Yup.string().when(['source'], {
|
||||||
is: (source: DockerRepositoryURLInputSource) => source === DockerRepositoryURLInputSource.Custom,
|
is: (source: DockerRepositoryURLInputSource) => source === DockerRepositoryURLInputSource.Custom,
|
||||||
then: (schema: Yup.StringSchema) =>
|
then: (schema: Yup.StringSchema) =>
|
||||||
|
|
|
@ -15,17 +15,47 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import produce from 'immer'
|
import produce from 'immer'
|
||||||
import { set } from 'lodash-es'
|
import { compact, get, set } from 'lodash-es'
|
||||||
|
import type { Scope } from '@ar/MFEAppTypes'
|
||||||
|
import { Parent } from '@ar/common/types'
|
||||||
import {
|
import {
|
||||||
DockerRepositoryURLInputSource,
|
DockerRepositoryURLInputSource,
|
||||||
UpstreamProxyAuthenticationMode,
|
UpstreamProxyAuthenticationMode,
|
||||||
type UpstreamRegistryRequest
|
type UpstreamRegistryRequest
|
||||||
} from '../../types'
|
} from '../../types'
|
||||||
|
|
||||||
export function getFormattedFormDataForAuthType(values: UpstreamRegistryRequest): UpstreamRegistryRequest {
|
export function getSecretSpacePath(referenceString: string, scope?: Scope) {
|
||||||
|
if (!scope) return referenceString
|
||||||
|
if (referenceString.startsWith('account.')) {
|
||||||
|
return compact([scope.accountId]).join('/')
|
||||||
|
}
|
||||||
|
if (referenceString.startsWith('org.')) {
|
||||||
|
return compact([scope.accountId, scope.orgIdentifier]).join('/')
|
||||||
|
}
|
||||||
|
return compact([scope.accountId, scope.orgIdentifier, scope.projectIdentifier]).join('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getReferenceStringFromSecretSpacePath(identifier: string, secretSpacePath: string) {
|
||||||
|
const [accountId, orgIdentifier, projectIdentifier] = secretSpacePath.split('/')
|
||||||
|
if (projectIdentifier) return identifier
|
||||||
|
if (orgIdentifier) return `org.${identifier}`
|
||||||
|
if (accountId) return `account.${identifier}`
|
||||||
|
return identifier
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFormattedFormDataForAuthType(
|
||||||
|
values: UpstreamRegistryRequest,
|
||||||
|
parent?: Parent,
|
||||||
|
scope?: Scope
|
||||||
|
): UpstreamRegistryRequest {
|
||||||
return produce(values, (draft: UpstreamRegistryRequest) => {
|
return produce(values, (draft: UpstreamRegistryRequest) => {
|
||||||
if (draft.config.authType === UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD) {
|
if (draft.config.authType === UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD) {
|
||||||
set(draft, 'config.auth.authType', draft.config.authType)
|
set(draft, 'config.auth.authType', draft.config.authType)
|
||||||
|
if (parent === Parent.Enterprise) {
|
||||||
|
const password = draft.config.auth?.secretIdentifier
|
||||||
|
set(draft, 'config.auth.secretSpacePath', getSecretSpacePath(get(password, 'referenceString', ''), scope))
|
||||||
|
set(draft, 'config.auth.secretIdentifier', get(password, 'identifier'))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (draft.config.source !== DockerRepositoryURLInputSource.Custom) {
|
if (draft.config.source !== DockerRepositoryURLInputSource.Custom) {
|
||||||
set(draft, 'config.url', '')
|
set(draft, 'config.url', '')
|
||||||
|
|
|
@ -51,10 +51,10 @@ export default function DeleteUpstreamProxy({ data }: UpstreamProxyActionProps):
|
||||||
onClick={handleDeleteService}
|
onClick={handleDeleteService}
|
||||||
permission={{
|
permission={{
|
||||||
resource: {
|
resource: {
|
||||||
resourceType: ResourceType.SERVICE,
|
resourceType: ResourceType.ARTIFACT_REGISTRY,
|
||||||
resourceIdentifier: data.identifier
|
resourceIdentifier: data.identifier
|
||||||
},
|
},
|
||||||
permission: PermissionIdentifier.DELETE_SERVICE
|
permission: PermissionIdentifier.DELETE_ARTIFACT_REGISTRY
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue