- Angular 17 with standalone components - Angular Material + Tailwind CSS - OIDC authorization code flow with Authentik - Role-based access control (USER, DEVELOPER, APPROVER, ADMIN) - Dashboard with pending requests, tunnel list, and create mapping - Nginx reverse proxy to backend API - Multi-container Docker Compose setup (frontend, backend, postgres) - Environment-based configuration (local, test, prod)
168 lines
4.2 KiB
TypeScript
168 lines
4.2 KiB
TypeScript
import { Component, OnInit } from '@angular/core';
|
|
import { CommonModule } from '@angular/common';
|
|
import { MatToolbarModule } from '@angular/material/toolbar';
|
|
import { MatButtonModule } from '@angular/material/button';
|
|
import { MatIconModule } from '@angular/material/icon';
|
|
import { MatSidenavModule } from '@angular/material/sidenav';
|
|
import { MatListModule } from '@angular/material/list';
|
|
import { MatCardModule } from '@angular/material/card';
|
|
import { AuthService } from '../../core/auth/auth.service';
|
|
import { ApiService } from '../../core/services/api.service';
|
|
import { User, Request, Tunnel } from '../../shared/models';
|
|
import { PendingRequestsComponent } from './pending-requests/pending-requests.component';
|
|
import { TunnelListComponent } from './tunnel-list/tunnel-list.component';
|
|
import { CreateMappingComponent } from './create-mapping/create-mapping.component';
|
|
|
|
@Component({
|
|
selector: 'app-dashboard',
|
|
standalone: true,
|
|
imports: [
|
|
CommonModule,
|
|
MatToolbarModule,
|
|
MatButtonModule,
|
|
MatIconModule,
|
|
MatSidenavModule,
|
|
MatListModule,
|
|
MatCardModule,
|
|
PendingRequestsComponent,
|
|
TunnelListComponent,
|
|
CreateMappingComponent,
|
|
],
|
|
template: `
|
|
<mat-toolbar color="primary" class="toolbar">
|
|
<span class="title">
|
|
<mat-icon>hub</mat-icon>
|
|
CFTunnels
|
|
</span>
|
|
<span class="spacer"></span>
|
|
<span class="user-info" *ngIf="user">
|
|
{{ user.username }}
|
|
<span class="roles">({{ user.roles.join(', ') }})</span>
|
|
</span>
|
|
<button mat-icon-button (click)="logout()" title="Logout">
|
|
<mat-icon>logout</mat-icon>
|
|
</button>
|
|
</mat-toolbar>
|
|
|
|
<div class="dashboard-container">
|
|
<div class="dashboard-content">
|
|
<!-- Pending Requests Section - APPROVER/ADMIN only -->
|
|
@if (canApprove()) {
|
|
<app-pending-requests
|
|
[requests]="pendingRequests"
|
|
(requestUpdated)="loadRequests()"
|
|
></app-pending-requests>
|
|
}
|
|
|
|
<!-- Tunnel List Section - DEVELOPER+ -->
|
|
@if (canViewTunnels()) {
|
|
<app-tunnel-list [tunnels]="tunnels"></app-tunnel-list>
|
|
}
|
|
|
|
<!-- Create Mapping Section - USER+ -->
|
|
<app-create-mapping></app-create-mapping>
|
|
</div>
|
|
</div>
|
|
`,
|
|
styles: [
|
|
`
|
|
.toolbar {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 1000;
|
|
}
|
|
|
|
.title {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.spacer {
|
|
flex: 1 1 auto;
|
|
}
|
|
|
|
.user-info {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
margin-right: 1rem;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.roles {
|
|
color: rgba(255, 255, 255, 0.7);
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
.dashboard-container {
|
|
display: flex;
|
|
justify-content: center;
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
.dashboard-content {
|
|
width: 100%;
|
|
max-width: 900px;
|
|
}
|
|
`,
|
|
],
|
|
})
|
|
export class DashboardComponent implements OnInit {
|
|
user: User | null = null;
|
|
pendingRequests: Request[] = [];
|
|
tunnels: Tunnel[] = [];
|
|
|
|
constructor(
|
|
private authService: AuthService,
|
|
private apiService: ApiService
|
|
) {}
|
|
|
|
ngOnInit(): void {
|
|
this.authService.user$.subscribe((user) => {
|
|
this.user = user;
|
|
if (user) {
|
|
this.loadData();
|
|
}
|
|
});
|
|
}
|
|
|
|
loadData(): void {
|
|
this.loadRequests();
|
|
this.loadTunnels();
|
|
}
|
|
|
|
loadRequests(): void {
|
|
this.apiService.getRequests().subscribe({
|
|
next: (response) => {
|
|
this.pendingRequests = response.data.filter(
|
|
(r) => r.status === 'PENDING'
|
|
);
|
|
},
|
|
error: (err) => console.error('Error loading requests:', err),
|
|
});
|
|
}
|
|
|
|
loadTunnels(): void {
|
|
this.apiService.getConfiguredTunnels().subscribe({
|
|
next: (response) => {
|
|
this.tunnels = response.data;
|
|
},
|
|
error: (err) => console.error('Error loading tunnels:', err),
|
|
});
|
|
}
|
|
|
|
canApprove(): boolean {
|
|
return this.authService.hasAnyRole(['APPROVER', 'ADMIN']);
|
|
}
|
|
|
|
canViewTunnels(): boolean {
|
|
return this.authService.hasAnyRole(['DEVELOPER', 'APPROVER', 'ADMIN']);
|
|
}
|
|
|
|
logout(): void {
|
|
this.authService.logout();
|
|
}
|
|
}
|