Fix router issues for UI (#17235)

Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
Shijun Sun 2022-07-29 19:04:01 +08:00 committed by GitHub
parent bff4e13087
commit 04fa3853c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 468 additions and 416 deletions

View File

@ -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>{{

View File

@ -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;
}
}

View File

@ -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>{{

View File

@ -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;
}
}

View File

@ -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
>&nbsp;{{ 'RESET_PWD.TITLE' | translate }}

View File

@ -32,7 +32,11 @@ describe('ArtifactListPageComponent', () => {
}),
params: {
subscribe: () => {
return of(null);
return {
unsubscribe() {
return null;
},
};
},
},
};

View File

@ -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']);
}

View File

@ -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()"
>&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>
</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>&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
<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)="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()"
>&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>
</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'"

View File

@ -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;
}

View File

@ -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,

View File

@ -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 {

View File

@ -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">
&lt;<a (click)="jumpDigest(i)">{{ digest | slice: 0:15 }}</a></span
>
</div>

View File

@ -51,6 +51,7 @@
}
.download-link {
margin-bottom: 8px;
font-size: 14px;
}

View File

@ -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;
}

View File

@ -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)",

View File

@ -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)",

View File

@ -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)",

View File

@ -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)",

View File

@ -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)",

View File

@ -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)",

View File

@ -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}} 个项目",

View File

@ -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)",

View File

@ -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