Show more data in webapp deliveries-table
Some checks failed
Build Docker and Deploy / Build Docker Container (push) Successful in 1m25s
Build Docker and Deploy / Run Unit-Tests (push) Failing after 10m51s
Build Docker and Deploy / Deploy to Server (push) Has been skipped

This commit is contained in:
2026-01-19 18:49:38 +01:00
parent 08fd34632a
commit b5e098a694
11 changed files with 225 additions and 8 deletions

View File

@@ -1,3 +1,5 @@
import { UserPreview } from "./user.model";
export type ClientType = 'ANDROID' | 'IOS' | 'LINUX' | 'MACOS' | 'WINDOWS';
export interface Client {
@@ -15,6 +17,19 @@ export interface ClientListResponse {
clients: Client[];
}
export interface ClientPreview {
client_id: string;
name: string | null;
type: ClientType;
agent_model: string;
agent_version: string;
}
export interface ClientPreviewResponse {
user: UserPreview;
client: ClientPreview;
}
export function getClientTypeIcon(type: ClientType): string {
switch (type) {
case 'ANDROID':

View File

@@ -26,6 +26,7 @@ import {
UpdateKeyRequest,
Client,
ClientListResponse,
ClientPreviewResponse,
SenderNameStatistics,
SenderNameListResponse,
DeliveryListResponse,
@@ -93,6 +94,10 @@ export class ApiService {
return this.http.delete<Client>(`${this.baseUrl}/users/${userId}/clients/${clientId}`);
}
getClientPreview(clientId: string): Observable<ClientPreviewResponse> {
return this.http.get<ClientPreviewResponse>(`${this.baseUrl}/preview/clients/${clientId}`);
}
// Channel endpoints
getChannels(userId: string, selector?: ChannelSelector): Observable<ChannelListResponse> {
let params = new HttpParams();

View File

@@ -0,0 +1,44 @@
import { Injectable, inject } from '@angular/core';
import { Observable, of, catchError, map, shareReplay } from 'rxjs';
import { ApiService } from './api.service';
export interface ResolvedClient {
clientId: string;
clientName: string | null;
userId: string;
userName: string | null;
agentModel: string;
agentVersion: string;
}
@Injectable({
providedIn: 'root'
})
export class ClientCacheService {
private apiService = inject(ApiService);
private cache = new Map<string, Observable<ResolvedClient | null>>();
resolveClient(clientId: string): Observable<ResolvedClient | null> {
if (!this.cache.has(clientId)) {
const request$ = this.apiService.getClientPreview(clientId).pipe(
map(response => ({
clientId: response.client.client_id,
clientName: response.client.name,
userId: response.user.user_id,
userName: response.user.username,
agentModel: response.client.agent_model,
agentVersion: response.client.agent_version,
})),
catchError(() => of(null)),
shareReplay(1)
);
this.cache.set(clientId, request$);
}
return this.cache.get(clientId)!;
}
clearCache(): void {
this.cache.clear();
}
}

View File

@@ -90,18 +90,33 @@
>
<thead>
<tr>
<th>Client ID</th>
<th>Client</th>
<th>User</th>
<th>Agent</th>
<th>Status</th>
<th>Retries</th>
<th>Created</th>
<th>Finalized</th>
<th>FCM-ID</th>
</tr>
</thead>
<tbody>
@for (delivery of deliveriesTable.data; track delivery.delivery_id) {
<tr>
<td>
<span class="mono">{{ delivery.receiver_client_id }}</span>
<div class="cell-name">{{ getResolvedClient(delivery.receiver_client_id)?.clientName || '-' }}</div>
<div class="cell-id mono">{{ delivery.receiver_client_id }}</div>
</td>
<td>
<div class="cell-name">{{ getResolvedClient(delivery.receiver_client_id)?.userName || '-' }}</div>
<div class="cell-id mono">{{ delivery.receiver_user_id }}</div>
</td>
<td>
@if (getResolvedClient(delivery.receiver_client_id); as client) {
{{ client.agentModel }} {{ client.agentVersion }}
} @else {
-
}
</td>
<td>
<nz-tag [nzColor]="getStatusColor(delivery.status)">
@@ -111,6 +126,20 @@
<td>{{ delivery.retry_count }}</td>
<td>{{ delivery.timestamp_created | date:'yyyy-MM-dd HH:mm:ss' }}</td>
<td>{{ delivery.timestamp_finalized ? (delivery.timestamp_finalized | date:'yyyy-MM-dd HH:mm:ss') : '-' }}</td>
<td>
@if (delivery.fcm_message_id) {
<span
nz-icon
nzType="copy"
class="action-icon"
nz-tooltip
nzTooltipTitle="Copy FCM ID"
[appCopyToClipboard]="delivery.fcm_message_id"
></span>
} @else {
-
}
</td>
</tr>
}
</tbody>

View File

@@ -8,13 +8,16 @@ import { NzTagModule } from 'ng-zorro-antd/tag';
import { NzSpinModule } from 'ng-zorro-antd/spin';
import { NzPopconfirmModule } from 'ng-zorro-antd/popconfirm';
import { NzTableModule } from 'ng-zorro-antd/table';
import { NzToolTipModule } from 'ng-zorro-antd/tooltip';
import { ApiService } from '../../../core/services/api.service';
import { AuthService } from '../../../core/services/auth.service';
import { NotificationService } from '../../../core/services/notification.service';
import { SettingsService } from '../../../core/services/settings.service';
import { KeyCacheService, ResolvedKey } from '../../../core/services/key-cache.service';
import { UserCacheService, ResolvedUser } from '../../../core/services/user-cache.service';
import { ClientCacheService, ResolvedClient } from '../../../core/services/client-cache.service';
import { Message, Delivery } from '../../../core/models';
import { CopyToClipboardDirective } from '../../../shared/directives/copy-to-clipboard.directive';
import { RelativeTimePipe } from '../../../shared/pipes/relative-time.pipe';
import { MetadataGridComponent, MetadataValueComponent } from '../../../shared/components/metadata-grid';
@@ -31,10 +34,12 @@ import { MetadataGridComponent, MetadataValueComponent } from '../../../shared/c
NzSpinModule,
NzPopconfirmModule,
NzTableModule,
NzToolTipModule,
RouterLink,
RelativeTimePipe,
MetadataGridComponent,
MetadataValueComponent,
CopyToClipboardDirective,
],
templateUrl: './message-detail.component.html',
styleUrl: './message-detail.component.scss'
@@ -48,11 +53,13 @@ export class MessageDetailComponent implements OnInit {
private settingsService = inject(SettingsService);
private keyCacheService = inject(KeyCacheService);
private userCacheService = inject(UserCacheService);
private clientCacheService = inject(ClientCacheService);
message = signal<Message | null>(null);
resolvedKey = signal<ResolvedKey | null>(null);
resolvedChannelOwner = signal<ResolvedUser | null>(null);
deliveries = signal<Delivery[]>([]);
resolvedClients = signal<Map<string, ResolvedClient>>(new Map());
loading = signal(true);
deleting = signal(false);
loadingDeliveries = signal(false);
@@ -107,6 +114,7 @@ export class MessageDetailComponent implements OnInit {
next: (response) => {
this.deliveries.set(response.deliveries);
this.loadingDeliveries.set(false);
this.resolveDeliveryClients(response.deliveries);
},
error: () => {
this.loadingDeliveries.set(false);
@@ -114,6 +122,27 @@ export class MessageDetailComponent implements OnInit {
});
}
private resolveDeliveryClients(deliveries: Delivery[]): void {
const uniqueClientIds = [...new Set(deliveries.map(d => d.receiver_client_id))];
for (const clientId of uniqueClientIds) {
this.clientCacheService.resolveClient(clientId).subscribe({
next: (resolved) => {
if (resolved) {
this.resolvedClients.update(map => {
const newMap = new Map(map);
newMap.set(clientId, resolved);
return newMap;
});
}
}
});
}
}
getResolvedClient(clientId: string): ResolvedClient | undefined {
return this.resolvedClients().get(clientId);
}
private resolveKey(keyId: string): void {
this.keyCacheService.resolveKey(keyId).subscribe({
next: (resolved) => this.resolvedKey.set(resolved)