grid
This commit is contained in:
@@ -41,7 +41,7 @@ export class User implements IUser {
|
|||||||
@Column({ default: true })
|
@Column({ default: true })
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
|
||||||
@ManyToOne(() => Role, (role) => role.user, { cascade: true })
|
@ManyToOne(() => Role, (role) => role.user, { cascade: true, eager: true })
|
||||||
@JoinColumn()
|
@JoinColumn()
|
||||||
@Transform(({ value }) => value.name)
|
@Transform(({ value }) => value.name)
|
||||||
role: Role;
|
role: Role;
|
||||||
|
|||||||
@@ -56,7 +56,10 @@ export class AuthService {
|
|||||||
username: payload.username,
|
username: payload.username,
|
||||||
externalId: payload.id,
|
externalId: payload.id,
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
user.lastLogin = new Date();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.isActive) {
|
if (!user.isActive) {
|
||||||
return resolve(null);
|
return resolve(null);
|
||||||
}
|
}
|
||||||
@@ -87,6 +90,8 @@ export class AuthService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
user.refreshToken = this.jwt.sign(rPay, { expiresIn: '1w' });
|
user.refreshToken = this.jwt.sign(rPay, { expiresIn: '1w' });
|
||||||
|
user.lastLogin = new Date();
|
||||||
|
this.userRepo.save(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
private createAuthCodeFormData(
|
private createAuthCodeFormData(
|
||||||
|
|||||||
11
client/package-lock.json
generated
11
client/package-lock.json
generated
@@ -8,6 +8,7 @@
|
|||||||
"name": "client",
|
"name": "client",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ag-grid-community/locale": "^32.1.0",
|
||||||
"@angular/animations": "^18.0.0",
|
"@angular/animations": "^18.0.0",
|
||||||
"@angular/cdk": "^18.2.4",
|
"@angular/cdk": "^18.2.4",
|
||||||
"@angular/common": "^18.0.0",
|
"@angular/common": "^18.0.0",
|
||||||
@@ -21,6 +22,7 @@
|
|||||||
"@ngneat/overview": "^6.0.0",
|
"@ngneat/overview": "^6.0.0",
|
||||||
"@ngxpert/hot-toast": "^3.0.1",
|
"@ngxpert/hot-toast": "^3.0.1",
|
||||||
"ag-grid-angular": "^32.1.0",
|
"ag-grid-angular": "^32.1.0",
|
||||||
|
"ag-grid-community": "^32.1.0",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"zone.js": "~0.14.3"
|
"zone.js": "~0.14.3"
|
||||||
@@ -39,6 +41,11 @@
|
|||||||
"typescript": "~5.4.2"
|
"typescript": "~5.4.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@ag-grid-community/locale": {
|
||||||
|
"version": "32.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ag-grid-community/locale/-/locale-32.1.0.tgz",
|
||||||
|
"integrity": "sha512-sMdCVc3gbFQE/mz8cGW9Q/niCJcOeiALCFQRor5j91dYoIcL00oK4dibQYSTCoFePBdRrBTbvaoVWSyo6MXfBA=="
|
||||||
|
},
|
||||||
"node_modules/@ampproject/remapping": {
|
"node_modules/@ampproject/remapping": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
|
||||||
@@ -4497,8 +4504,7 @@
|
|||||||
"node_modules/ag-charts-types": {
|
"node_modules/ag-charts-types": {
|
||||||
"version": "10.1.0",
|
"version": "10.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/ag-charts-types/-/ag-charts-types-10.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/ag-charts-types/-/ag-charts-types-10.1.0.tgz",
|
||||||
"integrity": "sha512-pk9ft8hbgTXJ/thI/SEUR1BoauNplYExpcHh7tMOqVikoDsta1O15TB1ZL4XWnl4TPIzROBmONKsz7d8a2HBuQ==",
|
"integrity": "sha512-pk9ft8hbgTXJ/thI/SEUR1BoauNplYExpcHh7tMOqVikoDsta1O15TB1ZL4XWnl4TPIzROBmONKsz7d8a2HBuQ=="
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/ag-grid-angular": {
|
"node_modules/ag-grid-angular": {
|
||||||
"version": "32.1.0",
|
"version": "32.1.0",
|
||||||
@@ -4517,7 +4523,6 @@
|
|||||||
"version": "32.1.0",
|
"version": "32.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-32.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-32.1.0.tgz",
|
||||||
"integrity": "sha512-RVvkjRH61nuCXwIqTKQPqNbKR+8cGBKw7S1qmmMXsy0pCBAJaQn4kL3v31hKHxDtV4bPscBXLFKGnKzHuss0GQ==",
|
"integrity": "sha512-RVvkjRH61nuCXwIqTKQPqNbKR+8cGBKw7S1qmmMXsy0pCBAJaQn4kL3v31hKHxDtV4bPscBXLFKGnKzHuss0GQ==",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ag-charts-types": "10.1.0"
|
"ag-charts-types": "10.1.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ag-grid-community/locale": "^32.1.0",
|
||||||
"@angular/animations": "^18.0.0",
|
"@angular/animations": "^18.0.0",
|
||||||
"@angular/cdk": "^18.2.4",
|
"@angular/cdk": "^18.2.4",
|
||||||
"@angular/common": "^18.0.0",
|
"@angular/common": "^18.0.0",
|
||||||
@@ -23,6 +24,7 @@
|
|||||||
"@ngneat/overview": "^6.0.0",
|
"@ngneat/overview": "^6.0.0",
|
||||||
"@ngxpert/hot-toast": "^3.0.1",
|
"@ngxpert/hot-toast": "^3.0.1",
|
||||||
"ag-grid-angular": "^32.1.0",
|
"ag-grid-angular": "^32.1.0",
|
||||||
|
"ag-grid-community": "^32.1.0",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"zone.js": "~0.14.3"
|
"zone.js": "~0.14.3"
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export class AuthService {
|
|||||||
private router: Router = inject(Router);
|
private router: Router = inject(Router);
|
||||||
private toast: HotToastService = inject(HotToastService);
|
private toast: HotToastService = inject(HotToastService);
|
||||||
|
|
||||||
private user: IUser | null = null;
|
private _user: IUser | null = null;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const token = localStorage.getItem('accessToken_vault');
|
const token = localStorage.getItem('accessToken_vault');
|
||||||
@@ -27,6 +27,14 @@ export class AuthService {
|
|||||||
this.refreshToken = refresh;
|
this.refreshToken = refresh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get user(): IUser {
|
||||||
|
return this._user!;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isAdmin(): boolean {
|
||||||
|
console.log(this.user, this.user.role == 'admin')
|
||||||
|
return this.user != null && this.user.role == 'admin';
|
||||||
|
}
|
||||||
|
|
||||||
getMe() {
|
getMe() {
|
||||||
if (!this.getAccessToken()) {
|
if (!this.getAccessToken()) {
|
||||||
@@ -35,7 +43,7 @@ export class AuthService {
|
|||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
this.http.get<IUser>('/api/auth/me').subscribe({
|
this.http.get<IUser>('/api/auth/me').subscribe({
|
||||||
next: user => {
|
next: user => {
|
||||||
this.user = user;
|
this._user = user;
|
||||||
resolve(true)
|
resolve(true)
|
||||||
},
|
},
|
||||||
error: () => {
|
error: () => {
|
||||||
@@ -51,7 +59,7 @@ export class AuthService {
|
|||||||
this.http.post<IUser>('/api/auth/auth-code', { code: authcode }).subscribe({
|
this.http.post<IUser>('/api/auth/auth-code', { code: authcode }).subscribe({
|
||||||
next: user => {
|
next: user => {
|
||||||
this.setTokens({ accessToken: user.accessToken, refreshToken: user.refreshToken});
|
this.setTokens({ accessToken: user.accessToken, refreshToken: user.refreshToken});
|
||||||
this.user = user;
|
this._user = user;
|
||||||
return resolve(true)
|
return resolve(true)
|
||||||
},
|
},
|
||||||
error: () => {
|
error: () => {
|
||||||
@@ -63,7 +71,7 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get authenticated(): boolean {
|
get authenticated(): boolean {
|
||||||
return this.user != null;
|
return this._user != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
<button mat-icon-button (click)="drawer.toggle()">
|
<button mat-icon-button (click)="drawer.toggle()">
|
||||||
<mat-icon>menu</mat-icon>
|
<mat-icon>menu</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<span routerLink="/" class="logo">Keyvault</span>
|
<span routerLink="/" class="logo">Keyvault Pro</span>
|
||||||
<span class="spacer"></span>
|
<span class="spacer"></span>
|
||||||
|
<span>{{ userName }}</span>
|
||||||
<button mat-icon-button (click)="logout()">
|
<button mat-icon-button (click)="logout()">
|
||||||
<mat-icon>logout</mat-icon>
|
<mat-icon>logout</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -30,3 +30,7 @@ mat-drawer {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mat-toolbar {
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
@@ -19,4 +19,8 @@ export class LayoutComponent {
|
|||||||
logout(){
|
logout(){
|
||||||
this.authService.logout();
|
this.authService.logout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get userName(): string {
|
||||||
|
return `${this.authService.user.firstName} ${this.authService.user.lastName}`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ export interface IUser {
|
|||||||
firstName: String;
|
firstName: String;
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
|
role: string;
|
||||||
}
|
}
|
||||||
@@ -4,11 +4,15 @@ import { ApiService } from '../../../shared/api.service';
|
|||||||
import { AgGridAngular } from 'ag-grid-angular';
|
import { AgGridAngular } from 'ag-grid-angular';
|
||||||
import { GridOptions,GridApi, GridReadyEvent, CellEditingStoppedEvent } from 'ag-grid-community';
|
import { GridOptions,GridApi, GridReadyEvent, CellEditingStoppedEvent } from 'ag-grid-community';
|
||||||
import { HotToastService } from '@ngxpert/hot-toast';
|
import { HotToastService } from '@ngxpert/hot-toast';
|
||||||
|
import { AuthService } from '../../../core/auth/auth.service';
|
||||||
|
import { DatePipe } from '@angular/common';
|
||||||
|
import { AG_GRID_LOCALE_DE } from '@ag-grid-community/locale';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-all-users',
|
selector: 'app-all-users',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [AgGridAngular],
|
imports: [AgGridAngular],
|
||||||
|
providers: [DatePipe],
|
||||||
templateUrl: './all-users.component.html',
|
templateUrl: './all-users.component.html',
|
||||||
styleUrl: './all-users.component.scss'
|
styleUrl: './all-users.component.scss'
|
||||||
})
|
})
|
||||||
@@ -16,24 +20,41 @@ export class AllUsersComponent {
|
|||||||
|
|
||||||
private toast: HotToastService = inject(HotToastService);
|
private toast: HotToastService = inject(HotToastService);
|
||||||
private api: ApiService = inject(ApiService);
|
private api: ApiService = inject(ApiService);
|
||||||
|
private authService = inject(AuthService);
|
||||||
|
private datePipe = inject(DatePipe);
|
||||||
|
|
||||||
gridApi!: GridApi;
|
gridApi!: GridApi;
|
||||||
|
|
||||||
private roles: string [] = [];
|
|
||||||
|
|
||||||
gridOptions: GridOptions = {
|
gridOptions: GridOptions = {
|
||||||
|
localeText: AG_GRID_LOCALE_DE,
|
||||||
rowData: [],
|
rowData: [],
|
||||||
columnDefs: [
|
columnDefs: [
|
||||||
{ field: 'username' , headerName: 'User', flex: 1, editable: true, sort: 'asc' },
|
{ field: 'username' , headerName: 'User', flex: 1, editable: this.authService.isAdmin, sort: 'asc', filter: true },
|
||||||
{ field: 'firstName', headerName: 'Vorname', flex: 1, editable: true},
|
{ field: 'firstName', headerName: 'Vorname', flex: 1, editable: this.authService.isAdmin, filter: true},
|
||||||
{ field: 'lastName', headerName: 'Nachname', flex: 1, editable: true},
|
{ field: 'lastName', headerName: 'Nachname', flex: 1, editable: this.authService.isAdmin, filter: true},
|
||||||
{ field: 'isActive', headerName: 'Aktiv', flex: 1, editable: true, },
|
{ field: 'isActive', headerName: 'Aktiv', width: 70, editable: (params) => params.data.id != this.authService.user.id && this.authService.isAdmin, },
|
||||||
{ field: 'role', headerName: 'Rolle', flex: 1, editable: true, cellEditor: 'agSelectCellEditor',
|
{ field: 'role', headerName: 'Rolle', width: 100 ,editable: this.authService.isAdmin, cellEditor: 'agSelectCellEditor',
|
||||||
cellEditorParams: {
|
cellEditorParams: {
|
||||||
values: ['user', 'develop', 'admin'],
|
values: ['user', 'develop', 'admin'],
|
||||||
},
|
},
|
||||||
singleClickEdit: true,
|
singleClickEdit: true,
|
||||||
|
cellEditorPopup: false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
field: 'createdAt'
|
||||||
|
, headerName: 'Erstellt'
|
||||||
|
, width: 120
|
||||||
|
, type: 'date'
|
||||||
|
, cellRenderer: (data: any) => this.datePipe.transform(new Date(data.value))
|
||||||
|
, tooltipValueGetter: (data: any) => this.datePipe.transform(new Date(data.value), 'medium')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'lastLogin'
|
||||||
|
, headerName: 'Letzter Login'
|
||||||
|
, width: 170
|
||||||
|
, type: 'date'
|
||||||
|
, cellRenderer: (data: any) => data.value ? this.datePipe.transform(new Date(data.value), 'medium') : '-'
|
||||||
|
}
|
||||||
],
|
],
|
||||||
loading: true,
|
loading: true,
|
||||||
overlayLoadingTemplate: 'Lade Daten...'
|
overlayLoadingTemplate: 'Lade Daten...'
|
||||||
@@ -42,6 +63,7 @@ export class AllUsersComponent {
|
|||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
loadUsers() {
|
loadUsers() {
|
||||||
this.api.getAllUsers().subscribe({
|
this.api.getAllUsers().subscribe({
|
||||||
next: n => {
|
next: n => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Client</title>
|
<title>Keyvault Pro</title>
|
||||||
<base href="/">
|
<base href="/">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="icon" href="key.svg">
|
<link rel="icon" href="key.svg">
|
||||||
|
|||||||
Reference in New Issue
Block a user