import { Component, inject, signal, OnInit } from '@angular/core'; import { CommonModule, DatePipe } from '@angular/common'; import { ActivatedRoute, Router, RouterLink } from '@angular/router'; import { FormsModule } from '@angular/forms'; import { NzCardModule } from 'ng-zorro-antd/card'; import { NzButtonModule } from 'ng-zorro-antd/button'; import { NzIconModule } from 'ng-zorro-antd/icon'; import { NzTagModule } from 'ng-zorro-antd/tag'; import { NzSpinModule } from 'ng-zorro-antd/spin'; import { NzPopconfirmModule } from 'ng-zorro-antd/popconfirm'; import { NzModalModule } from 'ng-zorro-antd/modal'; import { NzFormModule } from 'ng-zorro-antd/form'; import { NzInputModule } from 'ng-zorro-antd/input'; import { NzCheckboxModule } from 'ng-zorro-antd/checkbox'; import { NzSelectModule } from 'ng-zorro-antd/select'; import { NzToolTipModule } from 'ng-zorro-antd/tooltip'; import { NzTableModule } from 'ng-zorro-antd/table'; import { NzEmptyModule } from 'ng-zorro-antd/empty'; import { NzPaginationModule } from 'ng-zorro-antd/pagination'; import { ApiService } from '../../../core/services/api.service'; import { AuthService } from '../../../core/services/auth.service'; import { NotificationService } from '../../../core/services/notification.service'; import { ChannelCacheService, ResolvedChannel } from '../../../core/services/channel-cache.service'; import { UserCacheService, ResolvedUser } from '../../../core/services/user-cache.service'; import { KeyToken, KeyTokenPreview, parsePermissions, TokenPermission, ChannelWithSubscription, Message } from '../../../core/models'; import { RelativeTimePipe } from '../../../shared/pipes/relative-time.pipe'; import { MetadataGridComponent, MetadataValueComponent } from '../../../shared/components/metadata-grid'; interface PermissionOption { value: TokenPermission; label: string; description: string; } @Component({ selector: 'app-key-detail', standalone: true, imports: [ CommonModule, DatePipe, FormsModule, RouterLink, NzCardModule, NzButtonModule, NzIconModule, NzTagModule, NzSpinModule, NzPopconfirmModule, NzModalModule, NzFormModule, NzInputModule, NzCheckboxModule, NzSelectModule, NzToolTipModule, NzTableModule, NzEmptyModule, NzPaginationModule, RelativeTimePipe, MetadataGridComponent, MetadataValueComponent, ], templateUrl: './key-detail.component.html', styleUrl: './key-detail.component.scss' }) export class KeyDetailComponent implements OnInit { private route = inject(ActivatedRoute); private router = inject(Router); private apiService = inject(ApiService); private authService = inject(AuthService); private notification = inject(NotificationService); private channelCacheService = inject(ChannelCacheService); private userCacheService = inject(UserCacheService); key = signal(null); keyPreview = signal(null); currentKeyId = signal(null); loading = signal(true); channelNames = signal>(new Map()); availableChannels = signal([]); resolvedOwner = signal(null); // Messages messages = signal([]); loadingMessages = signal(false); messagesPageSize = 16; messagesTotalCount = signal(0); messagesCurrentPage = signal(1); messagesNextPageToken = signal(null); // Edit modal showEditModal = signal(false); editKeyName = ''; editKeyPermissions: TokenPermission[] = []; editKeyAllChannels = true; editKeyChannels: string[] = []; updating = signal(false); permissionOptions: PermissionOption[] = [ { value: 'A', label: 'Admin', description: 'Full access to all operations' }, { value: 'CR', label: 'Channel Read', description: 'Read messages from channels' }, { value: 'CS', label: 'Channel Send', description: 'Send messages to channels' }, { value: 'UR', label: 'User Read', description: 'Read user information' }, ]; ngOnInit(): void { const keyId = this.route.snapshot.paramMap.get('id'); if (keyId) { this.loadKey(keyId); } } loadKey(keyId: string): void { const userId = this.authService.getUserId(); if (!userId) return; this.loading.set(true); this.apiService.getKeyPreview(keyId).subscribe({ next: (preview) => { this.keyPreview.set(preview); this.resolveOwner(preview.owner_user_id); this.resolveChannelNamesFromPreview(preview); if (preview.owner_user_id === userId) { this.loadCurrentKey(); this.loadAvailableChannels(); this.apiService.getKey(userId, keyId).subscribe({ next: (key) => { this.key.set(key); this.loading.set(false); this.resolveChannelNames(key); this.loadMessages(keyId); }, error: () => { this.loading.set(false); } }); } else { this.loading.set(false); this.loadMessages(keyId); } }, error: () => { this.loading.set(false); } }); } loadMessages(keyId: string, nextPageToken?: string): void { this.loadingMessages.set(true); this.apiService.getMessages({ subscription_status: 'all', used_key: keyId, page_size: this.messagesPageSize, next_page_token: nextPageToken, trimmed: true }).subscribe({ next: (response) => { this.messages.set(response.messages); this.messagesTotalCount.set(response.total_count); this.messagesNextPageToken.set(response.next_page_token || null); this.loadingMessages.set(false); }, error: () => { this.loadingMessages.set(false); } }); } messagesGoToPage(page: number): void { const key = this.key(); if (!key) return; this.messagesCurrentPage.set(page); if (page === 1) { this.loadMessages(key.keytoken_id); } else { const token = this.messagesNextPageToken(); if (token) { this.loadMessages(key.keytoken_id, token); } } } viewMessage(message: Message): void { this.router.navigate(['/messages', message.message_id]); } getPriorityColor(priority: number): string { switch (priority) { case 0: return 'default'; case 1: return 'blue'; case 2: return 'orange'; default: return 'default'; } } getPriorityLabel(priority: number): string { switch (priority) { case 0: return 'Low'; case 1: return 'Normal'; case 2: return 'High'; default: return 'Unknown'; } } private resolveOwner(ownerId: string): void { this.userCacheService.resolveUser(ownerId).subscribe(resolved => { this.resolvedOwner.set(resolved); }); } loadCurrentKey(): void { const userId = this.authService.getUserId(); if (!userId) return; this.apiService.getCurrentKey(userId).subscribe({ next: (key) => { this.currentKeyId.set(key.keytoken_id); } }); } loadAvailableChannels(): void { this.channelCacheService.getAllChannels().subscribe(channels => { this.availableChannels.set(channels); }); } private resolveChannelNames(key: KeyToken): void { if (!key.all_channels && key.channels && key.channels.length > 0) { this.channelCacheService.resolveChannels(key.channels).subscribe(resolved => { this.channelNames.set(resolved); }); } } private resolveChannelNamesFromPreview(preview: KeyTokenPreview): void { if (!preview.all_channels && preview.channels && preview.channels.length > 0) { this.channelCacheService.resolveChannels(preview.channels).subscribe(resolved => { this.channelNames.set(resolved); }); } } keyData() { return this.key() ?? this.keyPreview(); } isOwner(): boolean { const userId = this.authService.getUserId(); const key = this.key(); if (key) return key.owner_user_id === userId; const preview = this.keyPreview(); if (preview) return preview.owner_user_id === userId; return false; } goBack(): void { this.router.navigate(['/keys']); } isCurrentKey(): boolean { const key = this.key(); return key?.keytoken_id === this.currentKeyId(); } getPermissions(): TokenPermission[] { const data = this.keyData(); return data ? parsePermissions(data.permissions) : []; } getPermissionColor(perm: TokenPermission): string { switch (perm) { case 'A': return 'red'; case 'CR': return 'blue'; case 'CS': return 'green'; case 'UR': return 'purple'; default: return 'default'; } } getPermissionLabel(perm: TokenPermission): string { const option = this.permissionOptions.find(o => o.value === perm); return option?.label || perm; } getChannelDisplayName(channelId: string): string { const resolved = this.channelNames().get(channelId); return resolved?.displayName || channelId; } getChannelLabel(channel: ChannelWithSubscription): string { return channel.display_name || channel.internal_name; } // Edit modal openEditModal(): void { const key = this.key(); if (!key) return; this.editKeyName = key.name; this.editKeyPermissions = parsePermissions(key.permissions); this.editKeyAllChannels = key.all_channels; this.editKeyChannels = key.channels ? [...key.channels] : []; this.showEditModal.set(true); } closeEditModal(): void { this.showEditModal.set(false); } updateKey(): void { const userId = this.authService.getUserId(); const key = this.key(); if (!userId || !key || !this.editKeyName.trim() || this.editKeyPermissions.length === 0) return; this.updating.set(true); this.apiService.updateKey(userId, key.keytoken_id, { name: this.editKeyName.trim(), permissions: this.editKeyPermissions.join(';'), all_channels: this.editKeyAllChannels, channels: this.editKeyAllChannels ? undefined : this.editKeyChannels }).subscribe({ next: (updated) => { this.key.set(updated); this.notification.success('Key updated'); this.updating.set(false); this.closeEditModal(); this.resolveChannelNames(updated); }, error: () => { this.updating.set(false); } }); } onEditPermissionChange(perm: TokenPermission, checked: boolean): void { if (checked) { if (perm === 'A') { this.editKeyPermissions = ['A']; } else if (!this.editKeyPermissions.includes(perm)) { this.editKeyPermissions = [...this.editKeyPermissions, perm]; } } else { this.editKeyPermissions = this.editKeyPermissions.filter(p => p !== perm); } } isEditPermissionChecked(perm: TokenPermission): boolean { return this.editKeyPermissions.includes(perm); } deleteKey(): void { const key = this.key(); const userId = this.authService.getUserId(); if (!key || !userId) return; if (this.isCurrentKey()) { this.notification.warning('Cannot delete the key you are currently using'); return; } this.apiService.deleteKey(userId, key.keytoken_id).subscribe({ next: () => { this.notification.success('Key deleted'); this.router.navigate(['/keys']); } }); } }