fix(backoffice): merchant-friendly wording in Monitoring
Analytics, Reports, and Diagnostics were already clean (no raw
HTTP/queue-worker strings found on audit). Monitoring had three spots
speaking developer language by default:
- Background queue names ("order-notifications") -> friendly labels
("Order notifications").
- Webhook event keys ("order.created") -> friendly labels ("New order
placed").
- Activity log's "api" category showed the raw HTTP line
("GET /api/products responded 200 in 84ms") as the primary message.
Now shows a plain-language summary by default ("Product data
refreshed successfully"), with the raw string moved to a collapsed
"Technical details" <details> per event (api/error/warning rows).
This commit is contained in:
@@ -8,6 +8,8 @@ export interface AdminMonitoringEvent {
|
|||||||
category: AdminMonitoringCategory;
|
category: AdminMonitoringCategory;
|
||||||
level: AdminMonitoringLevel;
|
level: AdminMonitoringLevel;
|
||||||
message: string;
|
message: string;
|
||||||
|
/** Raw technical detail (endpoint, status code, latency, etc.), shown only behind "Technical details". */
|
||||||
|
technicalDetail?: string;
|
||||||
actor: string;
|
actor: string;
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
<div class="queue-grid">
|
<div class="queue-grid">
|
||||||
@for (queue of facade.queues(); track queue.name) {
|
@for (queue of facade.queues(); track queue.name) {
|
||||||
<div class="queue-item">
|
<div class="queue-item">
|
||||||
<strong>{{ queue.name }}</strong>
|
<strong>{{ queueLabel(queue.name) }}</strong>
|
||||||
<span>{{ 'adminMonitoring.depth' | translate }}: {{ queue.depth }}</span>
|
<span>{{ 'adminMonitoring.depth' | translate }}: {{ queue.depth }}</span>
|
||||||
<app-badge [variant]="queue.status === 'healthy' ? 'success' : queue.status === 'degraded' ? 'warning' : 'danger'">{{ ('adminMonitoring.queueStatus.' + queue.status) | translate }}</app-badge>
|
<app-badge [variant]="queue.status === 'healthy' ? 'success' : queue.status === 'degraded' ? 'warning' : 'danger'">{{ ('adminMonitoring.queueStatus.' + queue.status) | translate }}</app-badge>
|
||||||
</div>
|
</div>
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
@for (delivery of facade.webhooks(); track delivery.id) {
|
@for (delivery of facade.webhooks(); track delivery.id) {
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{{ delivery.endpoint }}</th>
|
<th scope="row">{{ delivery.endpoint }}</th>
|
||||||
<td>{{ delivery.event }}</td>
|
<td>{{ webhookEventLabel(delivery.event) }}</td>
|
||||||
<td><app-badge [variant]="delivery.status === 'delivered' ? 'success' : delivery.status === 'failed' ? 'danger' : 'neutral'">{{ ('adminMonitoring.webhookStatus.' + delivery.status) | translate }}</app-badge></td>
|
<td><app-badge [variant]="delivery.status === 'delivered' ? 'success' : delivery.status === 'failed' ? 'danger' : 'neutral'">{{ ('adminMonitoring.webhookStatus.' + delivery.status) | translate }}</app-badge></td>
|
||||||
<td>{{ delivery.timestamp | date:'short' }}</td>
|
<td>{{ delivery.timestamp | date:'short' }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -97,7 +97,15 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>{{ ('adminMonitoring.categoryValue.' + event.category) | translate }}</td>
|
<td>{{ ('adminMonitoring.categoryValue.' + event.category) | translate }}</td>
|
||||||
<td><app-badge [variant]="event.level === 'error' ? 'danger' : event.level === 'warning' ? 'warning' : 'neutral'">{{ ('adminMonitoring.levelValue.' + event.level) | translate }}</app-badge></td>
|
<td><app-badge [variant]="event.level === 'error' ? 'danger' : event.level === 'warning' ? 'warning' : 'neutral'">{{ ('adminMonitoring.levelValue.' + event.level) | translate }}</app-badge></td>
|
||||||
<td>{{ event.message }}</td>
|
<td>
|
||||||
|
{{ event.message }}
|
||||||
|
@if (event.technicalDetail) {
|
||||||
|
<details class="monitoring-technical-detail">
|
||||||
|
<summary>{{ 'adminMonitoring.technicalDetails' | translate }}</summary>
|
||||||
|
<code>{{ event.technicalDetail }}</code>
|
||||||
|
</details>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
<td>{{ event.actor }}</td>
|
<td>{{ event.actor }}</td>
|
||||||
<td>{{ event.timestamp | date:'short' }}</td>
|
<td>{{ event.timestamp | date:'short' }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -13,3 +13,12 @@ select { min-height: 40px; padding: 0 10px; border: 1px solid var(--border-color
|
|||||||
// to a plain <td> - only the semantics change, not the visuals.
|
// to a plain <td> - only the semantics change, not the visuals.
|
||||||
tbody th[scope='row'] { font-weight: inherit; color: inherit; text-align: left; vertical-align: middle; }
|
tbody th[scope='row'] { font-weight: inherit; color: inherit; text-align: left; vertical-align: middle; }
|
||||||
tbody tr:last-child th[scope='row'] { border-bottom: none; }
|
tbody tr:last-child th[scope='row'] { border-bottom: none; }
|
||||||
|
|
||||||
|
.monitoring-technical-detail {
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: var(--font-size-sm, 0.8125rem);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
|
||||||
|
summary { cursor: pointer; color: var(--text-secondary); }
|
||||||
|
code { display: block; margin-top: 4px; word-break: break-all; }
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,7 +25,26 @@ export class AdminMonitoringPageComponent {
|
|||||||
|
|
||||||
readonly categories = ['all', 'audit', 'security', 'login', 'failed_login', 'api', 'error', 'warning'] as const;
|
readonly categories = ['all', 'audit', 'security', 'login', 'failed_login', 'api', 'error', 'warning'] as const;
|
||||||
|
|
||||||
|
private readonly queueLabels: Record<string, string> = {
|
||||||
|
'order-notifications': 'Order notifications',
|
||||||
|
'media-processing': 'Media processing',
|
||||||
|
'webhook-delivery': 'Webhook delivery',
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly webhookEventLabels: Record<string, string> = {
|
||||||
|
'order.created': 'New order placed',
|
||||||
|
'inventory.updated': 'Inventory updated',
|
||||||
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.facade.loadAll();
|
this.facade.loadAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
queueLabel(name: string): string {
|
||||||
|
return this.queueLabels[name] ?? name;
|
||||||
|
}
|
||||||
|
|
||||||
|
webhookEventLabel(event: string): string {
|
||||||
|
return this.webhookEventLabels[event] ?? event;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ export class AdminMonitoringLocalGateway implements AdminMonitoringGateway {
|
|||||||
category,
|
category,
|
||||||
level,
|
level,
|
||||||
message: this.messageFor(category, index),
|
message: this.messageFor(category, index),
|
||||||
|
technicalDetail: this.technicalDetailFor(category, index),
|
||||||
actor: ACTORS[index % ACTORS.length],
|
actor: ACTORS[index % ACTORS.length],
|
||||||
timestamp: new Date(Date.now() - index * 45 * 60 * 1000).toISOString(),
|
timestamp: new Date(Date.now() - index * 45 * 60 * 1000).toISOString(),
|
||||||
};
|
};
|
||||||
@@ -73,10 +74,19 @@ export class AdminMonitoringLocalGateway implements AdminMonitoringGateway {
|
|||||||
case 'security': return `Security policy check passed (#${index})`;
|
case 'security': return `Security policy check passed (#${index})`;
|
||||||
case 'login': return `Successful admin login (#${index})`;
|
case 'login': return `Successful admin login (#${index})`;
|
||||||
case 'failed_login': return `Failed login attempt (#${index})`;
|
case 'failed_login': return `Failed login attempt (#${index})`;
|
||||||
case 'api': return `GET /api/products responded 200 in ${80 + index}ms`;
|
case 'api': return `Product data refreshed successfully`;
|
||||||
case 'error': return `Unhandled exception in checkout flow (#${index})`;
|
case 'error': return `A background task failed unexpectedly`;
|
||||||
case 'warning': return `Slow query detected (#${index})`;
|
case 'warning': return `A data lookup took longer than usual`;
|
||||||
default: return `Event #${index}`;
|
default: return `Event #${index}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private technicalDetailFor(category: AdminMonitoringCategory, index: number): string | undefined {
|
||||||
|
switch (category) {
|
||||||
|
case 'api': return `GET /api/products responded 200 in ${80 + index}ms`;
|
||||||
|
case 'error': return `Unhandled exception in checkout flow (#${index})`;
|
||||||
|
case 'warning': return `Slow query detected (#${index})`;
|
||||||
|
default: return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1850,6 +1850,7 @@ export const en: Translations = {
|
|||||||
category: 'Category',
|
category: 'Category',
|
||||||
level: 'Severity',
|
level: 'Severity',
|
||||||
message: 'Description',
|
message: 'Description',
|
||||||
|
technicalDetails: 'Technical details',
|
||||||
categoryValue: {
|
categoryValue: {
|
||||||
all: 'All categories',
|
all: 'All categories',
|
||||||
audit: 'Admin activity',
|
audit: 'Admin activity',
|
||||||
|
|||||||
@@ -1845,6 +1845,7 @@ export const hy: Translations = {
|
|||||||
category: 'Կատեգորիա',
|
category: 'Կատեգորիա',
|
||||||
level: 'Կարևորություն',
|
level: 'Կարևորություն',
|
||||||
message: 'Նկարագրություն',
|
message: 'Նկարագրություն',
|
||||||
|
technicalDetails: 'Տեխնիկական մանրամասներ',
|
||||||
categoryValue: {
|
categoryValue: {
|
||||||
all: 'Բոլոր կատեգորիաները',
|
all: 'Բոլոր կատեգորիաները',
|
||||||
audit: 'Ադմինի գործողություններ',
|
audit: 'Ադմինի գործողություններ',
|
||||||
|
|||||||
@@ -1845,6 +1845,7 @@ export const ru: Translations = {
|
|||||||
category: 'Категория',
|
category: 'Категория',
|
||||||
level: 'Важность',
|
level: 'Важность',
|
||||||
message: 'Описание',
|
message: 'Описание',
|
||||||
|
technicalDetails: 'Технические детали',
|
||||||
categoryValue: {
|
categoryValue: {
|
||||||
all: 'Все категории',
|
all: 'Все категории',
|
||||||
audit: 'Действия администраторов',
|
audit: 'Действия администраторов',
|
||||||
|
|||||||
@@ -1858,6 +1858,7 @@ export interface Translations {
|
|||||||
category: string;
|
category: string;
|
||||||
level: string;
|
level: string;
|
||||||
message: string;
|
message: string;
|
||||||
|
technicalDetails: string;
|
||||||
categoryValue: {
|
categoryValue: {
|
||||||
all: string;
|
all: string;
|
||||||
audit: string;
|
audit: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user