dashboard
This commit is contained in:
@@ -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()]
|
||||
};
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<div class="header">{{ client.clientName }}</div>
|
||||
<div class="body">
|
||||
{{ client.description }}
|
||||
</div>
|
||||
<div class="footer">
|
||||
<button mat-button >Admins</button>
|
||||
<button mat-button >Redirect URIs</button>
|
||||
<button mat-flat-button color="warn" (click)="deleteClient()">Löschen</button>
|
||||
</div>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CardComponent } from './card.component';
|
||||
|
||||
describe('CardComponent', () => {
|
||||
let component: CardComponent;
|
||||
let fixture: ComponentFixture<CardComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [CardComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(CardComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -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<Client>();
|
||||
|
||||
private http = inject(HttpClient);
|
||||
private toast = inject(HotToastService);
|
||||
|
||||
|
||||
|
||||
deleteClient() {
|
||||
this.onDelete.emit(this.client);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<h2 mat-dialog-title>Neue Clientanwendung erstellen</h2>
|
||||
<mat-dialog-content>
|
||||
|
||||
|
||||
|
||||
<mat-stepper linear orientation="horizontal" #stepper>
|
||||
<mat-step [stepControl]="createClient" [editable]="true">
|
||||
|
||||
<form [formGroup]="createClient" >
|
||||
<ng-template matStepLabel>Basic Data</ng-template>
|
||||
<mat-form-field appearance="fill" >
|
||||
<mat-label>Name</mat-label>
|
||||
<input matInput placeholder="Name der Anwendung" formControlName="clientName">
|
||||
<mat-icon matSuffix>badge</mat-icon>
|
||||
<mat-hint>Name der Anwendung</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Client Secret</mat-label>
|
||||
<input matInput placeholder="Placeholder" formControlName="clientSecret">
|
||||
<mat-icon matSuffix>key</mat-icon>
|
||||
<mat-hint>Das kann danach nicht mehr angeschaut werden</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Beschreibung</mat-label>
|
||||
<textarea matInput placeholder="Placeholder" formControlName="description" rows="4"></textarea>
|
||||
<mat-icon matSuffix>description</mat-icon>
|
||||
<mat-hint>Beschreibung der Anwendung</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
</form>
|
||||
|
||||
<button mat-raised-button (click)="create()" [disabled]="createClient.invalid || isSaving">
|
||||
<div class="flex-row">
|
||||
<mat-spinner [diameter]="16" *ngIf="isSaving"></mat-spinner>
|
||||
<div>Next</div>
|
||||
</div>
|
||||
</button>
|
||||
</mat-step>
|
||||
<mat-step [editable]="true">
|
||||
<ng-template matStepLabel>Admins und Redirect URLs</ng-template>
|
||||
<div style="flex: 1 1 auto">space</div>
|
||||
<div> {{ client?.clientName }}</div>
|
||||
</mat-step>
|
||||
</mat-stepper>
|
||||
|
||||
|
||||
|
||||
|
||||
</mat-dialog-content>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<CreateClientComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [CreateClientComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(CreateClientComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -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<Client>('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;
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1 +1,29 @@
|
||||
<p>dashboard works!</p>
|
||||
<div class="header">
|
||||
<div class="title">SSO Beantastic</div>
|
||||
<div> {{ userName }} </div>
|
||||
</div>
|
||||
|
||||
<div class="create-container">
|
||||
<div class="flex-row">
|
||||
<button mat-fab id="createClient" (click)="createClient()" [disabled]="clients.length == 10" >
|
||||
<mat-icon>add</mat-icon>
|
||||
</button>
|
||||
|
||||
<label for="createClient">
|
||||
Client erstellen
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex-row" style="gap: 4px;">
|
||||
<div class="chip"><span class="counter">{{ clients.length }}</span> von 10</div>
|
||||
<div>Clients erstellt</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="card-container">
|
||||
<div class="card-container__list">
|
||||
@for (client of clients; track $index) {
|
||||
<app-card [client]="client" (onDelete)="openDeleteDialog($event)" ></app-card>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<Client[]>('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<unknown>) {
|
||||
|
||||
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: `
|
||||
<h3>Soll der Client wirklich gelöscht werden?</h3>
|
||||
<h5>Danach kann sich niemand mehr einloggen und alle Daten werden gelöscht.</h5>
|
||||
<div class="flex-row" style="justify-content: space-between">
|
||||
<button mat-flat-button color="warn" (click)="delete()">Löschen</button>
|
||||
<button mat-flat-button (click)="cancel()">Abbrechen</button>
|
||||
</div>
|
||||
`,
|
||||
standalone: true,
|
||||
imports: [MatButtonModule],
|
||||
})
|
||||
export class DeleteClientConfirmation {
|
||||
private _bottomSheetRef =
|
||||
inject<MatBottomSheetRef<DeleteClientConfirmation>>(MatBottomSheetRef);
|
||||
|
||||
delete() {
|
||||
this._bottomSheetRef.dismiss(true)
|
||||
}
|
||||
cancel() {
|
||||
this._bottomSheetRef.dismiss(false)
|
||||
}
|
||||
}
|
||||
10
idp_client/src/app/model/client.interface.ts
Normal file
10
idp_client/src/app/model/client.interface.ts
Normal file
@@ -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[];
|
||||
}
|
||||
4
idp_client/src/app/model/redirect-uri.interface.ts
Normal file
4
idp_client/src/app/model/redirect-uri.interface.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface RedirectUri {
|
||||
id?: string;
|
||||
uri: string;
|
||||
}
|
||||
Reference in New Issue
Block a user