mirror of
https://github.com/skot/ESP-Miner.git
synced 2025-06-16 13:20:56 +02:00
Separate Network settings UI into it's own tab (#389)
* Separate Network and regular Settings in UI * Add missing files * More cleanup and fixes * Network -> Network Configuration
This commit is contained in:
parent
3636d2b399
commit
2c13f23b54
@ -4,6 +4,7 @@ import { RouterModule, Routes } from '@angular/router';
|
|||||||
import { HomeComponent } from './components/home/home.component';
|
import { HomeComponent } from './components/home/home.component';
|
||||||
import { LogsComponent } from './components/logs/logs.component';
|
import { LogsComponent } from './components/logs/logs.component';
|
||||||
import { SettingsComponent } from './components/settings/settings.component';
|
import { SettingsComponent } from './components/settings/settings.component';
|
||||||
|
import { NetworkComponent } from './components/network/network.component';
|
||||||
import { SwarmComponent } from './components/swarm/swarm.component';
|
import { SwarmComponent } from './components/swarm/swarm.component';
|
||||||
import { AppLayoutComponent } from './layout/app.layout.component';
|
import { AppLayoutComponent } from './layout/app.layout.component';
|
||||||
|
|
||||||
@ -20,6 +21,10 @@ const routes: Routes = [
|
|||||||
path: 'logs',
|
path: 'logs',
|
||||||
component: LogsComponent
|
component: LogsComponent
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'network',
|
||||||
|
component: NetworkComponent
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'settings',
|
path: 'settings',
|
||||||
component: SettingsComponent
|
component: SettingsComponent
|
||||||
|
@ -11,9 +11,11 @@ import { ToastrModule } from 'ngx-toastr';
|
|||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { EditComponent } from './components/edit/edit.component';
|
import { EditComponent } from './components/edit/edit.component';
|
||||||
|
import { NetworkEditComponent } from './components/network-edit/network.edit.component';
|
||||||
import { HomeComponent } from './components/home/home.component';
|
import { HomeComponent } from './components/home/home.component';
|
||||||
import { LoadingComponent } from './components/loading/loading.component';
|
import { LoadingComponent } from './components/loading/loading.component';
|
||||||
import { LogsComponent } from './components/logs/logs.component';
|
import { LogsComponent } from './components/logs/logs.component';
|
||||||
|
import { NetworkComponent } from './components/network/network.component';
|
||||||
import { SettingsComponent } from './components/settings/settings.component';
|
import { SettingsComponent } from './components/settings/settings.component';
|
||||||
import { SwarmComponent } from './components/swarm/swarm.component';
|
import { SwarmComponent } from './components/swarm/swarm.component';
|
||||||
import { AppLayoutModule } from './layout/app.layout.module';
|
import { AppLayoutModule } from './layout/app.layout.module';
|
||||||
@ -26,8 +28,10 @@ import { MessageModule } from 'primeng/message';
|
|||||||
const components = [
|
const components = [
|
||||||
AppComponent,
|
AppComponent,
|
||||||
EditComponent,
|
EditComponent,
|
||||||
|
NetworkEditComponent,
|
||||||
HomeComponent,
|
HomeComponent,
|
||||||
LoadingComponent,
|
LoadingComponent,
|
||||||
|
NetworkComponent,
|
||||||
SettingsComponent,
|
SettingsComponent,
|
||||||
LogsComponent
|
LogsComponent
|
||||||
];
|
];
|
||||||
|
@ -1,29 +1,5 @@
|
|||||||
<ng-container *ngIf="form != null">
|
<ng-container *ngIf="form != null">
|
||||||
<form [formGroup]="form">
|
<form [formGroup]="form">
|
||||||
|
|
||||||
<div class="field grid p-fluid">
|
|
||||||
<label htmlFor="hostname" class="col-12 mb-2 md:col-2 md:mb-0">Hostname:</label>
|
|
||||||
<div class="col-12 md:col-10">
|
|
||||||
<input pInputText id="hostname" type="text" formControlName="hostname" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="field grid p-fluid">
|
|
||||||
<label htmlFor="ssid" class="col-12 mb-2 md:col-2 md:mb-0">WiFi SSID:</label>
|
|
||||||
<div class="col-12 md:col-10">
|
|
||||||
<input pInputText id="ssid" type="text" formControlName="ssid" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="field grid p-fluid">
|
|
||||||
<label htmlFor="wifiPass" class="col-12 mb-2 md:col-2 md:mb-0">WiFi Password:</label>
|
|
||||||
<div class="col-12 md:col-10 p-input-icon-right">
|
|
||||||
<i *ngIf="form.get('wifiPass')?.dirty" class="pi"
|
|
||||||
[ngClass]="{'pi-eye': !showWifiPassword, 'pi-eye-slash': showWifiPassword}"
|
|
||||||
(click)="toggleWifiPasswordVisibility()" style="cursor: pointer;"></i>
|
|
||||||
<input pInputText id="wifiPass" formControlName="wifiPass"
|
|
||||||
[type]="showWifiPassword ? 'text' : 'password'"
|
|
||||||
placeholder="Enter WiFi password" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="field grid p-fluid">
|
<div class="field grid p-fluid">
|
||||||
<label htmlFor="stratumURL" class="col-12 mb-2 md:col-2 md:mb-0">Stratum URL:</label>
|
<label htmlFor="stratumURL" class="col-12 mb-2 md:col-2 md:mb-0">Stratum URL:</label>
|
||||||
<div class="col-12 md:col-10">
|
<div class="col-12 md:col-10">
|
||||||
@ -172,7 +148,6 @@
|
|||||||
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<div class="field-checkbox">
|
<div class="field-checkbox">
|
||||||
<p-checkbox name="flipscreen" formControlName="flipscreen" inputId="flipscreen"
|
<p-checkbox name="flipscreen" formControlName="flipscreen" inputId="flipscreen"
|
||||||
|
@ -159,9 +159,6 @@ export class EditComponent implements OnInit {
|
|||||||
stratumPassword: ['*****', [Validators.required]],
|
stratumPassword: ['*****', [Validators.required]],
|
||||||
fallbackStratumUser: [info.fallbackStratumUser, [Validators.required]],
|
fallbackStratumUser: [info.fallbackStratumUser, [Validators.required]],
|
||||||
fallbackStratumPassword: ['password', [Validators.required]],
|
fallbackStratumPassword: ['password', [Validators.required]],
|
||||||
hostname: [info.hostname, [Validators.required]],
|
|
||||||
ssid: [info.ssid, [Validators.required]],
|
|
||||||
wifiPass: ['*****'],
|
|
||||||
coreVoltage: [info.coreVoltage, [Validators.required]],
|
coreVoltage: [info.coreVoltage, [Validators.required]],
|
||||||
frequency: [info.frequency, [Validators.required]],
|
frequency: [info.frequency, [Validators.required]],
|
||||||
autofanspeed: [info.autofanspeed == 1, [Validators.required]],
|
autofanspeed: [info.autofanspeed == 1, [Validators.required]],
|
||||||
@ -198,12 +195,6 @@ export class EditComponent implements OnInit {
|
|||||||
|
|
||||||
const form = this.form.getRawValue();
|
const form = this.form.getRawValue();
|
||||||
|
|
||||||
// Allow an empty wifi password
|
|
||||||
form.wifiPass = form.wifiPass == null ? '' : form.wifiPass;
|
|
||||||
|
|
||||||
if (form.wifiPass === '*****') {
|
|
||||||
delete form.wifiPass;
|
|
||||||
}
|
|
||||||
if (form.stratumPassword === '*****') {
|
if (form.stratumPassword === '*****') {
|
||||||
delete form.stratumPassword;
|
delete form.stratumPassword;
|
||||||
}
|
}
|
||||||
@ -227,9 +218,4 @@ export class EditComponent implements OnInit {
|
|||||||
this.showStratumPassword = !this.showStratumPassword;
|
this.showStratumPassword = !this.showStratumPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
showWifiPassword: boolean = false;
|
|
||||||
toggleWifiPasswordVisibility() {
|
|
||||||
this.showWifiPassword = !this.showWifiPassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
<ng-container *ngIf="form != null">
|
||||||
|
<form [formGroup]="form">
|
||||||
|
|
||||||
|
<div class="field grid p-fluid">
|
||||||
|
<label htmlFor="hostname" class="col-12 mb-2 md:col-2 md:mb-0">Hostname:</label>
|
||||||
|
<div class="col-12 md:col-10">
|
||||||
|
<input pInputText id="hostname" type="text" formControlName="hostname" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field grid p-fluid">
|
||||||
|
<label htmlFor="ssid" class="col-12 mb-2 md:col-2 md:mb-0">WiFi SSID:</label>
|
||||||
|
<div class="col-12 md:col-10">
|
||||||
|
<input pInputText id="ssid" type="text" formControlName="ssid" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field grid p-fluid">
|
||||||
|
<label htmlFor="wifiPass" class="col-12 mb-2 md:col-2 md:mb-0">WiFi Password:</label>
|
||||||
|
<div class="col-12 md:col-10 p-input-icon-right">
|
||||||
|
<i *ngIf="form.get('wifiPass')?.dirty" class="pi"
|
||||||
|
[ngClass]="{'pi-eye': !showWifiPassword, 'pi-eye-slash': showWifiPassword}"
|
||||||
|
(click)="toggleWifiPasswordVisibility()" style="cursor: pointer;"></i>
|
||||||
|
<input pInputText id="wifiPass" formControlName="wifiPass"
|
||||||
|
[type]="showWifiPassword ? 'text' : 'password'"
|
||||||
|
placeholder="Enter WiFi password" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-2">
|
||||||
|
<button pButton [disabled]="form.invalid" (click)="updateSystem()"
|
||||||
|
class="btn btn-primary mr-2">Save</button>
|
||||||
|
<b style="line-height: 34px;">You must restart this device after saving for changes to take effect.</b>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</ng-container>
|
@ -0,0 +1,5 @@
|
|||||||
|
.pi {
|
||||||
|
right: 1rem;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
top: 1rem;
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { NetworkEditComponent } from './network.edit.component';
|
||||||
|
|
||||||
|
describe('NetworkEditComponent', () => {
|
||||||
|
let component: NetworkEditComponent;
|
||||||
|
let fixture: ComponentFixture<NetworkEditComponent>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [NetworkEditComponent]
|
||||||
|
});
|
||||||
|
fixture = TestBed.createComponent(NetworkEditComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,70 @@
|
|||||||
|
import { HttpErrorResponse } from '@angular/common/http';
|
||||||
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
|
import { ToastrService } from 'ngx-toastr';
|
||||||
|
import { startWith } from 'rxjs';
|
||||||
|
import { LoadingService } from 'src/app/services/loading.service';
|
||||||
|
import { SystemService } from 'src/app/services/system.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-network-edit',
|
||||||
|
templateUrl: './network.edit.component.html',
|
||||||
|
styleUrls: ['./network.edit.component.scss']
|
||||||
|
})
|
||||||
|
export class NetworkEditComponent implements OnInit {
|
||||||
|
|
||||||
|
public form!: FormGroup;
|
||||||
|
|
||||||
|
@Input() uri = '';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private fb: FormBuilder,
|
||||||
|
private systemService: SystemService,
|
||||||
|
private toastr: ToastrService,
|
||||||
|
private toastrService: ToastrService,
|
||||||
|
private loadingService: LoadingService
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.systemService.getInfo(this.uri)
|
||||||
|
.pipe(this.loadingService.lockUIUntilComplete())
|
||||||
|
.subscribe(info => {
|
||||||
|
this.form = this.fb.group({
|
||||||
|
hostname: [info.hostname, [Validators.required]],
|
||||||
|
ssid: [info.ssid, [Validators.required]],
|
||||||
|
wifiPass: ['*****'],
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public updateSystem() {
|
||||||
|
|
||||||
|
const form = this.form.getRawValue();
|
||||||
|
|
||||||
|
// Allow an empty wifi password
|
||||||
|
form.wifiPass = form.wifiPass == null ? '' : form.wifiPass;
|
||||||
|
|
||||||
|
if (form.wifiPass === '*****') {
|
||||||
|
delete form.wifiPass;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.systemService.updateSystem(this.uri, form)
|
||||||
|
.pipe(this.loadingService.lockUIUntilComplete())
|
||||||
|
.subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.toastr.success('Success!', 'Saved.');
|
||||||
|
},
|
||||||
|
error: (err: HttpErrorResponse) => {
|
||||||
|
this.toastr.error('Error.', `Could not save. ${err.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
showWifiPassword: boolean = false;
|
||||||
|
toggleWifiPasswordVisibility() {
|
||||||
|
this.showWifiPassword = !this.showWifiPassword;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
<div class="card">
|
||||||
|
<h2>Network Configuration</h2>
|
||||||
|
|
||||||
|
<app-network-edit></app-network-edit>
|
||||||
|
</div>
|
@ -0,0 +1,35 @@
|
|||||||
|
input[type="text"],
|
||||||
|
input[type="number"],
|
||||||
|
input[type="password"],
|
||||||
|
input[type="range"] {
|
||||||
|
min-width: 250px;
|
||||||
|
max-width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
min-width: 268px;
|
||||||
|
max-width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.restart {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (min-width:900px) {
|
||||||
|
|
||||||
|
input[type="text"],
|
||||||
|
input[type="password"],
|
||||||
|
input[type="number"],
|
||||||
|
input[type="range"] {
|
||||||
|
min-width: 500px
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
min-width: 518px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: white;
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { NetworkComponent } from './network.component';
|
||||||
|
|
||||||
|
describe('NetworkComponent', () => {
|
||||||
|
let component: NetworkComponent;
|
||||||
|
let fixture: ComponentFixture<NetworkComponent>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [NetworkComponent]
|
||||||
|
});
|
||||||
|
fixture = TestBed.createComponent(NetworkComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,62 @@
|
|||||||
|
import { HttpErrorResponse, HttpEventType } from '@angular/common/http';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
|
import { ToastrService } from 'ngx-toastr';
|
||||||
|
import { map, Observable, shareReplay, startWith } from 'rxjs';
|
||||||
|
import { LoadingService } from 'src/app/services/loading.service';
|
||||||
|
import { SystemService } from 'src/app/services/system.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-network',
|
||||||
|
templateUrl: './network.component.html',
|
||||||
|
styleUrls: ['./network.component.scss']
|
||||||
|
})
|
||||||
|
export class NetworkComponent {
|
||||||
|
|
||||||
|
public form!: FormGroup;
|
||||||
|
|
||||||
|
public info$: Observable<any>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private fb: FormBuilder,
|
||||||
|
private systemService: SystemService,
|
||||||
|
private toastr: ToastrService,
|
||||||
|
private toastrService: ToastrService,
|
||||||
|
private loadingService: LoadingService,
|
||||||
|
) {
|
||||||
|
this.info$ = this.systemService.getInfo().pipe(shareReplay({refCount: true, bufferSize: 1}))
|
||||||
|
|
||||||
|
this.info$.pipe(this.loadingService.lockUIUntilComplete())
|
||||||
|
.subscribe(info => {
|
||||||
|
this.form = this.fb.group({
|
||||||
|
hostname: [info.hostname, [Validators.required]],
|
||||||
|
ssid: [info.ssid, [Validators.required]],
|
||||||
|
wifiPass: ['*****'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateSystem() {
|
||||||
|
|
||||||
|
const form = this.form.getRawValue();
|
||||||
|
|
||||||
|
// Allow an empty wifi password
|
||||||
|
form.wifiPass = form.wifiPass == null ? '' : form.wifiPass;
|
||||||
|
|
||||||
|
if (form.wifiPass === '*****') {
|
||||||
|
delete form.wifiPass;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.systemService.updateSystem(undefined, form)
|
||||||
|
.pipe(this.loadingService.lockUIUntilComplete())
|
||||||
|
.subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.toastr.success('Success!', 'Saved.');
|
||||||
|
},
|
||||||
|
error: (err: HttpErrorResponse) => {
|
||||||
|
this.toastr.error('Error.', `Could not save. ${err.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -71,8 +71,6 @@ export class SettingsComponent {
|
|||||||
]],
|
]],
|
||||||
stratumUser: [info.stratumUser, [Validators.required]],
|
stratumUser: [info.stratumUser, [Validators.required]],
|
||||||
stratumPassword: ['*****', [Validators.required]],
|
stratumPassword: ['*****', [Validators.required]],
|
||||||
ssid: [info.ssid, [Validators.required]],
|
|
||||||
wifiPass: ['*****'],
|
|
||||||
coreVoltage: [info.coreVoltage, [Validators.required]],
|
coreVoltage: [info.coreVoltage, [Validators.required]],
|
||||||
frequency: [info.frequency, [Validators.required]],
|
frequency: [info.frequency, [Validators.required]],
|
||||||
autofanspeed: [info.autofanspeed == 1, [Validators.required]],
|
autofanspeed: [info.autofanspeed == 1, [Validators.required]],
|
||||||
@ -116,12 +114,6 @@ export class SettingsComponent {
|
|||||||
form.invertfanpolarity = form.invertfanpolarity == true ? 1 : 0;
|
form.invertfanpolarity = form.invertfanpolarity == true ? 1 : 0;
|
||||||
form.autofanspeed = form.autofanspeed == true ? 1 : 0;
|
form.autofanspeed = form.autofanspeed == true ? 1 : 0;
|
||||||
|
|
||||||
// Allow an empty wifi password
|
|
||||||
form.wifiPass = form.wifiPass == null ? '' : form.wifiPass;
|
|
||||||
|
|
||||||
if (form.wifiPass === '*****') {
|
|
||||||
delete form.wifiPass;
|
|
||||||
}
|
|
||||||
if (form.stratumPassword === '*****') {
|
if (form.stratumPassword === '*****') {
|
||||||
delete form.stratumPassword;
|
delete form.stratumPassword;
|
||||||
}
|
}
|
||||||
@ -211,9 +203,4 @@ export class SettingsComponent {
|
|||||||
});
|
});
|
||||||
this.toastr.success('Success!', 'Bitaxe restarted');
|
this.toastr.success('Success!', 'Bitaxe restarted');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,22 +24,19 @@ export class AppMenuComponent implements OnInit {
|
|||||||
items: [
|
items: [
|
||||||
{ label: 'Dashboard', icon: 'pi pi-fw pi-home', routerLink: ['/'] },
|
{ label: 'Dashboard', icon: 'pi pi-fw pi-home', routerLink: ['/'] },
|
||||||
{ label: 'Swarm', icon: 'pi pi-fw pi-share-alt', routerLink: ['swarm'] },
|
{ label: 'Swarm', icon: 'pi pi-fw pi-share-alt', routerLink: ['swarm'] },
|
||||||
|
{ label: 'Network', icon: 'pi pi-fw pi-wifi', routerLink: ['network'] },
|
||||||
{ label: 'Settings', icon: 'pi pi-fw pi-cog', routerLink: ['settings'] },
|
{ label: 'Settings', icon: 'pi pi-fw pi-cog', routerLink: ['settings'] },
|
||||||
{ label: 'Logs', icon: 'pi pi-fw pi-list', routerLink: ['logs'] },
|
{ label: 'Logs', icon: 'pi pi-fw pi-list', routerLink: ['logs'] },
|
||||||
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public restart() {
|
public restart() {
|
||||||
this.systemService.restart().subscribe(res => {
|
this.systemService.restart().subscribe(res => {
|
||||||
|
|
||||||
});
|
});
|
||||||
this.toastr.success('Success!', 'Bitaxe restarted');
|
this.toastr.success('Success!', 'Bitaxe restarted');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user