From 308d6bbba0c08f61500fb67b43c771cf3ae7e41e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Schw=C3=B6rer?= Date: Wed, 3 Dec 2025 18:34:13 +0100 Subject: [PATCH] Simple Managment webapp [LLM] --- webapp/public/favicon.ico | Bin 15086 -> 99678 bytes .../account-info/account-info.component.html | 27 ----- .../account-info/account-info.component.ts | 29 ------ .../keys/key-list/key-list.component.html | 3 +- .../keys/key-list/key-list.component.scss | 12 ++- .../keys/key-list/key-list.component.ts | 5 +- .../message-list/message-list.component.html | 81 +++++++-------- .../message-list/message-list.component.scss | 20 +++- .../message-list/message-list.component.ts | 97 +++++++++++++++--- .../subscription-list.component.html | 19 +++- .../subscription-list.component.ts | 54 +++++++--- 11 files changed, 216 insertions(+), 131 deletions(-) diff --git a/webapp/public/favicon.ico b/webapp/public/favicon.ico index 57614f9c967596fad0a3989bec2b1deff33034f6..3729f2e10ab780386388a17f89025e6818e6b1ed 100644 GIT binary patch literal 99678 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 Nr__JTFzvMr^FKp4S3v*( diff --git a/webapp/src/app/features/account/account-info/account-info.component.html b/webapp/src/app/features/account/account-info/account-info.component.html index 95207cb..85edba8 100644 --- a/webapp/src/app/features/account/account-info/account-info.component.html +++ b/webapp/src/app/features/account/account-info/account-info.component.html @@ -78,33 +78,6 @@ - -
- -
- - - -
-

Deleting your account will permanently remove all your data including messages, channels, subscriptions, and keys.

- -
-
} diff --git a/webapp/src/app/features/account/account-info/account-info.component.ts b/webapp/src/app/features/account/account-info/account-info.component.ts index a69f305..d863c2a 100644 --- a/webapp/src/app/features/account/account-info/account-info.component.ts +++ b/webapp/src/app/features/account/account-info/account-info.component.ts @@ -1,6 +1,5 @@ import { Component, inject, signal, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { Router } from '@angular/router'; import { FormsModule } from '@angular/forms'; import { NzCardModule } from 'ng-zorro-antd/card'; import { NzButtonModule } from 'ng-zorro-antd/button'; @@ -9,7 +8,6 @@ import { NzDescriptionsModule } from 'ng-zorro-antd/descriptions'; import { NzTagModule } from 'ng-zorro-antd/tag'; import { NzSpinModule } from 'ng-zorro-antd/spin'; import { NzProgressModule } from 'ng-zorro-antd/progress'; -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'; @@ -33,7 +31,6 @@ import { RelativeTimePipe } from '../../../shared/pipes/relative-time.pipe'; NzTagModule, NzSpinModule, NzProgressModule, - NzPopconfirmModule, NzModalModule, NzFormModule, NzInputModule, @@ -47,11 +44,9 @@ export class AccountInfoComponent implements OnInit { private apiService = inject(ApiService); private authService = inject(AuthService); private notification = inject(NotificationService); - private router = inject(Router); user = signal(null); loading = signal(true); - deleting = signal(false); // Edit username modal showEditModal = signal(false); @@ -121,28 +116,4 @@ export class AccountInfoComponent implements OnInit { } }); } - - // Logout - logout(): void { - this.authService.logout(); - this.router.navigate(['/login']); - } - - // Delete account - deleteAccount(): void { - const userId = this.authService.getUserId(); - if (!userId) return; - - this.deleting.set(true); - this.apiService.deleteUser(userId).subscribe({ - next: () => { - this.notification.success('Account deleted'); - this.authService.logout(); - this.router.navigate(['/login']); - }, - error: () => { - this.deleting.set(false); - } - }); - } } diff --git a/webapp/src/app/features/keys/key-list/key-list.component.html b/webapp/src/app/features/keys/key-list/key-list.component.html index 1e54741..acdcd82 100644 --- a/webapp/src/app/features/keys/key-list/key-list.component.html +++ b/webapp/src/app/features/keys/key-list/key-list.component.html @@ -168,10 +168,11 @@ } diff --git a/webapp/src/app/features/keys/key-list/key-list.component.scss b/webapp/src/app/features/keys/key-list/key-list.component.scss index 39f6692..3de064b 100644 --- a/webapp/src/app/features/keys/key-list/key-list.component.scss +++ b/webapp/src/app/features/keys/key-list/key-list.component.scss @@ -60,7 +60,17 @@ label { display: flex; align-items: center; - gap: 4px; + margin-left: 0; + } + + nz-tag { + width: 32px; + text-align: center; + margin-right: 8px; + } + + .perm-label { + min-width: 100px; } .perm-desc { diff --git a/webapp/src/app/features/keys/key-list/key-list.component.ts b/webapp/src/app/features/keys/key-list/key-list.component.ts index d7653c6..3378f54 100644 --- a/webapp/src/app/features/keys/key-list/key-list.component.ts +++ b/webapp/src/app/features/keys/key-list/key-list.component.ts @@ -184,7 +184,10 @@ export class KeyListComponent implements OnInit { onPermissionChange(perm: TokenPermission, checked: boolean): void { if (checked) { - if (!this.newKeyPermissions.includes(perm)) { + if (perm === 'A') { + // Admin selected - clear other permissions + this.newKeyPermissions = ['A']; + } else if (!this.newKeyPermissions.includes(perm)) { this.newKeyPermissions = [...this.newKeyPermissions, perm]; } } else { diff --git a/webapp/src/app/features/messages/message-list/message-list.component.html b/webapp/src/app/features/messages/message-list/message-list.component.html index 81c1028..31398fc 100644 --- a/webapp/src/app/features/messages/message-list/message-list.component.html +++ b/webapp/src/app/features/messages/message-list/message-list.component.html @@ -7,52 +7,43 @@ - -
- - - - - - - - - - - - - + - @if (searchText || priorityFilter !== null || channelFilter) { - + @if (hasActiveFilters()) { +
+ @if (appliedSearchText) { + + "{{ appliedSearchText }}" + } + @for (channel of channelFilter; track channel) { + + {{ getChannelDisplayName(channel) }} + + } + @if (priorityFilter.length > 0) { + + {{ getPriorityLabel(+priorityFilter[0]) }} + + } + Clear all
- + } Title - Channel + Channel Sender - Priority + Priority Time diff --git a/webapp/src/app/features/messages/message-list/message-list.component.scss b/webapp/src/app/features/messages/message-list/message-list.component.scss index 9b48ec0..99868a6 100644 --- a/webapp/src/app/features/messages/message-list/message-list.component.scss +++ b/webapp/src/app/features/messages/message-list/message-list.component.scss @@ -9,10 +9,28 @@ } } -.filter-card { +.search-bar { margin-bottom: 16px; } +.active-filters { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 16px; + + nz-tag { + max-width: none; + } + + .clear-all { + margin-left: 8px; + font-size: 12px; + cursor: pointer; + } +} + .message-title { font-weight: 500; color: #333; diff --git a/webapp/src/app/features/messages/message-list/message-list.component.ts b/webapp/src/app/features/messages/message-list/message-list.component.ts index 9a27f1c..e9422bc 100644 --- a/webapp/src/app/features/messages/message-list/message-list.component.ts +++ b/webapp/src/app/features/messages/message-list/message-list.component.ts @@ -2,10 +2,9 @@ import { Component, inject, signal, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { Router } from '@angular/router'; import { FormsModule } from '@angular/forms'; -import { NzTableModule } from 'ng-zorro-antd/table'; +import { NzTableModule, NzTableFilterList } from 'ng-zorro-antd/table'; import { NzButtonModule } from 'ng-zorro-antd/button'; import { NzInputModule } from 'ng-zorro-antd/input'; -import { NzSelectModule } from 'ng-zorro-antd/select'; import { NzTagModule } from 'ng-zorro-antd/tag'; import { NzIconModule } from 'ng-zorro-antd/icon'; import { NzEmptyModule } from 'ng-zorro-antd/empty'; @@ -26,7 +25,6 @@ import { RelativeTimePipe } from '../../../shared/pipes/relative-time.pipe'; NzTableModule, NzButtonModule, NzInputModule, - NzSelectModule, NzTagModule, NzIconModule, NzEmptyModule, @@ -49,13 +47,39 @@ export class MessageListComponent implements OnInit { // Filters searchText = ''; - priorityFilter: number | null = null; - channelFilter = ''; + appliedSearchText = ''; + priorityFilter: string[] = []; + channelFilter: string[] = []; + + // Filter options + priorityFilters: NzTableFilterList = [ + { text: 'Low', value: '0' }, + { text: 'Normal', value: '1' }, + { text: 'High', value: '2' }, + ]; + channelFilters = signal([]); ngOnInit(): void { + this.loadChannels(); this.loadMessages(); } + loadChannels(): void { + const userId = this.authService.getUserId(); + if (!userId) return; + + this.apiService.getChannels(userId, 'all_any').subscribe({ + next: (response) => { + this.channelFilters.set( + response.channels.map(ch => ({ + text: ch.display_name, + value: ch.internal_name, + })) + ); + } + }); + } + loadMessages(append = false): void { this.loading.set(true); @@ -64,14 +88,14 @@ export class MessageListComponent implements OnInit { trimmed: true, }; - if (this.searchText) { - params.search = this.searchText; + if (this.appliedSearchText) { + params.search = this.appliedSearchText; } - if (this.priorityFilter !== null) { - params.priority = this.priorityFilter; + if (this.priorityFilter.length === 1) { + params.priority = parseInt(this.priorityFilter[0], 10); } - if (this.channelFilter) { - params.channel = this.channelFilter; + if (this.channelFilter.length > 0) { + params.channel = this.channelFilter.join(','); } if (append && this.nextPageToken()) { params.next_page_token = this.nextPageToken()!; @@ -98,16 +122,59 @@ export class MessageListComponent implements OnInit { } applyFilters(): void { + this.appliedSearchText = this.searchText; this.loadMessages(); } - clearFilters(): void { - this.searchText = ''; - this.priorityFilter = null; - this.channelFilter = ''; + onPriorityFilterChange(filters: string[] | null): void { + this.priorityFilter = filters ?? []; this.loadMessages(); } + onChannelFilterChange(filters: string[] | null): void { + this.channelFilter = filters ?? []; + this.loadMessages(); + } + + clearSearch(): void { + this.searchText = ''; + this.appliedSearchText = ''; + this.loadMessages(); + } + + clearChannelFilter(): void { + this.channelFilter = []; + this.loadMessages(); + } + + removeChannelFilter(channel: string): void { + this.channelFilter = this.channelFilter.filter(c => c !== channel); + this.loadMessages(); + } + + clearPriorityFilter(): void { + this.priorityFilter = []; + this.loadMessages(); + } + + clearAllFilters(): void { + this.searchText = ''; + this.appliedSearchText = ''; + this.channelFilter = []; + this.priorityFilter = []; + this.loadMessages(); + } + + hasActiveFilters(): boolean { + return !!this.appliedSearchText || this.channelFilter.length > 0 || this.priorityFilter.length > 0; + } + + getChannelDisplayName(internalName: string): string { + const filters = this.channelFilters(); + const channel = filters.find(f => f.value === internalName); + return channel?.text?.toString() ?? internalName; + } + loadMore(): void { if (this.nextPageToken()) { this.loadMessages(true); diff --git a/webapp/src/app/features/subscriptions/subscription-list/subscription-list.component.html b/webapp/src/app/features/subscriptions/subscription-list/subscription-list.component.html index 4c693bd..3d329d8 100644 --- a/webapp/src/app/features/subscriptions/subscription-list/subscription-list.component.html +++ b/webapp/src/app/features/subscriptions/subscription-list/subscription-list.component.html @@ -16,10 +16,21 @@ - + + + + @if (getTabDescription()) { + + } + - Direction + Type Channel Subscriber Owner @@ -44,8 +55,8 @@ @for (sub of subscriptions(); track sub.subscription_id) { - - {{ getDirectionLabel(sub) }} + + {{ getTypeLabel(sub).label }} diff --git a/webapp/src/app/features/subscriptions/subscription-list/subscription-list.component.ts b/webapp/src/app/features/subscriptions/subscription-list/subscription-list.component.ts index 7b18447..9091fa0 100644 --- a/webapp/src/app/features/subscriptions/subscription-list/subscription-list.component.ts +++ b/webapp/src/app/features/subscriptions/subscription-list/subscription-list.component.ts @@ -13,6 +13,7 @@ import { NzModalModule } from 'ng-zorro-antd/modal'; import { NzFormModule } from 'ng-zorro-antd/form'; import { NzInputModule } from 'ng-zorro-antd/input'; import { NzToolTipModule } from 'ng-zorro-antd/tooltip'; +import { NzAlertModule } from 'ng-zorro-antd/alert'; import { ApiService } from '../../../core/services/api.service'; import { AuthService } from '../../../core/services/auth.service'; import { NotificationService } from '../../../core/services/notification.service'; @@ -20,7 +21,19 @@ import { UserCacheService, ResolvedUser } from '../../../core/services/user-cach import { Subscription, SubscriptionFilter } from '../../../core/models'; import { RelativeTimePipe } from '../../../shared/pipes/relative-time.pipe'; -type TabDirection = 'both' | 'outgoing' | 'incoming'; +type SubscriptionTab = 'all' | 'own' | 'deactivated' | 'external' | 'incoming'; + +interface TabConfig { + filter: SubscriptionFilter; +} + +const TAB_CONFIGS: Record = { + all: { filter: {} }, + own: { filter: { direction: 'outgoing', confirmation: 'confirmed', external: 'false' } }, + deactivated: { filter: { direction: 'outgoing', confirmation: 'unconfirmed', external: 'false' } }, + external: { filter: { direction: 'outgoing', confirmation: 'all', external: 'true' } }, + incoming: { filter: { direction: 'incoming', confirmation: 'all', external: 'true' } }, +}; @Component({ selector: 'app-subscription-list', @@ -40,6 +53,7 @@ type TabDirection = 'both' | 'outgoing' | 'incoming'; NzFormModule, NzInputModule, NzToolTipModule, + NzAlertModule, RelativeTimePipe, ], templateUrl: './subscription-list.component.html', @@ -54,7 +68,7 @@ export class SubscriptionListComponent implements OnInit { subscriptions = signal([]); userNames = signal>(new Map()); loading = signal(false); - direction: TabDirection = 'both'; + activeTab: SubscriptionTab = 'all'; // Create subscription modal showCreateModal = signal(false); @@ -72,10 +86,7 @@ export class SubscriptionListComponent implements OnInit { this.loading.set(true); - const filter: SubscriptionFilter = {}; - if (this.direction !== 'both') { - filter.direction = this.direction; - } + const filter = TAB_CONFIGS[this.activeTab].filter; this.apiService.getSubscriptions(userId, filter).subscribe({ next: (response) => { @@ -108,8 +119,8 @@ export class SubscriptionListComponent implements OnInit { } onTabChange(index: number): void { - const directions: TabDirection[] = ['both', 'outgoing', 'incoming']; - this.direction = directions[index]; + const tabs: SubscriptionTab[] = ['all', 'own', 'deactivated', 'external', 'incoming']; + this.activeTab = tabs[index]; this.loadSubscriptions(); } @@ -199,10 +210,29 @@ export class SubscriptionListComponent implements OnInit { return { label: 'Pending', color: 'orange' }; } - getDirectionLabel(sub: Subscription): string { - if (this.isOutgoing(sub)) { - return 'Outgoing'; + getTypeLabel(sub: Subscription): { label: string; color: string } { + const userId = this.authService.getUserId(); + if (sub.subscriber_user_id === sub.channel_owner_user_id) { + return { label: 'Own', color: 'green' }; + } + if (sub.subscriber_user_id === userId) { + return { label: 'External', color: 'blue' }; + } + return { label: 'Incoming', color: 'purple' }; + } + + getTabDescription(): string | null { + switch (this.activeTab) { + case 'own': + return 'Active subscriptions to your channels.'; + case 'deactivated': + return 'Deactivated subscriptions to your channels. These can be reactivated by you.'; + case 'external': + return 'Your subscriptions to channels owned by other users.'; + case 'incoming': + return 'Subscription from other users to your channels.'; + default: + return null; } - return 'Incoming'; } }