Add data source properties retrieval and update related components
This commit is contained in:
@@ -74,6 +74,7 @@
|
|||||||
"buildTarget": "jambotron-ui:build:production"
|
"buildTarget": "jambotron-ui:build:production"
|
||||||
},
|
},
|
||||||
"development": {
|
"development": {
|
||||||
|
"disableHostCheck": true,
|
||||||
"buildTarget": "jambotron-ui:build:development"
|
"buildTarget": "jambotron-ui:build:development"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 154 KiB |
@@ -1 +1,27 @@
|
|||||||
<p>settings.component works!</p>
|
<p>settings.component works!</p>
|
||||||
|
|
||||||
|
<mat-card>
|
||||||
|
<mat-card-content>
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
<mat-tab-group>
|
||||||
|
<mat-tab label="Data Source properties">
|
||||||
|
|
||||||
|
<table mat-table
|
||||||
|
[dataSource]="properties" multiTemplateDataRows="true" class="mat-elevation-z8">
|
||||||
|
@for (column of displayedColumns; track column) {
|
||||||
|
<ng-container matColumnDef="{{column}}">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{column}}</th>
|
||||||
|
<td mat-cell *matCellDef="let element ; let k = dataIndex;">{{element[column]}}</td>
|
||||||
|
</ng-container>
|
||||||
|
}
|
||||||
|
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||||
|
<tr mat-row *matRowDef="let element; columns: displayedColumns;"
|
||||||
|
class="example-element-row"
|
||||||
|
>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</mat-tab>
|
||||||
|
</mat-tab-group>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
|||||||
@@ -1,11 +1,66 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
|
import {MatCard, MatCardContent} from '@angular/material/card';
|
||||||
|
import {MatDivider} from '@angular/material/divider';
|
||||||
|
import {MatTab, MatTabGroup} from '@angular/material/tabs';
|
||||||
|
import {
|
||||||
|
MatCell,
|
||||||
|
MatCellDef,
|
||||||
|
MatColumnDef,
|
||||||
|
MatHeaderCell, MatHeaderCellDef,
|
||||||
|
MatHeaderRow,
|
||||||
|
MatHeaderRowDef,
|
||||||
|
MatRow, MatRowDef, MatTable
|
||||||
|
} from '@angular/material/table';
|
||||||
|
import {MatIconButton} from '@angular/material/button';
|
||||||
|
import {MatList, MatListItem} from '@angular/material/list';
|
||||||
|
import {Bean} from '../../models/Bean';
|
||||||
|
import {SystemService} from '../../services/system.service';
|
||||||
|
import {NameValueItem} from '../../models/name-value-item';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-settings.component',
|
selector: 'app-settings.component',
|
||||||
imports: [],
|
imports: [
|
||||||
|
MatCard,
|
||||||
|
MatCardContent,
|
||||||
|
MatDivider,
|
||||||
|
MatTab,
|
||||||
|
MatTabGroup,
|
||||||
|
MatCell,
|
||||||
|
MatCellDef,
|
||||||
|
MatColumnDef,
|
||||||
|
MatHeaderCell,
|
||||||
|
|
||||||
|
MatTable,
|
||||||
|
MatHeaderCellDef,
|
||||||
|
MatHeaderRow,
|
||||||
|
MatHeaderRowDef,
|
||||||
|
MatRow,
|
||||||
|
MatRowDef
|
||||||
|
],
|
||||||
templateUrl: './settings.component.html',
|
templateUrl: './settings.component.html',
|
||||||
styleUrl: './settings.component.scss'
|
styleUrl: './settings.component.scss'
|
||||||
})
|
})
|
||||||
export class SettingsComponent {
|
export class SettingsComponent {
|
||||||
|
properties: NameValueItem[] = [] ;
|
||||||
|
|
||||||
|
displayedColumns: string[] = ['name', 'value'];
|
||||||
|
|
||||||
|
|
||||||
|
constructor(private systemService: SystemService) {
|
||||||
|
this.retrieveProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
retrieveProperties(): void {
|
||||||
|
this.systemService.getDataSourceProperties()
|
||||||
|
.subscribe(
|
||||||
|
(data: NameValueItem[]) => {
|
||||||
|
this.properties = data;
|
||||||
|
console.log(data);
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import {Component, CUSTOM_ELEMENTS_SCHEMA, inject} from '@angular/core';
|
import {Component, CUSTOM_ELEMENTS_SCHEMA, inject, OnInit} from '@angular/core';
|
||||||
//import {NavigationComponent} from '../navigation/navigation.component';
|
//import {NavigationComponent} from '../navigation/navigation.component';
|
||||||
import {NgClass} from '@angular/common';
|
import {NgClass} from '@angular/common';
|
||||||
import {NavigationComponent} from '../../layouts/admin-layout/navigation/navigation.component';
|
import {NavigationComponent} from '../../layouts/admin-layout/navigation/navigation.component';
|
||||||
import {RouterLink, RouterOutlet} from '@angular/router';
|
import {Router, RouterLink, RouterOutlet} from '@angular/router';
|
||||||
import {BreadcrumbComponent} from '../../components/breadcrumb/breadcrumb.component';
|
import {BreadcrumbComponent} from '../../components/breadcrumb/breadcrumb.component';
|
||||||
import {NavBarComponent} from '../../layouts/admin-layout/nav-bar/nav-bar.component';
|
import {NavBarComponent} from '../../layouts/admin-layout/nav-bar/nav-bar.component';
|
||||||
import {MatToolbar} from '@angular/material/toolbar';
|
import {MatToolbar} from '@angular/material/toolbar';
|
||||||
@@ -29,7 +29,7 @@ import {DialogComponent} from '../../components/dialog/dialog.component';
|
|||||||
templateUrl: './main.component.html',
|
templateUrl: './main.component.html',
|
||||||
styleUrl: './main.component.scss'
|
styleUrl: './main.component.scss'
|
||||||
})
|
})
|
||||||
export class MainComponent {
|
export class MainComponent implements OnInit{
|
||||||
// public props
|
// public props
|
||||||
navCollapsed: boolean = false;
|
navCollapsed: boolean = false;
|
||||||
navCollapsedMob: boolean = false;
|
navCollapsedMob: boolean = false;
|
||||||
@@ -52,13 +52,24 @@ export class MainComponent {
|
|||||||
private authService: AuthService = inject(AuthService);
|
private authService: AuthService = inject(AuthService);
|
||||||
private eventBusService: EventBusService = inject(EventBusService);
|
private eventBusService: EventBusService = inject(EventBusService);
|
||||||
|
|
||||||
constructor(
|
constructor(private router: Router) {
|
||||||
|
|
||||||
) {}
|
}
|
||||||
|
goToHome() {
|
||||||
|
this.router.navigate(['main/home']);
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.isLoggedIn = this.storageService.isLoggedIn();
|
this.isLoggedIn = this.storageService.isLoggedIn();
|
||||||
|
|
||||||
|
this.refreshToolbar();
|
||||||
|
|
||||||
|
this.eventBusSub = this.eventBusService.on('logout', () => {
|
||||||
|
this.logout();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshToolbar() {
|
||||||
if (this.isLoggedIn) {
|
if (this.isLoggedIn) {
|
||||||
const user = this.storageService.getUser();
|
const user = this.storageService.getUser();
|
||||||
this.roles = user.roles;
|
this.roles = user.roles;
|
||||||
@@ -68,10 +79,12 @@ export class MainComponent {
|
|||||||
|
|
||||||
this.username = user.username;
|
this.username = user.username;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.eventBusSub = this.eventBusService.on('logout', () => {
|
ngOnDestroy() {
|
||||||
this.logout();
|
if (this.eventBusSub) {
|
||||||
});
|
this.eventBusSub.unsubscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// public method
|
// public method
|
||||||
@@ -142,7 +155,8 @@ export class MainComponent {
|
|||||||
let user = this.storageService.getUser();
|
let user = this.storageService.getUser();
|
||||||
this.roles = user.roles;
|
this.roles = user.roles;
|
||||||
//this.username = user.username;
|
//this.username = user.username;
|
||||||
this.reloadPage();
|
//this.reloadPage();
|
||||||
|
this.refreshToolbar();
|
||||||
},
|
},
|
||||||
err => {
|
err => {
|
||||||
this.errorMessage = err.error.message;
|
this.errorMessage = err.error.message;
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export class NameValueItem {
|
||||||
|
name?:string;
|
||||||
|
value?:string;
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import {HttpClient} from '@angular/common/http';
|
|||||||
import {Observable} from 'rxjs';
|
import {Observable} from 'rxjs';
|
||||||
import {Tutorial} from '../models/tutorial.model';
|
import {Tutorial} from '../models/tutorial.model';
|
||||||
import {Bean} from '../models/Bean';
|
import {Bean} from '../models/Bean';
|
||||||
|
import {NameValueItem} from '../models/name-value-item';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@@ -12,6 +13,10 @@ export class SystemService {
|
|||||||
baseUrl : string = 'http://localhost:8080/api/system';
|
baseUrl : string = 'http://localhost:8080/api/system';
|
||||||
constructor(private http: HttpClient) { }
|
constructor(private http: HttpClient) { }
|
||||||
|
|
||||||
|
getDataSourceProperties(): Observable<NameValueItem[]>{
|
||||||
|
return this.http.get<NameValueItem[]>(this.baseUrl + "/data-source-properties")
|
||||||
|
}
|
||||||
|
|
||||||
getAllBeans(): Observable<Bean[]> {
|
getAllBeans(): Observable<Bean[]> {
|
||||||
return this.http.get<Bean[]>(this.baseUrl+"/beans");
|
return this.http.get<Bean[]>(this.baseUrl+"/beans");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ File: style.css
|
|||||||
|
|
||||||
html, body { height: 100%; }
|
html, body { height: 100%; }
|
||||||
body {
|
body {
|
||||||
background-image: url('../public/Firefly_jambo tron 118290.png');
|
background-image: url('../public/Firefly_jambo tron 118290.jpg');
|
||||||
background-attachment:fixed;
|
background-attachment:fixed;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import java.util.List;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
//@CrossOrigin(origins = "*", maxAge = 3600)
|
//@CrossOrigin(origins = "*", maxAge = 3600)
|
||||||
@CrossOrigin(origins = "http://localhost:4200", maxAge = 3600, allowCredentials="true")
|
@CrossOrigin(origins = "http://localhost:4200,https://a576-5-248-149-207.ngrok-free.app", maxAge = 3600, allowCredentials="true")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/auth")
|
@RequestMapping("/api/auth")
|
||||||
public class AuthController {
|
public class AuthController {
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package com.jambotronGroup.jambotron.controllers;
|
package com.jambotronGroup.jambotron.controllers;
|
||||||
|
|
||||||
import com.jambotronGroup.jambotron.model.Bean;
|
import com.jambotronGroup.jambotron.model.Bean;
|
||||||
|
import com.jambotronGroup.jambotron.model.NameValueItem;
|
||||||
import com.jambotronGroup.jambotron.model.Setting;
|
import com.jambotronGroup.jambotron.model.Setting;
|
||||||
import com.jambotronGroup.jambotron.system.SystemService;
|
import com.jambotronGroup.jambotron.system.SystemService;
|
||||||
|
import org.springframework.beans.factory.NamedBean;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
@@ -20,6 +23,24 @@ public class SystemController {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping("/data-source-properties")
|
||||||
|
public ResponseEntity<List<NameValueItem>> getDataSourceProperties(@RequestParam(required = false) String title) {
|
||||||
|
try {
|
||||||
|
List<NameValueItem> properties = systemService.getDataSourceProperties();
|
||||||
|
//userRepository.findAll().forEach(users::add);
|
||||||
|
|
||||||
|
|
||||||
|
if (properties.isEmpty()) {
|
||||||
|
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ResponseEntity<>(properties, HttpStatus.OK);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@GetMapping("/beans")
|
@GetMapping("/beans")
|
||||||
public ResponseEntity<List<Bean>> getLoadedBeans(@RequestParam(required = false) String title) {
|
public ResponseEntity<List<Bean>> getLoadedBeans(@RequestParam(required = false) String title) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ public class Bean {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
|
||||||
return String.format("name = %s type= %s", this.shortName, this.typeShortName);
|
return String.format("name = %s type= %s", this.shortName, this.typeShortName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.jambotronGroup.jambotron.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
public class NameValueItem {
|
||||||
|
|
||||||
|
public NameValueItem(String name, String value){
|
||||||
|
_name = name;
|
||||||
|
_value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Id
|
||||||
|
private String _name;
|
||||||
|
|
||||||
|
private String _value;
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return _value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(String _value) {
|
||||||
|
this._value = _value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return _name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String _name) {
|
||||||
|
this._name = _name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
|
||||||
|
return String.format("name = %s value= %s", this._name, this._value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,8 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
|||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableMethodSecurity
|
@EnableMethodSecurity
|
||||||
@@ -24,7 +26,7 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
|
|||||||
// securedEnabled = true,
|
// securedEnabled = true,
|
||||||
// jsr250Enabled = true,
|
// jsr250Enabled = true,
|
||||||
//prePostEnabled = true)
|
//prePostEnabled = true)
|
||||||
public class WebSecurityConfig {// extends WebSecurityConfigurerAdapter {
|
public class WebSecurityConfig implements WebMvcConfigurer {// extends WebSecurityConfigurerAdapter {
|
||||||
@Autowired
|
@Autowired
|
||||||
UserDetailsServiceImpl userDetailsService;
|
UserDetailsServiceImpl userDetailsService;
|
||||||
|
|
||||||
@@ -41,6 +43,11 @@ public class WebSecurityConfig {// extends WebSecurityConfigurerAdapter {
|
|||||||
// authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
|
// authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addCorsMappings(CorsRegistry registry) {
|
||||||
|
// Do not add any mappings to enable complete disabling of CORS.
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public DaoAuthenticationProvider authenticationProvider() {
|
public DaoAuthenticationProvider authenticationProvider() {
|
||||||
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
|
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
|
||||||
@@ -108,6 +115,8 @@ public class WebSecurityConfig {// extends WebSecurityConfigurerAdapter {
|
|||||||
.requestMatchers("/resources/public/media/**").permitAll()
|
.requestMatchers("/resources/public/media/**").permitAll()
|
||||||
.requestMatchers("/resources/public/browser/**").permitAll()
|
.requestMatchers("/resources/public/browser/**").permitAll()
|
||||||
.requestMatchers("/media/**").permitAll()
|
.requestMatchers("/media/**").permitAll()
|
||||||
|
|
||||||
|
.requestMatchers("/main/**").permitAll()
|
||||||
.requestMatchers("/api/test/**").permitAll()
|
.requestMatchers("/api/test/**").permitAll()
|
||||||
.requestMatchers("/api/tutorials").permitAll()
|
.requestMatchers("/api/tutorials").permitAll()
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.jambotronGroup.jambotron.system;
|
package com.jambotronGroup.jambotron.system;
|
||||||
|
|
||||||
import com.jambotronGroup.jambotron.model.Bean;
|
import com.jambotronGroup.jambotron.model.Bean;
|
||||||
|
import com.jambotronGroup.jambotron.model.NameValueItem;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -9,4 +10,6 @@ public interface SystemService {
|
|||||||
|
|
||||||
List<Bean> getLodedCustomBeans();
|
List<Bean> getLodedCustomBeans();
|
||||||
|
|
||||||
|
List<NameValueItem> getDataSourceProperties();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,16 +2,17 @@ package com.jambotronGroup.jambotron.system;
|
|||||||
|
|
||||||
import com.jambotronGroup.jambotron.JambotronApplication;
|
import com.jambotronGroup.jambotron.JambotronApplication;
|
||||||
import com.jambotronGroup.jambotron.model.Bean;
|
import com.jambotronGroup.jambotron.model.Bean;
|
||||||
|
import com.jambotronGroup.jambotron.model.NameValueItem;
|
||||||
import com.jambotronGroup.jambotron.utils.EntityLoggingConsumer;
|
import com.jambotronGroup.jambotron.utils.EntityLoggingConsumer;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.core.env.Environment;
|
import org.springframework.core.env.Environment;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -23,9 +24,23 @@ public class SystemServiceImpl implements SystemService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private Environment environment;
|
private Environment environment;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
DataSourceProperties _dataSourceProperties;
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(SystemServiceImpl.class);
|
private static final Logger logger = LoggerFactory.getLogger(SystemServiceImpl.class);
|
||||||
|
|
||||||
|
|
||||||
|
public List<NameValueItem> getDataSourceProperties(){
|
||||||
|
List<NameValueItem> returnValue = new ArrayList<NameValueItem>();
|
||||||
|
|
||||||
|
returnValue.add(new NameValueItem("Username", _dataSourceProperties.getUsername()));
|
||||||
|
returnValue.add(new NameValueItem("Password", _dataSourceProperties.getPassword()));
|
||||||
|
returnValue.add(new NameValueItem("Url", _dataSourceProperties.getUrl()));
|
||||||
|
|
||||||
|
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Bean> getLodedBeans() {
|
public List<Bean> getLodedBeans() {
|
||||||
// Get all bean names from the application context and map them to Bean objects
|
// Get all bean names from the application context and map them to Bean objects
|
||||||
|
|||||||
Reference in New Issue
Block a user