mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-23 17:17:46 +01:00
Fix UI defered issues.
This commit is contained in:
parent
0c0db587ec
commit
35489e40ff
@ -14,7 +14,11 @@
|
|||||||
<clr-icon shape="caret down"></clr-icon>
|
<clr-icon shape="caret down"></clr-icon>
|
||||||
</button>
|
</button>
|
||||||
<div class="dropdown-menu">
|
<div class="dropdown-menu">
|
||||||
<a href="javascript:void(0)" clrDropdownItem *ngFor="let f of filterOptions" (click)="toggleFilterOption(f.key)"><clr-icon shape="check" [hidden]="!f.checked"></clr-icon> {{f.description | translate}}</a>
|
<a href="javascript:void(0)" clrDropdownItem *ngFor="let f of filterOptions" (click)="toggleFilterOption(f.key)">
|
||||||
|
<clr-icon shape="check" [hidden]="!f.checked"></clr-icon>
|
||||||
|
<ng-template [ngIf]="!f.checked"><span style="display: inline-block;width: 16px;"></span></ng-template>
|
||||||
|
{{f.description | translate}}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</clr-dropdown>
|
</clr-dropdown>
|
||||||
<div class="flex-xs-middle">
|
<div class="flex-xs-middle">
|
||||||
|
@ -4,10 +4,10 @@
|
|||||||
<div class="modal-body" style="height: 12.8em; overflow-y: hidden;">
|
<div class="modal-body" style="height: 12.8em; overflow-y: hidden;">
|
||||||
<form #projectForm="ngForm">
|
<form #projectForm="ngForm">
|
||||||
<section class="form-block">
|
<section class="form-block">
|
||||||
<div class="form-group">
|
<div class="form-group" style="padding-left: 135px;">
|
||||||
<label for="create_project_name" class="col-md-4 form-group-label-override">{{'PROJECT.NAME' | translate}}</label>
|
<label for="create_project_name" class="col-md-3 form-group-label-override">{{'PROJECT.NAME' | translate}}</label>
|
||||||
<label for="create_project_name" aria-haspopup="true" role="tooltip" [class.invalid]="projectName.invalid && (projectName.dirty || projectName.touched)" [class.valid]="projectName.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-left">
|
<label for="create_project_name" aria-haspopup="true" role="tooltip" [class.invalid]="projectName.invalid && (projectName.dirty || projectName.touched)" [class.valid]="projectName.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-left">
|
||||||
<input type="text" id="create_project_name" [(ngModel)]="project.name" name="name" size="20" required minlength="2" #projectName="ngModel" targetExists="PROJECT_NAME">
|
<input type="text" id="create_project_name" [(ngModel)]="project.name" name="name" size="38" required minlength="2" #projectName="ngModel" targetExists="PROJECT_NAME">
|
||||||
<span class="tooltip-content" *ngIf="projectName.errors && projectName.errors.required && (projectName.dirty || projectName.touched)">
|
<span class="tooltip-content" *ngIf="projectName.errors && projectName.errors.required && (projectName.dirty || projectName.touched)">
|
||||||
{{'PROJECT.NAME_IS_REQUIRED' | translate}}
|
{{'PROJECT.NAME_IS_REQUIRED' | translate}}
|
||||||
</span>
|
</span>
|
||||||
@ -19,16 +19,17 @@
|
|||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group" style="padding-left: 135px;">
|
||||||
<label class="col-md-4 form-group-label-override">{{'PROJECT.PUBLIC_OR_PRIVATE' | translate}}</label>
|
<label class="col-md-3 form-group-label-override">{{'PROJECT.PUBLIC_OR_PRIVATE' | translate}}</label>
|
||||||
<div class="checkbox-inline">
|
<div class="checkbox-inline">
|
||||||
<input type="checkbox" id="create_project_public" [(ngModel)]="project.public" name="public">
|
<input type="checkbox" id="create_project_public" [(ngModel)]="project.public" name="public">
|
||||||
<label for="create_project_public"></label>
|
<label for="create_project_public"></label>
|
||||||
|
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-lg tooltip-right" style="top:-8px; left:-8px;">
|
||||||
|
<clr-icon shape="info-circle" class="info-tips-icon" size="24"></clr-icon>
|
||||||
|
<span class="tooltip-content" style="margin-left: 5px;">{{'PROJECT.INLINE_HELP_PUBLIC' | translate }}</span>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="inline-help-public">
|
|
||||||
{{'PROJECT.INLINE_HELP_PUBLIC' | translate }}
|
|
||||||
</p>
|
|
||||||
</section>
|
</section>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,12 +1,3 @@
|
|||||||
.inline-help-public {
|
|
||||||
color: #CCCCCC;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1.5em;
|
|
||||||
letter-spacing: 0.01em;
|
|
||||||
margin-top: 0;
|
|
||||||
padding: 0 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group-label-override {
|
.form-group-label-override {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
@ -5,8 +5,4 @@
|
|||||||
.option-right {
|
.option-right {
|
||||||
padding-right: 16px;
|
padding-right: 16px;
|
||||||
margin-top: 18px;
|
margin-top: 18px;
|
||||||
}
|
|
||||||
|
|
||||||
.datagrid-foot {
|
|
||||||
margin-bottom: 0.5px;
|
|
||||||
}
|
}
|
@ -19,9 +19,9 @@
|
|||||||
<clr-dg-column>{{'MEMBER.ROLE' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'MEMBER.ROLE' | translate}}</clr-dg-column>
|
||||||
<clr-dg-row *clrDgItems="let m of members">
|
<clr-dg-row *clrDgItems="let m of members">
|
||||||
<clr-dg-action-overflow [hidden]="m.user_id === currentUser.user_id || !hasProjectAdminRole">
|
<clr-dg-action-overflow [hidden]="m.user_id === currentUser.user_id || !hasProjectAdminRole">
|
||||||
<button class="action-item" (click)="changeRole(m, 1)">{{'MEMBER.PROJECT_ADMIN' | translate}}</button>
|
<button class="action-item" [hidden]="m.role_id === 1" (click)="changeRole(m, 1)">{{'MEMBER.PROJECT_ADMIN' | translate}}</button>
|
||||||
<button class="action-item" (click)="changeRole(m, 2)">{{'MEMBER.DEVELOPER' | translate}}</button>
|
<button class="action-item" [hidden]="m.role_id === 2" (click)="changeRole(m, 2)">{{'MEMBER.DEVELOPER' | translate}}</button>
|
||||||
<button class="action-item" (click)="changeRole(m, 3)">{{'MEMBER.GUEST' | translate}}</button>
|
<button class="action-item" [hidden]="m.role_id === 3" (click)="changeRole(m, 3)">{{'MEMBER.GUEST' | translate}}</button>
|
||||||
<button class="action-item" (click)="deleteMember(m)">{{'MEMBER.DELETE' | translate}}</button>
|
<button class="action-item" (click)="deleteMember(m)">{{'MEMBER.DELETE' | translate}}</button>
|
||||||
</clr-dg-action-overflow>
|
</clr-dg-action-overflow>
|
||||||
<clr-dg-cell>{{m.username}}</clr-dg-cell>
|
<clr-dg-cell>{{m.username}}</clr-dg-cell>
|
||||||
|
@ -111,8 +111,8 @@ export class MemberComponent implements OnInit, OnDestroy {
|
|||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
//Get projectId from route params snapshot.
|
//Get projectId from route params snapshot.
|
||||||
this.projectId = +this.route.snapshot.parent.params['id'];
|
this.projectId = +this.route.snapshot.parent.params['id'];
|
||||||
this.currentUser = this.session.getCurrentUser();
|
|
||||||
//Get current user from registered resolver.
|
//Get current user from registered resolver.
|
||||||
|
this.currentUser = this.session.getCurrentUser();
|
||||||
let resolverData = this.route.snapshot.parent.data;
|
let resolverData = this.route.snapshot.parent.data;
|
||||||
if(resolverData) {
|
if(resolverData) {
|
||||||
this.hasProjectAdminRole = (<Project>resolverData['projectResolver']).has_project_admin_role;
|
this.hasProjectAdminRole = (<Project>resolverData['projectResolver']).has_project_admin_role;
|
||||||
|
@ -7,6 +7,10 @@
|
|||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.subnav .nav {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.role-label {
|
.role-label {
|
||||||
color: #CCCCCC;
|
color: #CCCCCC;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="option-right">
|
<div class="option-right">
|
||||||
<div class="select" style="float: left;">
|
<div class="select" style="float: left;">
|
||||||
<select (change)="doFilterProjects($event)">
|
<select (change)="doFilterProjects($event)">
|
||||||
<option value="0">{{projectTypes[0] | translate}}</option>
|
<option value="0">{{projectTypes[0] | translate}}</option>
|
||||||
<option value="1">{{projectTypes[1] | translate}}</option>
|
<option value="1">{{projectTypes[1] | translate}}</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -5,4 +5,8 @@
|
|||||||
|
|
||||||
.sub-nav-bg-color {
|
.sub-nav-bg-color {
|
||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subnav .nav {
|
||||||
|
padding-left: 0;
|
||||||
}
|
}
|
@ -1,58 +1,50 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<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">
|
||||||
<div class="row flex-items-xs-between">
|
<div class="row flex-items-xs-between">
|
||||||
<div class="flex-xs-middle option-left">
|
<div class="flex-xs-middle option-left">
|
||||||
<button class="btn btn-link" (click)="openModal()"><clr-icon shape="add"></clr-icon> {{'REPLICATION.REPLICATION_RULE' | translate}}</button>
|
<button class="btn btn-link" (click)="openModal()"><clr-icon shape="add"></clr-icon> {{'REPLICATION.REPLICATION_RULE' | translate}}</button>
|
||||||
<create-edit-policy [projectId]="projectId" (reload)="reloadPolicies($event)"></create-edit-policy>
|
<create-edit-policy [projectId]="projectId" (reload)="reloadPolicies($event)"></create-edit-policy>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-xs-middle option-right">
|
<div class="flex-xs-middle option-right">
|
||||||
<clr-dropdown [clrMenuPosition]="'bottom-left'">
|
<div class="select" style="float: left;">
|
||||||
<button class="btn btn-link" clrDropdownToggle>
|
<select (change)="doFilterPolicyStatus($event)">
|
||||||
{{currentRuleStatus.description | translate}}
|
<option *ngFor="let r of ruleStatus" value="{{r.key}}">{{r.description | translate}}</option>
|
||||||
<clr-icon shape="caret down"></clr-icon>
|
</select>
|
||||||
</button>
|
</div>
|
||||||
<div class="dropdown-menu">
|
<grid-filter filterPlaceholder='{{"REPLICATION.FILTER_POLICIES_PLACEHOLDER" | translate}}' (filter)="doSearchPolicies($event)" [currentValue]="search.policyName"></grid-filter>
|
||||||
<a href="javascript:void(0)" clrDropdownItem *ngFor="let r of ruleStatus" (click)="doFilterPolicyStatus(r.key)"> {{r.description | translate}}</a>
|
<a href="javascript:void(0)" (click)="refreshPolicies()">
|
||||||
</div>
|
<clr-icon shape="refresh"></clr-icon>
|
||||||
</clr-dropdown>
|
</a>
|
||||||
<grid-filter filterPlaceholder='{{"REPLICATION.FILTER_POLICIES_PLACEHOLDER" | translate}}' (filter)="doSearchPolicies($event)" [currentValue]="search.policyName"></grid-filter>
|
</div>
|
||||||
<a href="javascript:void(0)" (click)="refreshPolicies()">
|
</div>
|
||||||
<clr-icon shape="refresh"></clr-icon>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</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">
|
||||||
<list-policy [policies]="changedPolicies" [projectless]="false" [selectedId]="initSelectedId" (selectOne)="selectOnePolicy($event)" (editOne)="openEditPolicy($event)" (reload)="reloadPolicies($event)"></list-policy>
|
<list-policy [policies]="changedPolicies" [projectless]="false" [selectedId]="initSelectedId" (selectOne)="selectOnePolicy($event)" (editOne)="openEditPolicy($event)" (reload)="reloadPolicies($event)"></list-policy>
|
||||||
</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">
|
||||||
<div class="row flex-items-xs-between">
|
<div class="row flex-items-xs-between">
|
||||||
<h5 class="flex-items-xs-bottom option-left-down" style="margin-left: 14px;">{{'REPLICATION.REPLICATION_JOBS' | translate}}</h5>
|
<h5 class="flex-items-xs-bottom option-left-down" style="margin-left: 14px;">{{'REPLICATION.REPLICATION_JOBS' | translate}}</h5>
|
||||||
<div class="flex-items-xs-bottom option-right-down">
|
<div class="flex-items-xs-bottom option-right-down">
|
||||||
<button class="btn btn-link" (click)="toggleSearchJobOptionalName(currentJobSearchOption)">{{toggleJobSearchOption[currentJobSearchOption] | translate}}</button>
|
<button class="btn btn-link" (click)="toggleSearchJobOptionalName(currentJobSearchOption)">{{toggleJobSearchOption[currentJobSearchOption] | translate}}</button>
|
||||||
<grid-filter filterPlaceholder='{{"REPLICATION.FILTER_JOBS_PLACEHOLDER" | translate}}' (filter)="doSearchJobs($event)" [currentValue]="search.repoName" ></grid-filter>
|
<grid-filter filterPlaceholder='{{"REPLICATION.FILTER_JOBS_PLACEHOLDER" | translate}}' (filter)="doSearchJobs($event)" [currentValue]="search.repoName" ></grid-filter>
|
||||||
<a href="javascript:void(0)" (click)="refreshJobs()">
|
<a href="javascript:void(0)" (click)="refreshJobs()">
|
||||||
<clr-icon shape="refresh"></clr-icon>
|
<clr-icon shape="refresh"></clr-icon>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row flex-items-xs-right option-right" [hidden]="currentJobSearchOption === 0">
|
</div>
|
||||||
<clr-dropdown [clrMenuPosition]="'bottom-left'">
|
<div class="row flex-items-xs-right option-right" [hidden]="currentJobSearchOption === 0">
|
||||||
<button class="btn btn-link" clrDropdownToggle>
|
<div class="select" style="float: left;">
|
||||||
{{currentJobStatus.description | translate}}
|
<select (change)="doFilterJobStatus($event)">
|
||||||
<clr-icon shape="caret down"></clr-icon>
|
<option *ngFor="let j of jobStatus" value="{{j.key}}" [selected]="currentJobStatus.key === j.key">{{j.description | translate}}</option>
|
||||||
</button>
|
</select>
|
||||||
<div class="dropdown-menu">
|
|
||||||
<a href="javascript:void(0)" clrDropdownItem *ngFor="let j of jobStatus" (click)="doFilterJobStatus(j.key)"> {{j.description | translate}}</a>
|
|
||||||
</div>
|
|
||||||
</clr-dropdown>
|
|
||||||
<div class="flex-items-xs-middle">
|
|
||||||
<clr-icon shape="date"></clr-icon><input type="date" #fromTime (change)="doJobSearchByStartTime(fromTime.value)">
|
|
||||||
<clr-icon shape="date"></clr-icon><input type="date" #toTime (change)="doJobSearchByEndTime(toTime.value)">
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex-items-xs-middle">
|
||||||
|
<clr-icon shape="date"></clr-icon><input type="date" #fromTime (change)="doJobSearchByStartTime(fromTime.value)">
|
||||||
|
<clr-icon shape="date"></clr-icon><input type="date" #toTime (change)="doJobSearchByEndTime(toTime.value)">
|
||||||
|
</div>
|
||||||
|
</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">
|
||||||
<list-job [jobs]="changedJobs" [totalPage]="jobsTotalPage" [totalRecordCount]="jobsTotalRecordCount" (paginate)="fetchPolicyJobs($event)"></list-job>
|
<list-job [jobs]="changedJobs" [totalPage]="jobsTotalPage" [totalRecordCount]="jobsTotalRecordCount" (paginate)="fetchPolicyJobs($event)"></list-job>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -20,6 +20,7 @@ import { MessageHandlerService } from '../shared/message-handler/message-handler
|
|||||||
|
|
||||||
import { ReplicationService } from './replication.service';
|
import { ReplicationService } from './replication.service';
|
||||||
|
|
||||||
|
import { SessionUser } from '../shared/session-user';
|
||||||
import { Policy } from './policy';
|
import { Policy } from './policy';
|
||||||
import { Job } from './job';
|
import { Job } from './job';
|
||||||
import { Target } from './target';
|
import { Target } from './target';
|
||||||
@ -27,13 +28,13 @@ import { Target } from './target';
|
|||||||
import { State } from 'clarity-angular';
|
import { State } from 'clarity-angular';
|
||||||
|
|
||||||
const ruleStatus = [
|
const ruleStatus = [
|
||||||
{ 'key': '', 'description': 'REPLICATION.ALL_STATUS'},
|
{ 'key': 'all', 'description': 'REPLICATION.ALL_STATUS'},
|
||||||
{ 'key': '1', 'description': 'REPLICATION.ENABLED'},
|
{ 'key': '1', 'description': 'REPLICATION.ENABLED'},
|
||||||
{ 'key': '0', 'description': 'REPLICATION.DISABLED'}
|
{ 'key': '0', 'description': 'REPLICATION.DISABLED'}
|
||||||
];
|
];
|
||||||
|
|
||||||
const jobStatus = [
|
const jobStatus = [
|
||||||
{ 'key': '', 'description': 'REPLICATION.ALL' },
|
{ 'key': 'all', 'description': 'REPLICATION.ALL' },
|
||||||
{ 'key': 'pending', 'description': 'REPLICATION.PENDING' },
|
{ 'key': 'pending', 'description': 'REPLICATION.PENDING' },
|
||||||
{ 'key': 'running', 'description': 'REPLICATION.RUNNING' },
|
{ 'key': 'running', 'description': 'REPLICATION.RUNNING' },
|
||||||
{ 'key': 'error', 'description': 'REPLICATION.ERROR' },
|
{ 'key': 'error', 'description': 'REPLICATION.ERROR' },
|
||||||
@ -62,7 +63,7 @@ class SearchOption {
|
|||||||
styleUrls: ['./replication.component.css']
|
styleUrls: ['./replication.component.css']
|
||||||
})
|
})
|
||||||
export class ReplicationComponent implements OnInit {
|
export class ReplicationComponent implements OnInit {
|
||||||
|
|
||||||
projectId: number;
|
projectId: number;
|
||||||
|
|
||||||
search: SearchOption;
|
search: SearchOption;
|
||||||
@ -92,7 +93,7 @@ export class ReplicationComponent implements OnInit {
|
|||||||
constructor(
|
constructor(
|
||||||
private messageHandlerService: MessageHandlerService,
|
private messageHandlerService: MessageHandlerService,
|
||||||
private replicationService: ReplicationService,
|
private replicationService: ReplicationService,
|
||||||
private route: ActivatedRoute) {
|
private route: ActivatedRoute) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@ -171,9 +172,9 @@ export class ReplicationComponent implements OnInit {
|
|||||||
if(policy) {
|
if(policy) {
|
||||||
this.search.policyId = policy.id;
|
this.search.policyId = policy.id;
|
||||||
this.search.repoName = '';
|
this.search.repoName = '';
|
||||||
this.search.status = ''
|
this.search.status = '';
|
||||||
this.currentJobSearchOption = 0;
|
this.currentJobSearchOption = 0;
|
||||||
this.currentJobStatus = { 'key': '', 'description': 'REPLICATION.ALL'};
|
this.currentJobStatus = { 'key': 'all', 'description': 'REPLICATION.ALL' };
|
||||||
this.fetchPolicyJobs();
|
this.fetchPolicyJobs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -183,19 +184,28 @@ export class ReplicationComponent implements OnInit {
|
|||||||
this.retrievePolicies();
|
this.retrievePolicies();
|
||||||
}
|
}
|
||||||
|
|
||||||
doFilterPolicyStatus(status: string) {
|
doFilterPolicyStatus($event: any) {
|
||||||
this.currentRuleStatus = this.ruleStatus.find(r=>r.key === status);
|
if ($event && $event.target && $event.target["value"]) {
|
||||||
if(status.trim() === '') {
|
let status = $event.target["value"];
|
||||||
this.changedPolicies = this.policies;
|
this.currentRuleStatus = this.ruleStatus.find(r=>r.key === status);
|
||||||
} else {
|
if(this.currentRuleStatus.key === 'all') {
|
||||||
this.changedPolicies = this.policies.filter(policy=>policy.enabled === +this.currentRuleStatus.key);
|
this.changedPolicies = this.policies;
|
||||||
|
} else {
|
||||||
|
this.changedPolicies = this.policies.filter(policy=>policy.enabled === +this.currentRuleStatus.key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
doFilterJobStatus(status: string) {
|
doFilterJobStatus($event: any) {
|
||||||
this.currentJobStatus = this.jobStatus.find(r=>r.key === status);
|
if ($event && $event.target && $event.target["value"]) {
|
||||||
this.search.status = status;
|
let status = $event.target["value"];
|
||||||
this.doSearchJobs(this.search.repoName);
|
this.currentJobStatus = this.jobStatus.find(r=>r.key === status);
|
||||||
|
if(this.currentJobStatus.key === 'all') {
|
||||||
|
status = '';
|
||||||
|
}
|
||||||
|
this.search.status = status;
|
||||||
|
this.doSearchJobs(this.search.repoName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
doSearchJobs(repoName: string) {
|
doSearchJobs(repoName: string) {
|
||||||
|
Loading…
Reference in New Issue
Block a user