harbor/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component...

559 lines
28 KiB
HTML

<confirmation-dialog
class="hidden-tag"
#confirmationDialog
(confirmAction)="confirmDeletion($event)">
</confirmation-dialog>
<app-copy-artifact></app-copy-artifact>
<app-copy-digest></app-copy-digest>
<div class="row tag-row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<clr-datagrid
#datagrid
[clrDgLoading]="loading"
(clrDgRefresh)="clrDgRefresh($event)"
[(clrDgSelected)]="selectedRow">
<clr-dg-action-bar class="action-bar">
<div>
<button
id="scan-btn"
[clrLoading]="scanBtnState"
type="button"
class="btn btn-secondary scan-btn"
[disabled]="
!(
canScanNow() &&
selectedRowHasVul() &&
hasEnabledScanner &&
hasScanImagePermission
)
"
(click)="scanNow()">
<clr-icon shape="shield-check" size="16"></clr-icon
>&nbsp;
<span>{{ 'VULNERABILITY.SCAN_NOW' | translate }}</span>
</button>
<button
id="stop-scan"
[clrLoading]="stopBtnState"
type="button"
class="btn btn-secondary scan-btn"
[disabled]="!(canStopScan() && hasScanImagePermission)"
(click)="stopNow()">
<clr-icon shape="stop" size="16"></clr-icon>&nbsp;
<span>{{ 'VULNERABILITY.STOP_NOW' | translate }}</span>
</button>
<clr-dropdown class="btn btn-link" *ngIf="!depth">
<span
clrDropdownTrigger
id="artifact-list-action"
class="btn pl-0">
{{ 'BUTTON.ACTIONS' | translate }}
<clr-icon shape="caret down"></clr-icon>
</span>
<clr-dropdown-menu
class="action-dropdown"
clrPosition="bottom-left"
*clrIfOpen>
<div
id="artifact-list-copy-digest"
class="action-dropdown-item no-border"
aria-label="copy digest"
clrDropdownItem
[clrDisabled]="
!(selectedRow.length === 1 && !depth)
"
(click)="showDigestId()">
{{ 'REPOSITORY.COPY_DIGEST_ID' | translate }}
</div>
<clr-dropdown>
<button
id="artifact-list-add-labels"
class="action-dropdown-item"
clrDropdownTrigger
[disabled]="
!canAddLabel() ||
!hasAddLabelImagePermission ||
depth ||
inprogress
">
{{ 'REPOSITORY.ADD_LABELS' | translate }}
</button>
<clr-dropdown-menu
[hidden]="!selectedRow.length">
<div class="filter-grid" clrDropdownItem>
<label class="dropdown-header">{{
'REPOSITORY.ADD_LABEL_TO_IMAGE'
| translate
}}</label>
<app-label-selector
[width]="200"
[ownedLabels]="
selectedRow[0]?.labels
"
(clickLabel)="stickLabel($event)"
[projectId]="projectId"
[scope]="'p'"></app-label-selector>
</div>
</clr-dropdown-menu>
</clr-dropdown>
<div
id="artifact-list-copy"
class="action-dropdown-item"
aria-label="retag"
[clrDisabled]="
!(selectedRow.length === 1) ||
!hasRetagImagePermission ||
depth
"
(click)="retag()"
clrDropdownItem>
{{ 'REPOSITORY.RETAG' | translate }}
</div>
<div
class="action-dropdown-item"
clrDropdownItem
*ngIf="hasDeleteImagePermission"
(click)="deleteArtifact()"
id="artifact-list-delete"
[clrDisabled]="
!hasDeleteImagePermission ||
!selectedRow.length ||
depth
">
{{ 'REPOSITORY.DELETE' | translate }}
</div>
</clr-dropdown-menu>
</clr-dropdown>
</div>
<div class="right-pos">
<app-artifact-filter
[withDivider]="true"
(filterEvent)="filterEvent($event)"
[projectId]="projectId"></app-artifact-filter>
<span class="refresh-btn" (click)="refresh()">
<clr-icon shape="refresh"></clr-icon>
</span>
</div>
</clr-dg-action-bar>
<clr-dg-column class="flex-max-width" [clrDgSortBy]="'digest'"
><ng-template
[clrDgHideableColumn]="{ hidden: hiddenArray[0] }">
{{ 'REPOSITORY.ARTIFACTS_COUNT' | translate }}
</ng-template>
</clr-dg-column>
<clr-dg-column class="pull-command-column">
<ng-template [clrDgHideableColumn]="{ hidden: hiddenArray[1] }">
{{ 'REPOSITORY.PULL_COMMAND' | translate }}
</ng-template>
</clr-dg-column>
<clr-dg-column *ngIf="depth">
<ng-template [clrDgHideableColumn]="{ hidden: hiddenArray[2] }">
{{ 'REPOSITORY.PLATFORM' | translate }}
</ng-template>
</clr-dg-column>
<clr-dg-column>
<ng-template [clrDgHideableColumn]="{ hidden: hiddenArray[3] }">
{{ 'REPOSITORY.TAGS' | translate }}
</ng-template>
</clr-dg-column>
<clr-dg-column class="co-signed-column">
<ng-template [clrDgHideableColumn]="{ hidden: hiddenArray[4] }">
{{ 'ACCESSORY.CO_SIGNED' | translate }}
</ng-template>
</clr-dg-column>
<clr-dg-column [clrDgSortBy]="'size'">
<ng-template [clrDgHideableColumn]="{ hidden: hiddenArray[5] }">
{{ 'REPOSITORY.SIZE' | translate }}
</ng-template>
</clr-dg-column>
<clr-dg-column class="vul-column">
<ng-template [clrDgHideableColumn]="{ hidden: hiddenArray[6] }">
{{ 'REPOSITORY.VULNERABILITY' | translate }}
</ng-template>
</clr-dg-column>
<clr-dg-column class="annotations-column">
<ng-template [clrDgHideableColumn]="{ hidden: hiddenArray[7] }">
{{ 'ARTIFACT.ANNOTATION' | translate }}
</ng-template>
</clr-dg-column>
<clr-dg-column>
<ng-template [clrDgHideableColumn]="{ hidden: hiddenArray[8] }">
{{ 'REPOSITORY.LABELS' | translate }}
</ng-template>
</clr-dg-column>
<clr-dg-column [clrDgSortBy]="pushComparator">
<ng-template [clrDgHideableColumn]="{ hidden: hiddenArray[9] }">
{{ 'REPOSITORY.PUSH_TIME' | translate }}
</ng-template>
</clr-dg-column>
<clr-dg-column [clrDgSortBy]="pullComparator">
<ng-template
[clrDgHideableColumn]="{ hidden: hiddenArray[10] }">
{{ 'REPOSITORY.PULL_TIME' | translate }}
</ng-template>
</clr-dg-column>
<clr-dg-placeholder>{{
'ARTIFACT.PLACEHOLDER' | translate
}}</clr-dg-placeholder>
<clr-dg-row
*ngFor="let artifact of artifactList; let i = index"
[clrDgItem]="artifact">
<clr-dg-cell class="flex-max-width truncated">
<div class="cell white-normal">
<div
class="artifact-icon clr-display-inline-block"
*ngIf="artifact.icon">
<img
*ngIf="getIcon(artifact.icon)"
class="artifact-icon"
[title]="artifact.type"
[src]="getIcon(artifact.icon)"
(error)="showDefaultIcon($event)" />
</div>
<a
href="javascript:void(0)"
class="digest margin-left-5"
(click)="goIntoArtifactSummaryPage(artifact)"
title="{{ artifact.digest }}">
{{ artifact.digest | slice : 0 : 15 }}</a
>
<clr-tooltip
*ngIf="
artifact?.references &&
artifact?.references?.length
">
<div clrTooltipTrigger class="level-border">
<div class="inner truncated">
<a
href="javascript:void(0)"
(click)="goIntoIndexArtifact(artifact)">
<clr-icon
size="24"
class="icon-folder"
shape="folder"></clr-icon>
</a>
</div>
</div>
<clr-tooltip-content
[clrPosition]="'top-right'"
[clrSize]="'lg'"
*clrIfOpen>
{{ 'REPOSITORY.ARTIFACT_TOOTIP' | translate }}
</clr-tooltip-content>
</clr-tooltip>
</div>
</clr-dg-cell>
<clr-dg-cell>
<hbr-copy-input
[title]="getPullCommand(artifact)"
*ngIf="getPullCommand(artifact)"
[iconMode]="true"
[defaultValue]="
getPullCommand(artifact)
"></hbr-copy-input>
</clr-dg-cell>
<clr-dg-cell *ngIf="depth">
<div class="cell">
{{ artifact.platform?.os }}
/{{ artifact.platform?.architecture
}}{{
artifact.platform?.variant
? '/' + artifact.platform?.variant
: ''
}}
</div>
</clr-dg-cell>
<clr-dg-cell class="center">
<div *ngIf="artifact.tags" class="truncated width-p-100">
<clr-tooltip class="width-p-100">
<div clrTooltipTrigger class="center">
<div class="center width-p-100">
<span
#tagsSpan
class="truncated width-p-75"
>{{ tagsString(artifact?.tags) }}</span
>
<span
*ngIf="
artifact?.tags?.length > 1 &&
isEllipsisActive(tagsSpan)
"
>({{ artifact?.tagNumber }} )</span
>
</div>
</div>
<clr-tooltip-content
[clrPosition]="'top-right'"
class="lg"
[clrSize]="'lg'"
*clrIfOpen>
<table
class="table table-noborder mt-0 table-tag">
<thead class="tag-thead">
<tr>
<th class="left tag-header-color">
{{
'REPOSITORY.TAGS'
| translate
| uppercase
}}
</th>
<th
*ngIf="withNotary"
class="left tag-header-color">
{{
'ACCESSORY.NOTARY_SIGNED'
| translate
| uppercase
}}
</th>
<th class="left tag-header-color">
{{
'REPOSITORY.PULL_TIME'
| translate
| uppercase
}}
</th>
<th class="left tag-header-color">
{{
'REPOSITORY.PUSH_TIME'
| translate
| uppercase
}}
</th>
</tr>
</thead>
<tbody class="tag-tbody">
<!--display less than 9 tags(too many tags will make UI stuck)-->
<tr
class="tag-tr"
*ngFor="let tag of artifact.tags">
<td class="left tag-body-color">
{{ tag.name }}
</td>
<td
*ngIf="withNotary"
class="left tag-body-color"
[ngSwitch]="tag.signed">
<div class="cell">
<clr-icon
shape="check-circle"
*ngSwitchCase="true"
size="20"
class="color-green"></clr-icon>
<clr-icon
shape="times-circle"
*ngSwitchCase="false"
size="16"
class="color-red"></clr-icon>
<a
href="javascript:void(0)"
*ngSwitchDefault
role="tooltip"
aria-haspopup="true"
class="tooltip tooltip-top-right">
<clr-icon
shape="help"
class="color-gray"
size="16"></clr-icon>
<span
class="tooltip-content"
>{{
'REPOSITORY.NOTARY_IS_UNDETERMINED'
| translate
}}</span
>
</a>
</div>
</td>
<td class="left tag-body-color">
{{
tag.pull_time ===
availableTime
? ''
: (tag.pull_time
| harborDatetime
: 'short')
}}
</td>
<td class="left tag-body-color">
{{
tag.push_time
| harborDatetime
: 'short'
}}
</td>
</tr>
<tr *ngIf="artifact?.tagNumber > 8">
...
</tr>
</tbody>
</table>
</clr-tooltip-content>
</clr-tooltip>
</div>
</clr-dg-cell>
<clr-dg-cell>
<span
*ngIf="artifact.coSigned === 'checking'"
class="spinner spinner-inline ml-2"></span>
<clr-icon
shape="check-circle"
*ngIf="artifact.coSigned === 'true'"
size="20"
class="signed"></clr-icon>
<clr-icon
shape="times-circle"
*ngIf="artifact.coSigned === 'false'"
size="16"
class="color-red"></clr-icon>
</clr-dg-cell>
<clr-dg-cell>
<div class="cell">
{{
artifact.size
? sizeTransform(artifact.size + '')
: ''
}}
</div>
</clr-dg-cell>
<clr-dg-cell>
<div class="cell">
<span *ngIf="!hasVul(artifact)">
{{ 'ARTIFACT.SCAN_UNSUPPORTED' | translate }}
</span>
<hbr-vulnerability-bar
(submitStopFinish)="submitStopFinish($event)"
(scanFinished)="scanFinished($event)"
*ngIf="hasVul(artifact)"
[inputScanner]="
handleScanOverview(artifact.scan_overview)
?.scanner
"
(submitFinish)="submitFinish($event)"
[projectName]="projectName"
[repoName]="repoName"
[artifactDigest]="artifact.digest"
[summary]="
handleScanOverview(artifact.scan_overview)
">
</hbr-vulnerability-bar>
</div>
</clr-dg-cell>
<clr-dg-cell>
<div class="cell" *ngIf="artifact.annotationsArray?.length">
<div class="bar-state">
<span class="label not-scan">{{
artifact.annotationsArray[0]
}}</span>
</div>
<div
class="signpost-item"
[hidden]="artifact.annotationsArray?.length <= 1">
<div class="trigger-item">
<clr-signpost>
<button
class="btn btn-link"
clrSignpostTrigger>
...
</button>
<clr-signpost-content
[clrPosition]="'left-top'"
*clrIfOpen>
<div>
<div
*ngFor="
let attr of artifact.annotationsArray
"
class="bar-state">
<span class="label not-scan">{{
attr
}}</span>
</div>
</div>
</clr-signpost-content>
</clr-signpost>
</div>
</div>
</div>
</clr-dg-cell>
<clr-dg-cell>
<div class="cell">
<hbr-label-piece
*ngIf="artifact.labels?.length"
[label]="artifact.labels[0]"
[labelWidth]="90">
</hbr-label-piece>
<div
class="signpost-item"
[hidden]="artifact.labels?.length <= 1">
<clr-signpost>
<button class="btn btn-link" clrSignpostTrigger>
...
</button>
<clr-signpost-content
[clrPosition]="'top-middle'"
*clrIfOpen>
<div
*ngFor="let label of artifact.labels"
class="margin-5px">
<hbr-label-piece
[labelWidth]="130"
[label]="label">
</hbr-label-piece>
</div>
</clr-signpost-content>
</clr-signpost>
</div>
</div>
</clr-dg-cell>
<clr-dg-cell>
<div class="cell">
{{ artifact.push_time | harborDatetime : 'short' }}
</div>
</clr-dg-cell>
<clr-dg-cell>
<div class="cell">
{{
artifact.pull_time === availableTime
? ''
: (artifact.pull_time
| harborDatetime : 'short')
}}
</div>
</clr-dg-cell>
<ng-container
ngProjectAs="clr-dg-row-detail"
*ngIf="artifact?.accessories?.length">
<sub-accessories
[projectName]="projectName"
[repositoryName]="repoName"
[artifactDigest]="artifact?.digest"
[total]="artifact?.accessoryNumber"
[accessories]="artifact?.accessories"
*clrIfExpanded></sub-accessories>
</ng-container>
</clr-dg-row>
<clr-dg-footer>
<clr-dg-pagination
#pagination
[clrDgTotalItems]="totalCount"
[(clrDgPage)]="currentPage"
[clrDgPageSize]="pageSize">
<clr-dg-page-size [clrPageSizeOptions]="[15, 25, 50]">{{
'PAGINATION.PAGE_SIZE' | translate
}}</clr-dg-page-size>
<span *ngIf="totalCount"
>{{ pagination.firstItem + 1 }} -
{{ pagination.lastItem + 1 }}
{{ 'REPOSITORY.OF' | translate }}</span
>
{{ totalCount }}
{{ 'REPOSITORY.ITEMS' | translate }}&nbsp;&nbsp;&nbsp;&nbsp;
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
</div>
</div>