More webapp changes+fixes
All checks were successful
Build Docker and Deploy / Build Docker Container (push) Successful in 1m41s
Build Docker and Deploy / Run Unit-Tests (push) Successful in 9m31s
Build Docker and Deploy / Deploy to Server (push) Successful in 18s

This commit is contained in:
2025-12-05 21:36:50 +01:00
parent c554479604
commit 2b7950f5dc
44 changed files with 1245 additions and 189 deletions

View File

@@ -94,6 +94,91 @@
</scn-metadata-value>
</scn-metadata-grid>
</nz-card>
<nz-card nzTitle="Messages" class="mt-16">
<nz-table
#messageTable
[nzData]="messages()"
[nzLoading]="loadingMessages()"
[nzShowPagination]="false"
[nzNoResult]="noMessagesResultTpl"
nzSize="small"
>
<ng-template #noMessagesResultTpl></ng-template>
<thead>
<tr>
<th>Title</th>
<th>Content</th>
<th nzWidth="0">Channel</th>
<th nzWidth="0">Sender</th>
<th nzWidth="0">Priority</th>
<th nzWidth="0">Time</th>
</tr>
</thead>
<tbody>
@for (message of messages(); track message.message_id) {
<tr class="clickable-row">
<td>
<a class="cell-link" [routerLink]="['/messages', message.message_id]">
<div class="message-title">{{ message.title }}</div>
<div class="message-id mono">{{ message.message_id }}</div>
</a>
</td>
<td>
<a class="cell-link" [routerLink]="['/messages', message.message_id]">
@if (message.content) {
<div class="message-content">{{ message.content | slice:0:128 }}{{ message.content.length > 128 ? '...' : '' }}</div>
} @else {
<span class="text-muted"></span>
}
</a>
</td>
<td>
<a class="cell-link" [routerLink]="['/messages', message.message_id]">
<div class="cell-name">{{ message.channel_internal_name }}</div>
<div class="cell-id mono">{{ message.channel_id }}</div>
</a>
</td>
<td>
<a class="cell-link" [routerLink]="['/messages', message.message_id]">
<span style="white-space: pre">{{ message.sender_name || '-' }}</span>
</a>
</td>
<td>
<a class="cell-link" [routerLink]="['/messages', message.message_id]">
<nz-tag [nzColor]="getPriorityColor(message.priority)">
{{ getPriorityLabel(message.priority) }}
</nz-tag>
</a>
</td>
<td>
<a class="cell-link" [routerLink]="['/messages', message.message_id]">
<div class="timestamp-absolute">{{ message.timestamp | date:'yyyy-MM-dd HH:mm:ss' }}</div>
<div class="timestamp-relative">{{ message.timestamp | relativeTime }}</div>
</a>
</td>
</tr>
} @empty {
<tr>
<td colspan="6">
<nz-empty nzNotFoundContent="No messages sent with this key"></nz-empty>
</td>
</tr>
}
</tbody>
</nz-table>
@if (messagesTotalCount() > messagesPageSize) {
<div class="pagination-controls">
<nz-pagination
[nzPageIndex]="messagesCurrentPage()"
[nzPageSize]="messagesPageSize"
[nzTotal]="messagesTotalCount()"
[nzDisabled]="loadingMessages()"
(nzPageIndexChange)="messagesGoToPage($event)"
></nz-pagination>
</div>
}
</nz-card>
} @else {
<nz-card>
<div class="not-found">

View File

@@ -90,9 +90,53 @@
.timestamp-absolute {
font-size: 13px;
color: #333;
white-space: pre;
}
.timestamp-relative {
font-size: 12px;
color: #999;
white-space: pre;
}
.clickable-row {
cursor: pointer;
&:hover {
background-color: #fafafa;
}
}
.message-title {
font-weight: 500;
color: #333;
}
.message-id {
font-size: 11px;
color: #999;
margin-top: 2px;
}
.message-content {
font-size: 12px;
color: #666;
}
.cell-name {
font-weight: 500;
color: #333;
}
.cell-id {
font-size: 11px;
color: #999;
margin-top: 2px;
}
.pagination-controls {
display: flex;
justify-content: center;
align-items: center;
padding: 16px 0;
}

View File

@@ -1,6 +1,6 @@
import { Component, inject, signal, OnInit } from '@angular/core';
import { CommonModule, DatePipe } from '@angular/common';
import { ActivatedRoute, Router } from '@angular/router';
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';
@@ -14,12 +14,15 @@ 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, parsePermissions, TokenPermission, ChannelWithSubscription } from '../../../core/models';
import { KeyToken, parsePermissions, TokenPermission, ChannelWithSubscription, Message } from '../../../core/models';
import { RelativeTimePipe } from '../../../shared/pipes/relative-time.pipe';
import { MetadataGridComponent, MetadataValueComponent } from '../../../shared/components/metadata-grid';
@@ -36,6 +39,7 @@ interface PermissionOption {
CommonModule,
DatePipe,
FormsModule,
RouterLink,
NzCardModule,
NzButtonModule,
NzIconModule,
@@ -48,6 +52,9 @@ interface PermissionOption {
NzCheckboxModule,
NzSelectModule,
NzToolTipModule,
NzTableModule,
NzEmptyModule,
NzPaginationModule,
RelativeTimePipe,
MetadataGridComponent,
MetadataValueComponent,
@@ -71,6 +78,14 @@ export class KeyDetailComponent implements OnInit {
availableChannels = signal<ChannelWithSubscription[]>([]);
resolvedOwner = signal<ResolvedUser | null>(null);
// Messages
messages = signal<Message[]>([]);
loadingMessages = signal(false);
messagesPageSize = 16;
messagesTotalCount = signal(0);
messagesCurrentPage = signal(1);
messagesNextPageToken = signal<string | null>(null);
// Edit modal
showEditModal = signal(false);
editKeyName = '';
@@ -106,6 +121,7 @@ export class KeyDetailComponent implements OnInit {
this.loading.set(false);
this.resolveChannelNames(key);
this.resolveOwner(key.owner_user_id);
this.loadMessages(keyId);
},
error: () => {
this.loading.set(false);
@@ -113,6 +129,64 @@ export class KeyDetailComponent implements OnInit {
});
}
loadMessages(keyId: string, nextPageToken?: string): void {
this.loadingMessages.set(true);
// Load more messages than page size to ensure we get enough after filtering
this.apiService.getMessages({
page_size: 64,
next_page_token: nextPageToken,
trimmed: true
}).subscribe({
next: (response) => {
// Filter messages by the key that was used to send them
const filtered = response.messages.filter(m => m.used_key_id === keyId);
this.messages.set(filtered.slice(0, this.messagesPageSize));
this.messagesTotalCount.set(filtered.length);
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);

View File

@@ -34,50 +34,60 @@
</thead>
<tbody>
@for (key of keys(); track key.keytoken_id) {
<tr class="clickable-row" (click)="viewKey(key)">
<tr class="clickable-row">
<td>
<div class="key-name">
{{ key.name }}
@if (isCurrentKey(key)) {
<nz-tag nzColor="cyan" class="current-tag">Current</nz-tag>
}
</div>
<div class="key-id mono">{{ key.keytoken_id }}</div>
<a class="cell-link" [routerLink]="['/keys', key.keytoken_id]">
<div class="key-name">
{{ key.name }}
@if (isCurrentKey(key)) {
<nz-tag nzColor="cyan" class="current-tag">Current</nz-tag>
}
</div>
<div class="key-id mono">{{ key.keytoken_id }}</div>
</a>
</td>
<td>
<div class="permissions">
@for (perm of getPermissions(key); track perm) {
<nz-tag
[nzColor]="getPermissionColor(perm)"
nz-tooltip
[nzTooltipTitle]="getPermissionLabel(perm)"
>
{{ perm }}
</nz-tag>
}
@if (key.all_channels) {
<nz-tag nzColor="default" nz-tooltip nzTooltipTitle="Access to all channels">
All Channels
</nz-tag>
} @else if (key.channels && key.channels.length > 0) {
@for (channelId of key.channels; track channelId) {
<nz-tag nzColor="orange" nz-tooltip [nzTooltipTitle]="channelId">
{{ getChannelDisplayName(channelId) }}
<a class="cell-link" [routerLink]="['/keys', key.keytoken_id]">
<div class="permissions">
@for (perm of getPermissions(key); track perm) {
<nz-tag
[nzColor]="getPermissionColor(perm)"
nz-tooltip
[nzTooltipTitle]="getPermissionLabel(perm)"
>
{{ perm }}
</nz-tag>
}
}
</div>
@if (key.all_channels) {
<nz-tag nzColor="default" nz-tooltip nzTooltipTitle="Access to all channels">
All Channels
</nz-tag>
} @else if (key.channels && key.channels.length > 0) {
@for (channelId of key.channels; track channelId) {
<nz-tag nzColor="orange" nz-tooltip [nzTooltipTitle]="channelId">
{{ getChannelDisplayName(channelId) }}
</nz-tag>
}
}
</div>
</a>
</td>
<td>{{ key.messages_sent }}</td>
<td>
@if (key.timestamp_lastused) {
<div class="timestamp-absolute">{{ key.timestamp_lastused | date:'yyyy-MM-dd HH:mm:ss' }}</div>
<div class="timestamp-relative">{{ key.timestamp_lastused | relativeTime }}</div>
} @else {
<span class="text-muted">Never</span>
}
<a class="cell-link" [routerLink]="['/keys', key.keytoken_id]">
{{ key.messages_sent }}
</a>
</td>
<td (click)="$event.stopPropagation()">
<td>
<a class="cell-link" [routerLink]="['/keys', key.keytoken_id]">
@if (key.timestamp_lastused) {
<div class="timestamp-absolute">{{ key.timestamp_lastused | date:'yyyy-MM-dd HH:mm:ss' }}</div>
<div class="timestamp-relative">{{ key.timestamp_lastused | relativeTime }}</div>
} @else {
<span class="text-muted">Never</span>
}
</a>
</td>
<td>
<div class="action-buttons">
<button
nz-button

View File

@@ -95,9 +95,11 @@
.timestamp-absolute {
font-size: 13px;
color: #333;
white-space: pre;
}
.timestamp-relative {
font-size: 12px;
color: #999;
white-space: pre;
}

View File

@@ -1,6 +1,6 @@
import { Component, inject, signal, OnInit } from '@angular/core';
import { CommonModule, DatePipe } from '@angular/common';
import { Router } from '@angular/router';
import { Router, RouterLink } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { NzTableModule } from 'ng-zorro-antd/table';
import { NzButtonModule } from 'ng-zorro-antd/button';
@@ -37,6 +37,7 @@ interface PermissionOption {
CommonModule,
DatePipe,
FormsModule,
RouterLink,
NzTableModule,
NzButtonModule,
NzIconModule,