diff --git a/idp/src/application/user/user.controller.ts b/idp/src/application/user/user.controller.ts index 3f52130..c5f1033 100644 --- a/idp/src/application/user/user.controller.ts +++ b/idp/src/application/user/user.controller.ts @@ -1,11 +1,18 @@ -import { Controller, Get, Req, UseGuards } from '@nestjs/common'; +import { Body, Controller, Delete, Get, Param, Post, Req, UseGuards } from '@nestjs/common'; import { UserService } from './user.service'; import { AuthGuard, Roles, RolesGuard } from 'src/core/secure/guards'; +import { Client } from 'src/model/client.entity'; +import { AuthenticatedRequest } from 'src/model'; +import { CreateClientDto } from 'src/model/dto/create-client.dto'; +import { ClientService } from 'src/client/client.service'; @UseGuards(AuthGuard, RolesGuard) @Controller('app/user') export class UserController { - constructor(private userService: UserService) {} + constructor( + private userService: UserService, + private clientService: ClientService, + ) {} @Get() getIt() { @@ -14,7 +21,25 @@ export class UserController { @Roles('admin') @Get('clients') - getClients(@Req() req: any) { - return this.userService.getUserClients(req['user']); + getClients(@Req() req: AuthenticatedRequest): Promise { + return this.userService.getUserClients(req.user); + } + + @Roles('admin') + @Post('client') + createClient(@Req() req: AuthenticatedRequest, @Body() b: CreateClientDto) { + return this.clientService.createClient( + req.user, + b.clientName, + b.clientSecret, + [], + b.description, + ); + } + + @Roles('admin') + @Delete('client/:id') + deleteClient(@Req() req: AuthenticatedRequest, @Param('id') id) { + return this.clientService.deleteClient(req.user, id); } } diff --git a/idp/src/application/user/user.module.ts b/idp/src/application/user/user.module.ts index 0f5e404..4536de8 100644 --- a/idp/src/application/user/user.module.ts +++ b/idp/src/application/user/user.module.ts @@ -3,10 +3,11 @@ import { UserController } from './user.controller'; import { SecureModule } from 'src/core/secure/secure.module'; import { UserService } from './user.service'; import { ClientRepository } from 'src/model/client.entity'; +import { ClientService } from 'src/client/client.service'; @Module({ controllers: [UserController], imports: [SecureModule], - providers: [UserService, ClientRepository], + providers: [UserService, ClientRepository, ClientService], }) export class UserModule {} diff --git a/idp/src/application/user/user.service.ts b/idp/src/application/user/user.service.ts index 5426adb..49ed7f2 100644 --- a/idp/src/application/user/user.service.ts +++ b/idp/src/application/user/user.service.ts @@ -1,13 +1,15 @@ import { Injectable } from '@nestjs/common'; -import { ClientRepository } from 'src/model/client.entity'; +import { Client, ClientRepository } from 'src/model/client.entity'; import { User } from 'src/model/user.entity'; @Injectable() export class UserService { constructor(private clientRepository: ClientRepository) {} - getUserClients(user: User) { - console.log(user.id); - return this.clientRepository.find(); + getUserClients(user: User): Promise { + return this.clientRepository.find({ + where: { admins: { id: user.id } }, + relations: ['admins'], + }); } } diff --git a/idp/src/client/client.service.ts b/idp/src/client/client.service.ts index 092f1d2..1ab3315 100644 --- a/idp/src/client/client.service.ts +++ b/idp/src/client/client.service.ts @@ -2,15 +2,18 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { v4 as uuidv4 } from 'uuid'; import { Client, ClientRepository } from 'src/model/client.entity'; import { RedirectUri } from 'src/model/redirect-uri.entity'; +import { User } from 'src/model/user.entity'; @Injectable() export class ClientService { constructor(private clientRepo: ClientRepository) {} async createClient( + user: User, clientName: string, clientSecret: string, redirectUris: string[], + description = '', ): Promise { const clientDto: Client = { id: uuidv4(), @@ -18,8 +21,10 @@ export class ClientService { clientSecret, redirectUris: [], authorizationCodes: [], + description, }; const c = this.clientRepo.create(clientDto); + c.admins = [user]; const client = await this.clientRepo.save(c); redirectUris.forEach(async (uri) => { @@ -59,4 +64,16 @@ export class ClientService { getClientById(clientId: string): Promise { return this.clientRepo.findById(clientId); } + + async deleteClient(user: User, id: string) { + const client = await this.clientRepo.findOne({ + where: { admins: { id: user.id }, id: id }, + }); + + if (client) { + return this.clientRepo.remove(client); + } + + throw new HttpException('client not found', HttpStatus.BAD_REQUEST); + } } diff --git a/idp/src/model/client.entity.ts b/idp/src/model/client.entity.ts index d54faa1..e084112 100644 --- a/idp/src/model/client.entity.ts +++ b/idp/src/model/client.entity.ts @@ -7,9 +7,12 @@ import { DataSource, Repository, OneToMany, + ManyToMany, + JoinTable, } from 'typeorm'; import { RedirectUri } from './redirect-uri.entity'; import { AuthorizationCode } from './auth-code.entity'; +import { User } from './user.entity'; @Entity() export class Client { @@ -23,6 +26,9 @@ export class Client { @Column() clientSecret: string; + @Column({ type: 'text', nullable: true }) + description: string; + @OneToMany(() => RedirectUri, (uri) => uri.client, { cascade: true, eager: true, @@ -31,6 +37,10 @@ export class Client { @OneToMany(() => AuthorizationCode, (code) => code.client) authorizationCodes: AuthorizationCode[]; + + @ManyToMany(() => User, (user) => user.clients, { onDelete: 'CASCADE' }) + @JoinTable() + admins?: User[]; } @Injectable() diff --git a/idp/src/model/dto/create-client.dto.ts b/idp/src/model/dto/create-client.dto.ts new file mode 100644 index 0000000..dd15786 --- /dev/null +++ b/idp/src/model/dto/create-client.dto.ts @@ -0,0 +1,5 @@ +export interface CreateClientDto { + clientName: string; + clientSecret: string; + description: string; +} diff --git a/idp/src/model/index.ts b/idp/src/model/index.ts new file mode 100644 index 0000000..fc141f7 --- /dev/null +++ b/idp/src/model/index.ts @@ -0,0 +1 @@ +export * from './interface'; diff --git a/idp/src/model/interface/authenticated.request.ts b/idp/src/model/interface/authenticated.request.ts new file mode 100644 index 0000000..949c535 --- /dev/null +++ b/idp/src/model/interface/authenticated.request.ts @@ -0,0 +1,5 @@ +import { User } from '../user.entity'; + +export interface AuthenticatedRequest extends Request { + user: User; +} diff --git a/idp/src/model/interface/index.ts b/idp/src/model/interface/index.ts new file mode 100644 index 0000000..122d018 --- /dev/null +++ b/idp/src/model/interface/index.ts @@ -0,0 +1 @@ +export * from './authenticated.request'; diff --git a/idp/src/model/user.entity.ts b/idp/src/model/user.entity.ts index b804e7e..b8f509f 100644 --- a/idp/src/model/user.entity.ts +++ b/idp/src/model/user.entity.ts @@ -9,10 +9,12 @@ import { OneToMany, CreateDateColumn, ManyToOne, + ManyToMany, } from 'typeorm'; import { AuthorizationCode } from './auth-code.entity'; import { SessionKey } from './session-key.entity'; import { Role } from './role.entity'; +import { Client } from './client.entity'; @Entity() export class User { @@ -51,6 +53,10 @@ export class User { @ManyToOne(() => Role, (role) => role.users) role?: Role; + @Exclude() + @ManyToMany(() => Client, (client) => client.admins) + clients?: Client[]; + accessToken?: string; refreshToken?: string; session_key?: string; diff --git a/idp/src/users/users.service.ts b/idp/src/users/users.service.ts index 3383685..33d27ac 100644 --- a/idp/src/users/users.service.ts +++ b/idp/src/users/users.service.ts @@ -24,7 +24,6 @@ export class UsersService { private sessionRepo: SessionKeyRepository, private logger: CustomLogger, ) {} - async createUser(userDto: CreateUserDto): Promise { const hashedPassword = await bcrypt.hash(userDto.password, 10); const user: User = { diff --git a/idp_client/angular.json b/idp_client/angular.json index d5d9d93..c334ae4 100644 --- a/idp_client/angular.json +++ b/idp_client/angular.json @@ -35,6 +35,7 @@ } ], "styles": [ + "@angular/material/prebuilt-themes/azure-blue.css", "src/styles.scss", "node_modules/@ngxpert/hot-toast/src/styles/styles.css" ], diff --git a/idp_client/package-lock.json b/idp_client/package-lock.json index c9a8602..fb408bc 100644 --- a/idp_client/package-lock.json +++ b/idp_client/package-lock.json @@ -13,6 +13,7 @@ "@angular/compiler": "^18.0.0", "@angular/core": "^18.0.0", "@angular/forms": "^18.0.0", + "@angular/material": "^18.2.3", "@angular/platform-browser": "^18.0.0", "@angular/platform-browser-dynamic": "^18.0.0", "@angular/router": "^18.0.0", @@ -338,6 +339,23 @@ } } }, + "node_modules/@angular/cdk": { + "version": "18.2.3", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.3.tgz", + "integrity": "sha512-lUcpYTxPZuntJ1FK7V2ugapCGMIhT6TUDjIGgXfS9AxGSSKgwr8HNs6Ze9pcjYC44UhP40sYAZuiaFwmE60A2A==", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "optionalDependencies": { + "parse5": "^7.1.2" + }, + "peerDependencies": { + "@angular/common": "^18.0.0 || ^19.0.0", + "@angular/core": "^18.0.0 || ^19.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@angular/cli": { "version": "18.2.1", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.2.1.tgz", @@ -465,6 +483,23 @@ "rxjs": "^6.5.3 || ^7.4.0" } }, + "node_modules/@angular/material": { + "version": "18.2.3", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-18.2.3.tgz", + "integrity": "sha512-JFfvXaMHMhskncaxxus4sDvie9VYdMkfYgfinkLXpZlPFyn1IzjDw0c1BcrcsuD7UxQVZ/v5tucCgq1FQfGRpA==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/animations": "^18.0.0 || ^19.0.0", + "@angular/cdk": "18.2.3", + "@angular/common": "^18.0.0 || ^19.0.0", + "@angular/core": "^18.0.0 || ^19.0.0", + "@angular/forms": "^18.0.0 || ^19.0.0", + "@angular/platform-browser": "^18.0.0 || ^19.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@angular/platform-browser": { "version": "18.2.1", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.2.1.tgz", @@ -6165,7 +6200,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.12" }, @@ -9998,7 +10033,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", - "dev": true, + "devOptional": true, "dependencies": { "entities": "^4.4.0" }, diff --git a/idp_client/package.json b/idp_client/package.json index 9a9ee03..8ea5dbd 100644 --- a/idp_client/package.json +++ b/idp_client/package.json @@ -15,6 +15,7 @@ "@angular/compiler": "^18.0.0", "@angular/core": "^18.0.0", "@angular/forms": "^18.0.0", + "@angular/material": "^18.2.3", "@angular/platform-browser": "^18.0.0", "@angular/platform-browser-dynamic": "^18.0.0", "@angular/router": "^18.0.0", diff --git a/idp_client/src/app/app.config.ts b/idp_client/src/app/app.config.ts index 790ff96..5e405f4 100644 --- a/idp_client/src/app/app.config.ts +++ b/idp_client/src/app/app.config.ts @@ -5,7 +5,8 @@ import { routes } from './app.routes'; import { provideHttpClient, withInterceptors } from '@angular/common/http'; import { provideHotToastConfig } from '@ngxpert/hot-toast'; import { authInterceptor } from './core/interceptor/auth.interceptor'; +import { provideAnimations } from '@angular/platform-browser/animations'; export const appConfig: ApplicationConfig = { - providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideHttpClient(withInterceptors([authInterceptor])), provideHotToastConfig(),] + providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideHttpClient(withInterceptors([authInterceptor])), provideHotToastConfig(), provideAnimations()] }; diff --git a/idp_client/src/app/dashboard/components/card/card.component.html b/idp_client/src/app/dashboard/components/card/card.component.html new file mode 100644 index 0000000..e966094 --- /dev/null +++ b/idp_client/src/app/dashboard/components/card/card.component.html @@ -0,0 +1,9 @@ +
{{ client.clientName }}
+
+ {{ client.description }} +
+ \ No newline at end of file diff --git a/idp_client/src/app/dashboard/components/card/card.component.scss b/idp_client/src/app/dashboard/components/card/card.component.scss new file mode 100644 index 0000000..5e847df --- /dev/null +++ b/idp_client/src/app/dashboard/components/card/card.component.scss @@ -0,0 +1,17 @@ +:host { + background-color: #fff; + padding: 12px; + display: block; + box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, .2), 0px 2px 2px 0px rgba(0, 0, 0, .14), 0px 1px 5px 0px rgba(0, 0, 0, .12); +} + +.footer { + display: flex; + flex-direction: row; + justify-content: flex-end; + gap: 8px; +} + +.header { + font-weight: bold; +} \ No newline at end of file diff --git a/idp_client/src/app/dashboard/components/card/card.component.spec.ts b/idp_client/src/app/dashboard/components/card/card.component.spec.ts new file mode 100644 index 0000000..7fc8912 --- /dev/null +++ b/idp_client/src/app/dashboard/components/card/card.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CardComponent } from './card.component'; + +describe('CardComponent', () => { + let component: CardComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [CardComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(CardComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/idp_client/src/app/dashboard/components/card/card.component.ts b/idp_client/src/app/dashboard/components/card/card.component.ts new file mode 100644 index 0000000..2c9dd6a --- /dev/null +++ b/idp_client/src/app/dashboard/components/card/card.component.ts @@ -0,0 +1,27 @@ +import { Component, EventEmitter, inject, Input, Output } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { Client } from '../../../model/client.interface'; +import { HttpClient } from '@angular/common/http'; +import { HotToastService } from '@ngxpert/hot-toast'; + +@Component({ + selector: 'app-card', + standalone: true, + imports: [MatButtonModule], + templateUrl: './card.component.html', + styleUrl: './card.component.scss' +}) +export class CardComponent { + + @Input() client: Client; + @Output() onDelete = new EventEmitter(); + + private http = inject(HttpClient); + private toast = inject(HotToastService); + + + + deleteClient() { + this.onDelete.emit(this.client); + } +} diff --git a/idp_client/src/app/dashboard/components/create-client/create-client.component.html b/idp_client/src/app/dashboard/components/create-client/create-client.component.html new file mode 100644 index 0000000..24df442 --- /dev/null +++ b/idp_client/src/app/dashboard/components/create-client/create-client.component.html @@ -0,0 +1,52 @@ +

Neue Clientanwendung erstellen

+ + + + + + + +
+ Basic Data + + Name + + badge + Name der Anwendung + + + + Client Secret + + key + Das kann danach nicht mehr angeschaut werden + + + + Beschreibung + + description + Beschreibung der Anwendung + + +
+ + +
+ + Admins und Redirect URLs +
space
+
{{ client?.clientName }}
+
+
+ + + + +
+ diff --git a/idp_client/src/app/dashboard/components/create-client/create-client.component.scss b/idp_client/src/app/dashboard/components/create-client/create-client.component.scss new file mode 100644 index 0000000..0e77e84 --- /dev/null +++ b/idp_client/src/app/dashboard/components/create-client/create-client.component.scss @@ -0,0 +1,18 @@ +form { + display: flex; + flex-direction: column; + gap: 24px; + margin-top: 24px; + flex: 1 1 auto; +} + +:host { + display: flex; + flex-direction: column; + gap: 12px; + height: 100%; +} + +mat-dialog-content { + flex: 1 1 auto; +} \ No newline at end of file diff --git a/idp_client/src/app/dashboard/components/create-client/create-client.component.spec.ts b/idp_client/src/app/dashboard/components/create-client/create-client.component.spec.ts new file mode 100644 index 0000000..f5d0ae8 --- /dev/null +++ b/idp_client/src/app/dashboard/components/create-client/create-client.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CreateClientComponent } from './create-client.component'; + +describe('CreateClientComponent', () => { + let component: CreateClientComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [CreateClientComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(CreateClientComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/idp_client/src/app/dashboard/components/create-client/create-client.component.ts b/idp_client/src/app/dashboard/components/create-client/create-client.component.ts new file mode 100644 index 0000000..fdfdf5c --- /dev/null +++ b/idp_client/src/app/dashboard/components/create-client/create-client.component.ts @@ -0,0 +1,63 @@ +import { CommonModule } from '@angular/common'; +import { HttpClient } from '@angular/common/http'; +import { Component, inject, ViewChild } from '@angular/core'; +import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import {MatStepper, MatStepperModule} from '@angular/material/stepper'; +import { HotToastService } from '@ngxpert/hot-toast'; +import { Client } from '../../../model/client.interface'; + +@Component({ + selector: 'app-create-client', + standalone: true, + imports: [ReactiveFormsModule, MatFormFieldModule, MatInputModule, MatIconModule, MatDialogModule, MatButtonModule, MatProgressSpinnerModule, CommonModule, MatStepperModule], + templateUrl: './create-client.component.html', + styleUrl: './create-client.component.scss' +}) +export class CreateClientComponent { + + isSaving = false; + private http = inject(HttpClient); + private toast = inject(HotToastService); + + @ViewChild('stepper') stepper: MatStepper; + + createClient = new FormGroup({ + clientName: new FormControl(null, [Validators.required, Validators.minLength(4), Validators.maxLength(200)]), + clientSecret: new FormControl(null, [Validators.required, Validators.minLength(4), Validators.maxLength(200)]), + description: new FormControl(null, [Validators.required, Validators.minLength(4), Validators.maxLength(500)]) + }) + + client: Client; + + + create() { + if (this.createClient.invalid) { return; } + + + this.createClient.disable() + this.isSaving = true; + + this.http.post('api/app/user/client', this.createClient.value).pipe( + this.toast.observe({ + loading: 'Client erstellen', + error: 'Client konnte nicht erstellt werden', + success: 'Client erstellt' + }) + ) + .subscribe({ + next: data => { + this.client = data; + this.createClient.enable(); + this.stepper.next(); + this.isSaving = false; + } + }) + + } +} diff --git a/idp_client/src/app/dashboard/dashboard.component.html b/idp_client/src/app/dashboard/dashboard.component.html index 9c5fce9..58f5ef5 100644 --- a/idp_client/src/app/dashboard/dashboard.component.html +++ b/idp_client/src/app/dashboard/dashboard.component.html @@ -1 +1,29 @@ -

dashboard works!

+
+
SSO Beantastic
+
{{ userName }}
+
+ +
+
+ + + +
+
+
{{ clients.length }} von 10
+
Clients erstellt
+
+
+ + +
+
+ @for (client of clients; track $index) { + + } +
+
\ No newline at end of file diff --git a/idp_client/src/app/dashboard/dashboard.component.scss b/idp_client/src/app/dashboard/dashboard.component.scss index e69de29..f701720 100644 --- a/idp_client/src/app/dashboard/dashboard.component.scss +++ b/idp_client/src/app/dashboard/dashboard.component.scss @@ -0,0 +1,56 @@ +:host { + display: flex; + flex-direction: column; + height: 100vh; + width: 100vw; + align-items: center; + gap: 24px; + overflow: hidden; +} +.card-container { + overflow: hidden; + padding: 12px 0; + display: flex; + flex-direction: column; +} +.card-container__list{ + margin: 0 auto; + display: flex; + flex-direction: column; + gap: 12px; + width: 500px; + overflow: auto; +} + +.create-container{ + border: 1px dotted #ccc; + width: 500px; + display: flex; + padding: 12px; + justify-content: space-between; + box-sizing: border-box; +} + +.chip { + background-color: black; + color: white; + border-radius: 10px; + padding: 4px 8px; +} + +.counter{ + color:rgb(252, 239, 0); +} + +.header { + width: 100vw; + padding: 12px 24px; + display: flex; + box-sizing: border-box; + align-items: center; + justify-content: space-between; + + .title { + font-size: 1.5rem; + } +} \ No newline at end of file diff --git a/idp_client/src/app/dashboard/dashboard.component.ts b/idp_client/src/app/dashboard/dashboard.component.ts index 0650c6b..3a887b2 100644 --- a/idp_client/src/app/dashboard/dashboard.component.ts +++ b/idp_client/src/app/dashboard/dashboard.component.ts @@ -2,11 +2,19 @@ import { Component, inject, OnInit } from '@angular/core'; import { UserService } from '../auth/user.service'; import { Router } from '@angular/router'; import { HttpClient } from '@angular/common/http'; +import { CardComponent } from './components/card/card.component'; +import { Client } from '../model/client.interface'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { MatDialog, MatDialogModule } from '@angular/material/dialog'; +import { CreateClientComponent } from './components/create-client/create-client.component'; +import { CreateHotToastRef, HotToastService } from '@ngxpert/hot-toast'; +import {MatBottomSheet, MatBottomSheetModule, MatBottomSheetRef} from '@angular/material/bottom-sheet'; @Component({ selector: 'app-dashboard', standalone: true, - imports: [], + imports: [CardComponent, MatButtonModule, MatIconModule, MatDialogModule, MatBottomSheetModule], templateUrl: './dashboard.component.html', styleUrl: './dashboard.component.scss' }) @@ -15,12 +23,15 @@ export class DashboardComponent implements OnInit { private userService: UserService = inject(UserService); private router: Router = inject(Router); private http: HttpClient = inject(HttpClient); + private dialog: MatDialog = inject(MatDialog); + private toast: HotToastService = inject(HotToastService); + private bottomSheet = inject(MatBottomSheet); + + clients: Client[] = []; ngOnInit(): void { - console.log("ONINIT") if (!this.userService.user) { - console.log("REDIRECT") this.router.navigateByUrl("/login"); return; } @@ -29,11 +40,93 @@ export class DashboardComponent implements OnInit { } load() { - this.http.get('api/app/user/clients').subscribe(res => { - console.log(res) + this.http.get('api/app/user/clients').subscribe(res => { + this.clients = res; }) } - + get userName(): string { + return this.userService.user?.firstName + ' ' + this.userService.user?.lastName + } + + createClient() { + this.dialog.open(CreateClientComponent, { + panelClass: 'create-client__dialog' + }) + } + + openDeleteDialog(client: Client) { + const toast = this.toast.loading(`Lösche Client ${client.clientName} ...`, { autoClose: false}); + this.bottomSheet.open(DeleteClientConfirmation).afterDismissed() + .subscribe({ + next: data => { + if (data) { + this.deleteClient(client, toast) + } else { + toast.updateMessage('Löschen abgebrochen'); + toast.updateToast({ + type: 'error', + dismissible: true, + }) + } + } + }) + } + + deleteClient(client: Client, toast: CreateHotToastRef) { + + this.http.delete('api/app/user/client/' + client.id) + .subscribe({ + next: () => { + + toast.updateMessage(`Client ${client.clientName} gelöscht`); + toast.updateToast({ + type: 'success', + dismissible: true, + + }); + this.load(); + setTimeout(() => { + toast.close(); + }, 3000); + }, + + error: () => { + toast.updateMessage(`Client ${client.clientName} konnte nicht gelöscht werden`); + toast.updateToast({ + type: 'error', + dismissible: true, + + }) + } + }) + } + } + + +@Component({ + selector: 'bottom-sheet-overview-example-sheet', + template: ` +

Soll der Client wirklich gelöscht werden?

+
Danach kann sich niemand mehr einloggen und alle Daten werden gelöscht.
+
+ + +
+ `, + standalone: true, + imports: [MatButtonModule], +}) +export class DeleteClientConfirmation { + private _bottomSheetRef = + inject>(MatBottomSheetRef); + + delete() { + this._bottomSheetRef.dismiss(true) + } + cancel() { + this._bottomSheetRef.dismiss(false) + } +} \ No newline at end of file diff --git a/idp_client/src/app/model/client.interface.ts b/idp_client/src/app/model/client.interface.ts new file mode 100644 index 0000000..c87cf72 --- /dev/null +++ b/idp_client/src/app/model/client.interface.ts @@ -0,0 +1,10 @@ +import { RedirectUri } from "./redirect-uri.interface"; +import { User } from "./user.interface"; + +export interface Client { + admins: User; + clientName: string; + id: string; + description: string; + redirectUris: RedirectUri[]; +} \ No newline at end of file diff --git a/idp_client/src/app/model/redirect-uri.interface.ts b/idp_client/src/app/model/redirect-uri.interface.ts new file mode 100644 index 0000000..c97ea9f --- /dev/null +++ b/idp_client/src/app/model/redirect-uri.interface.ts @@ -0,0 +1,4 @@ +export interface RedirectUri { + id?: string; + uri: string; +} \ No newline at end of file diff --git a/idp_client/src/index.html b/idp_client/src/index.html index b2c29e0..51d5e25 100644 --- a/idp_client/src/index.html +++ b/idp_client/src/index.html @@ -6,6 +6,7 @@ + diff --git a/idp_client/src/styles.scss b/idp_client/src/styles.scss index dff6426..b858833 100644 --- a/idp_client/src/styles.scss +++ b/idp_client/src/styles.scss @@ -5,6 +5,7 @@ html, body { padding: 0; overflow: hidden; font-family: Raleway, sans-serif; + background-color: #f0f0f0; } .safe { @@ -13,4 +14,66 @@ html, body { .user { background-image: url("assets/icons/user.svg"); +} + +.flex-row{ + display: flex; + flex-direction: row; + align-items: center; + gap: 12px; +} + +.create-client__dialog{ + width: 70vw; + min-width: 500px; + max-width: calc(100vw - 24px); + + height: 70vh; + min-height: 600px; + max-height: 100vh; +} + +.global-spinner{ + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + background-color: rgba(80, 80, 80, 0.3); + display: flex; + align-items: center; + justify-content: center; +} + +.mat-horizontal-content-container{ + flex: 1 1 auto; + display: flex; + flex-direction: column; +} + +.mat-horizontal-stepper-content { + flex: 1 1 auto; + display: flex; + flex-direction: column; +} + +mat-dialog-content { + flex: 1 1 auto; +} + +mat-dialog-content { + height: 100%; +} + +mat-stepper { + height: 100%; +} + +.mat-horizontal-stepper-wrapper { + height: 100%; +} + +.mat-horizontal-stepper-content-inactive { + height: 0; + max-height: 0; } \ No newline at end of file