mirror of
https://github.com/goharbor/harbor.git
synced 2025-01-23 16:11:24 +01:00
Fix router issues for UI (#17235)
Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
parent
bff4e13087
commit
04fa3853c9
@ -1,14 +1,12 @@
|
||||
<h5 class="history-header font-style mt-3" id="history-header">
|
||||
{{ 'CLEARANCES.PURGE_HISTORY' | translate }}
|
||||
</h5>
|
||||
<span class="refresh-btn" (click)="refresh()">
|
||||
<clr-icon shape="refresh"></clr-icon>
|
||||
</span>
|
||||
|
||||
<clr-datagrid
|
||||
[(clrDgSelected)]="selectedRow"
|
||||
[clrDgLoading]="loading"
|
||||
(clrDgRefresh)="getJobs(true, $event)">
|
||||
<clr-dg-action-bar>
|
||||
<clr-dg-action-bar class="action-bar">
|
||||
<div class="btn-group">
|
||||
<button
|
||||
id="stop-purge"
|
||||
@ -19,6 +17,9 @@
|
||||
{{ 'REPLICATION.STOPJOB' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
<span class="refresh-btn" (click)="refresh()">
|
||||
<clr-icon shape="refresh"></clr-icon>
|
||||
</span>
|
||||
</clr-dg-action-bar>
|
||||
<clr-dg-column [clrDgField]="'id'">{{
|
||||
'GC.JOB_ID' | translate
|
||||
@ -31,6 +32,9 @@
|
||||
'UPDATE_TIME' | translate
|
||||
}}</clr-dg-column>
|
||||
<clr-dg-column>{{ 'LOGS' | translate }}</clr-dg-column>
|
||||
<clr-dg-placeholder>{{
|
||||
'CLEARANCES.NO_PURGE_RECORDS' | translate
|
||||
}}</clr-dg-placeholder>
|
||||
<clr-dg-row *ngFor="let job of jobs" [clrDgItem]="job">
|
||||
<clr-dg-cell>{{ job.id }}</clr-dg-cell>
|
||||
<clr-dg-cell>{{
|
||||
|
@ -4,11 +4,17 @@
|
||||
width: 97%;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
margin-right: 2rem;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: #007CBB;
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,11 @@
|
||||
<h5 class="history-header mt-3 font-style" id="history-header">
|
||||
{{ 'GC.JOB_HISTORY' | translate }}
|
||||
</h5>
|
||||
<span class="refresh-btn" (click)="refresh()">
|
||||
<clr-icon shape="refresh"></clr-icon>
|
||||
</span>
|
||||
<clr-datagrid
|
||||
[(clrDgSelected)]="selectedRow"
|
||||
[clrDgLoading]="loading"
|
||||
(clrDgRefresh)="getJobs(true, $event)">
|
||||
<clr-dg-action-bar>
|
||||
<clr-dg-action-bar class="action-bar">
|
||||
<div class="btn-group">
|
||||
<button
|
||||
id="stop-gc"
|
||||
@ -19,6 +16,9 @@
|
||||
{{ 'REPLICATION.STOPJOB' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
<span class="refresh-btn" (click)="refresh()">
|
||||
<clr-icon shape="refresh"></clr-icon>
|
||||
</span>
|
||||
</clr-dg-action-bar>
|
||||
<clr-dg-column [clrDgField]="'id'">{{
|
||||
'GC.JOB_ID' | translate
|
||||
@ -31,6 +31,9 @@
|
||||
'UPDATE_TIME' | translate
|
||||
}}</clr-dg-column>
|
||||
<clr-dg-column>{{ 'LOGS' | translate }}</clr-dg-column>
|
||||
<clr-dg-placeholder>{{
|
||||
'CLEARANCES.NO_GC_RECORDS' | translate
|
||||
}}</clr-dg-placeholder>
|
||||
<clr-dg-row *ngFor="let job of jobs" [clrDgItem]="job">
|
||||
<clr-dg-cell>{{ job.id }}</clr-dg-cell>
|
||||
<clr-dg-cell>{{
|
||||
|
@ -4,11 +4,17 @@
|
||||
width: 97%;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
margin-right: 2rem;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: #007CBB;
|
||||
}
|
||||
}
|
||||
|
@ -59,8 +59,10 @@
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
id="changePwd"
|
||||
[hidden]="!canCreateUser"
|
||||
[disabled]="!(selectedRow.length === 1)"
|
||||
[disabled]="
|
||||
!(selectedRow.length === 1) ||
|
||||
!canCreateUser
|
||||
"
|
||||
(click)="openChangePwdModal()">
|
||||
<clr-icon shape="edit" size="16"></clr-icon
|
||||
> {{ 'RESET_PWD.TITLE' | translate }}
|
||||
|
@ -32,7 +32,11 @@ describe('ArtifactListPageComponent', () => {
|
||||
}),
|
||||
params: {
|
||||
subscribe: () => {
|
||||
return of(null);
|
||||
return {
|
||||
unsubscribe() {
|
||||
return null;
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -11,42 +11,53 @@
|
||||
// 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 { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { Project } from '../../../project';
|
||||
import { ArtifactListPageService } from './artifact-list-page.service';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'artifact-list-page',
|
||||
templateUrl: 'artifact-list-page.component.html',
|
||||
styleUrls: ['./artifact-list-page.component.scss'],
|
||||
})
|
||||
export class ArtifactListPageComponent implements OnInit {
|
||||
export class ArtifactListPageComponent implements OnDestroy {
|
||||
projectId: string;
|
||||
projectName: string;
|
||||
repoName: string;
|
||||
referArtifactNameArray: string[] = [];
|
||||
depth: string;
|
||||
artifactDigest: string;
|
||||
routeParamsSub: Subscription;
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private artifactListPageService: ArtifactListPageService
|
||||
) {
|
||||
this.route.params.subscribe(params => {
|
||||
this.depth = this.route.snapshot.params['depth'];
|
||||
if (this.depth) {
|
||||
const arr: string[] = this.depth.split('-');
|
||||
this.referArtifactNameArray = arr.slice(0, arr.length - 1);
|
||||
this.artifactDigest = this.depth.split('-')[arr.length - 1];
|
||||
} else {
|
||||
this.referArtifactNameArray = [];
|
||||
this.artifactDigest = null;
|
||||
}
|
||||
});
|
||||
if (!this.routeParamsSub) {
|
||||
this.routeParamsSub = this.route.params.subscribe(params => {
|
||||
this.depth = this.route.snapshot.firstChild.params['depth'];
|
||||
if (this.depth) {
|
||||
const arr: string[] = this.depth.split('-');
|
||||
this.referArtifactNameArray = arr.slice(0, arr.length - 1);
|
||||
this.artifactDigest = this.depth.split('-')[arr.length - 1];
|
||||
} else {
|
||||
this.referArtifactNameArray = [];
|
||||
this.artifactDigest = null;
|
||||
}
|
||||
this.init();
|
||||
});
|
||||
}
|
||||
}
|
||||
ngOnDestroy() {
|
||||
if (this.routeParamsSub) {
|
||||
this.routeParamsSub.unsubscribe();
|
||||
this.routeParamsSub = null;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
init() {
|
||||
this.projectId = this.route.snapshot.parent.params['id'];
|
||||
let resolverData = this.route.snapshot.parent.data;
|
||||
if (resolverData) {
|
||||
@ -55,7 +66,6 @@ export class ArtifactListPageComponent implements OnInit {
|
||||
this.repoName = this.route.snapshot.params['repo'];
|
||||
this.artifactListPageService.init(+this.projectId);
|
||||
}
|
||||
|
||||
watchGoBackEvt(projectId: string | number): void {
|
||||
this.router.navigate(['harbor', 'projects', projectId, 'repositories']);
|
||||
}
|
||||
|
@ -55,298 +55,321 @@
|
||||
</div>
|
||||
</clr-modal>
|
||||
<div class="row tag-row">
|
||||
<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()"
|
||||
>×</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>
|
||||
</div>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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
|
||||
<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
|
||||
>
|
||||
<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>
|
||||
<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)="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
|
||||
(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
|
||||
"
|
||||
[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>
|
||||
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()"
|
||||
>×</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>
|
||||
</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>
|
||||
<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>
|
||||
<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'"
|
||||
|
@ -95,7 +95,6 @@
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
right: 35px;
|
||||
margin-top: 34px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@ -485,3 +484,8 @@ clr-datagrid {
|
||||
.width-p-75 {
|
||||
width: 75%;
|
||||
}
|
||||
.action-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
@ -20,10 +20,6 @@ import {
|
||||
USERSTATICPERMISSION,
|
||||
} from '../../../../../../../shared/services';
|
||||
import { ArtifactFront as Artifact } from '../../../artifact';
|
||||
import { LabelPieceComponent } from '../../../../../../../shared/components/label/label-piece/label-piece.component';
|
||||
import { ConfirmationDialogComponent } from '../../../../../../../shared/components/confirmation-dialog';
|
||||
import { ImageNameInputComponent } from '../../../../../../../shared/components/image-name-input/image-name-input.component';
|
||||
import { CopyInputComponent } from '../../../../../../../shared/components/push-image/copy-input.component';
|
||||
import { ErrorHandler } from '../../../../../../../shared/units/error-handler';
|
||||
import { OperationService } from '../../../../../../../shared/components/operation/operation.service';
|
||||
import { ArtifactService as NewArtifactService } from '../../../../../../../../../ng-swagger-gen/services/artifact.service';
|
||||
@ -43,19 +39,16 @@ describe('ArtifactListTabComponent (inline template)', () => {
|
||||
let spyLabels: jasmine.Spy;
|
||||
let spyLabels1: jasmine.Spy;
|
||||
let spyScanner: jasmine.Spy;
|
||||
let scannerMock = {
|
||||
const scannerMock = {
|
||||
disabled: false,
|
||||
name: 'Trivy',
|
||||
};
|
||||
let mockActivatedRoute = {
|
||||
const mockActivatedRoute = {
|
||||
snapshot: {
|
||||
params: {
|
||||
id: 1,
|
||||
repo: 'test',
|
||||
digest: 'ABC',
|
||||
subscribe: () => {
|
||||
return of(null);
|
||||
},
|
||||
},
|
||||
data: {
|
||||
projectResolver: {
|
||||
@ -70,13 +63,8 @@ describe('ArtifactListTabComponent (inline template)', () => {
|
||||
name: 'library',
|
||||
},
|
||||
}),
|
||||
params: {
|
||||
subscribe: () => {
|
||||
return of(null);
|
||||
},
|
||||
},
|
||||
};
|
||||
let mockArtifacts: Artifact[] = [
|
||||
const mockArtifacts: Artifact[] = [
|
||||
{
|
||||
id: 1,
|
||||
type: 'image',
|
||||
@ -266,6 +254,11 @@ describe('ArtifactListTabComponent (inline template)', () => {
|
||||
},
|
||||
];
|
||||
const mockRouter = {
|
||||
events: {
|
||||
subscribe: () => {
|
||||
return of(null);
|
||||
},
|
||||
},
|
||||
navigate: () => {},
|
||||
};
|
||||
const mockOperationService = {
|
||||
@ -351,13 +344,7 @@ describe('ArtifactListTabComponent (inline template)', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [SharedTestingModule],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
declarations: [
|
||||
ArtifactListTabComponent,
|
||||
LabelPieceComponent,
|
||||
ConfirmationDialogComponent,
|
||||
ImageNameInputComponent,
|
||||
CopyInputComponent,
|
||||
],
|
||||
declarations: [ArtifactListTabComponent],
|
||||
providers: [
|
||||
{
|
||||
provide: ArtifactListPageService,
|
||||
|
@ -197,11 +197,8 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
||||
onSendingStopScanCommand: boolean = false;
|
||||
onStopScanArtifactsLength: number = 0;
|
||||
scanStoppedArtifactLength: number = 0;
|
||||
|
||||
artifactDigest: string;
|
||||
depth: string;
|
||||
hasInit: boolean = false;
|
||||
triggerSub: Subscription;
|
||||
labelNameFilterSub: Subscription;
|
||||
stickLabelNameFilterSub: Subscription;
|
||||
mutipleFilter = clone(mutipleFilter);
|
||||
@ -209,7 +206,6 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
||||
openSelectFilterPiece = false;
|
||||
// could Pagination filter
|
||||
filters: string[];
|
||||
|
||||
scanFinishedArtifactLength: number = 0;
|
||||
onScanArtifactsLength: number = 0;
|
||||
stopBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||
@ -226,38 +222,33 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
||||
private appConfigService: AppConfigService,
|
||||
public artifactListPageService: ArtifactListPageService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.artifactListPageService.resetClonedLabels();
|
||||
initRouterData() {
|
||||
this.projectId =
|
||||
this.activatedRoute.snapshot?.parent?.parent?.params['id'];
|
||||
let resolverData = this.activatedRoute.snapshot?.parent?.parent?.data;
|
||||
if (!this.projectId) {
|
||||
this.errorHandlerService.error('Project ID cannot be unset.');
|
||||
return;
|
||||
}
|
||||
const resolverData = this.activatedRoute.snapshot?.parent?.parent?.data;
|
||||
if (resolverData) {
|
||||
this.projectName = (<Project>resolverData['projectResolver']).name;
|
||||
}
|
||||
this.repoName = this.activatedRoute.snapshot?.parent?.params['repo'];
|
||||
if (!this.repoName) {
|
||||
this.errorHandlerService.error('Repo name cannot be unset.');
|
||||
return;
|
||||
}
|
||||
this.depth = this.activatedRoute.snapshot.params['depth'];
|
||||
if (this.depth) {
|
||||
const arr: string[] = this.depth.split('-');
|
||||
this.artifactDigest = this.depth.split('-')[arr.length - 1];
|
||||
}
|
||||
this.lastFilteredTagName = '';
|
||||
}
|
||||
ngOnInit() {
|
||||
this.artifactListPageService.resetClonedLabels();
|
||||
this.registryUrl = this.appConfigService.getConfig().registry_url;
|
||||
this.activatedRoute.params?.subscribe(params => {
|
||||
this.depth =
|
||||
this.activatedRoute.snapshot?.firstChild?.params['depth'];
|
||||
if (this.depth) {
|
||||
const arr: string[] = this.depth.split('-');
|
||||
this.artifactDigest = this.depth.split('-')[arr.length - 1];
|
||||
}
|
||||
if (this.hasInit) {
|
||||
this.currentPage = 1;
|
||||
this.totalCount = 0;
|
||||
const st: ClrDatagridStateInterface = {
|
||||
page: {
|
||||
from: 0,
|
||||
to: this.pageSize - 1,
|
||||
size: this.pageSize,
|
||||
},
|
||||
};
|
||||
this.clrLoad(st);
|
||||
}
|
||||
this.init();
|
||||
});
|
||||
this.initRouterData();
|
||||
if (!this.updateArtifactSub) {
|
||||
this.updateArtifactSub = this.eventService.subscribe(
|
||||
HarborEvent.UPDATE_VULNERABILITY_INFO,
|
||||
@ -272,48 +263,6 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.triggerSub) {
|
||||
this.triggerSub.unsubscribe();
|
||||
this.triggerSub = null;
|
||||
}
|
||||
if (this.labelNameFilterSub) {
|
||||
this.labelNameFilterSub.unsubscribe();
|
||||
this.labelNameFilterSub = null;
|
||||
}
|
||||
if (this.stickLabelNameFilterSub) {
|
||||
this.stickLabelNameFilterSub.unsubscribe();
|
||||
this.stickLabelNameFilterSub = null;
|
||||
}
|
||||
if (this.updateArtifactSub) {
|
||||
this.updateArtifactSub.unsubscribe();
|
||||
this.updateArtifactSub = null;
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
this.hasInit = true;
|
||||
this.depth = this.activatedRoute.snapshot.params['depth'];
|
||||
if (this.depth) {
|
||||
const arr: string[] = this.depth.split('-');
|
||||
this.artifactDigest = this.depth.split('-')[arr.length - 1];
|
||||
}
|
||||
if (!this.projectId) {
|
||||
this.errorHandlerService.error('Project ID cannot be unset.');
|
||||
return;
|
||||
}
|
||||
const resolverData = this.activatedRoute.snapshot.params.data;
|
||||
if (resolverData) {
|
||||
const pro: Project = <Project>resolverData['projectResolver'];
|
||||
this.projectName = pro.name;
|
||||
}
|
||||
if (!this.repoName) {
|
||||
this.errorHandlerService.error('Repo name cannot be unset.');
|
||||
return;
|
||||
}
|
||||
this.lastFilteredTagName = '';
|
||||
if (!this.labelNameFilterSub) {
|
||||
this.labelNameFilterSub = this.labelNameFilter
|
||||
.pipe(debounceTime(500))
|
||||
@ -349,7 +298,20 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.labelNameFilterSub) {
|
||||
this.labelNameFilterSub.unsubscribe();
|
||||
this.labelNameFilterSub = null;
|
||||
}
|
||||
if (this.stickLabelNameFilterSub) {
|
||||
this.stickLabelNameFilterSub.unsubscribe();
|
||||
this.stickLabelNameFilterSub = null;
|
||||
}
|
||||
if (this.updateArtifactSub) {
|
||||
this.updateArtifactSub.unsubscribe();
|
||||
this.updateArtifactSub = null;
|
||||
}
|
||||
}
|
||||
public get filterLabelPieceWidth() {
|
||||
let len = this.lastFilteredTagName.length
|
||||
? this.lastFilteredTagName.length * 6 + 60
|
||||
@ -381,11 +343,11 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
||||
this.clrLoad(st);
|
||||
}
|
||||
|
||||
// todo
|
||||
clrDgRefresh(state: ClrDatagridStateInterface) {
|
||||
setTimeout(() => {
|
||||
//add setTimeout to avoid ng check error
|
||||
this.clrLoad(state);
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
|
||||
clrLoad(state: ClrDatagridStateInterface): void {
|
||||
|
@ -8,7 +8,9 @@
|
||||
<span class="back-icon"><</span>
|
||||
<a (click)="goBack()">{{ repositoryName }}</a>
|
||||
|
||||
<span *ngFor="let digest of referArtifactNameArray; let i = index">
|
||||
<span
|
||||
class="back-icon"
|
||||
*ngFor="let digest of referArtifactNameArray; let i = index">
|
||||
<<a (click)="jumpDigest(i)">{{ digest | slice: 0:15 }}</a></span
|
||||
>
|
||||
</div>
|
||||
|
@ -51,6 +51,7 @@
|
||||
}
|
||||
|
||||
.download-link {
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,22 @@ export enum RouteConfigId {
|
||||
P2P_POLICIES_PAGE = 'PolicyComponent',
|
||||
P2P_TASKS_PAGE = 'P2pTaskListComponent',
|
||||
}
|
||||
// should not reuse the routes that meet these RegExps
|
||||
const ShouldNotReuseRouteRegExps: RegExp[] = [
|
||||
/\/harbor\/projects\/(\d+)\/repositories$/,
|
||||
/\/harbor\/projects\/(\d+)\/repositories\/(\S+)\/artifacts-tab$/,
|
||||
/\/harbor\/projects\/(\d+)\/helm-charts\/(\S+)\/versions\/(\S+)/,
|
||||
];
|
||||
|
||||
function testRoute(url: string) {
|
||||
let flag: boolean = false;
|
||||
ShouldNotReuseRouteRegExps.forEach(item => {
|
||||
if (item.test(url)) {
|
||||
flag = true;
|
||||
}
|
||||
});
|
||||
return flag;
|
||||
}
|
||||
|
||||
export class HarborRouteReuseStrategy implements RouteReuseStrategy {
|
||||
/**
|
||||
@ -73,6 +89,12 @@ export class HarborRouteReuseStrategy implements RouteReuseStrategy {
|
||||
curr: ActivatedRouteSnapshot
|
||||
): boolean {
|
||||
this.shouldKeepCache(future, curr);
|
||||
if (
|
||||
testRoute(curr['_routerState']?.url) &&
|
||||
testRoute(future['_routerState']?.url)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return future.routeConfig === curr.routeConfig;
|
||||
}
|
||||
|
||||
|
@ -1752,7 +1752,9 @@
|
||||
"SKIP_DATABASE": "Skip Audit Log Database",
|
||||
"SKIP_DATABASE_TOOLTIP": "Skip to log audit log in the database, only available when audit log forward endpoint is configured",
|
||||
"STOP_GC_SUCCESS": "Trigger stopping GC operation successfully",
|
||||
"STOP_PURGE_SUCCESS": "Trigger stopping purging operation successfully"
|
||||
"STOP_PURGE_SUCCESS": "Trigger stopping purging operation successfully",
|
||||
"NO_GC_RECORDS": "We couldn't find any GC histories!",
|
||||
"NO_PURGE_RECORDS": "We couldn't find any purge histories!"
|
||||
},
|
||||
"CVE_EXPORT": {
|
||||
"EXPORT_SOME_PROJECTS": "Export CVEs - {{number}} project(s)",
|
||||
|
@ -1752,7 +1752,9 @@
|
||||
"SKIP_DATABASE": "Skip Audit Log Database",
|
||||
"SKIP_DATABASE_TOOLTIP": "Skip to log audit log in the database, only available when audit log forward endpoint is configured",
|
||||
"STOP_GC_SUCCESS": "Trigger stopping GC operation successfully",
|
||||
"STOP_PURGE_SUCCESS": "Trigger stopping purging operation successfully"
|
||||
"STOP_PURGE_SUCCESS": "Trigger stopping purging operation successfully",
|
||||
"NO_GC_RECORDS": "We couldn't find any GC histories!",
|
||||
"NO_PURGE_RECORDS": "We couldn't find any purge histories!"
|
||||
},
|
||||
"CVE_EXPORT": {
|
||||
"EXPORT_SOME_PROJECTS": "Export CVEs - {{number}} project(s)",
|
||||
|
@ -1751,7 +1751,9 @@
|
||||
"SKIP_DATABASE": "Skip Audit Log Database",
|
||||
"SKIP_DATABASE_TOOLTIP": "Skip to log audit log in the database, only available when audit log forward endpoint is configured",
|
||||
"STOP_GC_SUCCESS": "Trigger stopping GC operation successfully",
|
||||
"STOP_PURGE_SUCCESS": "Trigger stopping purging operation successfully"
|
||||
"STOP_PURGE_SUCCESS": "Trigger stopping purging operation successfully",
|
||||
"NO_GC_RECORDS": "We couldn't find any GC histories!",
|
||||
"NO_PURGE_RECORDS": "We couldn't find any purge histories!"
|
||||
},
|
||||
"CVE_EXPORT": {
|
||||
"EXPORT_SOME_PROJECTS": "Export CVEs - {{number}} project(s)",
|
||||
|
@ -1721,7 +1721,9 @@
|
||||
"SKIP_DATABASE": "Skip Audit Log Database",
|
||||
"SKIP_DATABASE_TOOLTIP": "Skip to log audit log in the database, only available when audit log forward endpoint is configured",
|
||||
"STOP_GC_SUCCESS": "Trigger stopping GC operation successfully",
|
||||
"STOP_PURGE_SUCCESS": "Trigger stopping purging operation successfully"
|
||||
"STOP_PURGE_SUCCESS": "Trigger stopping purging operation successfully",
|
||||
"NO_GC_RECORDS": "We couldn't find any GC histories!",
|
||||
"NO_PURGE_RECORDS": "We couldn't find any purge histories!"
|
||||
},
|
||||
"CVE_EXPORT": {
|
||||
"EXPORT_SOME_PROJECTS": "Export CVEs - {{number}} project(s)",
|
||||
|
@ -1748,7 +1748,9 @@
|
||||
"SKIP_DATABASE": "Skip Audit Log Database",
|
||||
"SKIP_DATABASE_TOOLTIP": "Skip to log audit log in the database, only available when audit log forward endpoint is configured",
|
||||
"STOP_GC_SUCCESS": "Trigger stopping GC operation successfully",
|
||||
"STOP_PURGE_SUCCESS": "Trigger stopping purging operation successfully"
|
||||
"STOP_PURGE_SUCCESS": "Trigger stopping purging operation successfully",
|
||||
"NO_GC_RECORDS": "We couldn't find any GC histories!",
|
||||
"NO_PURGE_RECORDS": "We couldn't find any purge histories!"
|
||||
},
|
||||
"CVE_EXPORT": {
|
||||
"EXPORT_SOME_PROJECTS": "Export CVEs - {{number}} project(s)",
|
||||
|
@ -1752,7 +1752,9 @@
|
||||
"SKIP_DATABASE": "Skip Audit Log Database",
|
||||
"SKIP_DATABASE_TOOLTIP": "Skip to log audit log in the database, only available when audit log forward endpoint is configured",
|
||||
"STOP_GC_SUCCESS": "Trigger stopping GC operation successfully",
|
||||
"STOP_PURGE_SUCCESS": "Trigger stopping purging operation successfully"
|
||||
"STOP_PURGE_SUCCESS": "Trigger stopping purging operation successfully",
|
||||
"NO_GC_RECORDS": "We couldn't find any GC histories!",
|
||||
"NO_PURGE_RECORDS": "We couldn't find any purge histories!"
|
||||
},
|
||||
"CVE_EXPORT": {
|
||||
"EXPORT_SOME_PROJECTS": "Export CVEs - {{number}} project(s)",
|
||||
|
@ -1750,7 +1750,9 @@
|
||||
"SKIP_DATABASE": "跳过日志数据库",
|
||||
"SKIP_DATABASE_TOOLTIP": "开启此项将不会在数据库中记录日志,需先配置日志转发端点",
|
||||
"STOP_GC_SUCCESS": "成功触发停止垃圾回收的操作",
|
||||
"STOP_PURGE_SUCCESS": "成功触发停止清理日志的操作"
|
||||
"STOP_PURGE_SUCCESS": "成功触发停止清理日志的操作",
|
||||
"NO_GC_RECORDS": "未发现任何清理记录!",
|
||||
"NO_PURGE_RECORDS": "未发现任何清理记录!"
|
||||
},
|
||||
"CVE_EXPORT": {
|
||||
"EXPORT_SOME_PROJECTS": "导出 CVEs - {{number}} 个项目",
|
||||
|
@ -1743,7 +1743,9 @@
|
||||
"SKIP_DATABASE": "Skip Audit Log Database",
|
||||
"SKIP_DATABASE_TOOLTIP": "Skip to log audit log in the database, only available when audit log forward endpoint is configured",
|
||||
"STOP_GC_SUCCESS": "Trigger stopping GC operation successfully",
|
||||
"STOP_PURGE_SUCCESS": "Trigger stopping purging operation successfully"
|
||||
"STOP_PURGE_SUCCESS": "Trigger stopping purging operation successfully",
|
||||
"NO_GC_RECORDS": "We couldn't find any GC histories!",
|
||||
"NO_PURGE_RECORDS": "We couldn't find any purge histories!"
|
||||
},
|
||||
"CVE_EXPORT": {
|
||||
"EXPORT_SOME_PROJECTS": "Export CVEs - {{number}} project(s)",
|
||||
|
@ -16,7 +16,7 @@
|
||||
Documentation This resource provides any keywords related to the Harbor private registry appliance
|
||||
|
||||
*** Variables ***
|
||||
${artifact_action_xpath} //clr-dg-action-bar/clr-dropdown/span[contains(@class,'dropdown-toggle')]
|
||||
${artifact_action_xpath} //*[@id='artifact-list-action']
|
||||
${artifact_action_delete_xpath} //clr-dropdown-menu//div[contains(.,'Delete')]
|
||||
${artifact_action_copy_xpath} //clr-dropdown-menu//div[contains(.,'Copy') and @aria-label='retag']
|
||||
${artifact_achieve_icon} //artifact-list-tab//clr-datagrid//clr-dg-row[contains(.,'sha256')]//clr-dg-cell[1]//clr-tooltip//a
|
||||
|
Loading…
Reference in New Issue
Block a user