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
|
@ -24,7 +24,7 @@ import (
|
|||
"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.
|
||||
type pathCacheGetter struct {
|
||||
spacePathStore store.SpacePathStore
|
||||
|
|
|
@ -94,12 +94,16 @@ type (
|
|||
DeleteServiceAccount(ctx context.Context, id int64) error
|
||||
|
||||
// ListServiceAccounts returns a list of service accounts for a specific parent.
|
||||
ListServiceAccounts(ctx context.Context,
|
||||
parentType enum.ParentResourceType, parentID int64) ([]*types.ServiceAccount, error)
|
||||
ListServiceAccounts(
|
||||
ctx context.Context,
|
||||
parentType enum.ParentResourceType, parentID int64,
|
||||
) ([]*types.ServiceAccount, error)
|
||||
|
||||
// CountServiceAccounts returns a count of service accounts for a specific parent.
|
||||
CountServiceAccounts(ctx context.Context,
|
||||
parentType enum.ParentResourceType, parentID int64) (int64, error)
|
||||
CountServiceAccounts(
|
||||
ctx context.Context,
|
||||
parentType enum.ParentResourceType, parentID int64,
|
||||
) (int64, error)
|
||||
|
||||
/*
|
||||
* SERVICE RELATED OPERATIONS.
|
||||
|
@ -167,6 +171,9 @@ type (
|
|||
// FindByRef finds the space using the spaceRef as either the id or the space path.
|
||||
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(ctx context.Context, spaceRef string, deletedAt int64) (*types.Space, error)
|
||||
|
||||
|
@ -192,8 +199,10 @@ type (
|
|||
Update(ctx context.Context, space *types.Space) error
|
||||
|
||||
// UpdateOptLock updates the space using the optimistic locking mechanism.
|
||||
UpdateOptLock(ctx context.Context, space *types.Space,
|
||||
mutateFn func(space *types.Space) error) (*types.Space, error)
|
||||
UpdateOptLock(
|
||||
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(ctx context.Context, id int64) (*types.Space, error)
|
||||
|
@ -205,8 +214,10 @@ type (
|
|||
Purge(ctx context.Context, id int64, deletedAt *int64) error
|
||||
|
||||
// Restore restores a soft deleted space.
|
||||
Restore(ctx context.Context, space *types.Space,
|
||||
newIdentifier *string, newParentID *int64) (*types.Space, error)
|
||||
Restore(
|
||||
ctx context.Context, space *types.Space,
|
||||
newIdentifier *string, newParentID *int64,
|
||||
) (*types.Space, error)
|
||||
|
||||
// Count the child spaces of a space.
|
||||
Count(ctx context.Context, id int64, opts *types.SpaceFilter) (int64, error)
|
||||
|
@ -239,8 +250,10 @@ type (
|
|||
GetSize(ctx context.Context, id int64) (int64, error)
|
||||
|
||||
// UpdateOptLock the repo details using the optimistic locking mechanism.
|
||||
UpdateOptLock(ctx context.Context, repo *types.Repository,
|
||||
mutateFn func(repository *types.Repository) error) (*types.Repository, error)
|
||||
UpdateOptLock(
|
||||
ctx context.Context, repo *types.Repository,
|
||||
mutateFn func(repository *types.Repository) error,
|
||||
) (*types.Repository, error)
|
||||
|
||||
// SoftDelete a repo.
|
||||
SoftDelete(ctx context.Context, repo *types.Repository, deletedAt int64) error
|
||||
|
@ -249,8 +262,10 @@ type (
|
|||
Purge(ctx context.Context, id int64, deletedAt *int64) error
|
||||
|
||||
// Restore a deleted repo using the optimistic locking mechanism.
|
||||
Restore(ctx context.Context, repo *types.Repository,
|
||||
newIdentifier *string, newParentID *int64) (*types.Repository, error)
|
||||
Restore(
|
||||
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(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)
|
||||
ListUsers(ctx context.Context, spaceID int64, filter types.MembershipUserFilter) ([]types.MembershipUser, 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.
|
||||
|
@ -362,8 +381,10 @@ type (
|
|||
Update(ctx context.Context, pr *types.PullReq) error
|
||||
|
||||
// UpdateOptLock the pull request details using the optimistic locking mechanism.
|
||||
UpdateOptLock(ctx context.Context, pr *types.PullReq,
|
||||
mutateFn func(pr *types.PullReq) error) (*types.PullReq, error)
|
||||
UpdateOptLock(
|
||||
ctx context.Context, pr *types.PullReq,
|
||||
mutateFn func(pr *types.PullReq) error,
|
||||
) (*types.PullReq, error)
|
||||
|
||||
// UpdateActivitySeq the pull request's activity sequence number.
|
||||
// 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
|
||||
|
||||
// CreateWithPayload create a new system activity from the provided payload.
|
||||
CreateWithPayload(ctx context.Context,
|
||||
CreateWithPayload(
|
||||
ctx context.Context,
|
||||
pr *types.PullReq,
|
||||
principalID int64,
|
||||
payload types.PullReqActivityPayload,
|
||||
|
@ -406,7 +428,8 @@ type (
|
|||
Update(ctx context.Context, act *types.PullReqActivity) error
|
||||
|
||||
// UpdateOptLock updates the pull request activity using the optimistic locking mechanism.
|
||||
UpdateOptLock(ctx context.Context,
|
||||
UpdateOptLock(
|
||||
ctx context.Context,
|
||||
act *types.PullReqActivity,
|
||||
mutateFn func(act *types.PullReqActivity) error,
|
||||
) (*types.PullReqActivity, error)
|
||||
|
@ -543,8 +566,10 @@ type (
|
|||
Update(ctx context.Context, hook *types.Webhook) error
|
||||
|
||||
// UpdateOptLock updates the webhook using the optimistic locking mechanism.
|
||||
UpdateOptLock(ctx context.Context, hook *types.Webhook,
|
||||
mutateFn func(hook *types.Webhook) error) (*types.Webhook, error)
|
||||
UpdateOptLock(
|
||||
ctx context.Context, hook *types.Webhook,
|
||||
mutateFn func(hook *types.Webhook) error,
|
||||
) (*types.Webhook, error)
|
||||
|
||||
// Delete deletes the webhook for the given id.
|
||||
Delete(ctx context.Context, id int64) error
|
||||
|
@ -553,12 +578,16 @@ type (
|
|||
DeleteByIdentifier(ctx context.Context, parentType enum.WebhookParent, parentID int64, identifier string) error
|
||||
|
||||
// Count counts the webhooks for a given parent type and id.
|
||||
Count(ctx context.Context, parentType enum.WebhookParent, parentID int64,
|
||||
opts *types.WebhookFilter) (int64, error)
|
||||
Count(
|
||||
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(ctx context.Context, parentType enum.WebhookParent, parentID int64,
|
||||
opts *types.WebhookFilter) ([]*types.Webhook, error)
|
||||
List(
|
||||
ctx context.Context, parentType enum.WebhookParent, parentID int64,
|
||||
opts *types.WebhookFilter,
|
||||
) ([]*types.Webhook, error)
|
||||
}
|
||||
|
||||
// WebhookExecutionStore defines the webhook execution data storage.
|
||||
|
@ -573,8 +602,10 @@ type (
|
|||
DeleteOld(ctx context.Context, olderThan time.Time) (int64, error)
|
||||
|
||||
// ListForWebhook lists the webhook executions for a given webhook id.
|
||||
ListForWebhook(ctx context.Context, webhookID int64,
|
||||
opts *types.WebhookExecutionFilter) ([]*types.WebhookExecution, error)
|
||||
ListForWebhook(
|
||||
ctx context.Context, webhookID int64,
|
||||
opts *types.WebhookExecutionFilter,
|
||||
) ([]*types.WebhookExecution, error)
|
||||
|
||||
// ListForTrigger lists the webhook executions for a given trigger id.
|
||||
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 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 {
|
||||
|
@ -677,7 +711,8 @@ type (
|
|||
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(ctx context.Context,
|
||||
List(
|
||||
ctx context.Context,
|
||||
infraProviderConfigID int64,
|
||||
filter types.ListQueryFilter,
|
||||
) ([]*types.InfraProviderResource, error)
|
||||
|
@ -707,8 +742,10 @@ type (
|
|||
ListLatest(ctx context.Context, repoID int64, pagination types.ListQueryFilter) ([]*types.Pipeline, error)
|
||||
|
||||
// UpdateOptLock updates the pipeline using the optimistic locking mechanism.
|
||||
UpdateOptLock(ctx context.Context, pipeline *types.Pipeline,
|
||||
mutateFn func(pipeline *types.Pipeline) error) (*types.Pipeline, error)
|
||||
UpdateOptLock(
|
||||
ctx context.Context, pipeline *types.Pipeline,
|
||||
mutateFn func(pipeline *types.Pipeline) error,
|
||||
) (*types.Pipeline, error)
|
||||
|
||||
// Delete deletes a pipeline ID from the datastore.
|
||||
Delete(ctx context.Context, id int64) error
|
||||
|
@ -743,8 +780,10 @@ type (
|
|||
Count(ctx context.Context, spaceID int64, pagination types.ListQueryFilter) (int64, error)
|
||||
|
||||
// UpdateOptLock updates the secret using the optimistic locking mechanism.
|
||||
UpdateOptLock(ctx context.Context, secret *types.Secret,
|
||||
mutateFn func(secret *types.Secret) error) (*types.Secret, error)
|
||||
UpdateOptLock(
|
||||
ctx context.Context, secret *types.Secret,
|
||||
mutateFn func(secret *types.Secret) error,
|
||||
) (*types.Secret, error)
|
||||
|
||||
// Update tries to update a secret.
|
||||
Update(ctx context.Context, secret *types.Secret) error
|
||||
|
@ -837,8 +876,10 @@ type (
|
|||
Count(ctx context.Context, spaceID int64, pagination types.ListQueryFilter) (int64, error)
|
||||
|
||||
// UpdateOptLock updates the connector using the optimistic locking mechanism.
|
||||
UpdateOptLock(ctx context.Context, connector *types.Connector,
|
||||
mutateFn func(connector *types.Connector) error) (*types.Connector, error)
|
||||
UpdateOptLock(
|
||||
ctx context.Context, connector *types.Connector,
|
||||
mutateFn func(connector *types.Connector) error,
|
||||
) (*types.Connector, error)
|
||||
|
||||
// Update tries to update a connector.
|
||||
Update(ctx context.Context, connector *types.Connector) error
|
||||
|
@ -858,8 +899,10 @@ type (
|
|||
Find(ctx context.Context, id int64) (*types.Template, error)
|
||||
|
||||
// FindByIdentifierAndType returns a template given a space ID, identifier and a type
|
||||
FindByIdentifierAndType(ctx context.Context, spaceID int64,
|
||||
identifier string, resolverType enum.ResolverType) (*types.Template, error)
|
||||
FindByIdentifierAndType(
|
||||
ctx context.Context, spaceID int64,
|
||||
identifier string, resolverType enum.ResolverType,
|
||||
) (*types.Template, error)
|
||||
|
||||
// Create creates a new template.
|
||||
Create(ctx context.Context, template *types.Template) error
|
||||
|
@ -868,8 +911,10 @@ type (
|
|||
Count(ctx context.Context, spaceID int64, pagination types.ListQueryFilter) (int64, error)
|
||||
|
||||
// UpdateOptLock updates the template using the optimistic locking mechanism.
|
||||
UpdateOptLock(ctx context.Context, template *types.Template,
|
||||
mutateFn func(template *types.Template) error) (*types.Template, error)
|
||||
UpdateOptLock(
|
||||
ctx context.Context, template *types.Template,
|
||||
mutateFn func(template *types.Template) error,
|
||||
) (*types.Template, error)
|
||||
|
||||
// Update tries to update a template.
|
||||
Update(ctx context.Context, template *types.Template) error
|
||||
|
@ -878,7 +923,12 @@ type (
|
|||
Delete(ctx context.Context, id int64) error
|
||||
|
||||
// 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(ctx context.Context, spaceID int64, filter types.ListQueryFilter) ([]*types.Template, error)
|
||||
|
@ -895,8 +945,10 @@ type (
|
|||
Update(ctx context.Context, trigger *types.Trigger) error
|
||||
|
||||
// UpdateOptLock updates the trigger using the optimistic locking mechanism.
|
||||
UpdateOptLock(ctx context.Context, trigger *types.Trigger,
|
||||
mutateFn func(trigger *types.Trigger) error) (*types.Trigger, error)
|
||||
UpdateOptLock(
|
||||
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(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)
|
||||
|
||||
// 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(ctx context.Context, ids []int64) ([]*types.UserGroup, error)
|
||||
|
@ -1145,7 +1201,8 @@ type (
|
|||
spaceID int64,
|
||||
gitspaceInstanceID int64,
|
||||
) (*types.InfraProvisioned, error)
|
||||
FindLatestByGitspaceInstanceIdentifier(ctx context.Context,
|
||||
FindLatestByGitspaceInstanceIdentifier(
|
||||
ctx context.Context,
|
||||
spaceID int64,
|
||||
gitspaceInstanceIdentifier string,
|
||||
) (*types.InfraProvisioned, error)
|
||||
|
|
|
@ -125,6 +125,40 @@ func (s *SpaceStore) FindByRef(ctx context.Context, spaceRef string) (*types.Spa
|
|||
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.
|
||||
func (s *SpaceStore) FindByRefAndDeletedAt(
|
||||
ctx context.Context,
|
||||
|
@ -701,7 +735,8 @@ func (s *SpaceStore) list(
|
|||
return s.mapToSpaces(ctx, s.db, dst)
|
||||
}
|
||||
|
||||
func (s *SpaceStore) listAll(ctx context.Context,
|
||||
func (s *SpaceStore) listAll(
|
||||
ctx context.Context,
|
||||
id int64,
|
||||
opts *types.SpaceFilter,
|
||||
) ([]*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.
|
||||
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)
|
||||
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)
|
||||
|
||||
for i := len(segments) - 2; i >= 0; i-- {
|
||||
parentAlias := "s" + fmt.Sprint(i)
|
||||
alias := "s" + fmt.Sprint(i+1)
|
||||
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)).
|
||||
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])
|
||||
}
|
||||
|
||||
|
|
|
@ -129,6 +129,7 @@ import (
|
|||
"github.com/harness/gitness/livelog"
|
||||
"github.com/harness/gitness/lock"
|
||||
"github.com/harness/gitness/pubsub"
|
||||
"github.com/harness/gitness/registry/app/pkg/docker"
|
||||
"github.com/harness/gitness/ssh"
|
||||
"github.com/harness/gitness/store/database/dbtx"
|
||||
"github.com/harness/gitness/types"
|
||||
|
@ -270,6 +271,7 @@ func initSystem(ctx context.Context, config *types.Config) (*cliserver.System, e
|
|||
aiagent.WireSet,
|
||||
capabilities.WireSet,
|
||||
capabilitiesservice.WireSet,
|
||||
docker.ProvideReporter,
|
||||
secretservice.WireSet,
|
||||
containerGit.WireSet,
|
||||
containerUser.WireSet,
|
||||
|
|
|
@ -432,16 +432,17 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
|
|||
mediaTypesRepository := database2.ProvideMediaTypeDao(db)
|
||||
blobRepository := database2.ProvideBlobDao(db, mediaTypesRepository)
|
||||
storageService := docker.StorageServiceProvider(config, storageDriver)
|
||||
manifestRepository := database2.ProvideManifestDao(db, mediaTypesRepository)
|
||||
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)
|
||||
manifestRepository := database2.ProvideManifestDao(db, mediaTypesRepository)
|
||||
manifestReferenceRepository := database2.ProvideManifestRefDao(db)
|
||||
tagRepository := database2.ProvideTagDao(db)
|
||||
imageRepository := database2.ProvideImageDao(db)
|
||||
artifactRepository := database2.ProvideArtifactDao(db)
|
||||
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)
|
||||
bandwidthStatRepository := database2.ProvideBandwidthStatDao(db)
|
||||
downloadStatRepository := database2.ProvideDownloadStatDao(db)
|
||||
|
|
|
@ -25,19 +25,53 @@ import (
|
|||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func GetArtifactMetadata(artifacts *[]types.ArtifactMetadata) []artifactapi.ArtifactMetadata {
|
||||
artifactMetadataList := make([]artifactapi.ArtifactMetadata, 0, len(*artifacts))
|
||||
for _, artifact := range *artifacts {
|
||||
artifactMetadata := mapToArtifactMetadata(artifact)
|
||||
func GetArtifactMetadata(
|
||||
artifacts []types.ArtifactMetadata,
|
||||
rootIdentifier string,
|
||||
registryURL string,
|
||||
) []artifactapi.ArtifactMetadata {
|
||||
artifactMetadataList := make([]artifactapi.ArtifactMetadata, 0, len(artifacts))
|
||||
for _, artifact := range artifacts {
|
||||
artifactMetadata := mapToArtifactMetadata(artifact, rootIdentifier, registryURL)
|
||||
artifactMetadataList = append(artifactMetadataList, *artifactMetadata)
|
||||
}
|
||||
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)
|
||||
packageType := artifact.PackageType
|
||||
pullCommand := GetPullCommand(rootIdentifier, artifact.RepoName, artifact.Name, artifact.Version,
|
||||
string(packageType), registryURL)
|
||||
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,
|
||||
Name: artifact.Name,
|
||||
LatestVersion: artifact.LatestVersion,
|
||||
|
@ -103,8 +137,15 @@ func GetAllArtifactResponse(
|
|||
count int64,
|
||||
pageNumber int64,
|
||||
pageSize int,
|
||||
rootIdentifier string,
|
||||
registryURL string,
|
||||
) *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)
|
||||
listArtifact := &artifactapi.ListArtifact{
|
||||
ItemCount: &count,
|
||||
|
@ -120,6 +161,33 @@ func GetAllArtifactResponse(
|
|||
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(
|
||||
artifactLabels *[]string,
|
||||
count int64,
|
||||
|
|
|
@ -57,6 +57,21 @@ type RegistryRequestInfo struct {
|
|||
pageNumber int64
|
||||
searchTerm 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
|
||||
|
@ -117,50 +132,45 @@ func (c *APIController) GetRegistryRequestBaseInfo(
|
|||
|
||||
func (c *APIController) GetRegistryRequestInfo(
|
||||
ctx context.Context,
|
||||
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,
|
||||
registryRequestParams RegistryRequestParams,
|
||||
) (*RegistryRequestInfo, error) {
|
||||
packageTypes := []string{}
|
||||
if packageTypesParam != nil {
|
||||
packageTypes = *packageTypesParam
|
||||
if registryRequestParams.packageTypesParam != nil {
|
||||
packageTypes = *registryRequestParams.packageTypesParam
|
||||
}
|
||||
registryIDs := []string{}
|
||||
if registryRequestParams.registryIDsParam != nil {
|
||||
registryIDs = *registryRequestParams.registryIDsParam
|
||||
}
|
||||
sortByField := ""
|
||||
sortByOrder := ""
|
||||
if sortOrder != nil {
|
||||
sortByOrder = string(*sortOrder)
|
||||
if registryRequestParams.sortOrder != nil {
|
||||
sortByOrder = string(*registryRequestParams.sortOrder)
|
||||
}
|
||||
|
||||
if sortField != nil {
|
||||
sortByField = string(*sortField)
|
||||
if registryRequestParams.sortField != nil {
|
||||
sortByField = string(*registryRequestParams.sortField)
|
||||
}
|
||||
|
||||
labels := []string{}
|
||||
|
||||
if labelsParam != nil {
|
||||
labels = *labelsParam
|
||||
if registryRequestParams.labelsParam != nil {
|
||||
labels = *registryRequestParams.labelsParam
|
||||
}
|
||||
|
||||
sortByField = GetSortByField(sortByField, resource)
|
||||
sortByField = GetSortByField(sortByField, registryRequestParams.resource)
|
||||
sortByOrder = GetSortByOrder(sortByOrder)
|
||||
|
||||
offset := GetOffset(size, page)
|
||||
limit := GetPageLimit(size)
|
||||
pageNumber := GetPageNumber(page)
|
||||
offset := GetOffset(registryRequestParams.size, registryRequestParams.page)
|
||||
limit := GetPageLimit(registryRequestParams.size)
|
||||
pageNumber := GetPageNumber(registryRequestParams.page)
|
||||
|
||||
searchTerm := ""
|
||||
if search != nil {
|
||||
searchTerm = string(*search)
|
||||
if registryRequestParams.search != nil {
|
||||
searchTerm = string(*registryRequestParams.search)
|
||||
}
|
||||
|
||||
baseInfo, err := c.GetRegistryRequestBaseInfo(ctx, parentRef, regRef)
|
||||
baseInfo, err := c.GetRegistryRequestBaseInfo(ctx, registryRequestParams.parentRef, registryRequestParams.regRef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -175,6 +185,7 @@ func (c *APIController) GetRegistryRequestInfo(
|
|||
pageNumber: pageNumber,
|
||||
searchTerm: searchTerm,
|
||||
labels: labels,
|
||||
registryIDs: registryIDs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@ import (
|
|||
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/registry/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
)
|
||||
|
||||
|
@ -29,20 +28,21 @@ func (c *APIController) GetAllArtifacts(
|
|||
ctx context.Context,
|
||||
r artifact.GetAllArtifactsRequestObject,
|
||||
) (artifact.GetAllArtifactsResponseObject, error) {
|
||||
ref := ""
|
||||
if r.Params.RegIdentifier != nil {
|
||||
ref2, err2 := GetRegRef(string(r.SpaceRef), string(*r.Params.RegIdentifier))
|
||||
if err2 != nil {
|
||||
return c.getAllArtifacts400JsonResponse(err2)
|
||||
}
|
||||
ref = ref2
|
||||
registryRequestParams := &RegistryRequestParams{
|
||||
packageTypesParam: nil,
|
||||
page: r.Params.Page,
|
||||
size: r.Params.Size,
|
||||
search: r.Params.SearchTerm,
|
||||
resource: ArtifactResource,
|
||||
parentRef: string(r.SpaceRef),
|
||||
regRef: "",
|
||||
labelsParam: nil,
|
||||
sortOrder: r.Params.SortOrder,
|
||||
sortField: r.Params.SortField,
|
||||
registryIDsParam: r.Params.RegIdentifier,
|
||||
}
|
||||
|
||||
regInfo, err := c.GetRegistryRequestInfo(
|
||||
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,
|
||||
)
|
||||
regInfo, err := c.GetRegistryRequestInfo(ctx, *registryRequestParams)
|
||||
if err != nil {
|
||||
return c.getAllArtifacts400JsonResponse(err)
|
||||
}
|
||||
|
@ -70,28 +70,16 @@ func (c *APIController) GetAllArtifacts(
|
|||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
var artifacts *[]types.ArtifactMetadata
|
||||
var count int64
|
||||
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,
|
||||
)
|
||||
latestVersion := false
|
||||
if r.Params.LatestVersion != nil {
|
||||
latestVersion = bool(*r.Params.LatestVersion)
|
||||
}
|
||||
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 {
|
||||
return artifact.GetAllArtifacts500JSONResponse{
|
||||
InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse(
|
||||
|
@ -100,7 +88,8 @@ func (c *APIController) GetAllArtifacts(
|
|||
}, nil
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -28,11 +28,21 @@ func (c *APIController) ListArtifactLabels(
|
|||
ctx context.Context,
|
||||
r artifact.ListArtifactLabelsRequestObject,
|
||||
) (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(
|
||||
ctx, nil, r.Params.Page, r.Params.Size,
|
||||
r.Params.SearchTerm, ArtifactResource, "", string(r.RegistryRef),
|
||||
nil, nil, nil,
|
||||
)
|
||||
ctx, *registryRequestParams)
|
||||
|
||||
space, err := c.SpaceStore.FindByRef(ctx, regInfo.ParentRef)
|
||||
if err != nil {
|
||||
|
|
|
@ -36,11 +36,20 @@ func (c *APIController) GetAllArtifactVersions(
|
|||
ctx context.Context,
|
||||
r artifact.GetAllArtifactVersionsRequestObject,
|
||||
) (artifact.GetAllArtifactVersionsResponseObject, error) {
|
||||
regInfo, _ := c.GetRegistryRequestInfo(
|
||||
ctx, nil, r.Params.Page, r.Params.Size,
|
||||
r.Params.SearchTerm, ArtifactVersionResource, "", string(r.RegistryRef),
|
||||
nil, r.Params.SortOrder, r.Params.SortField,
|
||||
)
|
||||
registryRequestParams := &RegistryRequestParams{
|
||||
packageTypesParam: nil,
|
||||
page: r.Params.Page,
|
||||
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)
|
||||
if err != nil {
|
||||
|
|
|
@ -32,11 +32,20 @@ func (c *APIController) GetAllRegistries(
|
|||
ctx context.Context,
|
||||
r artifact.GetAllRegistriesRequestObject,
|
||||
) (artifact.GetAllRegistriesResponseObject, error) {
|
||||
regInfo, _ := c.GetRegistryRequestInfo(
|
||||
ctx, r.Params.PackageType, r.Params.Page, r.Params.Size,
|
||||
r.Params.SearchTerm, RepositoryResource, string(r.SpaceRef), "", nil,
|
||||
r.Params.SortOrder, r.Params.SortField,
|
||||
)
|
||||
registryRequestParams := &RegistryRequestParams{
|
||||
packageTypesParam: nil,
|
||||
page: r.Params.Page,
|
||||
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)
|
||||
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,
|
||||
r artifact.UpdateArtifactLabelsRequestObject,
|
||||
) (artifact.UpdateArtifactLabelsResponseObject, error) {
|
||||
regInfo, _ := c.GetRegistryRequestInfo(
|
||||
ctx, nil, nil, nil, nil,
|
||||
ArtifactVersionResource, "", string(r.RegistryRef), nil, nil, nil,
|
||||
)
|
||||
registryRequestParams := &RegistryRequestParams{
|
||||
packageTypesParam: 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)
|
||||
if err != nil {
|
||||
|
|
|
@ -400,7 +400,7 @@ func GetPermissionChecks(
|
|||
) []types.PermissionCheck {
|
||||
var permissionChecks []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},
|
||||
Permission: permission,
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ package oci
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
@ -34,6 +35,10 @@ import (
|
|||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type TokenResponseOCI struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
func (h *Handler) GetToken(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
session, ok := request.AuthSessionFrom(ctx)
|
||||
|
@ -79,8 +84,12 @@ func (h *Handler) GetToken(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
if jwtToken != "" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err := w.Write([]byte(fmt.Sprintf("{\"token\":\"%s\"}", jwtToken)))
|
||||
if err != nil {
|
||||
enc := json.NewEncoder(w)
|
||||
if err := enc.Encode(
|
||||
TokenResponseOCI{
|
||||
Token: jwtToken,
|
||||
},
|
||||
); err != nil {
|
||||
log.Error().Msgf("failed to write token response: %v", err)
|
||||
}
|
||||
return
|
||||
|
|
|
@ -173,17 +173,44 @@ paths:
|
|||
- Spaces
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/spaceRefPathParam"
|
||||
- $ref: "#/components/parameters/LabelsParam"
|
||||
- $ref: "#/components/parameters/packageTypeParam"
|
||||
- $ref: "#/components/parameters/RegistryIdentifierParam"
|
||||
- $ref: "#/components/parameters/pageNumber"
|
||||
- $ref: "#/components/parameters/pageSize"
|
||||
- $ref: "#/components/parameters/sortOrder"
|
||||
- $ref: "#/components/parameters/sortField"
|
||||
- $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:
|
||||
200:
|
||||
$ref: "#/components/responses/ListArtifactResponse"
|
||||
$ref: "#/components/responses/ListRegistryArtifactResponse"
|
||||
400:
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
401:
|
||||
|
@ -764,6 +791,20 @@ components:
|
|||
required:
|
||||
- status
|
||||
- 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:
|
||||
description: response for list versions of artifact
|
||||
content:
|
||||
|
@ -896,6 +937,36 @@ components:
|
|||
$ref: "#/components/schemas/ArtifactMetadata"
|
||||
required:
|
||||
- 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:
|
||||
type: object
|
||||
description: A list of Artifact versions
|
||||
|
@ -1029,6 +1100,36 @@ components:
|
|||
type:
|
||||
$ref: "#/components/schemas/ClientSetupStepType"
|
||||
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
|
||||
description: Artifact Metadata
|
||||
properties:
|
||||
|
@ -1503,6 +1604,8 @@ components:
|
|||
required: false
|
||||
description: Registry Identifier
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
spaceRefPathParam:
|
||||
name: space_ref
|
||||
|
@ -1601,6 +1704,13 @@ components:
|
|||
description: sortField
|
||||
schema:
|
||||
type: string
|
||||
latestVersion:
|
||||
name: latest_version
|
||||
in: query
|
||||
required: false
|
||||
description: Latest Version Filter.
|
||||
schema:
|
||||
type: boolean
|
||||
fromDateParam:
|
||||
name: from
|
||||
in: query
|
||||
|
|
|
@ -74,6 +74,9 @@ type ServerInterface interface {
|
|||
// List Artifact Versions
|
||||
// (GET /registry/{registry_ref}/artifact/{artifact}/versions)
|
||||
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
|
||||
// (GET /registry/{registry_ref}/client-setup-details)
|
||||
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)
|
||||
}
|
||||
|
||||
// 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
|
||||
// (GET /registry/{registry_ref}/client-setup-details)
|
||||
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))
|
||||
}
|
||||
|
||||
// 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
|
||||
func (siw *ServerInterfaceWrapper) GetClientSetupDetails(w http.ResponseWriter, r *http.Request) {
|
||||
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
|
||||
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" -------------
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ------------- 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) {
|
||||
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.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.Get(options.BaseURL+"/registry/{registry_ref}/client-setup-details", wrapper.GetClientSetupDetails)
|
||||
})
|
||||
|
@ -1559,6 +1640,14 @@ type ListArtifactVersionResponseJSONResponse struct {
|
|||
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 {
|
||||
// Data A list of Harness Artifact Registries
|
||||
Data ListRegistry `json:"data"`
|
||||
|
@ -2719,6 +2808,73 @@ func (response GetAllArtifactVersions500JSONResponse) VisitGetAllArtifactVersion
|
|||
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 {
|
||||
RegistryRef RegistryRefPathParam `json:"registry_ref"`
|
||||
Params GetClientSetupDetailsParams
|
||||
|
@ -3040,6 +3196,9 @@ type StrictServerInterface interface {
|
|||
// List Artifact Versions
|
||||
// (GET /registry/{registry_ref}/artifact/{artifact}/versions)
|
||||
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
|
||||
// (GET /registry/{registry_ref}/client-setup-details)
|
||||
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
|
||||
func (sh *strictHandler) GetClientSetupDetails(w http.ResponseWriter, r *http.Request, registryRef RegistryRefPathParam, params GetClientSetupDetailsParams) {
|
||||
var request GetClientSetupDetailsRequestObject
|
||||
|
@ -3682,69 +3868,70 @@ func (sh *strictHandler) GetAllRegistries(w http.ResponseWriter, r *http.Request
|
|||
// Base64 encoded, gzipped, json marshaled Swagger object
|
||||
var swaggerSpec = []string{
|
||||
|
||||
"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",
|
||||
"y6/L/wUAAP//B0v/XHmOAAA=",
|
||||
"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",
|
||||
}
|
||||
|
||||
// GetSwagger returns the content of the embedded swagger specification file
|
||||
|
|
|
@ -75,13 +75,14 @@ type ArtifactMetadata 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"`
|
||||
PullCommand *string `json:"pullCommand,omitempty"`
|
||||
RegistryIdentifier string `json:"registryIdentifier"`
|
||||
RegistryPath string `json:"registryPath"`
|
||||
Version *string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
// ArtifactStats Harness Artifact Stats
|
||||
|
@ -321,6 +322,24 @@ type ListRegistry struct {
|
|||
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
|
||||
type PackageType string
|
||||
|
||||
|
@ -343,6 +362,20 @@ type Registry struct {
|
|||
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
|
||||
type RegistryConfig struct {
|
||||
// Type refers to type of registry i.e virtual or upstream
|
||||
|
@ -427,7 +460,7 @@ type VirtualConfig struct {
|
|||
type LabelsParam []string
|
||||
|
||||
// RegistryIdentifierParam defines model for RegistryIdentifierParam.
|
||||
type RegistryIdentifierParam string
|
||||
type RegistryIdentifierParam []string
|
||||
|
||||
// RegistryTypeParam defines model for RegistryTypeParam.
|
||||
type RegistryTypeParam string
|
||||
|
@ -444,6 +477,9 @@ type DigestParam string
|
|||
// FromDateParam defines model for fromDateParam.
|
||||
type FromDateParam string
|
||||
|
||||
// LatestVersion defines model for latestVersion.
|
||||
type LatestVersion bool
|
||||
|
||||
// PackageTypeParam defines model for packageTypeParam.
|
||||
type PackageTypeParam []string
|
||||
|
||||
|
@ -612,6 +648,15 @@ type ListArtifactVersionResponse struct {
|
|||
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.
|
||||
type ListRegistryResponse struct {
|
||||
// Data A list of Harness Artifact Registries
|
||||
|
@ -717,6 +762,27 @@ type GetAllArtifactVersionsParams struct {
|
|||
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.
|
||||
type GetClientSetupDetailsParams struct {
|
||||
// Artifact Artifat
|
||||
|
@ -737,12 +803,6 @@ type GetArtifactStatsForSpaceParams struct {
|
|||
|
||||
// GetAllArtifactsParams defines parameters for GetAllArtifacts.
|
||||
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 *RegistryIdentifierParam `form:"reg_identifier,omitempty" json:"reg_identifier,omitempty"`
|
||||
|
||||
|
@ -760,6 +820,9 @@ type GetAllArtifactsParams struct {
|
|||
|
||||
// SearchTerm search Term.
|
||||
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.
|
||||
|
|
|
@ -30,6 +30,7 @@ import (
|
|||
"github.com/harness/gitness/registry/app/pkg/docker"
|
||||
"github.com/harness/gitness/registry/app/store/database"
|
||||
"github.com/harness/gitness/registry/config"
|
||||
"github.com/harness/gitness/registry/gc"
|
||||
"github.com/harness/gitness/types"
|
||||
|
||||
"github.com/google/wire"
|
||||
|
@ -64,9 +65,11 @@ func BlobStorageProvider(c *types.Config) (storagedriver.StorageDriver, error) {
|
|||
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,
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -77,6 +80,7 @@ var WireSet = wire.NewSet(
|
|||
pkg.WireSet,
|
||||
docker.WireSet,
|
||||
router.WireSet,
|
||||
gc.WireSet,
|
||||
)
|
||||
|
||||
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
|
||||
// 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)
|
||||
log.Ctx(ctx).Info().Msgf("Opening file %s %s", d.fullPath(path), d.rootDirectory)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
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,
|
||||
blobRepo store.BlobRepository, spaceStore corestore.SpaceStore,
|
||||
cfg *types.Config, storageService *registrystorage.Service,
|
||||
mtRepository store.MediaTypesRepository, manifestRepository store.ManifestRepository,
|
||||
gcService gc.Service,
|
||||
) *App {
|
||||
app := &App{
|
||||
|
@ -64,7 +63,7 @@ func NewApp(
|
|||
storageService: storageService,
|
||||
}
|
||||
app.configureSecret(cfg)
|
||||
gcService.Start(ctx, sqlDB, spaceStore, blobRepo, storageDeleter, cfg, mtRepository, manifestRepository)
|
||||
gcService.Start(ctx, sqlDB, spaceStore, blobRepo, storageDeleter, cfg)
|
||||
return app
|
||||
}
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ func GetRegistryCheckAccess(
|
|||
for i := range reqPermissions {
|
||||
permissionCheck := types.PermissionCheck{
|
||||
Permission: reqPermissions[i],
|
||||
Scope: types.Scope{SpacePath: space.Identifier},
|
||||
Scope: types.Scope{SpacePath: space.Path},
|
||||
Resource: types.Resource{
|
||||
Type: enum.ResourceTypeRegistry,
|
||||
Identifier: registry.Name,
|
||||
|
|
|
@ -23,6 +23,8 @@ import (
|
|||
"fmt"
|
||||
"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/manifestlist"
|
||||
"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/gc"
|
||||
"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/distribution/distribution/v3"
|
||||
|
@ -53,8 +56,10 @@ type manifestService struct {
|
|||
imageDao store.ImageRepository
|
||||
artifactDao store.ArtifactRepository
|
||||
manifestRefDao store.ManifestReferenceRepository
|
||||
spacePathStore gitnessappstore.SpacePathStore
|
||||
gcService gc.Service
|
||||
tx dbtx.Transactor
|
||||
reporter event.Reporter
|
||||
}
|
||||
|
||||
func NewManifestService(
|
||||
|
@ -62,7 +67,7 @@ func NewManifestService(
|
|||
blobRepo store.BlobRepository, mtRepository store.MediaTypesRepository, tagDao store.TagRepository,
|
||||
imageDao store.ImageRepository, artifactDao store.ArtifactRepository,
|
||||
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 {
|
||||
return &manifestService{
|
||||
registryDao: registryDao,
|
||||
|
@ -76,6 +81,8 @@ func NewManifestService(
|
|||
manifestRefDao: manifestRefDao,
|
||||
gcService: gcService,
|
||||
tx: tx,
|
||||
reporter: reporter,
|
||||
spacePathStore: spacePathStore,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,44 +173,90 @@ func (l *manifestService) dbTagManifest(
|
|||
tagName, imageName string,
|
||||
info pkg.RegistryInfo,
|
||||
) error {
|
||||
dbRepo, err := l.registryDao.GetByParentIDAndName(ctx, info.ParentID, info.RegIdentifier)
|
||||
dbRegistry, err := l.registryDao.GetByParentIDAndName(ctx, info.ParentID, info.RegIdentifier)
|
||||
if err != nil {
|
||||
return err
|
||||
return formatFailedToTagErr(err)
|
||||
}
|
||||
newDigest, err := types.NewDigest(dgst)
|
||||
if err != nil {
|
||||
return err
|
||||
return formatFailedToTagErr(err)
|
||||
}
|
||||
dbManifest, err := l.manifestDao.FindManifestByDigest(ctx, dbRepo.ID, info.Image, newDigest)
|
||||
if err != nil {
|
||||
if errors.Is(err, store2.ErrResourceNotFound) {
|
||||
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)
|
||||
}
|
||||
return err
|
||||
if err != nil {
|
||||
return formatFailedToTagErr(err)
|
||||
}
|
||||
|
||||
// We need to find and lock a GC manifest task that is related with the manifest that we're about to tag. This
|
||||
// is needed to ensure we lock any related online GC tasks to prevent race conditions around the tag creation. See:
|
||||
|
||||
return l.tx.WithTx(
|
||||
ctx, func(ctx context.Context) error {
|
||||
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()
|
||||
|
||||
if _, err := l.gcService.ManifestFindAndLockBefore(
|
||||
ctx, dbRepo.ID, dbManifest.ID,
|
||||
time.Now().Add(manifestTagGCReviewWindow),
|
||||
); err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return err
|
||||
// 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: dbRepo.ID,
|
||||
RegistryID: registryID,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
|
@ -227,17 +280,47 @@ func (l *manifestService) dbTagManifest(
|
|||
tag := &types.Tag{
|
||||
Name: tagName,
|
||||
ImageName: imageName,
|
||||
RegistryID: dbRepo.ID,
|
||||
ManifestID: dbManifest.ID,
|
||||
RegistryID: registryID,
|
||||
ManifestID: manifestID,
|
||||
}
|
||||
|
||||
if err := l.tagDao.CreateOrUpdate(ctx, tag); err != nil {
|
||||
return err
|
||||
return l.tagDao.CreateOrUpdate(ctx, tag)
|
||||
}
|
||||
|
||||
// Retrieves the spacePath and packageType.
|
||||
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
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
packageType, err := event.GetPackageTypeFromString(string(dbRepo.PackageType))
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Err(err).Msg("Failed to find packageType")
|
||||
return "", event.PackageType(0), err
|
||||
}
|
||||
|
||||
return spacePath.Value, packageType, nil
|
||||
}
|
||||
|
||||
// Reports event asynchronously.
|
||||
func (l *manifestService) reportEventAsync(
|
||||
ctx context.Context,
|
||||
regID,
|
||||
imageName,
|
||||
tagName string,
|
||||
packageType event.PackageType,
|
||||
spacePath string,
|
||||
) {
|
||||
go l.reporter.ReportEvent(ctx, &event.ArtifactDetails{
|
||||
RegistryID: regID,
|
||||
ImagePath: imageName + ":" + tagName,
|
||||
PackageType: packageType,
|
||||
}, spacePath)
|
||||
}
|
||||
|
||||
func (l *manifestService) DBPut(
|
||||
|
@ -327,7 +410,7 @@ func (l *manifestService) dbPutManifestV2(
|
|||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -433,7 +516,7 @@ func (l *manifestService) DBFindRepositoryBlob(
|
|||
image := info.Image
|
||||
b, err := l.blobRepo.FindByDigestAndRepoID(ctx, desc.Digest, repoID, image)
|
||||
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, err
|
||||
|
@ -453,11 +536,11 @@ func (l *manifestService) handleSubject(
|
|||
return err
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
if errors.Is(err, store2.ErrResourceNotFound) {
|
||||
if errors.Is(err, gitnessstore.ErrResourceNotFound) {
|
||||
// in case something happened to the referenced manifest after validation
|
||||
// return distribution.ManifestBlobUnknownError{Digest: subject.Digest}
|
||||
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)
|
||||
if err != nil && !errors.Is(err, store2.ErrResourceNotFound) {
|
||||
if err != nil && !errors.Is(err, gitnessstore.ErrResourceNotFound) {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -625,7 +708,7 @@ func (l *manifestService) dbPutImageIndex(
|
|||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -758,7 +841,7 @@ func (l *manifestService) dbFindManifestListManifest(
|
|||
imageName, dgst,
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, store2.ErrResourceNotFound) {
|
||||
if errors.Is(err, gitnessstore.ErrResourceNotFound) {
|
||||
return nil, fmt.Errorf(
|
||||
"manifest %s not found for %s/%s", digest.String(),
|
||||
repository.Name, imageName,
|
||||
|
@ -849,7 +932,7 @@ func (l *manifestService) DeleteManifest(
|
|||
}
|
||||
m, err := l.manifestDao.FindManifestByDigest(ctx, registry.ID, imageName, newDigest)
|
||||
if err != nil {
|
||||
if errors.Is(err, store2.ErrResourceNotFound) {
|
||||
if errors.Is(err, gitnessstore.ErrResourceNotFound) {
|
||||
return util.ErrManifestNotFound
|
||||
}
|
||||
return err
|
||||
|
|
|
@ -16,8 +16,9 @@ package docker
|
|||
|
||||
import (
|
||||
"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"
|
||||
"github.com/harness/gitness/registry/app/event"
|
||||
"github.com/harness/gitness/registry/app/pkg"
|
||||
"github.com/harness/gitness/registry/app/storage"
|
||||
"github.com/harness/gitness/registry/app/store"
|
||||
|
@ -49,17 +50,17 @@ func ManifestServiceProvider(
|
|||
manifestDao store.ManifestRepository, blobRepo store.BlobRepository, mtRepository store.MediaTypesRepository,
|
||||
manifestRefDao store.ManifestReferenceRepository, tagDao store.TagRepository, imageDao store.ImageRepository,
|
||||
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 {
|
||||
return NewManifestService(
|
||||
registryDao, manifestDao, blobRepo, mtRepository, tagDao, imageDao,
|
||||
artifactDao, layerDao, manifestRefDao, tx, gcService,
|
||||
artifactDao, layerDao, manifestRefDao, tx, gcService, reporter, spacePathStore,
|
||||
)
|
||||
}
|
||||
|
||||
func RemoteRegistryProvider(
|
||||
local *LocalRegistry, app *App, upstreamProxyConfigRepo store.UpstreamProxyConfigRepository,
|
||||
spacePathStore corestore.SpacePathStore, secretService secret.Service,
|
||||
spacePathStore gitnessstore.SpacePathStore, secretService secret.Service,
|
||||
) *RemoteRegistry {
|
||||
return NewRemoteRegistry(local, app, upstreamProxyConfigRepo, spacePathStore, secretService).(*RemoteRegistry)
|
||||
}
|
||||
|
@ -68,7 +69,7 @@ func ControllerProvider(
|
|||
local *LocalRegistry,
|
||||
remote *RemoteRegistry,
|
||||
controller *pkg.CoreController,
|
||||
spaceStore corestore.SpaceStore,
|
||||
spaceStore gitnessstore.SpaceStore,
|
||||
authorizer authz.Authorizer,
|
||||
) *Controller {
|
||||
return NewController(local, remote, controller, spaceStore, authorizer)
|
||||
|
@ -78,8 +79,12 @@ func StorageServiceProvider(cfg *types.Config, driver storagedriver.StorageDrive
|
|||
return GetStorageService(cfg, driver)
|
||||
}
|
||||
|
||||
func ProvideReporter() event.Reporter {
|
||||
return &event.Noop{}
|
||||
}
|
||||
|
||||
var ControllerSet = wire.NewSet(ControllerProvider)
|
||||
var RegistrySet = wire.NewSet(LocalRegistryProvider, ManifestServiceProvider, RemoteRegistryProvider)
|
||||
var StorageServiceSet = wire.NewSet(StorageServiceProvider)
|
||||
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(
|
||||
ctx context.Context, parentID int64,
|
||||
packageTypes *[]string, sortByField string,
|
||||
registryIDs *[]string, sortByField string,
|
||||
sortByOrder string, limit int, offset int, search string,
|
||||
labels []string,
|
||||
latestVersion bool,
|
||||
) (*[]types.ArtifactMetadata, error)
|
||||
|
||||
CountAllArtifactsByParentID(
|
||||
ctx context.Context, parentID int64,
|
||||
packageTypes *[]string, search string,
|
||||
labels []string,
|
||||
registryIDs *[]string, search string,
|
||||
latestVersion bool,
|
||||
) (int64, error)
|
||||
|
||||
GetAllArtifactsByRepo(
|
||||
|
@ -460,4 +460,5 @@ type GCManifestTaskRepository interface {
|
|||
Postpone(ctx context.Context, b *types.GCManifestTask, d time.Duration) error
|
||||
IsDangling(ctx context.Context, b *types.GCManifestTask) (bool, 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"`
|
||||
CreatedAt int64 `db:"created_at"`
|
||||
ModifiedAt int64 `db:"modified_at"`
|
||||
Version string `db:"version"`
|
||||
}
|
||||
|
||||
type tagMetadataDB struct {
|
||||
|
@ -337,54 +338,54 @@ func sqlPartialMatch(value string) string {
|
|||
func (t tagDao) GetAllArtifactsByParentID(
|
||||
ctx context.Context,
|
||||
parentID int64,
|
||||
packageTypes *[]string,
|
||||
registryIDs *[]string,
|
||||
sortByField string,
|
||||
sortByOrder string,
|
||||
limit int,
|
||||
offset int,
|
||||
search string,
|
||||
labels []string,
|
||||
latestVersion bool,
|
||||
) (*[]types.ArtifactMetadata, error) {
|
||||
q := databaseg.Builder.Select(
|
||||
"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,"+
|
||||
" t.tag_updated_at as modified_at, ar.image_labels as labels, t2.download_count as download_count ",
|
||||
`r.registry_name as repo_name,
|
||||
t.tag_image_name as name,
|
||||
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").
|
||||
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").
|
||||
Where("r.registry_parent_id = ?", parentID).
|
||||
Join(
|
||||
"images ar ON ar.image_registry_id = t.tag_registry_id AND"+
|
||||
" ar.image_name = t.tag_image_name",
|
||||
).
|
||||
LeftJoin(
|
||||
"(SELECT i.image_name, t1.download_count FROM"+
|
||||
" ( SELECT a.artifact_image_id, COUNT(d.download_stat_id) as download_count"+
|
||||
" FROM artifacts a "+
|
||||
" LEFT JOIN download_stats d ON d.download_stat_artifact_id = a.artifact_id GROUP BY"+
|
||||
" a.artifact_image_id ) as t1 "+
|
||||
" JOIN images ON i.image_id = t1.artifact_image_id "+
|
||||
" JOIN registries r ON r.registry_id = i.image_registry_id "+
|
||||
" WHERE r.registry_parent_id = ? GROUP BY i.image_name) as t2"+
|
||||
" ON t.tag_image_name = t2.image_name", parentID,
|
||||
`( 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
|
||||
FROM artifacts a JOIN download_stats d ON d.download_stat_artifact_id = a.artifact_id
|
||||
GROUP BY a.artifact_image_id ) as t1
|
||||
JOIN images i ON i.image_id = t1.artifact_image_id
|
||||
JOIN registries r ON r.registry_id = i.image_registry_id
|
||||
WHERE r.registry_parent_id = ? GROUP BY i.image_name) as t2
|
||||
ON t.tag_image_name = t2.image_name`, parentID,
|
||||
)
|
||||
|
||||
if latestVersion {
|
||||
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(*packageTypes) > 0 {
|
||||
q = q.Where(sq.Eq{"r.registry_package_type": packageTypes})
|
||||
}
|
||||
|
||||
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)
|
||||
if len(*registryIDs) > 0 {
|
||||
q = q.Where(sq.Eq{"r.registry_name": registryIDs})
|
||||
}
|
||||
|
||||
if search != "" {
|
||||
|
@ -409,39 +410,35 @@ func (t tagDao) GetAllArtifactsByParentID(
|
|||
|
||||
func (t tagDao) CountAllArtifactsByParentID(
|
||||
ctx context.Context, parentID int64,
|
||||
packageTypes *[]string, search string, labels []string,
|
||||
registryIDs *[]string, search string, latestVersion bool,
|
||||
) (int64, error) {
|
||||
// nolint:goconst
|
||||
q := databaseg.Builder.Select("COUNT(*)").
|
||||
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"). // nolint:goconst
|
||||
Where("r.registry_parent_id = ?", parentID).
|
||||
Join(
|
||||
"images ar ON ar.image_registry_id = t.tag_registry_id" +
|
||||
" AND ar.image_name = t.tag_image_name",
|
||||
).
|
||||
Where("a.rank = 1 ")
|
||||
)
|
||||
|
||||
if len(*packageTypes) > 0 {
|
||||
q = q.Where(sq.Eq{"r.registry_package_type": packageTypes})
|
||||
if latestVersion {
|
||||
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 != "" {
|
||||
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()
|
||||
if err != nil {
|
||||
return -1, errors.Wrap(err, "Failed to convert query to sql")
|
||||
|
@ -461,9 +458,9 @@ func (t tagDao) GetTagDetail(
|
|||
name string,
|
||||
) (*types.TagDetail, error) {
|
||||
q := databaseg.Builder.Select(
|
||||
"tag_id as id, tag_name as name ,"+
|
||||
" tag_image_name as image_name, tag_created_at as created_at, "+
|
||||
" tag_updated_at as updated_at, manifest_total_size as size",
|
||||
`tag_id as id, tag_name as name,
|
||||
tag_image_name as image_name, tag_created_at as created_at,
|
||||
tag_updated_at as updated_at, manifest_total_size as size`,
|
||||
).
|
||||
From("tags").
|
||||
Join("manifests ON manifest_id = tag_manifest_id").
|
||||
|
@ -494,13 +491,13 @@ func (t tagDao) GetLatestTagMetadata(
|
|||
imageName string,
|
||||
) (*types.ArtifactMetadata, error) {
|
||||
q := databaseg.Builder.Select(
|
||||
"r.registry_name as repo_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_updated_at as modified_at, ar.image_labels as labels",
|
||||
`r.registry_name as repo_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_updated_at as modified_at, ar.image_labels as labels`,
|
||||
).
|
||||
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(
|
||||
"images ar ON ar.image_registry_id = t.tag_registry_id "+
|
||||
"AND ar.image_name = t.tag_image_name",
|
||||
|
@ -618,18 +615,18 @@ func (t tagDao) GetAllArtifactsByRepo(
|
|||
labels []string,
|
||||
) (*[]types.ArtifactMetadata, error) {
|
||||
q := databaseg.Builder.Select(
|
||||
"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,"+
|
||||
" t.tag_updated_at as modified_at, ar.image_labels as labels, "+
|
||||
" COALESCE(t2.download_count, 0) as download_count ",
|
||||
`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,
|
||||
t.tag_updated_at as modified_at, ar.image_labels as labels,
|
||||
COALESCE(t2.download_count, 0) as download_count `,
|
||||
).
|
||||
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 = ? AND r.registry_name = ? ) AS a"+
|
||||
" ON t.tag_id = a.id", parentID, repoKey,
|
||||
`(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 = ? AND r.registry_name = ? ) AS a
|
||||
ON t.tag_id = a.id`, parentID, repoKey, // nolint:goconst
|
||||
).
|
||||
Join("registries r ON t.tag_registry_id = r.registry_id").
|
||||
Join(
|
||||
|
@ -637,15 +634,15 @@ func (t tagDao) GetAllArtifactsByRepo(
|
|||
" AND ar.image_name = t.tag_image_name",
|
||||
).
|
||||
LeftJoin(
|
||||
"( 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"+
|
||||
" FROM artifacts a "+
|
||||
" JOIN download_stats d ON d.download_stat_artifact_id = a.artifact_id GROUP BY"+
|
||||
" a.artifact_image_id ) as t1 "+
|
||||
" JOIN images i ON i.image_id = t1.artifact_image_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"+
|
||||
" ON t.tag_image_name = t2.image_name", parentID, repoKey,
|
||||
`( 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
|
||||
FROM artifacts a
|
||||
JOIN download_stats d ON d.download_stat_artifact_id = a.artifact_id GROUP BY
|
||||
a.artifact_image_id ) as t1
|
||||
JOIN images i ON i.image_id = t1.artifact_image_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
|
||||
ON t.tag_image_name = t2.image_name`, parentID, repoKey,
|
||||
).
|
||||
Where("a.rank = 1 ")
|
||||
|
||||
|
@ -676,6 +673,7 @@ func (t tagDao) GetAllArtifactsByRepo(
|
|||
return t.mapToArtifactMetadataList(ctx, dst)
|
||||
}
|
||||
|
||||
// nolint:goconst
|
||||
func (t tagDao) CountAllArtifactsByRepo(
|
||||
ctx context.Context, parentID int64, repoKey string,
|
||||
search string, labels []string,
|
||||
|
@ -683,10 +681,10 @@ func (t tagDao) CountAllArtifactsByRepo(
|
|||
q := databaseg.Builder.Select("COUNT(*)").
|
||||
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 = ? AND r.registry_name = ? ) AS a ON t.tag_id = a.id", parentID, repoKey,
|
||||
`(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 = ? 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(
|
||||
|
@ -726,10 +724,10 @@ func (t tagDao) GetAllTagsByRepoAndImage(
|
|||
search string,
|
||||
) (*[]types.TagMetadata, error) {
|
||||
q := databaseg.Builder.Select(
|
||||
"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, "+
|
||||
" m.manifest_schema_version, m.manifest_non_conformant, m.manifest_payload, "+
|
||||
" mt.mt_media_type ",
|
||||
`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,
|
||||
m.manifest_schema_version, m.manifest_non_conformant, m.manifest_payload,
|
||||
mt.mt_media_type `,
|
||||
).
|
||||
From("tags t").
|
||||
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),
|
||||
CreatedAt: time.UnixMilli(dst.CreatedAt),
|
||||
ModifiedAt: time.UnixMilli(dst.ModifiedAt),
|
||||
Version: dst.Version,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ func New() Service {
|
|||
func (s *Noop) Start(
|
||||
_ context.Context, _ *sqlx.DB, _ corestore.SpaceStore,
|
||||
_ store.BlobRepository, _ storagedriver.StorageDeleter,
|
||||
_ *types.Config, _ store.MediaTypesRepository, _ store.ManifestRepository,
|
||||
_ *types.Config,
|
||||
) {
|
||||
// NOOP
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ type Service interface {
|
|||
Start(
|
||||
ctx context.Context, sqlDB *sqlx.DB, spaceStore corestore.SpaceStore,
|
||||
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)
|
||||
BlobReschedule(ctx context.Context, b *registrytypes.GCBlobTask, d time.Duration) error
|
||||
|
|
|
@ -14,15 +14,11 @@
|
|||
|
||||
package types
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type GCBlobTask struct {
|
||||
BlobID int64
|
||||
ReviewAfter time.Time
|
||||
ReviewAfter int64
|
||||
ReviewCount int
|
||||
CreatedAt time.Time
|
||||
CreatedAt int64
|
||||
Event string
|
||||
}
|
||||
|
||||
|
@ -30,14 +26,8 @@ type GCBlobTask struct {
|
|||
type GCManifestTask struct {
|
||||
RegistryID int64
|
||||
ManifestID int64
|
||||
ReviewAfter time.Time
|
||||
ReviewAfter int64
|
||||
ReviewCount int
|
||||
CreatedAt time.Time
|
||||
CreatedAt int64
|
||||
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
|
||||
CreatedAt time.Time
|
||||
ModifiedAt time.Time
|
||||
Version string
|
||||
}
|
||||
|
||||
type TagMetadata struct {
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
"@codemirror/view": "^6.9.6",
|
||||
"@harnessio/design-system": "^2.1.1",
|
||||
"@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",
|
||||
"@tanstack/react-query": "4.20.4",
|
||||
"@types/dompurify": "^3.0.2",
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
*/
|
||||
|
||||
import type React from 'react'
|
||||
import type { Button } from '@harnessio/uicore'
|
||||
|
||||
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 NGBreadcrumbs from '@ar/__mocks__/components/NGBreadcrumbs'
|
||||
import type DependencyView from '@ar/__mocks__/components/DependencyView'
|
||||
|
@ -43,6 +43,16 @@ export interface Scope {
|
|||
space?: string
|
||||
}
|
||||
|
||||
export interface PipelineExecutionPathProps {
|
||||
executionIdentifier: string
|
||||
pipelineIdentifier: string
|
||||
module: 'ci' | 'cd'
|
||||
}
|
||||
|
||||
export interface ServiceDetailsPathProps {
|
||||
serviceId: string
|
||||
}
|
||||
|
||||
export interface PermissionsRequest {
|
||||
resource: { resourceType: ResourceType; resourceIdentifier?: string }
|
||||
permissions: PermissionIdentifier[]
|
||||
|
@ -63,7 +73,7 @@ export interface ParentContextObj {
|
|||
}
|
||||
|
||||
export interface Components {
|
||||
RbacButton: typeof Button
|
||||
RbacButton: typeof RbacButton
|
||||
NGBreadcrumbs: typeof NGBreadcrumbs
|
||||
RbacMenuItem: typeof RbacMenuItem
|
||||
}
|
||||
|
@ -96,6 +106,8 @@ export interface CustomUtils {
|
|||
getCustomHeaders: () => Record<string, string>
|
||||
getApiBaseUrl: (url: string) => string
|
||||
getRouteDefinitions?: (routeParams: Record<string, string>) => ARRouteDefinitionsReturn
|
||||
getRouteToPipelineExecutionView?: (params: Scope & PipelineExecutionPathProps) => string
|
||||
getRouteToServiceDetailsView?: (params: Scope & ServiceDetailsPathProps) => string
|
||||
}
|
||||
|
||||
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 { defaultTo, noop } from 'lodash-es'
|
||||
import { Button, Container } from '@harnessio/uicore'
|
||||
import { Container } from '@harnessio/uicore'
|
||||
|
||||
import type { MFEAppProps } from '@ar/MFEAppTypes'
|
||||
|
||||
|
@ -29,6 +29,7 @@ import {
|
|||
useQueryParamsOptions,
|
||||
useUpdateQueryParams
|
||||
} from '@ar/__mocks__/hooks'
|
||||
import RbacButton from '@ar/__mocks__/components/RbacButton'
|
||||
import RbacMenuItem from '@ar/__mocks__/components/RbacMenuItem'
|
||||
import NGBreadcrumbs from '@ar/__mocks__/components/NGBreadcrumbs'
|
||||
import DependencyView from '@ar/__mocks__/components/DependencyView'
|
||||
|
@ -76,7 +77,7 @@ const GitnessApp = (props: Partial<MFEAppProps>): JSX.Element => {
|
|||
scope={defaultTo(scope, {})}
|
||||
customScope={defaultTo(customScope, {})}
|
||||
components={Object.assign(
|
||||
{ RbacButton: Button, NGBreadcrumbs, RbacMenuItem, SecretFormInput, VulnerabilityView, DependencyView },
|
||||
{ RbacButton, NGBreadcrumbs, RbacMenuItem, SecretFormInput, VulnerabilityView, DependencyView },
|
||||
components
|
||||
)}
|
||||
NavComponent={NavComponent}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
import type { IconName } from '@harnessio/icons'
|
||||
import type { StringsMap } from '@ar/frameworks/strings'
|
||||
import { RepositoryPackageType } from './types'
|
||||
import { EnvironmentType, RepositoryPackageType } from './types'
|
||||
|
||||
export interface RepositoryTypeListItem {
|
||||
label: keyof StringsMap
|
||||
|
@ -80,3 +80,20 @@ export const RepositoryTypes: RepositoryTypeListItem[] = [
|
|||
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 {
|
||||
DELETE_SERVICE = 'core_service_delete',
|
||||
EDIT_SERVICE = 'core_service_edit'
|
||||
VIEW_ARTIFACT_REGISTRY = 'artifact_artregistry_view',
|
||||
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 {
|
||||
SERVICE = 'SERVICE'
|
||||
ARTIFACT_REGISTRY = 'ARTIFACT_REGISTRY'
|
||||
}
|
||||
|
|
|
@ -29,8 +29,8 @@ export type FormikFowardRef<T = unknown> =
|
|||
| null
|
||||
|
||||
export enum EnvironmentType {
|
||||
Production = 'Production',
|
||||
PreProduction = 'PreProduction'
|
||||
Prod = 'Production',
|
||||
NonProd = 'PreProduction'
|
||||
}
|
||||
|
||||
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>[]
|
||||
small?: boolean
|
||||
bold?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
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)
|
||||
return (
|
||||
<Container data-testid={id}>
|
||||
<Container className={className} data-testid={id}>
|
||||
<ButtonGroup className={css.btnGroup}>
|
||||
{tabs.map(each => (
|
||||
<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 {
|
||||
const { label, name, placeholder, formik } = props
|
||||
const { label, name, placeholder, formik, disabled } = props
|
||||
|
||||
const formValue = get(formik.values, name, []) as string[]
|
||||
|
||||
|
@ -53,6 +53,7 @@ function PatternInput<T>(props: PatternInputProps & { formik: FormikContextType<
|
|||
onChange={selectedItems => {
|
||||
formik.setFieldValue(name, selectedItems)
|
||||
}}
|
||||
readonly={disabled}
|
||||
/>
|
||||
</FormGroup>
|
||||
)
|
||||
|
|
|
@ -83,11 +83,13 @@ export default function IncludeExcludePatterns<T>(props: IncludeExcludePatternsP
|
|||
label={includePatternListProps.label}
|
||||
name={includePatternListProps.name}
|
||||
placeholder={includePatternListProps.placeholder}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<PatternInput
|
||||
label={excludePatternListProps.label}
|
||||
name={excludePatternListProps.name}
|
||||
placeholder={excludePatternListProps.placeholder}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Container>
|
||||
)
|
||||
|
|
|
@ -70,7 +70,7 @@ export const Description = (props: DescriptionComponentProps): 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 [isTagsOpen, setTagsOpen] = useState<boolean>(hasValue || false)
|
||||
|
||||
|
@ -96,7 +96,17 @@ export const Tags = (props: TagsComponentProps): JSX.Element => {
|
|||
/>
|
||||
)}
|
||||
</Label>
|
||||
{isTagsOpen && <FormInput.KVTagInput name={name} isArray tagsProps={tagsProps} disabled={disabled} />}
|
||||
{isTagsOpen && (
|
||||
<FormInput.KVTagInput
|
||||
name={name}
|
||||
isArray
|
||||
tagsProps={{
|
||||
...tagsProps,
|
||||
disabled
|
||||
}}
|
||||
disabled={disabled}
|
||||
/>
|
||||
)}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -18,7 +18,9 @@ import React, { useState } from 'react'
|
|||
import classNames from 'classnames'
|
||||
import { Button, ButtonVariation, Layout, useToggleOpen } from '@harnessio/uicore'
|
||||
|
||||
import { useParentComponents } from '@ar/hooks'
|
||||
import TagIcon from '@ar/components/MultiTagsInput/TagIcon'
|
||||
import type { RbacButtonProps } from '@ar/__mocks__/components/RbacButton'
|
||||
import MultiTagsInput from '@ar/components/MultiTagsInput/MultiTagsInput'
|
||||
|
||||
import css from './PageTitle.module.scss'
|
||||
|
@ -27,14 +29,16 @@ interface ArtifactTagsProps {
|
|||
labels: string[]
|
||||
placeholder?: string
|
||||
onChange: (items: string[]) => Promise<boolean>
|
||||
permission?: RbacButtonProps['permission']
|
||||
}
|
||||
|
||||
const EMPTY_TAG_VALUE = '+ Labels'
|
||||
|
||||
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 [query, setQuery] = useState('')
|
||||
const { RbacButton } = useParentComponents()
|
||||
const { isOpen: isEdit, open, close } = useToggleOpen(false)
|
||||
|
||||
const handleOnSubmit = async () => {
|
||||
|
@ -83,13 +87,14 @@ export default function ArtifactTags(props: ArtifactTagsProps): JSX.Element | nu
|
|||
}}
|
||||
/>
|
||||
{!isEdit && !!selectedItems.length && (
|
||||
<Button
|
||||
<RbacButton
|
||||
className={css.iconBtn}
|
||||
minimal
|
||||
iconProps={{ size: 20 }}
|
||||
variation={ButtonVariation.ICON}
|
||||
icon="code-edit"
|
||||
onClick={() => open()}
|
||||
permission={permission}
|
||||
/>
|
||||
)}
|
||||
{isEdit && (
|
||||
|
@ -107,13 +112,14 @@ export default function ArtifactTags(props: ArtifactTagsProps): JSX.Element | nu
|
|||
setQuery('')
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
<RbacButton
|
||||
className={css.iconBtn}
|
||||
minimal
|
||||
variation={ButtonVariation.ICON}
|
||||
iconProps={{ size: 20 }}
|
||||
icon="small-tick"
|
||||
onClick={handleOnSubmit}
|
||||
permission={permission}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -20,16 +20,35 @@
|
|||
}
|
||||
|
||||
.nameCellContainer {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
gap: var(--spacing-small);
|
||||
}
|
||||
|
||||
.copyButton {
|
||||
.copyUrlBtn {
|
||||
& :global(.bp3-button-text) {
|
||||
margin-left: var(--spacing-small) !important;
|
||||
font-size: 12px;
|
||||
padding-left: var(--spacing-xsmall);
|
||||
}
|
||||
|
||||
& .copyUrlIcon {
|
||||
--icon-padding: 0px !important;
|
||||
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 */
|
||||
// This is an auto-generated file
|
||||
export declare const copyButton: string
|
||||
export declare const copyUrlBtn: string
|
||||
export declare const copyUrlIcon: string
|
||||
export declare const deploymentsRow: string
|
||||
export declare const nameCellContainer: string
|
||||
export declare const nonProd: string
|
||||
export declare const prod: string
|
||||
export declare const toggleAccordion: string
|
||||
|
|
|
@ -15,13 +15,14 @@
|
|||
*/
|
||||
|
||||
import React, { FC, PropsWithChildren, useState } from 'react'
|
||||
import classNames from 'classnames'
|
||||
import { defaultTo } from 'lodash-es'
|
||||
import copy from 'clipboard-copy'
|
||||
import { Link } from 'react-router-dom'
|
||||
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 { Color } from '@harnessio/design-system'
|
||||
import { Color, FontVariation } from '@harnessio/design-system'
|
||||
|
||||
import { killEvent } from '@ar/common/utils'
|
||||
import { useStrings } from '@ar/frameworks/strings/String'
|
||||
|
@ -31,6 +32,8 @@ import RepositoryLocationBadge from '@ar/components/Badge/RepositoryLocationBadg
|
|||
|
||||
import { DefaultIconProps } from './constants'
|
||||
import { handleToggleExpandableRow } from './utils'
|
||||
import CommandBlock from '../CommandBlock/CommandBlock'
|
||||
import { NonProdTag, ProdTag } from '../Tag/Tags'
|
||||
|
||||
import css from './TableCells.module.scss'
|
||||
|
||||
|
@ -67,10 +70,11 @@ export const CopyUrlCell: FC<PropsWithChildren<CopyUrlCellProps>> = ({ value, ch
|
|||
}
|
||||
return (
|
||||
<Button
|
||||
className={css.copyButton}
|
||||
className={classNames(css.copyButton, css.copyUrlBtn)}
|
||||
intent="primary"
|
||||
minimal
|
||||
icon="link"
|
||||
variation={ButtonVariation.LINK}
|
||||
iconProps={{ size: 12, className: css.copyUrlIcon }}
|
||||
onClick={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 {
|
||||
value: RepositoryConfigType
|
||||
}
|
||||
|
@ -158,26 +202,58 @@ const ToggleAccordionCell = (props: ToggleAccordionCellProps): JSX.Element => {
|
|||
|
||||
interface LinkCellProps {
|
||||
label: string
|
||||
subLabel?: string
|
||||
prefix?: React.ReactElement
|
||||
postfix?: React.ReactElement
|
||||
linkTo: string
|
||||
}
|
||||
|
||||
const LinkCell = (props: LinkCellProps): JSX.Element => {
|
||||
const { prefix, postfix, label, linkTo } = props
|
||||
const { prefix, postfix, label, linkTo, subLabel } = props
|
||||
return (
|
||||
<Layout.Horizontal className={css.nameCellContainer} spacing="small">
|
||||
<Layout.Horizontal
|
||||
className={css.nameCellContainer}
|
||||
flex={{ justifyContent: 'flex-start', alignItems: 'flex-start' }}>
|
||||
{prefix}
|
||||
<Layout.Vertical>
|
||||
<Link to={linkTo}>
|
||||
<Text color={Color.PRIMARY_7} lineClamp={1}>
|
||||
{label}
|
||||
</Text>
|
||||
</Link>
|
||||
{subLabel && <Text lineClamp={1}>{subLabel}</Text>}
|
||||
</Layout.Vertical>
|
||||
{postfix}
|
||||
</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 {
|
||||
UrlCell,
|
||||
SizeCell,
|
||||
|
@ -185,6 +261,9 @@ export default {
|
|||
LinkCell,
|
||||
TextCell,
|
||||
CopyUrlCell,
|
||||
CopyTextCell,
|
||||
DeploymentsCell,
|
||||
PullCommandCell,
|
||||
LastModifiedCell,
|
||||
ToggleAccordionCell,
|
||||
RepositoryLocationBadgeCell
|
||||
|
|
|
@ -31,3 +31,19 @@
|
|||
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 */
|
||||
// This is an auto-generated file
|
||||
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
|
||||
|
|
|
@ -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}
|
||||
permission={{
|
||||
resource: {
|
||||
resourceType: ResourceType.SERVICE,
|
||||
resourceType: ResourceType.ARTIFACT_REGISTRY,
|
||||
resourceIdentifier: repoKey
|
||||
},
|
||||
permission: PermissionIdentifier.DELETE_SERVICE
|
||||
permission: PermissionIdentifier.DELETE_ARTIFACT_REGISTRY
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
|
|
@ -47,10 +47,10 @@ export default function EditRepositoryMenuItem({ repoKey }: ArtifactActionProps)
|
|||
onClick={handleOpenRepository}
|
||||
permission={{
|
||||
resource: {
|
||||
resourceType: ResourceType.SERVICE,
|
||||
resourceType: ResourceType.ARTIFACT_REGISTRY,
|
||||
resourceIdentifier: repoKey
|
||||
},
|
||||
permission: PermissionIdentifier.EDIT_SERVICE
|
||||
permission: PermissionIdentifier.EDIT_ARTIFACT_REGISTRY
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
|
|
|
@ -43,10 +43,10 @@ export default function SetupClientMenuItem({ data, repoKey }: ArtifactActionPro
|
|||
onClick={showSetupClientModal}
|
||||
permission={{
|
||||
resource: {
|
||||
resourceType: ResourceType.SERVICE,
|
||||
resourceType: ResourceType.ARTIFACT_REGISTRY,
|
||||
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 ArtifactTags from '@ar/components/PageTitle/ArtifactTags'
|
||||
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 RepositoryIcon from '@ar/frameworks/RepositoryStep/RepositoryIcon'
|
||||
|
@ -109,6 +110,13 @@ function ArtifactDetailsHeaderContent(props: ArtifactDetailsHeaderContentProps):
|
|||
onChange={handleUpdateArtifactLabels}
|
||||
labels={defaultTo(labels, [])}
|
||||
placeholder={getString('artifactDetails.artifactLabelInputPlaceholder')}
|
||||
permission={{
|
||||
permission: PermissionIdentifier.EDIT_ARTIFACT_REGISTRY,
|
||||
resource: {
|
||||
resourceType: ResourceType.ARTIFACT_REGISTRY,
|
||||
resourceIdentifier: repositoryIdentifier
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Layout.Vertical>
|
||||
</Layout.Horizontal>
|
||||
|
|
|
@ -35,3 +35,12 @@
|
|||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.filterTabContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& button {
|
||||
--button-height: 32px !important;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
/* eslint-disable */
|
||||
// This is an auto-generated file
|
||||
export declare const filterTabContainer: string
|
||||
export declare const pageBody: string
|
||||
export declare const subHeader: string
|
||||
export declare const subHeaderItems: string
|
||||
|
|
|
@ -14,47 +14,38 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useMemo, useRef } from 'react'
|
||||
import React, { useMemo } from 'react'
|
||||
import classNames from 'classnames'
|
||||
import { flushSync } from 'react-dom'
|
||||
import { defaultTo } from 'lodash-es'
|
||||
import { Expander } from '@blueprintjs/core'
|
||||
import { HarnessDocTooltip, Page, Button, ButtonVariation } from '@harnessio/uicore'
|
||||
import {
|
||||
ExpandingSearchInput,
|
||||
HarnessDocTooltip,
|
||||
Page,
|
||||
type ExpandingSearchInputHandle,
|
||||
Button,
|
||||
ButtonVariation
|
||||
} from '@harnessio/uicore'
|
||||
import { useGetAllArtifactsQuery } from '@harnessio/react-har-service-client'
|
||||
GetAllHarnessArtifactsQueryQueryParams,
|
||||
useGetAllHarnessArtifactsQuery
|
||||
} from '@harnessio/react-har-service-client'
|
||||
|
||||
import { useStrings } from '@ar/frameworks/strings'
|
||||
import { DEFAULT_PAGE_INDEX, PreferenceScope } from '@ar/constants'
|
||||
import { ButtonTab, ButtonTabs } from '@ar/components/ButtonTabs/ButtonTabs'
|
||||
import { useGetSpaceRef, useParentComponents, useParentHooks } from '@ar/hooks'
|
||||
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 ArtifactListTable from './components/ArtifactListTable/ArtifactListTable'
|
||||
import RepositorySelector from './components/RepositorySelector/RepositorySelector'
|
||||
import ArtifactSearchInput from './components/ArtifactSearchInput/ArtifactSearchInput'
|
||||
import { useArtifactListQueryParamOptions, type ArtifactListPageQueryParams } from './utils'
|
||||
|
||||
import css from './ArtifactListPage.module.scss'
|
||||
|
||||
interface ArtifactListPageProps {
|
||||
withHeader?: boolean
|
||||
parentRepoKey?: string
|
||||
pageBodyClassName?: string
|
||||
}
|
||||
|
||||
function ArtifactListPage({ withHeader = true, parentRepoKey, pageBodyClassName }: ArtifactListPageProps): JSX.Element {
|
||||
function ArtifactListPage(): JSX.Element {
|
||||
const { getString } = useStrings()
|
||||
const { NGBreadcrumbs } = useParentComponents()
|
||||
const { useQueryParams, useUpdateQueryParams, usePreferenceStore } = useParentHooks()
|
||||
const searchRef = useRef({} as ExpandingSearchInputHandle)
|
||||
const { updateQueryParams } = useUpdateQueryParams<Partial<ArtifactListPageQueryParams>>()
|
||||
const queryParams = useQueryParams<ArtifactListPageQueryParams>(useArtifactListQueryParamOptions())
|
||||
const { searchTerm, isDeployedArtifacts, packageTypes, repositoryKey, page, size, labels } = queryParams
|
||||
const shouldRenderRepositorySelectFilter = !parentRepoKey
|
||||
const shouldRenderPackageTypeSelectFilter = !parentRepoKey
|
||||
const { searchTerm, isDeployedArtifacts, repositoryKey, page, size, latestVersion, packageTypes, labels } =
|
||||
queryParams
|
||||
const spaceRef = useGetSpaceRef('')
|
||||
|
||||
const { preference: sortingPreference, setPreference: setSortingPreference } = usePreferenceStore<string | undefined>(
|
||||
|
@ -73,50 +64,39 @@ function ArtifactListPage({ withHeader = true, parentRepoKey, pageBodyClassName
|
|||
refetch,
|
||||
isLoading: loading,
|
||||
error
|
||||
} = useGetAllArtifactsQuery({
|
||||
} = useGetAllHarnessArtifactsQuery({
|
||||
space_ref: spaceRef,
|
||||
queryParams: {
|
||||
page,
|
||||
size,
|
||||
label: labels,
|
||||
package_type: packageTypes,
|
||||
search_term: searchTerm,
|
||||
sort_field: sortField,
|
||||
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: {
|
||||
arrayFormat: 'repeat'
|
||||
}
|
||||
})
|
||||
|
||||
const handleClearAllFilters = (): void => {
|
||||
flushSync(searchRef.current.clear)
|
||||
updateQueryParams({
|
||||
page: 0,
|
||||
searchTerm: '',
|
||||
packageTypes: [],
|
||||
isDeployedArtifacts: false
|
||||
isDeployedArtifacts: false,
|
||||
latestVersion: 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 hasFilter = !!searchTerm || isDeployedArtifacts || latestVersion
|
||||
const responseData = data?.content?.data
|
||||
|
||||
return (
|
||||
<>
|
||||
{withHeader && (
|
||||
<Page.Header
|
||||
title={
|
||||
<div className="ng-tooltip-native">
|
||||
|
@ -126,55 +106,66 @@ function ArtifactListPage({ withHeader = true, parentRepoKey, pageBodyClassName
|
|||
}
|
||||
breadcrumbs={<NGBreadcrumbs links={[]} />}
|
||||
/>
|
||||
)}
|
||||
<Page.SubHeader className={css.subHeader}>
|
||||
<div className={css.subHeaderItems}>
|
||||
{shouldRenderRepositorySelectFilter && (
|
||||
<ArtifactSearchInput
|
||||
searchTerm={searchTerm || ''}
|
||||
onChange={text => {
|
||||
updateQueryParams({ searchTerm: text || undefined, page: DEFAULT_PAGE_INDEX })
|
||||
}}
|
||||
placeholder={getString('search')}
|
||||
/>
|
||||
<RepositorySelector
|
||||
value={repositoryKey}
|
||||
onChange={val => {
|
||||
updateQueryParams({ repositoryKey: val, page: DEFAULT_PAGE_INDEX })
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{shouldRenderPackageTypeSelectFilter && (
|
||||
<PackageTypeSelector
|
||||
value={packageTypes}
|
||||
onChange={val => {
|
||||
updateQueryParams({ packageTypes: val, page: DEFAULT_PAGE_INDEX })
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<LabelsSelector
|
||||
value={labels}
|
||||
onChange={val => {
|
||||
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 />
|
||||
<ExpandingSearchInput
|
||||
alwaysExpanded
|
||||
width={200}
|
||||
placeholder={getString('search')}
|
||||
onChange={text => {
|
||||
updateQueryParams({ searchTerm: text || undefined, page: DEFAULT_PAGE_INDEX })
|
||||
}}
|
||||
defaultValue={searchTerm}
|
||||
ref={searchRef}
|
||||
<ButtonTabs
|
||||
className={css.filterTabContainer}
|
||||
small
|
||||
bold
|
||||
selectedTabId={
|
||||
latestVersion ? ArtifactListVersionFilter.LATEST_VERSION : ArtifactListVersionFilter.ALL_VERSION
|
||||
}
|
||||
onChange={newTab => {
|
||||
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>
|
||||
</Page.SubHeader>
|
||||
<Page.Body
|
||||
className={classNames(css.pageBody, pageBodyClassName)}
|
||||
className={classNames(css.pageBody)}
|
||||
loading={loading}
|
||||
error={error?.message}
|
||||
retryOnError={() => refetch()}
|
||||
|
@ -199,7 +190,6 @@ function ArtifactListPage({ withHeader = true, parentRepoKey, pageBodyClassName
|
|||
setSortingPreference(JSON.stringify(sortArray))
|
||||
updateQueryParams({ sort: sortArray })
|
||||
}}
|
||||
onClickLabel={handleClickLabel}
|
||||
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--header'] {
|
||||
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 {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cellBtn {
|
||||
--padding: 0 !important;
|
||||
}
|
||||
|
||||
.optionsMenu {
|
||||
min-width: unset;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
/* eslint-disable */
|
||||
// This is an auto-generated file
|
||||
export declare const cellBtn: string
|
||||
export declare const nameCellContainer: string
|
||||
export declare const optionsMenu: string
|
||||
export declare const table: 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 { useParentHooks } from '@ar/hooks'
|
||||
import {
|
||||
ArtifactDeploymentsCell,
|
||||
ArtifactDownloadsCell,
|
||||
ArtifactListPullCommandCell,
|
||||
ArtifactListVulnerabilitiesCell,
|
||||
ArtifactNameCell,
|
||||
LatestArtifactCell,
|
||||
RepositoryNameCell
|
||||
LatestArtifactCell
|
||||
} from './ArtifactListTableCell'
|
||||
import css from './ArtifactListTable.module.scss'
|
||||
|
||||
|
@ -40,11 +42,10 @@ export interface ArtifactListTableProps extends ArtifactListColumnActions {
|
|||
setSortBy: (sortBy: string[]) => void
|
||||
sortBy: string[]
|
||||
minimal?: boolean
|
||||
onClickLabel: (val: string) => void
|
||||
}
|
||||
|
||||
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 { getString } = useStrings()
|
||||
|
||||
|
@ -72,17 +73,16 @@ export default function ArtifactListTable(props: ArtifactListTableProps): JSX.El
|
|||
}
|
||||
return [
|
||||
{
|
||||
Header: getString('artifactList.table.columns.name'),
|
||||
Header: getString('artifactList.table.columns.artifactName'),
|
||||
accessor: 'name',
|
||||
Cell: ArtifactNameCell,
|
||||
serverSortProps: getServerSortProps('name'),
|
||||
onClickLabel
|
||||
serverSortProps: getServerSortProps('name')
|
||||
},
|
||||
{
|
||||
Header: getString('artifactList.table.columns.repository'),
|
||||
accessor: 'registryIdentifier',
|
||||
Cell: RepositoryNameCell,
|
||||
serverSortProps: getServerSortProps('registryIdentifier')
|
||||
Header: getString('artifactList.table.columns.pullCommand'),
|
||||
accessor: 'pullCommand',
|
||||
Cell: ArtifactListPullCommandCell,
|
||||
disableSortBy: true
|
||||
},
|
||||
{
|
||||
Header: getString('artifactList.table.columns.downloads'),
|
||||
|
@ -91,13 +91,25 @@ export default function ArtifactListTable(props: ArtifactListTableProps): JSX.El
|
|||
serverSortProps: getServerSortProps('downloadsCount')
|
||||
},
|
||||
{
|
||||
Header: getString('artifactList.table.columns.latestVersion'),
|
||||
accessor: 'latestVersion',
|
||||
Header: getString('artifactList.table.columns.environments'),
|
||||
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,
|
||||
serverSortProps: getServerSortProps('latestVersion')
|
||||
serverSortProps: getServerSortProps('lastUpdated')
|
||||
}
|
||||
].filter(Boolean) as unknown as Column<ArtifactMetadata>[]
|
||||
}, [currentOrder, currentSort, getString, onClickLabel])
|
||||
}, [currentOrder, currentSort, getString])
|
||||
|
||||
return (
|
||||
<TableV2<ArtifactMetadata>
|
||||
|
|
|
@ -14,22 +14,25 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import React, { useState } from 'react'
|
||||
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 { Layout, Text } from '@harnessio/uicore'
|
||||
import { Color } from '@harnessio/design-system'
|
||||
import type { ArtifactMetadata } from '@harnessio/react-har-service-client'
|
||||
import type { ArtifactMetadata, StoDigestMetadata } 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 { useParentComponents, useRoutes } from '@ar/hooks'
|
||||
import TableCells from '@ar/components/TableCells/TableCells'
|
||||
import type { RepositoryPackageType } from '@ar/common/types'
|
||||
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'
|
||||
import { useStrings } from '@ar/frameworks/strings'
|
||||
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> & {
|
||||
column: ColumnInstance<D>
|
||||
|
@ -51,15 +54,19 @@ export const ArtifactNameCell: Renderer<{
|
|||
const { original } = row
|
||||
const { onClickLabel } = column
|
||||
const routes = useRoutes()
|
||||
const value = original.name
|
||||
const { name: value, version } = original
|
||||
return (
|
||||
<Layout.Vertical>
|
||||
<TableCells.LinkCell
|
||||
prefix={<RepositoryIcon packageType={original.packageType as RepositoryPackageType} iconProps={{ size: 24 }} />}
|
||||
linkTo={routes.toARArtifactDetails({
|
||||
linkTo={routes.toARVersionDetailsTab({
|
||||
repositoryIdentifier: original.registryIdentifier,
|
||||
artifactIdentifier: value
|
||||
artifactIdentifier: value,
|
||||
versionIdentifier: defaultTo(version, ''),
|
||||
versionTab: VersionDetailsTab.OVERVIEW
|
||||
})}
|
||||
label={value}
|
||||
subLabel={version}
|
||||
postfix={
|
||||
<LabelsPopover
|
||||
popoverProps={{
|
||||
|
@ -68,45 +75,16 @@ export const ArtifactNameCell: Renderer<{
|
|||
labels={defaultTo(original.labels, [])}
|
||||
tagProps={{
|
||||
interactive: true,
|
||||
onClick: e => onClickLabel(e.currentTarget.ariaValueText as string)
|
||||
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')}
|
||||
</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}
|
||||
/>
|
||||
</Layout.Vertical>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -114,23 +92,107 @@ export const ArtifactDownloadsCell: CellType = ({ value }) => {
|
|||
return <TableCells.CountCell value={value} icon="download-box" iconProps={{ size: 12 }} />
|
||||
}
|
||||
|
||||
export const LatestArtifactCell: CellType = ({ row }) => {
|
||||
const { getString } = useStrings()
|
||||
export const ArtifactDeploymentsCell: CellType = ({ row }) => {
|
||||
const { original } = row
|
||||
const { latestVersion, lastModified } = original || {}
|
||||
if (!latestVersion) {
|
||||
const { deploymentMetadata } = original
|
||||
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 (
|
||||
<Text color={Color.GREY_900} font={{ size: 'small' }}>
|
||||
{getString('na')}
|
||||
</Text>
|
||||
<RbacMenuItem
|
||||
text={getString('artifactList.table.actions.VulnerabilityStatus.digestMenuItemText', {
|
||||
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 (
|
||||
<Layout.Vertical spacing="small">
|
||||
<Text color={Color.PRIMARY_7} font={{ size: 'small' }}>
|
||||
{latestVersion}
|
||||
<Button
|
||||
className={css.cellBtn}
|
||||
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>
|
||||
<TableCells.LastModifiedCell value={defaultTo(lastModified, 0)} />
|
||||
</Layout.Vertical>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
table:
|
||||
noArtifactsTitle: There are no artifact available.
|
||||
allRepositories: All Registries
|
||||
allRepositories: Registries
|
||||
latestVersions: Latest Versions
|
||||
allVersions: All Versions
|
||||
columns:
|
||||
name: Name
|
||||
artifactName: Artifact Name
|
||||
repository: Registry
|
||||
tags: Labels
|
||||
downloads: Downloads (In the last week)
|
||||
downloads: Downloads
|
||||
latestVersion: Latest Version
|
||||
pullCommand: Pull Command
|
||||
environments: Environments
|
||||
sto: Security Vulnerabilities
|
||||
lastUpdated: Uploaded At
|
||||
actions:
|
||||
editRepository: Edit 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 { 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 { RepositoryPackageType } from '@ar/common/types'
|
||||
import type { UseQueryParamsOptions } from '@ar/__mocks__/hooks'
|
||||
|
||||
type GetArtifactListQueryParams = {
|
||||
accountIdentifier: string
|
||||
orgIdentifier: string
|
||||
projectIdentifier: string
|
||||
export type ArtifactListPageQueryParams = {
|
||||
page: number
|
||||
size: number
|
||||
sort: string[]
|
||||
searchTerm?: string
|
||||
isDeployedArtifacts: boolean
|
||||
packageTypes: RepositoryPackageType[]
|
||||
repositoryKey?: string
|
||||
labels: string[]
|
||||
latestVersion: boolean
|
||||
isDeployedArtifacts: boolean
|
||||
searchTerm?: string
|
||||
repositoryKey?: string
|
||||
}
|
||||
|
||||
export type ArtifactListPageQueryParams = Omit<
|
||||
GetArtifactListQueryParams,
|
||||
'accountIdentifier' | 'orgIdentifier' | 'projectIdentifier'
|
||||
>
|
||||
|
||||
export const useArtifactListQueryParamOptions = (): UseQueryParamsOptions<ArtifactListPageQueryParams> => {
|
||||
const { useQueryParamsOptions } = useParentHooks()
|
||||
const _options = useQueryParamsOptions(
|
||||
|
@ -48,10 +41,11 @@ export const useArtifactListQueryParamOptions = (): UseQueryParamsOptions<Artifa
|
|||
size: DEFAULT_PAGE_SIZE,
|
||||
sort: DEFAULT_PIPELINE_LIST_TABLE_SORT,
|
||||
isDeployedArtifacts: false,
|
||||
latestVersion: false,
|
||||
packageTypes: [],
|
||||
labels: []
|
||||
},
|
||||
{ ignoreEmptyString: false }
|
||||
{ ignoreEmptyString: true }
|
||||
)
|
||||
const options = useMemo(() => ({ ..._options, strictNullHandling: true }), [_options])
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
div[class*='TableV2--cells'],
|
||||
div[class*='TableV2--header'] {
|
||||
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'] {
|
||||
|
|
|
@ -26,6 +26,7 @@ import {
|
|||
DigestNameCell,
|
||||
DownloadsCell,
|
||||
OsArchCell,
|
||||
ScanStatusCell,
|
||||
SizeCell,
|
||||
UploadedByCell
|
||||
} from './DigestTableCells'
|
||||
|
@ -69,6 +70,12 @@ export default function DigestListTable(props: DigestListTableProps): JSX.Elemen
|
|||
accessor: 'downloadsCount',
|
||||
Cell: DownloadsCell
|
||||
},
|
||||
{
|
||||
Header: getString('digestList.table.columns.scanStatus'),
|
||||
accessor: 'scanStatus',
|
||||
Cell: ScanStatusCell,
|
||||
version
|
||||
},
|
||||
{
|
||||
Header: '',
|
||||
accessor: 'menu',
|
||||
|
|
|
@ -15,13 +15,18 @@
|
|||
*/
|
||||
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
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 { useStrings } from '@ar/frameworks/strings'
|
||||
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 TableCells from '@ar/components/TableCells/TableCells'
|
||||
import type { ArtifactDetailsPathParams } from '@ar/routes/types'
|
||||
import { VersionDetailsTab } from '@ar/pages/version-details/components/VersionDetailsTabs/constants'
|
||||
|
||||
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 }} />
|
||||
}
|
||||
|
||||
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 = () => {
|
||||
return <></>
|
||||
}
|
||||
|
|
|
@ -6,5 +6,6 @@ table:
|
|||
digest: Digest
|
||||
osArch: OS/Arch
|
||||
size: Size
|
||||
uploadedBy: Uploaded By
|
||||
uploadedBy: Uploaded At
|
||||
downloads: Downloads/Last Download
|
||||
scanStatus: Scan Status
|
||||
|
|
|
@ -16,31 +16,33 @@
|
|||
|
||||
import React, { useCallback, useContext } from 'react'
|
||||
import type { FormikProps } from 'formik'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { Expander } from '@blueprintjs/core'
|
||||
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 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 RepositoryDetailsHeaderWidget from '@ar/frameworks/RepositoryStep/RepositoryDetailsHeaderWidget'
|
||||
import RepositoryConfigurationFormWidget from '@ar/frameworks/RepositoryStep/RepositoryConfigurationFormWidget'
|
||||
import { RepositoryProviderContext } from './context/RepositoryProvider'
|
||||
|
||||
import type { Repository } from './types'
|
||||
import { RepositoryDetailsTab } from './constants'
|
||||
import { RepositoryProviderContext } from './context/RepositoryProvider'
|
||||
import RegistryArtifactListPage from '../artifact-list/RegistryArtifactListPage'
|
||||
import css from './RepositoryDetailsPage.module.scss'
|
||||
|
||||
export default function RepositoryDetails(): JSX.Element | null {
|
||||
const pathParams = useParams<RepositoryDetailsPathParams>()
|
||||
const { useUpdateQueryParams, useQueryParams } = useParentHooks()
|
||||
const { updateQueryParams } = useUpdateQueryParams()
|
||||
const { RbacButton } = useParentComponents()
|
||||
const { getString } = useStrings()
|
||||
const { repositoryIdentifier } = useDecodedParams<RepositoryDetailsPathParams>()
|
||||
const { tab: selectedTabId = RepositoryDetailsTab.PACKAGES } = useQueryParams<{ tab: RepositoryDetailsTab }>()
|
||||
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 { type } = config
|
||||
|
@ -62,12 +64,19 @@ export default function RepositoryDetails(): JSX.Element | null {
|
|||
|
||||
const renderActionBtns = (): JSX.Element => (
|
||||
<Layout.Horizontal className={css.btnContainer}>
|
||||
<Button
|
||||
variation={ButtonVariation.PRIMARY}
|
||||
<RbacButton
|
||||
text={getString('save')}
|
||||
className={css.saveButton}
|
||||
variation={ButtonVariation.PRIMARY}
|
||||
onClick={handleSubmitForm}
|
||||
disabled={!isDirty}
|
||||
disabled={!isDirty || isUpdating}
|
||||
permission={{
|
||||
permission: PermissionIdentifier.EDIT_ARTIFACT_REGISTRY,
|
||||
resource: {
|
||||
resourceType: ResourceType.ARTIFACT_REGISTRY,
|
||||
resourceIdentifier: repositoryIdentifier
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
className={css.discardBtn}
|
||||
|
@ -95,11 +104,7 @@ export default function RepositoryDetails(): JSX.Element | null {
|
|||
title={getString('repositoryDetails.tabs.packages')}
|
||||
panel={
|
||||
<Container>
|
||||
<ArtifactListPage
|
||||
withHeader={false}
|
||||
parentRepoKey={pathParams.repositoryIdentifier}
|
||||
pageBodyClassName={css.packagesPageBody}
|
||||
/>
|
||||
<RegistryArtifactListPage pageBodyClassName={css.packagesPageBody} />
|
||||
</Container>
|
||||
}
|
||||
/>
|
||||
|
@ -111,7 +116,7 @@ export default function RepositoryDetails(): JSX.Element | null {
|
|||
packageType={data.packageType as RepositoryPackageType}
|
||||
type={type as RepositoryConfigType}
|
||||
ref={stepRef}
|
||||
readonly={false}
|
||||
readonly={isReadonly}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -53,10 +53,10 @@ export default function DeleteRepositoryMenuItem({ data }: RepositoryActionsProp
|
|||
onClick={handleDeleteService}
|
||||
permission={{
|
||||
resource: {
|
||||
resourceType: ResourceType.SERVICE,
|
||||
resourceType: ResourceType.ARTIFACT_REGISTRY,
|
||||
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}
|
||||
permission={{
|
||||
resource: {
|
||||
resourceType: ResourceType.SERVICE,
|
||||
resourceType: ResourceType.ARTIFACT_REGISTRY,
|
||||
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}
|
||||
permission={{
|
||||
resource: {
|
||||
resourceType: ResourceType.SERVICE,
|
||||
resourceType: ResourceType.ARTIFACT_REGISTRY,
|
||||
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}
|
||||
permission={{
|
||||
resource: {
|
||||
resourceType: ResourceType.SERVICE,
|
||||
resourceType: ResourceType.ARTIFACT_REGISTRY,
|
||||
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}
|
||||
permission={{
|
||||
resource: {
|
||||
resourceType: ResourceType.SERVICE,
|
||||
resourceType: ResourceType.ARTIFACT_REGISTRY,
|
||||
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 {
|
||||
const { readonly, factory = repositoryFactory } = props
|
||||
const { data } = useContext(RepositoryProviderContext)
|
||||
const { data, setIsUpdating } = useContext(RepositoryProviderContext)
|
||||
const { showSuccess, showError, clear } = useToaster()
|
||||
const { getString } = useStrings()
|
||||
const spaceRef = useGetSpaceRef()
|
||||
|
@ -54,6 +54,7 @@ function RepositoryConfigurationForm(props: RepositoryConfigurationFormProps, fo
|
|||
|
||||
const handleModifyRepository = async (values: VirtualRegistryRequest): Promise<void> => {
|
||||
try {
|
||||
setIsUpdating(true)
|
||||
const response = await modifyRepository({
|
||||
registry_ref: spaceRef,
|
||||
body: values as unknown as RegistryRequestRequestBody
|
||||
|
@ -65,6 +66,8 @@ function RepositoryConfigurationForm(props: RepositoryConfigurationFormProps, fo
|
|||
}
|
||||
} catch (e: any) {
|
||||
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 formattedValuesForCleanupPolicy = getFormattedFormDataForCleanupPolicy(formattedValues)
|
||||
const response = await createRepository({
|
||||
queryParams: {
|
||||
space_ref: decodeRef(parentRef)
|
||||
},
|
||||
body: {
|
||||
...formattedValuesForCleanupPolicy,
|
||||
parentRef: decodeRef(parentRef)
|
||||
|
|
|
@ -29,8 +29,10 @@ import type { Repository } from '@ar/pages/repository-details/types'
|
|||
export interface RepositoryProviderProps {
|
||||
data: Repository | undefined
|
||||
isDirty: boolean
|
||||
isUpdating: boolean
|
||||
setIsDirty: (val: boolean) => void
|
||||
setIsLoading: (val: boolean) => void
|
||||
setIsUpdating: (val: boolean) => void
|
||||
isReadonly: boolean
|
||||
refetch: () => void
|
||||
}
|
||||
|
@ -38,8 +40,10 @@ export interface RepositoryProviderProps {
|
|||
export const RepositoryProviderContext = createContext<RepositoryProviderProps>({
|
||||
data: undefined,
|
||||
isDirty: false,
|
||||
isUpdating: false,
|
||||
setIsDirty: noop,
|
||||
setIsLoading: noop,
|
||||
setIsUpdating: noop,
|
||||
isReadonly: false,
|
||||
refetch: noop
|
||||
})
|
||||
|
@ -48,6 +52,7 @@ const RepositoryProvider: FC<PropsWithChildren<{ className?: string }>> = ({ chi
|
|||
const { repositoryIdentifier } = useDecodedParams<RepositoryDetailsPathParams>()
|
||||
const [isDirty, setIsDirty] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [isUpdating, setIsUpdating] = useState(false)
|
||||
const { usePermission } = useParentHooks()
|
||||
const spaceRef = useGetSpaceRef()
|
||||
const { scope } = useAppStore()
|
||||
|
@ -62,10 +67,10 @@ const RepositoryProvider: FC<PropsWithChildren<{ className?: string }>> = ({ chi
|
|||
projectIdentifier
|
||||
},
|
||||
resource: {
|
||||
resourceType: ResourceType.SERVICE,
|
||||
resourceType: ResourceType.ARTIFACT_REGISTRY,
|
||||
resourceIdentifier: repositoryIdentifier
|
||||
},
|
||||
permissions: [PermissionIdentifier.EDIT_SERVICE]
|
||||
permissions: [PermissionIdentifier.EDIT_ARTIFACT_REGISTRY]
|
||||
},
|
||||
[accountId, projectIdentifier, orgIdentifier, repositoryIdentifier]
|
||||
)
|
||||
|
@ -85,8 +90,10 @@ const RepositoryProvider: FC<PropsWithChildren<{ className?: string }>> = ({ chi
|
|||
value={{
|
||||
data: repositoryData?.content?.data as RepositoryProviderProps['data'],
|
||||
isDirty,
|
||||
isUpdating,
|
||||
setIsDirty,
|
||||
setIsLoading,
|
||||
setIsUpdating,
|
||||
isReadonly: !isEdit,
|
||||
refetch
|
||||
}}>
|
||||
|
|
|
@ -40,7 +40,8 @@ export function useCreateRepositoryModal(props: useCreateRepositoryModalProps) {
|
|||
stepRef.current?.submitForm()
|
||||
}
|
||||
|
||||
const [showModal, hideModal] = useModalHook(() => (
|
||||
const [showModal, hideModal] = useModalHook(
|
||||
() => (
|
||||
<ModalDialog
|
||||
isOpen={true}
|
||||
enforceFocus={false}
|
||||
|
@ -70,13 +71,16 @@ export function useCreateRepositoryModal(props: useCreateRepositoryModalProps) {
|
|||
text={getString('repositoryDetails.repositoryForm.create')}
|
||||
data-id="service-save"
|
||||
onClick={handleSubmitForm}
|
||||
disabled={showOverlay}
|
||||
/>
|
||||
<Button variation={ButtonVariation.TERTIARY} text={getString('cancel')} onClick={hideModal} />
|
||||
</Layout.Horizontal>
|
||||
}>
|
||||
<RepositoryCreateForm onSuccess={onSuccess} setShowOverlay={setShowOverlay} ref={stepRef} />
|
||||
</ModalDialog>
|
||||
))
|
||||
),
|
||||
[showOverlay]
|
||||
)
|
||||
|
||||
return [showModal, hideModal]
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ function RepositoryListPage(): JSX.Element {
|
|||
const spaceRef = useGetSpaceRef()
|
||||
const queryParamOptions = useArtifactRepositoriesQueryParamOptions()
|
||||
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>(
|
||||
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
|
||||
|
||||
|
|
|
@ -39,14 +39,14 @@ export default function CreateRepositoryButton(props: CreateRepositoryButtonProp
|
|||
|
||||
// CHANGE_ME: update permissions once we get actual premission for AR module
|
||||
const [canDoAction] = usePermission({
|
||||
permissions: [PermissionIdentifier.EDIT_SERVICE],
|
||||
permissions: [PermissionIdentifier.EDIT_ARTIFACT_REGISTRY],
|
||||
resourceScope: {
|
||||
accountIdentifier: scope.accountId,
|
||||
orgIdentifier: scope.orgIdentifier,
|
||||
projectIdentifier: scope.projectIdentifier
|
||||
},
|
||||
resource: {
|
||||
resourceType: ResourceType.SERVICE
|
||||
resourceType: ResourceType.ARTIFACT_REGISTRY
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ import { useMemo } from 'react'
|
|||
|
||||
import { useParentHooks } from '@ar/hooks'
|
||||
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'
|
||||
|
||||
type GetArtifactRepositoryQueryParams = {
|
||||
|
@ -29,7 +29,6 @@ type GetArtifactRepositoryQueryParams = {
|
|||
size: number
|
||||
sort: string[]
|
||||
searchTerm?: string
|
||||
environmentTypes: EnvironmentType[]
|
||||
repositoryTypes: RepositoryPackageType[]
|
||||
}
|
||||
|
||||
|
@ -46,7 +45,6 @@ export const useArtifactRepositoriesQueryParamOptions =
|
|||
page: DEFAULT_PAGE_INDEX,
|
||||
size: DEFAULT_PAGE_SIZE,
|
||||
sort: DEFAULT_PIPELINE_LIST_TABLE_SORT,
|
||||
environmentTypes: [],
|
||||
repositoryTypes: []
|
||||
},
|
||||
{ ignoreEmptyString: false }
|
||||
|
|
|
@ -38,6 +38,7 @@ function DockerRepositoryUrlInput(
|
|||
<FormInput.RadioGroup
|
||||
name="config.source"
|
||||
radioGroup={{ inline: true }}
|
||||
disabled={readonly}
|
||||
label={getString('upstreamProxyDetails.createForm.source.title')}
|
||||
items={[
|
||||
{
|
||||
|
|
|
@ -71,6 +71,7 @@ export default function UpstreamProxyAuthenticationFormContent({
|
|||
value: UpstreamProxyAuthenticationMode.ANONYMOUS
|
||||
}
|
||||
]}
|
||||
disabled={readonly}
|
||||
/>
|
||||
{selectedRadioValue === UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD && (
|
||||
<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 { URL_REGEX } from '@ar/constants'
|
||||
import { useGetSpaceRef } from '@ar/hooks'
|
||||
import { useAppStore, useGetSpaceRef } from '@ar/hooks'
|
||||
import { useStrings } from '@ar/frameworks/strings'
|
||||
import { queryClient } from '@ar/utils/queryClient'
|
||||
import type { FormikFowardRef } from '@ar/common/types'
|
||||
|
@ -41,6 +41,7 @@ import {
|
|||
type UpstreamRegistry,
|
||||
type UpstreamRegistryRequest
|
||||
} from '../../types'
|
||||
import { getFormattedFormDataForAuthType } from './utils'
|
||||
|
||||
import css from './Forms.module.scss'
|
||||
|
||||
|
@ -54,9 +55,10 @@ function UpstreamProxyConfigurationForm(
|
|||
formikRef: FormikFowardRef
|
||||
): JSX.Element {
|
||||
const { readonly, factory = repositoryFactory } = props
|
||||
const { data } = useContext(RepositoryProviderContext)
|
||||
const { data, setIsUpdating } = useContext(RepositoryProviderContext)
|
||||
const { showSuccess, showError, clear } = useToaster()
|
||||
const { getString } = useStrings()
|
||||
const { parent } = useAppStore()
|
||||
const spaceRef = useGetSpaceRef()
|
||||
|
||||
const { mutateAsync: modifyUpstreamProxy } = useModifyRegistryMutation()
|
||||
|
@ -74,6 +76,7 @@ function UpstreamProxyConfigurationForm(
|
|||
|
||||
const handleModifyUpstreamProxy = async (values: UpstreamRegistryRequest): Promise<void> => {
|
||||
try {
|
||||
setIsUpdating(true)
|
||||
const response = await modifyUpstreamProxy({
|
||||
registry_ref: spaceRef,
|
||||
body: values
|
||||
|
@ -85,13 +88,16 @@ function UpstreamProxyConfigurationForm(
|
|||
}
|
||||
} catch (e: any) {
|
||||
showError(getErrorInfoFromErrorObject(e, true))
|
||||
} finally {
|
||||
setIsUpdating(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async (values: UpstreamRegistryRequest): Promise<void> => {
|
||||
const repositoryType = factory.getRepositoryType(values.packageType)
|
||||
if (repositoryType) {
|
||||
const transformedCleanupPolicy = getFormattedFormDataForCleanupPolicy(values)
|
||||
const transfomedAuthType = getFormattedFormDataForAuthType(values, parent)
|
||||
const transformedCleanupPolicy = getFormattedFormDataForCleanupPolicy(transfomedAuthType)
|
||||
const transformedValues = repositoryType.processUpstreamProxyFormData(
|
||||
transformedCleanupPolicy
|
||||
) as UpstreamRegistryRequest
|
||||
|
@ -111,7 +117,8 @@ function UpstreamProxyConfigurationForm(
|
|||
authType: Yup.string()
|
||||
.required()
|
||||
.oneOf([UpstreamProxyAuthenticationMode.ANONYMOUS, UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD]),
|
||||
auth: Yup.object().when(['authType'], {
|
||||
auth: Yup.object()
|
||||
.when(['authType'], {
|
||||
is: (authType: UpstreamProxyAuthenticationMode) =>
|
||||
authType === UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD,
|
||||
then: (schema: Yup.ObjectSchema<UserPassword | Anonymous>) =>
|
||||
|
@ -120,7 +127,8 @@ function UpstreamProxyConfigurationForm(
|
|||
secretIdentifier: Yup.string().trim().required(getString('validationMessages.passwordRequired'))
|
||||
}),
|
||||
otherwise: Yup.object().optional().nullable()
|
||||
}),
|
||||
})
|
||||
.nullable(),
|
||||
url: Yup.string().when(['source'], {
|
||||
is: (source: DockerRepositoryURLInputSource) => source === DockerRepositoryURLInputSource.Custom,
|
||||
then: (schema: Yup.StringSchema) =>
|
||||
|
|
|
@ -29,7 +29,7 @@ import {
|
|||
} from '@harnessio/uicore'
|
||||
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 { decodeRef } from '@ar/hooks/useGetSpaceRef'
|
||||
import { setFormikRef } from '@ar/common/utils'
|
||||
|
@ -127,6 +127,7 @@ function UpstreamProxyCreateForm(props: UpstreamProxyCreateFormProps, formikRef:
|
|||
const { showSuccess, showError, clear } = useToaster()
|
||||
const { getString } = useStrings()
|
||||
const spaceRef = useGetSpaceRef('')
|
||||
const { parent, scope } = useAppStore()
|
||||
|
||||
const { isLoading: loading, mutateAsync: createUpstreamProxy } = useCreateRegistryMutation()
|
||||
|
||||
|
@ -137,6 +138,9 @@ function UpstreamProxyCreateForm(props: UpstreamProxyCreateFormProps, formikRef:
|
|||
const handleCreateUpstreamProxy = async (values: UpstreamRegistryRequest): Promise<void> => {
|
||||
try {
|
||||
const response = await createUpstreamProxy({
|
||||
queryParams: {
|
||||
space_ref: decodeRef(spaceRef)
|
||||
},
|
||||
body: {
|
||||
...values,
|
||||
parentRef: decodeRef(spaceRef)
|
||||
|
@ -155,7 +159,7 @@ function UpstreamProxyCreateForm(props: UpstreamProxyCreateFormProps, formikRef:
|
|||
const handleSubmit = (values: UpstreamRegistryRequest): void => {
|
||||
const repositoryType = factory.getRepositoryType(values.packageType)
|
||||
if (repositoryType) {
|
||||
const transfomedAuthType = getFormattedFormDataForAuthType(values)
|
||||
const transfomedAuthType = getFormattedFormDataForAuthType(values, parent, scope)
|
||||
const transformedCleanupPolicy = getFormattedFormDataForCleanupPolicy(transfomedAuthType)
|
||||
const transformedValues = repositoryType.processUpstreamProxyFormData(
|
||||
transformedCleanupPolicy
|
||||
|
@ -192,7 +196,8 @@ function UpstreamProxyCreateForm(props: UpstreamProxyCreateFormProps, formikRef:
|
|||
authType: Yup.string()
|
||||
.required()
|
||||
.oneOf([UpstreamProxyAuthenticationMode.ANONYMOUS, UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD]),
|
||||
auth: Yup.object().when(['authType'], {
|
||||
auth: Yup.object()
|
||||
.when(['authType'], {
|
||||
is: (authType: UpstreamProxyAuthenticationMode) =>
|
||||
authType === UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD,
|
||||
then: (schema: Yup.ObjectSchema<UserPassword | Anonymous>) =>
|
||||
|
@ -200,7 +205,8 @@ function UpstreamProxyCreateForm(props: UpstreamProxyCreateFormProps, formikRef:
|
|||
userName: Yup.string().trim().required(getString('validationMessages.userNameRequired')),
|
||||
secretIdentifier: Yup.string().trim().required(getString('validationMessages.passwordRequired'))
|
||||
})
|
||||
}),
|
||||
})
|
||||
.nullable(),
|
||||
url: Yup.string().when(['source'], {
|
||||
is: (source: DockerRepositoryURLInputSource) => source === DockerRepositoryURLInputSource.Custom,
|
||||
then: (schema: Yup.StringSchema) =>
|
||||
|
|
|
@ -15,17 +15,47 @@
|
|||
*/
|
||||
|
||||
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 {
|
||||
DockerRepositoryURLInputSource,
|
||||
UpstreamProxyAuthenticationMode,
|
||||
type UpstreamRegistryRequest
|
||||
} 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) => {
|
||||
if (draft.config.authType === UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD) {
|
||||
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) {
|
||||
set(draft, 'config.url', '')
|
||||
|
|
|
@ -51,10 +51,10 @@ export default function DeleteUpstreamProxy({ data }: UpstreamProxyActionProps):
|
|||
onClick={handleDeleteService}
|
||||
permission={{
|
||||
resource: {
|
||||
resourceType: ResourceType.SERVICE,
|
||||
resourceType: ResourceType.ARTIFACT_REGISTRY,
|
||||
resourceIdentifier: data.identifier
|
||||
},
|
||||
permission: PermissionIdentifier.DELETE_SERVICE
|
||||
permission: PermissionIdentifier.DELETE_ARTIFACT_REGISTRY
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
|
|
@ -33,10 +33,10 @@ export default function RestoreUpstreamProxy({ data }: UpstreamProxyActionProps)
|
|||
onClick={noop}
|
||||
permission={{
|
||||
resource: {
|
||||
resourceType: ResourceType.SERVICE,
|
||||
resourceType: ResourceType.ARTIFACT_REGISTRY,
|
||||
resourceIdentifier: data.identifier
|
||||
},
|
||||
permission: PermissionIdentifier.DELETE_SERVICE
|
||||
permission: PermissionIdentifier.EDIT_ARTIFACT_REGISTRY
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue