diff --git a/api/src/model/entitites/user.entity.ts b/api/src/model/entitites/user.entity.ts index 1fb532d..c2a3c5a 100644 --- a/api/src/model/entitites/user.entity.ts +++ b/api/src/model/entitites/user.entity.ts @@ -41,7 +41,7 @@ export class User implements IUser { @Column({ default: true }) isActive: boolean; - @ManyToOne(() => Role, (role) => role.user, { cascade: true }) + @ManyToOne(() => Role, (role) => role.user, { cascade: true, eager: true }) @JoinColumn() @Transform(({ value }) => value.name) role: Role; diff --git a/api/src/modules/auth/auth.service.ts b/api/src/modules/auth/auth.service.ts index f802dbc..d5e0113 100644 --- a/api/src/modules/auth/auth.service.ts +++ b/api/src/modules/auth/auth.service.ts @@ -56,7 +56,10 @@ export class AuthService { username: payload.username, externalId: payload.id, }); + } else { + user.lastLogin = new Date(); } + if (!user.isActive) { return resolve(null); } @@ -87,6 +90,8 @@ export class AuthService { }; user.refreshToken = this.jwt.sign(rPay, { expiresIn: '1w' }); + user.lastLogin = new Date(); + this.userRepo.save(user); } private createAuthCodeFormData( diff --git a/client/package-lock.json b/client/package-lock.json index 655adf7..c520eaa 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -8,6 +8,7 @@ "name": "client", "version": "0.0.0", "dependencies": { + "@ag-grid-community/locale": "^32.1.0", "@angular/animations": "^18.0.0", "@angular/cdk": "^18.2.4", "@angular/common": "^18.0.0", @@ -21,6 +22,7 @@ "@ngneat/overview": "^6.0.0", "@ngxpert/hot-toast": "^3.0.1", "ag-grid-angular": "^32.1.0", + "ag-grid-community": "^32.1.0", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.14.3" @@ -39,6 +41,11 @@ "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": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -4497,8 +4504,7 @@ "node_modules/ag-charts-types": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/ag-charts-types/-/ag-charts-types-10.1.0.tgz", - "integrity": "sha512-pk9ft8hbgTXJ/thI/SEUR1BoauNplYExpcHh7tMOqVikoDsta1O15TB1ZL4XWnl4TPIzROBmONKsz7d8a2HBuQ==", - "peer": true + "integrity": "sha512-pk9ft8hbgTXJ/thI/SEUR1BoauNplYExpcHh7tMOqVikoDsta1O15TB1ZL4XWnl4TPIzROBmONKsz7d8a2HBuQ==" }, "node_modules/ag-grid-angular": { "version": "32.1.0", @@ -4517,7 +4523,6 @@ "version": "32.1.0", "resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-32.1.0.tgz", "integrity": "sha512-RVvkjRH61nuCXwIqTKQPqNbKR+8cGBKw7S1qmmMXsy0pCBAJaQn4kL3v31hKHxDtV4bPscBXLFKGnKzHuss0GQ==", - "peer": true, "dependencies": { "ag-charts-types": "10.1.0" } diff --git a/client/package.json b/client/package.json index 3a388ed..166d1c2 100644 --- a/client/package.json +++ b/client/package.json @@ -10,6 +10,7 @@ }, "private": true, "dependencies": { + "@ag-grid-community/locale": "^32.1.0", "@angular/animations": "^18.0.0", "@angular/cdk": "^18.2.4", "@angular/common": "^18.0.0", @@ -23,6 +24,7 @@ "@ngneat/overview": "^6.0.0", "@ngxpert/hot-toast": "^3.0.1", "ag-grid-angular": "^32.1.0", + "ag-grid-community": "^32.1.0", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.14.3" @@ -40,4 +42,4 @@ "karma-jasmine-html-reporter": "~2.1.0", "typescript": "~5.4.2" } -} \ No newline at end of file +} diff --git a/client/src/app/core/auth/auth.service.ts b/client/src/app/core/auth/auth.service.ts index 7a557f0..cf87c03 100644 --- a/client/src/app/core/auth/auth.service.ts +++ b/client/src/app/core/auth/auth.service.ts @@ -17,7 +17,7 @@ export class AuthService { private router: Router = inject(Router); private toast: HotToastService = inject(HotToastService); - private user: IUser | null = null; + private _user: IUser | null = null; constructor() { const token = localStorage.getItem('accessToken_vault'); @@ -27,6 +27,14 @@ export class AuthService { 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() { if (!this.getAccessToken()) { @@ -35,7 +43,7 @@ export class AuthService { return new Promise(resolve => { this.http.get('/api/auth/me').subscribe({ next: user => { - this.user = user; + this._user = user; resolve(true) }, error: () => { @@ -51,7 +59,7 @@ export class AuthService { this.http.post('/api/auth/auth-code', { code: authcode }).subscribe({ next: user => { this.setTokens({ accessToken: user.accessToken, refreshToken: user.refreshToken}); - this.user = user; + this._user = user; return resolve(true) }, error: () => { @@ -63,7 +71,7 @@ export class AuthService { } get authenticated(): boolean { - return this.user != null; + return this._user != null; } diff --git a/client/src/app/core/layout/layout.component.html b/client/src/app/core/layout/layout.component.html index 9c2daa5..092edc1 100644 --- a/client/src/app/core/layout/layout.component.html +++ b/client/src/app/core/layout/layout.component.html @@ -2,8 +2,9 @@ - + + {{ userName }} diff --git a/client/src/app/core/layout/layout.component.scss b/client/src/app/core/layout/layout.component.scss index 8a5803b..603a6e9 100644 --- a/client/src/app/core/layout/layout.component.scss +++ b/client/src/app/core/layout/layout.component.scss @@ -29,4 +29,8 @@ mat-drawer { button { width: 100%; } +} + +mat-toolbar { + gap: 12px; } \ No newline at end of file diff --git a/client/src/app/core/layout/layout.component.ts b/client/src/app/core/layout/layout.component.ts index 67e21c3..1b2d958 100644 --- a/client/src/app/core/layout/layout.component.ts +++ b/client/src/app/core/layout/layout.component.ts @@ -19,4 +19,8 @@ export class LayoutComponent { logout(){ this.authService.logout(); } + + get userName(): string { + return `${this.authService.user.firstName} ${this.authService.user.lastName}` + } } diff --git a/client/src/app/model/interface/user.interface.ts b/client/src/app/model/interface/user.interface.ts index af4250d..f78ff82 100644 --- a/client/src/app/model/interface/user.interface.ts +++ b/client/src/app/model/interface/user.interface.ts @@ -5,4 +5,5 @@ export interface IUser { firstName: String; refreshToken: string; accessToken: string; + role: string; } \ No newline at end of file diff --git a/client/src/app/modules/admin/all-users/all-users.component.ts b/client/src/app/modules/admin/all-users/all-users.component.ts index 74d5634..d90de35 100644 --- a/client/src/app/modules/admin/all-users/all-users.component.ts +++ b/client/src/app/modules/admin/all-users/all-users.component.ts @@ -4,11 +4,15 @@ import { ApiService } from '../../../shared/api.service'; import { AgGridAngular } from 'ag-grid-angular'; import { GridOptions,GridApi, GridReadyEvent, CellEditingStoppedEvent } from 'ag-grid-community'; 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({ selector: 'app-all-users', standalone: true, imports: [AgGridAngular], + providers: [DatePipe], templateUrl: './all-users.component.html', styleUrl: './all-users.component.scss' }) @@ -16,24 +20,41 @@ export class AllUsersComponent { private toast: HotToastService = inject(HotToastService); private api: ApiService = inject(ApiService); + private authService = inject(AuthService); + private datePipe = inject(DatePipe); gridApi!: GridApi; - private roles: string [] = []; - gridOptions: GridOptions = { + localeText: AG_GRID_LOCALE_DE, rowData: [], columnDefs: [ - { field: 'username' , headerName: 'User', flex: 1, editable: true, sort: 'asc' }, - { field: 'firstName', headerName: 'Vorname', flex: 1, editable: true}, - { field: 'lastName', headerName: 'Nachname', flex: 1, editable: true}, - { field: 'isActive', headerName: 'Aktiv', flex: 1, editable: true, }, - { field: 'role', headerName: 'Rolle', flex: 1, editable: true, cellEditor: 'agSelectCellEditor', + { field: 'username' , headerName: 'User', flex: 1, editable: this.authService.isAdmin, sort: 'asc', filter: true }, + { field: 'firstName', headerName: 'Vorname', flex: 1, editable: this.authService.isAdmin, filter: true}, + { field: 'lastName', headerName: 'Nachname', flex: 1, editable: this.authService.isAdmin, filter: true}, + { field: 'isActive', headerName: 'Aktiv', width: 70, editable: (params) => params.data.id != this.authService.user.id && this.authService.isAdmin, }, + { field: 'role', headerName: 'Rolle', width: 100 ,editable: this.authService.isAdmin, cellEditor: 'agSelectCellEditor', cellEditorParams: { values: ['user', 'develop', 'admin'], }, 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, overlayLoadingTemplate: 'Lade Daten...' @@ -42,6 +63,7 @@ export class AllUsersComponent { ngOnInit(): void { } + loadUsers() { this.api.getAllUsers().subscribe({ next: n => { diff --git a/client/src/index.html b/client/src/index.html index 50b5df8..da35e08 100644 --- a/client/src/index.html +++ b/client/src/index.html @@ -2,7 +2,7 @@ - Client + Keyvault Pro