mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-25 10:07:43 +01:00
Fix bugs for round 1 testing
Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
parent
ceded08507
commit
0275108cb2
@ -101,7 +101,7 @@
|
|||||||
<clr-icon shape="caret down"></clr-icon>
|
<clr-icon shape="caret down"></clr-icon>
|
||||||
</span>
|
</span>
|
||||||
<clr-dropdown-menu class="action-dropdown" clrPosition="bottom-left" *clrIfOpen>
|
<clr-dropdown-menu class="action-dropdown" clrPosition="bottom-left" *clrIfOpen>
|
||||||
<div class="action-dropdown-item" aria-label="copy digest" clrDropdownItem
|
<div class="action-dropdown-item no-border" aria-label="copy digest" clrDropdownItem
|
||||||
[clrDisabled]="!(selectedRow.length==1&& !depth)" (click)="showDigestId()">
|
[clrDisabled]="!(selectedRow.length==1&& !depth)" (click)="showDigestId()">
|
||||||
{{'REPOSITORY.COPY_DIGEST_ID' | translate}}</div>
|
{{'REPOSITORY.COPY_DIGEST_ID' | translate}}</div>
|
||||||
<clr-dropdown *ngIf="!withAdmiral">
|
<clr-dropdown *ngIf="!withAdmiral">
|
||||||
|
@ -415,4 +415,7 @@ clr-datagrid {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.no-border:focus {
|
||||||
|
outline: none;
|
||||||
}
|
}
|
@ -100,6 +100,10 @@ export class ArtifactVulnerabilitiesComponent implements OnInit, OnDestroy {
|
|||||||
this.scan_overview = res;
|
this.scan_overview = res;
|
||||||
if (this.scan_overview && this.scan_overview[DEFAULT_SUPPORTED_MIME_TYPE]) {
|
if (this.scan_overview && this.scan_overview[DEFAULT_SUPPORTED_MIME_TYPE]) {
|
||||||
this.scanningResults = this.scan_overview[DEFAULT_SUPPORTED_MIME_TYPE].vulnerabilities;
|
this.scanningResults = this.scan_overview[DEFAULT_SUPPORTED_MIME_TYPE].vulnerabilities;
|
||||||
|
// sort
|
||||||
|
if (this.scanningResults) {
|
||||||
|
this.scanningResults.sort(((a, b) => this.getLevel(b) - this.getLevel(a)));
|
||||||
|
}
|
||||||
this.scanner = this.scan_overview[DEFAULT_SUPPORTED_MIME_TYPE].scanner;
|
this.scanner = this.scan_overview[DEFAULT_SUPPORTED_MIME_TYPE].scanner;
|
||||||
}
|
}
|
||||||
}, error => {
|
}, error => {
|
||||||
|
@ -96,6 +96,10 @@ export class ResultGridComponent implements OnInit, OnDestroy {
|
|||||||
if (report.vulnerabilities) {
|
if (report.vulnerabilities) {
|
||||||
this.dataCache = report.vulnerabilities;
|
this.dataCache = report.vulnerabilities;
|
||||||
this.scanningResults = this.dataCache.filter((item: VulnerabilityItem) => item.id !== '');
|
this.scanningResults = this.dataCache.filter((item: VulnerabilityItem) => item.id !== '');
|
||||||
|
// sort
|
||||||
|
if (this.scanningResults) {
|
||||||
|
this.scanningResults.sort(((a, b) => this.getLevel(b) - this.getLevel(a)));
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,8 +65,9 @@ export class ResultTipHistogramComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get total(): number {
|
get total(): number {
|
||||||
if (this.vulnerabilitySummary &&
|
if (this.vulnerabilitySummary
|
||||||
this.vulnerabilitySummary.summary) {
|
&& this.vulnerabilitySummary.summary
|
||||||
|
&& this.vulnerabilitySummary.summary.total) {
|
||||||
return this.vulnerabilitySummary.summary.total;
|
return this.vulnerabilitySummary.summary.total;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -48,7 +48,7 @@
|
|||||||
</clr-dg-action-bar>
|
</clr-dg-action-bar>
|
||||||
<clr-dg-column [clrDgField]="'name'">{{'WEBHOOK.NAME' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgField]="'name'">{{'WEBHOOK.NAME' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'WEBHOOK.NOTIFY_TYPE' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'WEBHOOK.NOTIFY_TYPE' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'WEBHOOK.TARGET' | translate}}</clr-dg-column>
|
<clr-dg-column class="width-340">{{'WEBHOOK.TARGET' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'WEBHOOK.ENABLED' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'WEBHOOK.ENABLED' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'WEBHOOK.EVENT_TYPES' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'WEBHOOK.EVENT_TYPES' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'WEBHOOK.CREATED' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'WEBHOOK.CREATED' | translate}}</clr-dg-column>
|
||||||
|
@ -54,4 +54,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
}
|
||||||
|
.width-340 {
|
||||||
|
min-width: 340px!important;
|
||||||
}
|
}
|
@ -82,6 +82,9 @@ export class WebhookComponent implements OnInit {
|
|||||||
.subscribe(
|
.subscribe(
|
||||||
response => {
|
response => {
|
||||||
this.metadata = response;
|
this.metadata = response;
|
||||||
|
if (this.metadata && this.metadata.event_type) {
|
||||||
|
this.metadata.event_type.sort();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
this.messageHandlerService.handleError(error);
|
this.messageHandlerService.handleError(error);
|
||||||
|
@ -1405,7 +1405,8 @@
|
|||||||
"VIEW_DOC": "view documentation",
|
"VIEW_DOC": "view documentation",
|
||||||
"ALL_SCANNERS": "All scanners",
|
"ALL_SCANNERS": "All scanners",
|
||||||
"HELP_INFO_1": "The default scanner has been installed. To install other scanners refer to the ",
|
"HELP_INFO_1": "The default scanner has been installed. To install other scanners refer to the ",
|
||||||
"HELP_INFO_2": "documentation."
|
"HELP_INFO_2": "documentation.",
|
||||||
|
"NO_DEFAULT_SCANNER": "No default scanner"
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1402,6 +1402,7 @@
|
|||||||
"VIEW_DOC": "view documentation",
|
"VIEW_DOC": "view documentation",
|
||||||
"ALL_SCANNERS": "All scanners",
|
"ALL_SCANNERS": "All scanners",
|
||||||
"HELP_INFO_1": "The default scanner has been installed. To install other scanners refer to the ",
|
"HELP_INFO_1": "The default scanner has been installed. To install other scanners refer to the ",
|
||||||
"HELP_INFO_2": "documentation."
|
"HELP_INFO_2": "documentation.",
|
||||||
|
"NO_DEFAULT_SCANNER": "No default scanner"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1372,6 +1372,7 @@
|
|||||||
"VIEW_DOC": "view documentation",
|
"VIEW_DOC": "view documentation",
|
||||||
"ALL_SCANNERS": "All scanners",
|
"ALL_SCANNERS": "All scanners",
|
||||||
"HELP_INFO_1": "The default scanner has been installed. To install other scanners refer to the ",
|
"HELP_INFO_1": "The default scanner has been installed. To install other scanners refer to the ",
|
||||||
"HELP_INFO_2": "documentation."
|
"HELP_INFO_2": "documentation.",
|
||||||
|
"NO_DEFAULT_SCANNER": "No default scanner"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1400,7 +1400,8 @@
|
|||||||
"VIEW_DOC": "view documentation",
|
"VIEW_DOC": "view documentation",
|
||||||
"ALL_SCANNERS": "All scanners",
|
"ALL_SCANNERS": "All scanners",
|
||||||
"HELP_INFO_1": "The default scanner has been installed. To install other scanners refer to the ",
|
"HELP_INFO_1": "The default scanner has been installed. To install other scanners refer to the ",
|
||||||
"HELP_INFO_2": "documentation."
|
"HELP_INFO_2": "documentation.",
|
||||||
|
"NO_DEFAULT_SCANNER": "No default scanner"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1405,6 +1405,7 @@
|
|||||||
"VIEW_DOC": "view documentation",
|
"VIEW_DOC": "view documentation",
|
||||||
"ALL_SCANNERS": "All scanners",
|
"ALL_SCANNERS": "All scanners",
|
||||||
"HELP_INFO_1": "The default scanner has been installed. To install other scanners refer to the ",
|
"HELP_INFO_1": "The default scanner has been installed. To install other scanners refer to the ",
|
||||||
"HELP_INFO_2": "documentation."
|
"HELP_INFO_2": "documentation.",
|
||||||
|
"NO_DEFAULT_SCANNER": "No default scanner"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1400,6 +1400,7 @@
|
|||||||
"VIEW_DOC": "查看文档",
|
"VIEW_DOC": "查看文档",
|
||||||
"ALL_SCANNERS": "全部扫描器",
|
"ALL_SCANNERS": "全部扫描器",
|
||||||
"HELP_INFO_1": "默认扫描器已安装。获取扫描器安装帮助,请查看",
|
"HELP_INFO_1": "默认扫描器已安装。获取扫描器安装帮助,请查看",
|
||||||
"HELP_INFO_2": "文档。"
|
"HELP_INFO_2": "文档。",
|
||||||
|
"NO_DEFAULT_SCANNER": "未配置默认扫描器"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,23 +16,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="refresh-div mr-1">
|
|
||||||
<span class="refresh-btn" (click)="refresh()">
|
|
||||||
<clr-icon shape="refresh"></clr-icon>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||||
<clr-datagrid [clrDgLoading]="loading" (clrDgRefresh)="getQuotaList($event)">
|
<clr-datagrid [(clrDgSelected)]="selectedRow" [clrDgLoading]="loading" (clrDgRefresh)="getQuotaList($event)">
|
||||||
|
<clr-dg-action-bar>
|
||||||
|
<div class="clr-row">
|
||||||
|
<div class="clr-col">
|
||||||
|
<button type="button" class="btn btn-secondary" [disabled]="!(selectedRow && selectedRow.length === 1)" (click)="editQuota()">
|
||||||
|
{{'QUOTA.EDIT' | translate}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="clr-col-1">
|
||||||
|
<div class="action-head-pos">
|
||||||
|
<span class="refresh-btn" (click)="refresh()">
|
||||||
|
<clr-icon shape="refresh"></clr-icon>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</clr-dg-action-bar>
|
||||||
<clr-dg-column>{{'QUOTA.PROJECT' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'QUOTA.PROJECT' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'QUOTA.OWNER' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'QUOTA.OWNER' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column [clrDgSortBy]="countComparator">{{'QUOTA.COUNT' | translate }}</clr-dg-column>
|
<clr-dg-column [clrDgSortBy]="countComparator">{{'QUOTA.COUNT' | translate }}</clr-dg-column>
|
||||||
<clr-dg-column [clrDgSortBy]="storageComparator">{{'QUOTA.STORAGE' | translate }}</clr-dg-column>
|
<clr-dg-column [clrDgSortBy]="storageComparator">{{'QUOTA.STORAGE' | translate }}</clr-dg-column>
|
||||||
<clr-dg-placeholder>{{'QUOTA.PLACEHOLDER' | translate }}</clr-dg-placeholder>
|
<clr-dg-placeholder>{{'QUOTA.PLACEHOLDER' | translate }}</clr-dg-placeholder>
|
||||||
<clr-dg-row *ngFor="let quota of quotaList" [clrDgItem]='quota'>
|
<clr-dg-row *ngFor="let quota of quotaList" [clrDgItem]='quota'>
|
||||||
<clr-dg-action-overflow>
|
|
||||||
<button class="action-item" (click)="editQuota(quota)">{{'QUOTA.EDIT' | translate}}</button>
|
|
||||||
</clr-dg-action-overflow>
|
|
||||||
<clr-dg-cell>
|
<clr-dg-cell>
|
||||||
<a href="javascript:void(0)" (click)="goToLink(quota?.ref?.id)">{{quota?.ref?.name}}</a></clr-dg-cell>
|
<a href="javascript:void(0)" (click)="goToLink(quota?.ref?.id)">{{quota?.ref?.name}}</a></clr-dg-cell>
|
||||||
<clr-dg-cell>{{quota?.ref?.owner_name}}</clr-dg-cell>
|
<clr-dg-cell>{{quota?.ref?.owner_name}}</clr-dg-cell>
|
||||||
|
@ -51,11 +51,6 @@
|
|||||||
margin-bottom: .35rem;
|
margin-bottom: .35rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.refresh-div {
|
|
||||||
margin-top: auto;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
::ng-deep {
|
::ng-deep {
|
||||||
.progress {
|
.progress {
|
||||||
&.warning>progress {
|
&.warning>progress {
|
||||||
@ -74,3 +69,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.action-head-pos {
|
||||||
|
padding-right: 18px;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
}
|
@ -124,7 +124,8 @@ describe('ProjectQuotasComponent', () => {
|
|||||||
await timeout(10);
|
await timeout(10);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
await fixture.whenStable();
|
||||||
component.editQuota(component.quotaList[0]);
|
component.selectedRow = [component.quotaList[0]];
|
||||||
|
component.editQuota();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
await fixture.whenStable();
|
||||||
const countInput: HTMLInputElement = fixture.nativeElement.querySelector('#count');
|
const countInput: HTMLInputElement = fixture.nativeElement.querySelector('#count');
|
||||||
|
@ -58,6 +58,7 @@ export class ProjectQuotasComponent implements OnChanges {
|
|||||||
}
|
}
|
||||||
countComparator: Comparator<Quota> = new CustomComparator<Quota>(quotaSort.count, quotaSort.sortType);
|
countComparator: Comparator<Quota> = new CustomComparator<Quota>(quotaSort.count, quotaSort.sortType);
|
||||||
storageComparator: Comparator<Quota> = new CustomComparator<Quota>(quotaSort.storage, quotaSort.sortType);
|
storageComparator: Comparator<Quota> = new CustomComparator<Quota>(quotaSort.storage, quotaSort.sortType);
|
||||||
|
selectedRow: Quota[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private configService: ConfigurationService,
|
private configService: ConfigurationService,
|
||||||
@ -66,21 +67,23 @@ export class ProjectQuotasComponent implements OnChanges {
|
|||||||
private router: Router,
|
private router: Router,
|
||||||
private errorHandler: ErrorHandler) { }
|
private errorHandler: ErrorHandler) { }
|
||||||
|
|
||||||
editQuota(quotaHardLimitValue: Quota) {
|
editQuota() {
|
||||||
const defaultTexts = [this.translate.get('QUOTA.EDIT_PROJECT_QUOTAS')
|
if (this.selectedRow && this.selectedRow.length === 1) {
|
||||||
, this.translate.get('QUOTA.SET_QUOTAS', { params: quotaHardLimitValue.ref.name })
|
const defaultTexts = [this.translate.get('QUOTA.EDIT_PROJECT_QUOTAS')
|
||||||
, this.translate.get('QUOTA.COUNT_QUOTA'), this.translate.get('QUOTA.STORAGE_QUOTA')];
|
, this.translate.get('QUOTA.SET_QUOTAS', { params: this.selectedRow[0].ref.name })
|
||||||
forkJoin(...defaultTexts).subscribe(res => {
|
, this.translate.get('QUOTA.COUNT_QUOTA'), this.translate.get('QUOTA.STORAGE_QUOTA')];
|
||||||
const defaultTextsObj = {
|
forkJoin(...defaultTexts).subscribe(res => {
|
||||||
editQuota: res[0],
|
const defaultTextsObj = {
|
||||||
setQuota: res[1],
|
editQuota: res[0],
|
||||||
countQuota: res[2],
|
setQuota: res[1],
|
||||||
storageQuota: res[3],
|
countQuota: res[2],
|
||||||
quotaHardLimitValue: quotaHardLimitValue,
|
storageQuota: res[3],
|
||||||
isSystemDefaultQuota: false
|
quotaHardLimitValue: this.selectedRow[0],
|
||||||
};
|
isSystemDefaultQuota: false
|
||||||
this.editQuotaDialog.openEditQuotaModal(defaultTextsObj);
|
};
|
||||||
});
|
this.editQuotaDialog.openEditQuotaModal(defaultTextsObj);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
editDefaultQuota(quotaHardLimitValue: QuotaHardLimitInterface) {
|
editDefaultQuota(quotaHardLimitValue: QuotaHardLimitInterface) {
|
||||||
@ -237,5 +240,6 @@ export class ProjectQuotasComponent implements OnChanges {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
this.getQuotaList(state);
|
this.getQuotaList(state);
|
||||||
|
this.selectedRow = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import { ScanningMetrics } from "../config";
|
|||||||
import { SharedModule } from "../../../utils/shared/shared.module";
|
import { SharedModule } from "../../../utils/shared/shared.module";
|
||||||
import { ErrorHandler } from "../../../utils/error-handler";
|
import { ErrorHandler } from "../../../utils/error-handler";
|
||||||
import { CURRENT_BASE_HREF } from "../../../utils/utils";
|
import { CURRENT_BASE_HREF } from "../../../utils/utils";
|
||||||
|
import { Scanner } from "../../../../app/config/scanner/scanner";
|
||||||
|
|
||||||
let component: VulnerabilityConfigComponent;
|
let component: VulnerabilityConfigComponent;
|
||||||
let fixture: ComponentFixture<VulnerabilityConfigComponent>;
|
let fixture: ComponentFixture<VulnerabilityConfigComponent>;
|
||||||
@ -34,6 +35,19 @@ let mockedManualMetrics: ScanningMetrics = {
|
|||||||
},
|
},
|
||||||
ongoing: true
|
ongoing: true
|
||||||
};
|
};
|
||||||
|
const mockedScanner: Scanner = {
|
||||||
|
"uuid": "ca3c27f3-72f3-11ea-9e46-0242ac170004",
|
||||||
|
"name": "clair",
|
||||||
|
"description": "",
|
||||||
|
"url": "http://10.92.161.247:8080",
|
||||||
|
"disabled": false,
|
||||||
|
"is_default": true,
|
||||||
|
"auth": "",
|
||||||
|
"skip_certVerify": false,
|
||||||
|
"use_internal_addr": true,
|
||||||
|
"create_time": "2020-03-31T02:03:18.379132Z",
|
||||||
|
"update_time": "2020-03-31T02:03:18.379135Z"
|
||||||
|
};
|
||||||
let fakedScanAllRepoService = {
|
let fakedScanAllRepoService = {
|
||||||
getSchedule() {
|
getSchedule() {
|
||||||
return of(mockedSchedule);
|
return of(mockedSchedule);
|
||||||
@ -48,7 +62,10 @@ let fakedScanAllRepoService = {
|
|||||||
return of(true);
|
return of(true);
|
||||||
},
|
},
|
||||||
getScanners() {
|
getScanners() {
|
||||||
return of([]);
|
return of([mockedScanner]);
|
||||||
|
},
|
||||||
|
getScannerMetadata() {
|
||||||
|
return of(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let fakedErrorHandler = {
|
let fakedErrorHandler = {
|
||||||
|
@ -117,6 +117,8 @@ export class VulnerabilityConfigComponent implements OnInit, OnDestroy {
|
|||||||
scanners.forEach(scanner => {
|
scanners.forEach(scanner => {
|
||||||
if (scanner.is_default) {
|
if (scanner.is_default) {
|
||||||
flag = true;
|
flag = true;
|
||||||
|
this.initMetrics();
|
||||||
|
this.getSchedule();
|
||||||
this.getScannerMetadata(scanner.uuid);
|
this.getScannerMetadata(scanner.uuid);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -126,6 +128,11 @@ export class VulnerabilityConfigComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
if (!flag) {
|
if (!flag) {
|
||||||
this.onGettingUpdatedTimeStr = false;
|
this.onGettingUpdatedTimeStr = false;
|
||||||
|
this.translate.get("SCANNER.NO_DEFAULT_SCANNER")
|
||||||
|
.subscribe(res => {
|
||||||
|
this.errorHandler.warning(res);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, error => {
|
}, error => {
|
||||||
this.onGettingUpdatedTimeStr = false;
|
this.onGettingUpdatedTimeStr = false;
|
||||||
@ -158,8 +165,6 @@ export class VulnerabilityConfigComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.getScanText();
|
this.getScanText();
|
||||||
this.getSchedule();
|
|
||||||
this.initMetrics();
|
|
||||||
this.getScanners();
|
this.getScanners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user