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

724 lines
38 KiB
HTML
Raw Normal View History

<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
[clrDgLoading]="loading"
(clrDgRefresh)="clrDgRefresh($event)"
class="datagrid-top"
[(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
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
class="action-dropdown-item"
clrDropdownTrigger
[disabled]="
!canAddLabel() ||
!hasAddLabelImagePermission ||
depth ||
inprogress
"
(click)="addLabels()">
{{ 'REPOSITORY.ADD_LABELS' | translate }}
</button>
<clr-dropdown-menu
[hidden]="!selectedRow.length">
<div class="filter-grid">
<label class="dropdown-header">{{
'REPOSITORY.ADD_LABEL_TO_IMAGE'
| translate
}}</label>
<div
class="form-group filter-label-input">
<input
clrInput
type="text"
placeholder="Filter labels"
[(ngModel)]="stickName"
(keyup)="
handleStickInputFilter()
" />
</div>
<div
[hidden]="
artifactListPageService
?.imageStickLabels.length
"
class="no-labels">
{{ 'LABEL.NO_LABELS' | translate }}
</div>
<div
[hidden]="
!artifactListPageService
?.imageStickLabels.length
"
class="has-label">
<button
type="button"
class="dropdown-item"
clrDropdownItem
*ngFor="
let label of artifactListPageService?.imageStickLabels
"
[hidden]="!label.show"
(click)="stickLabel(label)">
<clr-icon
shape="check"
class="pull-left"
[hidden]="!label.iconsShow">
</clr-icon>
<div class="labelDiv">
<hbr-label-piece
[label]="label.label"
[labelWidth]="130">
</hbr-label-piece>
</div>
</button>
</div>
</div>
</clr-dropdown-menu>
</clr-dropdown>
<div
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="row flex-items-xs-right rightPos">
<div id="filterArea" *ngIf="!depth">
<div
class="filterLabelPiece"
*ngIf="
openLabelFilterPiece &&
filterByType === 'labels'
"
[style.left.px]="110">
<hbr-label-piece
*ngIf="showlabel"
[hidden]="!filterOneLabel"
[label]="filterOneLabel"
[labelWidth]="130"></hbr-label-piece>
</div>
<div class="flex-xs-middle">
<div class="execution-select">
<div
class="select filter-tag"
[hidden]="!openSelectFilterPiece">
<clr-select-container>
<select
clrSelect
[(ngModel)]="filterByType"
(change)="selectFilterType()">
<option
*ngFor="
let filter of mutipleFilter
"
value="{{ filter.filterBy }}">
{{
filter.filterByShowText
| translate
}}
</option>
</select>
</clr-select-container>
</div>
<div class="flex-xs-middle">
<hbr-filter
[withDivider]="true"
[readonly]="isFilterReadonly"
filterPlaceholder="{{
getFilterPlaceholder() | translate
}}"
(filterEvt)="
doSearchArtifactByFilter($event)
"
(openFlag)="openFlagEvent($event)"
[currentValue]="lastFilteredTagName">
</hbr-filter>
<div
[hidden]="!openSelectFilterPiece"
class="label-filter-panel list-filter">
<div
*ngFor="
let filter of mutipleFilter
">
<ul
class="list-unstyled"
*ngIf="
filterByType ===
filter.filterBy
">
<li
class="cursor-pointer"
(click)="
selectFilter(
item.showItem,
item.filterText
)
"
*ngFor="
let item of filter.listItem
">
{{
item.showItem
| translate
}}
</li>
</ul>
</div>
</div>
<div
class="label-filter-panel"
[hidden]="
!(
openLabelFilterPanel &&
filterByType === 'labels'
)
">
<a
class="filterClose"
(click)="closeFilter()"
>&times;</a
>
<label
class="filterLabelHeader filter-dark"
>{{
'REPOSITORY.FILTER_ARTIFACT_BY_LABEL'
| translate
}}</label
>
<div class="form-group mb-05">
<input
clrInput
type="text"
placeholder="Filter labels"
[(ngModel)]="filterName"
(keyup)="handleInputFilter()" />
</div>
<div
[hidden]="
artifactListPageService
?.imageFilterLabels.length
"
class="no-labels">
{{ 'LABEL.NO_LABELS' | translate }}
</div>
<div
[hidden]="
!artifactListPageService
?.imageFilterLabels.length
"
class="has-label">
<button
type="button"
class="labelBtn"
*ngFor="
let label of artifactListPageService?.imageFilterLabels
"
[hidden]="!label.show"
(click)="
rightFilterLabel(label)
">
<clr-icon
shape="check"
class="pull-left"
[hidden]="
!label.iconsShow
"></clr-icon>
<div class="labelDiv top-3-px">
<hbr-label-piece
[label]="label.label"
[labelWidth]="
160
"></hbr-label-piece>
</div>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<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'"
>{{ 'REPOSITORY.ARTIFACTS_COUNT' | translate }}
</clr-dg-column>
<clr-dg-column class="pull-command-column">{{
'REPOSITORY.PULL_COMMAND' | translate
}}</clr-dg-column>
<clr-dg-column *ngIf="depth">{{
'REPOSITORY.PLATFORM' | translate
}}</clr-dg-column>
<clr-dg-column class="tag-column">{{
'REPOSITORY.TAGS' | translate
}}</clr-dg-column>
<clr-dg-column class="co-signed-column">{{
'ACCESSORY.CO_SIGNED' | translate
}}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="'size'">{{
'REPOSITORY.SIZE' | translate
}}</clr-dg-column>
<clr-dg-column class="vul-column">{{
'REPOSITORY.VULNERABILITY' | translate
}}</clr-dg-column>
<clr-dg-column class="annotations-column">{{
'ARTIFACT.ANNOTATION' | translate
}}</clr-dg-column>
<clr-dg-column>{{ 'REPOSITORY.LABELS' | translate }}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="pushComparator">{{
'REPOSITORY.PUSH_TIME' | translate
}}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="pullComparator">{{
'REPOSITORY.PULL_TIME' | translate
}}</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">
<div class="trigger-item">
<clr-signpost>
<button
class="btn btn-link"
clrSignpostTrigger>
...
</button>
<clr-signpost-content
[clrPosition]="'top-middle'"
*clrIfOpen>
<div>
<hbr-label-piece
*ngFor="
let label of artifact.labels
"
[label]="label">
</hbr-label-piece>
</div>
</clr-signpost-content>
</clr-signpost>
</div>
</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
(deleteAccessory)="deleteAccessory($event)"
[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>