Rule chain page
This commit is contained in:
parent
3d6b058b9d
commit
aa1a43fcea
103
ui-ngx/package-lock.json
generated
103
ui-ngx/package-lock.json
generated
@ -5140,9 +5140,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"handlebars": {
|
"handlebars": {
|
||||||
"version": "4.5.1",
|
"version": "4.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.2.tgz",
|
||||||
"integrity": "sha512-C29UoFzHe9yM61lOsIlCE5/mQVGrnIOrOq7maQl76L7tYPCgC1og0Ajt6uWnX4ZTxBPnjw+CUvawphwCfJgUnA==",
|
"integrity": "sha512-29Zxv/cynYB7mkT1rVWQnV7mGX6v7H/miQ6dbEpYTKq5eJBN7PsRB+ViYJlcT6JINTSu4dVB9kOqEun78h6Exg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"neo-async": "^2.6.0",
|
"neo-async": "^2.6.0",
|
||||||
@ -7411,6 +7411,97 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ngx-color-picker/-/ngx-color-picker-8.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/ngx-color-picker/-/ngx-color-picker-8.2.0.tgz",
|
||||||
"integrity": "sha512-rzR+cByjNG9M/UskU5vNoH7cUc6oM8STTDFKOZmnlX4ALOuM1+61CBjsNTGETWfo9a/h5mbGX02oh5/iNAa7vA=="
|
"integrity": "sha512-rzR+cByjNG9M/UskU5vNoH7cUc6oM8STTDFKOZmnlX4ALOuM1+61CBjsNTGETWfo9a/h5mbGX02oh5/iNAa7vA=="
|
||||||
},
|
},
|
||||||
|
"ngx-flowchart": {
|
||||||
|
"version": "git://github.com/thingsboard/ngx-flowchart.git#d26ee52089a6d9cf8147c5f162144825fceb3009",
|
||||||
|
"from": "git://github.com/thingsboard/ngx-flowchart.git#master",
|
||||||
|
"requires": {
|
||||||
|
"@angular/animations": "~8.0.0",
|
||||||
|
"@angular/common": "~8.0.0",
|
||||||
|
"@angular/compiler": "~8.0.0",
|
||||||
|
"@angular/core": "~8.0.0",
|
||||||
|
"@angular/forms": "~8.0.0",
|
||||||
|
"@angular/platform-browser": "~8.0.0",
|
||||||
|
"@angular/platform-browser-dynamic": "~8.0.0",
|
||||||
|
"@angular/router": "~8.0.0",
|
||||||
|
"rxjs": "~6.4.0",
|
||||||
|
"tslib": "^1.9.0",
|
||||||
|
"zone.js": "~0.9.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@angular/animations": {
|
||||||
|
"version": "8.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-8.0.3.tgz",
|
||||||
|
"integrity": "sha512-9zciJ4YRR0bodFSYgsgXdYMz8wKKyVjch7XZADGkWubXT8mGuwlpdPMlQ6n9Cwj8Ebu0u52WxMeQsX76K9RlYA==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "^1.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@angular/common": {
|
||||||
|
"version": "8.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@angular/common/-/common-8.0.3.tgz",
|
||||||
|
"integrity": "sha512-2YLYGVUf9eJZcocRmD3/9UHj4qFHt2t4ftDWJmrFM9zo2PZF+G5O9fASO7qoBbwpx3KFZtQO4dprKl2dFugRjg==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "^1.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@angular/compiler": {
|
||||||
|
"version": "8.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-8.0.3.tgz",
|
||||||
|
"integrity": "sha512-1/vF8D6l1O6IfWiDtaj6nC+B8CtkVtFgXgooDzLBO6XAkaCuJCnhKT1HnpWG5GtVsGaY9MGoTl1vE9ZMDbRQjg==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "^1.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@angular/core": {
|
||||||
|
"version": "8.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@angular/core/-/core-8.0.3.tgz",
|
||||||
|
"integrity": "sha512-IIxrtIPNuv2+HudER9J1nmPGiGJ4aRpeiFM9V4lSiSFv50RzuaoG60XqYIpUyuBdgvyKigcrfSbu9+x1vyN0hw==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "^1.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@angular/forms": {
|
||||||
|
"version": "8.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-8.0.3.tgz",
|
||||||
|
"integrity": "sha512-22s82QDRQ72K4vMYuNh3NAN+da9uanwoydnfKlp2rb9dZAb2QVX9NN6gSoMrkSSr2O9KTP6pWiw6A3/MW8sGRA==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "^1.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@angular/platform-browser": {
|
||||||
|
"version": "8.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-8.0.3.tgz",
|
||||||
|
"integrity": "sha512-ceAPP2Ijmk2sZ1rnOU/WNlE3DtT6K6ljpjO9oUfXKMoSMdWirJKAraT3m/BAzmYwMSXpPBxA7c3paZjiLL6t5A==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "^1.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@angular/platform-browser-dynamic": {
|
||||||
|
"version": "8.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-8.0.3.tgz",
|
||||||
|
"integrity": "sha512-ZjQjSYslSQAKzM4llvyMFxnSjFpbhT1U9FOdKwscPe475zAKX0087qsHrP2CRwkJRfwtdcmj9wMUQIPlzMpHLA==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "^1.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@angular/router": {
|
||||||
|
"version": "8.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@angular/router/-/router-8.0.3.tgz",
|
||||||
|
"integrity": "sha512-CU5pLTfQVUnTN93mdIKJrVjXiNldUkk30DPz4lpdxpZjYOqFGXeeSeQWmToHSofLPodNcAB4kkZ41VyXvlBu7w==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "^1.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rxjs": {
|
||||||
|
"version": "6.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz",
|
||||||
|
"integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "^1.9.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"ngx-hm-carousel": {
|
"ngx-hm-carousel": {
|
||||||
"version": "1.7.2",
|
"version": "1.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/ngx-hm-carousel/-/ngx-hm-carousel-1.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/ngx-hm-carousel/-/ngx-hm-carousel-1.7.2.tgz",
|
||||||
@ -10796,9 +10887,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"uglify-js": {
|
"uglify-js": {
|
||||||
"version": "3.6.8",
|
"version": "3.6.9",
|
||||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.8.tgz",
|
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.9.tgz",
|
||||||
"integrity": "sha512-XhHJ3S3ZyMwP8kY1Gkugqx3CJh2C3O0y8NPiSxtm1tyD/pktLAkFZsFGpuNfTZddKDQ/bbDBLAd2YyA1pbi8HQ==",
|
"integrity": "sha512-pcnnhaoG6RtrvHJ1dFncAe8Od6Nuy30oaJ82ts6//sGSXOP5UjBMEthiProjXmMNHOfd93sqlkztifFMcb+4yw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
|||||||
@ -59,6 +59,7 @@
|
|||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
"ngx-clipboard": "^12.2.0",
|
"ngx-clipboard": "^12.2.0",
|
||||||
"ngx-color-picker": "^8.2.0",
|
"ngx-color-picker": "^8.2.0",
|
||||||
|
"ngx-flowchart": "git://github.com/thingsboard/ngx-flowchart.git#master",
|
||||||
"ngx-hm-carousel": "^1.7.2",
|
"ngx-hm-carousel": "^1.7.2",
|
||||||
"ngx-translate-messageformat-compiler": "^4.5.0",
|
"ngx-translate-messageformat-compiler": "^4.5.0",
|
||||||
"objectpath": "^1.2.2",
|
"objectpath": "^1.2.2",
|
||||||
|
|||||||
@ -0,0 +1,19 @@
|
|||||||
|
<!--
|
||||||
|
|
||||||
|
Copyright © 2016-2019 The Thingsboard Authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
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.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<div>Rule chain</div>
|
||||||
|
<fc-canvas></fc-canvas>
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
///
|
||||||
|
/// Copyright © 2016-2019 The Thingsboard Authors
|
||||||
|
///
|
||||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
/// you may not use this file except in compliance with the License.
|
||||||
|
/// You may obtain a copy of the License at
|
||||||
|
///
|
||||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
///
|
||||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
/// 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 {UserService} from '@core/http/user.service';
|
||||||
|
import {User} from '@shared/models/user.model';
|
||||||
|
import {Authority} from '@shared/models/authority.enum';
|
||||||
|
import {PageComponent} from '@shared/components/page.component';
|
||||||
|
import {Store} from '@ngrx/store';
|
||||||
|
import {AppState} from '@core/core.state';
|
||||||
|
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
|
||||||
|
import {HasConfirmForm} from '@core/guards/confirm-on-exit.guard';
|
||||||
|
import {ActionAuthUpdateUserDetails} from '@core/auth/auth.actions';
|
||||||
|
import {environment as env} from '@env/environment';
|
||||||
|
import {TranslateService} from '@ngx-translate/core';
|
||||||
|
import {ActionSettingsChangeLanguage} from '@core/settings/settings.actions';
|
||||||
|
import {ChangePasswordDialogComponent} from '@modules/home/pages/profile/change-password-dialog.component';
|
||||||
|
import {MatDialog} from '@angular/material';
|
||||||
|
import {DialogService} from '@core/services/dialog.service';
|
||||||
|
import {AuthService} from '@core/auth/auth.service';
|
||||||
|
import {ActivatedRoute} from '@angular/router';
|
||||||
|
import { Dashboard } from '@shared/models/dashboard.models';
|
||||||
|
import { RuleChain } from '@shared/models/rule-chain.models';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'tb-rulechain-page',
|
||||||
|
templateUrl: './rulechain-page.component.html',
|
||||||
|
styleUrls: []
|
||||||
|
})
|
||||||
|
export class RuleChainPageComponent extends PageComponent implements OnInit {
|
||||||
|
|
||||||
|
ruleChain: RuleChain;
|
||||||
|
|
||||||
|
constructor(protected store: Store<AppState>,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private userService: UserService,
|
||||||
|
private authService: AuthService,
|
||||||
|
private translate: TranslateService,
|
||||||
|
public dialog: MatDialog,
|
||||||
|
public dialogService: DialogService,
|
||||||
|
public fb: FormBuilder) {
|
||||||
|
super(store);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.ruleChain = this.route.snapshot.data.ruleChain;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -14,12 +14,44 @@
|
|||||||
/// limitations under the License.
|
/// limitations under the License.
|
||||||
///
|
///
|
||||||
|
|
||||||
import {NgModule} from '@angular/core';
|
import { Injectable, NgModule } from '@angular/core';
|
||||||
import {RouterModule, Routes} from '@angular/router';
|
import { ActivatedRouteSnapshot, Resolve, RouterModule, Routes } from '@angular/router';
|
||||||
|
|
||||||
import {EntitiesTableComponent} from '../../components/entity/entities-table.component';
|
import {EntitiesTableComponent} from '../../components/entity/entities-table.component';
|
||||||
import {Authority} from '@shared/models/authority.enum';
|
import {Authority} from '@shared/models/authority.enum';
|
||||||
import {RuleChainsTableConfigResolver} from '@modules/home/pages/rulechain/rulechains-table-config.resolver';
|
import {RuleChainsTableConfigResolver} from '@modules/home/pages/rulechain/rulechains-table-config.resolver';
|
||||||
|
import { Dashboard } from '@shared/models/dashboard.models';
|
||||||
|
import { DashboardService } from '@core/http/dashboard.service';
|
||||||
|
import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { BreadCrumbConfig, BreadCrumbLabelFunction } from '@shared/components/breadcrumb';
|
||||||
|
import { RuleChain } from '@shared/models/rule-chain.models';
|
||||||
|
import { RuleChainService } from '@core/http/rule-chain.service';
|
||||||
|
import { DashboardPageComponent } from '@home/pages/dashboard/dashboard-page.component';
|
||||||
|
import { dashboardBreadcumbLabelFunction, DashboardResolver } from '@home/pages/dashboard/dashboard-routing.module';
|
||||||
|
import { RuleChainPageComponent } from '@home/pages/rulechain/rulechain-page.component';
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RuleChainResolver implements Resolve<RuleChain> {
|
||||||
|
|
||||||
|
constructor(private ruleChainService: RuleChainService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(route: ActivatedRouteSnapshot): Observable<RuleChain> {
|
||||||
|
const ruleChainId = route.params.ruleChainId;
|
||||||
|
return this.ruleChainService.getRuleChain(ruleChainId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ruleChainBreadcumbLabelFunction: BreadCrumbLabelFunction = ((route, translate, component) => {
|
||||||
|
let label: string = component.ruleChain.name;
|
||||||
|
if (component.ruleChain.root) {
|
||||||
|
label += ` (${translate.instant('rulechain.root')})`;
|
||||||
|
}
|
||||||
|
return label;
|
||||||
|
});
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
@ -41,6 +73,22 @@ const routes: Routes = [
|
|||||||
resolve: {
|
resolve: {
|
||||||
entitiesTableConfig: RuleChainsTableConfigResolver
|
entitiesTableConfig: RuleChainsTableConfigResolver
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ':ruleChainId',
|
||||||
|
component: RuleChainPageComponent,
|
||||||
|
data: {
|
||||||
|
breadcrumb: {
|
||||||
|
labelFunction: ruleChainBreadcumbLabelFunction,
|
||||||
|
icon: 'settings_ethernet'
|
||||||
|
} as BreadCrumbConfig,
|
||||||
|
auth: [Authority.TENANT_ADMIN],
|
||||||
|
title: 'rulechain.rulechain',
|
||||||
|
widgetEditMode: false
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
ruleChain: RuleChainResolver
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -50,7 +98,8 @@ const routes: Routes = [
|
|||||||
imports: [RouterModule.forChild(routes)],
|
imports: [RouterModule.forChild(routes)],
|
||||||
exports: [RouterModule],
|
exports: [RouterModule],
|
||||||
providers: [
|
providers: [
|
||||||
RuleChainsTableConfigResolver
|
RuleChainsTableConfigResolver,
|
||||||
|
RuleChainResolver
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class RuleChainRoutingModule { }
|
export class RuleChainRoutingModule { }
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import {RuleChainComponent} from '@modules/home/pages/rulechain/rulechain.compon
|
|||||||
import {RuleChainRoutingModule} from '@modules/home/pages/rulechain/rulechain-routing.module';
|
import {RuleChainRoutingModule} from '@modules/home/pages/rulechain/rulechain-routing.module';
|
||||||
import {HomeComponentsModule} from '@modules/home/components/home-components.module';
|
import {HomeComponentsModule} from '@modules/home/components/home-components.module';
|
||||||
import { RuleChainTabsComponent } from '@home/pages/rulechain/rulechain-tabs.component';
|
import { RuleChainTabsComponent } from '@home/pages/rulechain/rulechain-tabs.component';
|
||||||
|
import { RuleChainPageComponent } from './rulechain-page.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
@ -29,7 +30,8 @@ import { RuleChainTabsComponent } from '@home/pages/rulechain/rulechain-tabs.com
|
|||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
RuleChainComponent,
|
RuleChainComponent,
|
||||||
RuleChainTabsComponent
|
RuleChainTabsComponent,
|
||||||
|
RuleChainPageComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
|||||||
@ -128,9 +128,7 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
|
|||||||
if ($event) {
|
if ($event) {
|
||||||
$event.stopPropagation();
|
$event.stopPropagation();
|
||||||
}
|
}
|
||||||
// TODO:
|
this.router.navigateByUrl(`ruleChains/${ruleChain.id.id}`);
|
||||||
// this.router.navigateByUrl(`customers/${customer.id.id}/users`);
|
|
||||||
this.dialogService.todo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exportRuleChain($event: Event, ruleChain: RuleChain) {
|
exportRuleChain($event: Event, ruleChain: RuleChain) {
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import { LogoComponent } from './components/logo.component';
|
|||||||
import { TbSnackBarComponent, ToastDirective } from './components/toast.directive';
|
import { TbSnackBarComponent, ToastDirective } from './components/toast.directive';
|
||||||
import { BreadcrumbComponent } from '@app/shared/components/breadcrumb.component';
|
import { BreadcrumbComponent } from '@app/shared/components/breadcrumb.component';
|
||||||
import { NgxFlowModule, FlowInjectionToken } from '@flowjs/ngx-flow';
|
import { NgxFlowModule, FlowInjectionToken } from '@flowjs/ngx-flow';
|
||||||
|
import { NgxFlowchartModule } from 'ngx-flowchart/dist/ngx-flowchart';
|
||||||
import Flow from '@flowjs/flow.js';
|
import Flow from '@flowjs/flow.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -235,7 +236,8 @@ import { FileInputComponent } from './components/file-input.component';
|
|||||||
HotkeyModule,
|
HotkeyModule,
|
||||||
ColorPickerModule,
|
ColorPickerModule,
|
||||||
NgxHmCarouselModule,
|
NgxHmCarouselModule,
|
||||||
NgxFlowModule
|
NgxFlowModule,
|
||||||
|
NgxFlowchartModule
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
FooterComponent,
|
FooterComponent,
|
||||||
@ -317,6 +319,7 @@ import { FileInputComponent } from './components/file-input.component';
|
|||||||
HotkeyModule,
|
HotkeyModule,
|
||||||
ColorPickerModule,
|
ColorPickerModule,
|
||||||
NgxHmCarouselModule,
|
NgxHmCarouselModule,
|
||||||
|
NgxFlowchartModule,
|
||||||
ColorPickerDialogComponent,
|
ColorPickerDialogComponent,
|
||||||
MaterialIconsDialogComponent,
|
MaterialIconsDialogComponent,
|
||||||
ColorInputComponent,
|
ColorInputComponent,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user