feat: Add Health Check Endpoint
This commit is contained in:
parent
d26feb396f
commit
5044aa7573
@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"folders": [
|
|
||||||
{
|
|
||||||
"path": "../../../../../dcb-user-service"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "../../../.."
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"settings": {}
|
|
||||||
}
|
|
||||||
@ -68,13 +68,26 @@ export interface ReportParams {
|
|||||||
endDate?: string;
|
endDate?: string;
|
||||||
merchantPartnerId?: number;
|
merchantPartnerId?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HealthCheckStatus {
|
export interface HealthCheckStatus {
|
||||||
service: string;
|
service: string;
|
||||||
url: string;
|
url: string;
|
||||||
status: 'UP' | 'DOWN';
|
status: 'UP' | 'DOWN';
|
||||||
statusCode: number;
|
statusCode: number;
|
||||||
checkedAt: string;
|
checkedAt: string;
|
||||||
|
responseTime: string;
|
||||||
|
uptime?: number;
|
||||||
|
note?: string;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HealthCheckResponse {
|
||||||
|
summary: {
|
||||||
|
total: number;
|
||||||
|
up: number;
|
||||||
|
down: number;
|
||||||
|
timestamp: string;
|
||||||
|
};
|
||||||
|
details: HealthCheckStatus[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChartDataNormalized : normalisation des données pour tous types de chart
|
// ChartDataNormalized : normalisation des données pour tous types de chart
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import {
|
|||||||
SubscriptionReport,
|
SubscriptionReport,
|
||||||
SyncResponse,
|
SyncResponse,
|
||||||
HealthCheckStatus,
|
HealthCheckStatus,
|
||||||
|
HealthCheckResponse,
|
||||||
ChartDataNormalized
|
ChartDataNormalized
|
||||||
} from '../models/dcb-reporting.models';
|
} from '../models/dcb-reporting.models';
|
||||||
import { environment } from '@environments/environment';
|
import { environment } from '@environments/environment';
|
||||||
@ -282,7 +283,7 @@ export class ReportService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------
|
// ---------------------
|
||||||
// Health checks (rest of the code remains the same)
|
// Health checks
|
||||||
// ---------------------
|
// ---------------------
|
||||||
|
|
||||||
private checkApiAvailability(
|
private checkApiAvailability(
|
||||||
@ -316,6 +317,8 @@ export class ReportService {
|
|||||||
timeout(this.DEFAULT_TIMEOUT),
|
timeout(this.DEFAULT_TIMEOUT),
|
||||||
map((resp: HttpResponse<any>) => {
|
map((resp: HttpResponse<any>) => {
|
||||||
const finalResponseTime = Date.now() - startTime;
|
const finalResponseTime = Date.now() - startTime;
|
||||||
|
const body: any = resp.body;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
service,
|
service,
|
||||||
url,
|
url,
|
||||||
@ -323,6 +326,7 @@ export class ReportService {
|
|||||||
statusCode: resp.status,
|
statusCode: resp.status,
|
||||||
checkedAt: new Date().toISOString(),
|
checkedAt: new Date().toISOString(),
|
||||||
responseTime: `${finalResponseTime}ms`,
|
responseTime: `${finalResponseTime}ms`,
|
||||||
|
uptime: body?.uptime,
|
||||||
note: 'Used GET fallback'
|
note: 'Used GET fallback'
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
@ -397,16 +401,17 @@ export class ReportService {
|
|||||||
/**
|
/**
|
||||||
* Health check global de toutes les APIs
|
* Health check global de toutes les APIs
|
||||||
* Scanne chaque URL d'API directement
|
* Scanne chaque URL d'API directement
|
||||||
*/
|
*/
|
||||||
|
private buildHealthUrl(baseUrl: string): string {
|
||||||
|
return `${baseUrl.replace(/\/$/, '')}/health`;
|
||||||
|
}
|
||||||
|
|
||||||
globalHealthCheck(): Observable<HealthCheckStatus[]> {
|
globalHealthCheck(): Observable<HealthCheckStatus[]> {
|
||||||
const healthChecks: Observable<HealthCheckStatus>[] = [];
|
return forkJoin(
|
||||||
|
Object.entries(this.apiEndpoints).map(([service, url]) =>
|
||||||
// Vérifiez chaque service avec sa racine
|
this.checkApiAvailability(service, this.buildHealthUrl(url))
|
||||||
Object.entries(this.apiEndpoints).forEach(([service, url]) => {
|
)
|
||||||
healthChecks.push(this.checkApiAvailability(service, url));
|
);
|
||||||
});
|
|
||||||
|
|
||||||
return forkJoin(healthChecks);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -438,10 +443,7 @@ export class ReportService {
|
|||||||
/**
|
/**
|
||||||
* Health check détaillé avec métriques
|
* Health check détaillé avec métriques
|
||||||
*/
|
*/
|
||||||
detailedHealthCheck(): Observable<{
|
detailedHealthCheck(): Observable<HealthCheckResponse> {
|
||||||
summary: { total: number; up: number; down: number; timestamp: string };
|
|
||||||
details: HealthCheckStatus[];
|
|
||||||
}> {
|
|
||||||
return this.globalHealthCheck().pipe(
|
return this.globalHealthCheck().pipe(
|
||||||
map(results => {
|
map(results => {
|
||||||
const adjustedResults = results.map(result => ({
|
const adjustedResults = results.map(result => ({
|
||||||
@ -462,7 +464,24 @@ export class ReportService {
|
|||||||
details: adjustedResults
|
details: adjustedResults
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
catchError(err => this.handleError(err))
|
catchError(err => this.handleHealthError(err))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gestion des erreurs
|
||||||
|
*/
|
||||||
|
private handleHealthError(error: any): Observable<HealthCheckResponse> {
|
||||||
|
console.error('Health check error:', error);
|
||||||
|
|
||||||
|
return of({
|
||||||
|
summary: {
|
||||||
|
total: 0,
|
||||||
|
up: 0,
|
||||||
|
down: 0,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
},
|
||||||
|
details: []
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1 +0,0 @@
|
|||||||
<p>Integrations</p>
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
import { Integrations } from './integrations';
|
|
||||||
describe('Integrations', () => {});
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-integrations',
|
|
||||||
templateUrl: './integrations.html',
|
|
||||||
})
|
|
||||||
export class Integrations {}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class IntegrationsService {
|
|
||||||
constructor() {}
|
|
||||||
}
|
|
||||||
@ -730,105 +730,81 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- CONFIGURATIONS TECHNIQUES -->
|
<!-- LISTE DES CONFIGURATIONS (READONLY) -->
|
||||||
<div class="row g-3 mb-4">
|
<div class="row g-3">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="d-flex justify-content-between align-items-center border-bottom pb-2">
|
<div class="d-flex justify-content-between align-items-center border-bottom pb-2 mb-3">
|
||||||
<h6 class="mb-0 text-primary">
|
<h6 class="mb-0 text-primary">
|
||||||
<ng-icon name="lucideSettings" class="me-2"></ng-icon>
|
<ng-icon name="lucideSettings" class="me-2"></ng-icon>
|
||||||
Configurations Techniques
|
Configurations
|
||||||
</h6>
|
</h6>
|
||||||
<button
|
<span class="badge bg-secondary">{{ selectedMerchantForEdit.configs.length || 0 }} config(s)</span>
|
||||||
type="button"
|
|
||||||
class="btn btn-outline-primary btn-sm"
|
|
||||||
(click)="addConfigInEdit()"
|
|
||||||
[disabled]="updatingMerchant"
|
|
||||||
>
|
|
||||||
<ng-icon name="lucidePlus" class="me-1"></ng-icon>
|
|
||||||
Ajouter une configuration
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (!selectedMerchantForEdit.configs || selectedMerchantForEdit.configs.length === 0) {
|
@if (!selectedMerchantForEdit.configs || selectedMerchantForEdit.configs.length === 0) {
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="alert alert-warning">
|
<div class="alert alert-info">
|
||||||
<ng-icon name="lucideAlertCircle" class="me-2"></ng-icon>
|
<ng-icon name="lucideInfo" class="me-2"></ng-icon>
|
||||||
Au moins une configuration est requise
|
Aucune configuration disponible
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<!-- Liste des configurations -->
|
<!-- Liste des configurations en mode lecture -->
|
||||||
@for (config of selectedMerchantForEdit.configs; track trackByConfigId($index, config); let i = $index) {
|
@for (config of selectedMerchantForEdit.configs; track config.id || $index; let i = $index) {
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card border-0 shadow-sm">
|
<div class="card border-0 shadow-sm mb-3">
|
||||||
<div class="card-header bg-light py-2 d-flex justify-content-between align-items-center">
|
<div class="card-header bg-light py-2 d-flex justify-content-between align-items-center">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<ng-icon [name]="getConfigTypeIconSafe(config.name)" class="me-2 text-primary"></ng-icon>
|
<ng-icon name="lucideSettings" class="me-2 text-primary"></ng-icon>
|
||||||
<span class="fw-semibold">Configuration {{ i + 1 }}</span>
|
<span class="fw-semibold">Configuration {{ i + 1 }}</span>
|
||||||
</div>
|
</div>
|
||||||
@if (selectedMerchantForEdit.configs.length > 1) {
|
@if (config.name.includes('SECRET') || config.name.includes('KEY') || config.value.includes('password')) {
|
||||||
<button
|
<span class="badge bg-warning text-dark">
|
||||||
type="button"
|
<ng-icon name="lucideShield" class="me-1"></ng-icon>
|
||||||
class="btn btn-sm btn-outline-danger"
|
Sensible
|
||||||
(click)="removeConfigInEdit(i)"
|
</span>
|
||||||
[disabled]="updatingMerchant"
|
|
||||||
>
|
|
||||||
<ng-icon name="lucideTrash2" class="me-1"></ng-icon>
|
|
||||||
Supprimer
|
|
||||||
</button>
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
|
<!-- Type de configuration -->
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label">Type <span class="text-danger">*</span></label>
|
<div class="mb-2">
|
||||||
<select
|
<small class="text-muted d-block">Type</small>
|
||||||
class="form-select"
|
<div class="d-flex align-items-center">
|
||||||
[(ngModel)]="config.name"
|
<ng-icon name="lucideSettings" class="me-2 text-muted"></ng-icon>
|
||||||
[name]="'editConfigType_' + i"
|
<span class="fw-medium">
|
||||||
required
|
{{ config.name || 'Non spécifié' }}
|
||||||
[disabled]="updatingMerchant"
|
</span>
|
||||||
>
|
|
||||||
<option value="" disabled>Sélectionnez un type</option>
|
|
||||||
@for (type of configTypes; track type.name) {
|
|
||||||
<option [value]="type.name">{{ type.label }}</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label class="form-label">Opérateur <span class="text-danger">*</span></label>
|
|
||||||
<select
|
|
||||||
class="form-select"
|
|
||||||
[(ngModel)]="config.operatorId"
|
|
||||||
[name]="'editOperatorId_' + i"
|
|
||||||
required
|
|
||||||
[disabled]="updatingMerchant"
|
|
||||||
>
|
|
||||||
<option value="" disabled>Sélectionnez un opérateur</option>
|
|
||||||
@for (operator of operators; track operator.id) {
|
|
||||||
<option [value]="operator.id">{{ operator.name }}</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-12">
|
|
||||||
<label class="form-label">Valeur <span class="text-danger">*</span></label>
|
|
||||||
<textarea
|
|
||||||
class="form-control font-monospace"
|
|
||||||
[(ngModel)]="config.value"
|
|
||||||
[name]="'editValue_' + i"
|
|
||||||
required
|
|
||||||
[disabled]="updatingMerchant"
|
|
||||||
rows="3"
|
|
||||||
placeholder="Valeur de configuration"
|
|
||||||
></textarea>
|
|
||||||
@if (isSensitiveConfig(config)) {
|
|
||||||
<div class="form-text text-warning">
|
|
||||||
<ng-icon name="lucideShield" class="me-1"></ng-icon>
|
|
||||||
Cette configuration contient des informations sensibles
|
|
||||||
</div>
|
</div>
|
||||||
}
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Opérateur -->
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-2">
|
||||||
|
<small class="text-muted d-block">Opérateur ID</small>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<ng-icon name="lucideBuilding" class="me-2 text-muted"></ng-icon>
|
||||||
|
<span class="fw-medium">
|
||||||
|
{{ config.operatorId || 'Non spécifié' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Valeur -->
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="mb-2">
|
||||||
|
<small class="text-muted d-block">Valeur</small>
|
||||||
|
<div class="p-3 bg-light rounded border">
|
||||||
|
<pre class="mb-0" style="white-space: pre-wrap; word-break: break-all;">
|
||||||
|
{{ config.value || 'Aucune valeur' }}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -837,23 +813,15 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- CONTACTS TECHNIQUES -->
|
<!-- CONTACTS TECHNIQUES (READONLY) -->
|
||||||
<div class="row g-3">
|
<div class="row g-3 mt-4">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="d-flex justify-content-between align-items-center border-bottom pb-2">
|
<div class="d-flex justify-content-between align-items-center border-bottom pb-2 mb-3">
|
||||||
<h6 class="mb-0 text-primary">
|
<h6 class="mb-0 text-primary">
|
||||||
<ng-icon name="lucideUsers" class="me-2"></ng-icon>
|
<ng-icon name="lucideUsers" class="me-2"></ng-icon>
|
||||||
Contacts Techniques
|
Contacts Techniques
|
||||||
</h6>
|
</h6>
|
||||||
<button
|
<span class="badge bg-secondary">{{ selectedMerchantForEdit.technicalContacts.length || 0 }} contact(s)</span>
|
||||||
type="button"
|
|
||||||
class="btn btn-outline-primary btn-sm"
|
|
||||||
(click)="addTechnicalContactInEdit()"
|
|
||||||
[disabled]="updatingMerchant"
|
|
||||||
>
|
|
||||||
<ng-icon name="lucideUserPlus" class="me-1"></ng-icon>
|
|
||||||
Ajouter un contact
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -861,89 +829,69 @@
|
|||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="alert alert-warning">
|
<div class="alert alert-warning">
|
||||||
<ng-icon name="lucideAlertCircle" class="me-2"></ng-icon>
|
<ng-icon name="lucideAlertCircle" class="me-2"></ng-icon>
|
||||||
Au moins un contact technique est requis
|
Aucun contact technique défini
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
<!-- Liste des contacts techniques en mode lecture -->
|
||||||
<!-- Liste des contacts techniques -->
|
@for (contact of selectedMerchantForEdit.technicalContacts; track contact.id || $index; let i = $index) {
|
||||||
@for (contact of selectedMerchantForEdit.technicalContacts; track trackByContactId($index, contact); let i = $index) {
|
<div class="col-12 col-md-6 col-lg-4">
|
||||||
<div class="col-12">
|
<div class="card border-0 shadow-sm h-100">
|
||||||
<div class="card border-0 shadow-sm">
|
<div class="card-header bg-light py-2">
|
||||||
<div class="card-header bg-light py-2 d-flex justify-content-between align-items-center">
|
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<ng-icon name="lucideUser" class="me-2 text-primary"></ng-icon>
|
<ng-icon name="lucideUser" class="me-2 text-primary"></ng-icon>
|
||||||
<span class="fw-semibold">Contact {{ i + 1 }}</span>
|
<span class="fw-semibold">Contact {{ i + 1 }}</span>
|
||||||
</div>
|
</div>
|
||||||
@if (selectedMerchantForEdit.technicalContacts.length > 1) {
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-sm btn-outline-danger"
|
|
||||||
(click)="removeTechnicalContactInEdit(i)"
|
|
||||||
[disabled]="updatingMerchant"
|
|
||||||
>
|
|
||||||
<ng-icon name="lucideTrash2" class="me-1"></ng-icon>
|
|
||||||
Supprimer
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row g-3">
|
<!-- Nom complet -->
|
||||||
<div class="col-md-6">
|
<div class="mb-3">
|
||||||
<label class="form-label">Prénom <span class="text-danger">*</span></label>
|
<small class="text-muted d-block">Nom complet</small>
|
||||||
<input
|
<div class="d-flex align-items-center">
|
||||||
type="text"
|
<ng-icon name="lucideUser" class="me-2 text-muted"></ng-icon>
|
||||||
class="form-control"
|
<span class="fw-medium">
|
||||||
[(ngModel)]="contact.firstName"
|
{{ contact.firstName || '' }} {{ contact.lastName || '' }}
|
||||||
[name]="'editFirstName_' + i"
|
@if (!contact.firstName && !contact.lastName) {
|
||||||
required
|
<span class="text-muted fst-italic">Non spécifié</span>
|
||||||
[disabled]="updatingMerchant"
|
}
|
||||||
placeholder="Prénom"
|
</span>
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
</div>
|
||||||
<label class="form-label">Nom <span class="text-danger">*</span></label>
|
|
||||||
<input
|
<!-- Téléphone -->
|
||||||
type="text"
|
<div class="mb-3">
|
||||||
class="form-control"
|
<small class="text-muted d-block">Téléphone</small>
|
||||||
[(ngModel)]="contact.lastName"
|
<div class="d-flex align-items-center">
|
||||||
[name]="'editLastName_' + i"
|
<ng-icon name="lucidePhone" class="me-2 text-muted"></ng-icon>
|
||||||
required
|
@if (contact.phone) {
|
||||||
[disabled]="updatingMerchant"
|
<a href="tel:{{ contact.phone }}" class="text-decoration-none">
|
||||||
placeholder="Nom"
|
{{ contact.phone }}
|
||||||
>
|
</a>
|
||||||
|
} @else {
|
||||||
|
<span class="text-muted fst-italic">Non spécifié</span>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
</div>
|
||||||
<label class="form-label">Téléphone <span class="text-danger">*</span></label>
|
|
||||||
<input
|
<!-- Email -->
|
||||||
type="text"
|
<div class="mb-3">
|
||||||
class="form-control"
|
<small class="text-muted d-block">Email</small>
|
||||||
[(ngModel)]="contact.phone"
|
<div class="d-flex align-items-center">
|
||||||
[name]="'editPhone_' + i"
|
<ng-icon name="lucideMail" class="me-2 text-muted"></ng-icon>
|
||||||
required
|
@if (contact.email) {
|
||||||
[disabled]="updatingMerchant"
|
<a href="mailto:{{ contact.email }}" class="text-decoration-none">
|
||||||
placeholder="+XX X XX XX XX XX"
|
{{ contact.email }}
|
||||||
>
|
</a>
|
||||||
</div>
|
} @else {
|
||||||
<div class="col-md-6">
|
<span class="text-muted fst-italic">Non spécifié</span>
|
||||||
<label class="form-label">Email <span class="text-danger">*</span></label>
|
}
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
class="form-control"
|
|
||||||
[(ngModel)]="contact.email"
|
|
||||||
[name]="'editEmail_' + i"
|
|
||||||
required
|
|
||||||
[disabled]="updatingMerchant"
|
|
||||||
placeholder="email@exemple.com"
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-footer mt-4 border-top pt-3">
|
<div class="modal-footer mt-4 border-top pt-3">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@ -102,8 +102,6 @@ export class MerchantConfigService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getMerchantById(userId: number): Observable<Merchant> {
|
getMerchantById(userId: number): Observable<Merchant> {
|
||||||
//const numericId = this.convertIdToNumber(id);
|
|
||||||
|
|
||||||
console.log(`📥 Loading merchant ${userId}`);
|
console.log(`📥 Loading merchant ${userId}`);
|
||||||
|
|
||||||
return this.http.get<ApiMerchant>(`${this.baseApiUrl}/${userId}`).pipe(
|
return this.http.get<ApiMerchant>(`${this.baseApiUrl}/${userId}`).pipe(
|
||||||
@ -118,8 +116,6 @@ export class MerchantConfigService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateMerchant(id: number, updateMerchantDto: UpdateMerchantDto): Observable<Merchant> {
|
updateMerchant(id: number, updateMerchantDto: UpdateMerchantDto): Observable<Merchant> {
|
||||||
//const numericId = this.convertIdToNumber(id);
|
|
||||||
|
|
||||||
const apiDto = this.dataAdapter.convertUpdateMerchantToApi(updateMerchantDto);
|
const apiDto = this.dataAdapter.convertUpdateMerchantToApi(updateMerchantDto);
|
||||||
|
|
||||||
console.log(`📤 Updating merchant ${id}:`, apiDto);
|
console.log(`📤 Updating merchant ${id}:`, apiDto);
|
||||||
|
|||||||
@ -310,27 +310,6 @@ export class MerchantConfigManagement implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gestion des contacts dans l'édition
|
|
||||||
addTechnicalContactInEdit(): void {
|
|
||||||
if (!this.selectedMerchantForEdit?.technicalContacts) {
|
|
||||||
this.selectedMerchantForEdit!.technicalContacts = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.selectedMerchantForEdit?.technicalContacts.push({
|
|
||||||
firstName: '',
|
|
||||||
lastName: '',
|
|
||||||
phone: '',
|
|
||||||
email: ''
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
removeTechnicalContactInEdit(index: number): void {
|
|
||||||
if (this.selectedMerchantForEdit?.technicalContacts &&
|
|
||||||
this.selectedMerchantForEdit.technicalContacts.length > 1) {
|
|
||||||
this.selectedMerchantForEdit.technicalContacts.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Méthodes pour la gestion des configurations
|
* Méthodes pour la gestion des configurations
|
||||||
*/
|
*/
|
||||||
@ -352,25 +331,6 @@ export class MerchantConfigManagement implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Gestion des configs dans l'édition
|
|
||||||
addConfigInEdit(): void {
|
|
||||||
if (!this.selectedMerchantForEdit?.configs) {
|
|
||||||
this.selectedMerchantForEdit!.configs = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.selectedMerchantForEdit?.configs.push({
|
|
||||||
name: ConfigType.API_KEY,
|
|
||||||
value: '',
|
|
||||||
operatorId: Operator.ORANGE_OSN
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
removeConfigInEdit(index: number): void {
|
|
||||||
if (this.selectedMerchantForEdit?.configs && this.selectedMerchantForEdit.configs.length > 1) {
|
|
||||||
this.selectedMerchantForEdit.configs.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== CONVERSION IDS ====================
|
// ==================== CONVERSION IDS ====================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -891,7 +851,7 @@ export class MerchantConfigManagement implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Appel API pour mettre à jour le marchand (version avec switchMap)
|
* Appel API pour mettre à jour le marchand
|
||||||
*/
|
*/
|
||||||
updateMerchant(): void {
|
updateMerchant(): void {
|
||||||
if (!this.selectedMerchantForEdit) {
|
if (!this.selectedMerchantForEdit) {
|
||||||
@ -1210,57 +1170,12 @@ export class MerchantConfigManagement implements OnInit, OnDestroy {
|
|||||||
errors.push('Le téléphone est requis');
|
errors.push('Le téléphone est requis');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validation des configurations
|
|
||||||
if (!merchant.configs || merchant.configs.length === 0) {
|
|
||||||
errors.push('Au moins une configuration est requise');
|
|
||||||
} else {
|
|
||||||
merchant.configs.forEach((config, index) => {
|
|
||||||
if (!config.name?.trim()) {
|
|
||||||
errors.push(`Le type de configuration ${index + 1} est requis`);
|
|
||||||
}
|
|
||||||
if (!config.value?.trim()) {
|
|
||||||
errors.push(`La valeur de configuration ${index + 1} est requise`);
|
|
||||||
}
|
|
||||||
if (!config.operatorId) {
|
|
||||||
errors.push(`L'opérateur de configuration ${index + 1} est requis`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validation des contacts techniques
|
|
||||||
if (!merchant.technicalContacts || merchant.technicalContacts.length === 0) {
|
|
||||||
errors.push('Au moins un contact technique est requis');
|
|
||||||
} else {
|
|
||||||
merchant.technicalContacts.forEach((contact, index) => {
|
|
||||||
if (!contact.firstName?.trim()) {
|
|
||||||
errors.push(`Le prénom du contact ${index + 1} est requis`);
|
|
||||||
}
|
|
||||||
if (!contact.lastName?.trim()) {
|
|
||||||
errors.push(`Le nom du contact ${index + 1} est requis`);
|
|
||||||
}
|
|
||||||
if (!contact.phone?.trim()) {
|
|
||||||
errors.push(`Le téléphone du contact ${index + 1} est requis`);
|
|
||||||
}
|
|
||||||
if (!contact.email?.trim()) {
|
|
||||||
errors.push(`L'email du contact ${index + 1} est requis`);
|
|
||||||
} else if (!this.isValidEmail(contact.email)) {
|
|
||||||
errors.push(`L'email du contact ${index + 1} est invalide`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isValid: errors.length === 0,
|
isValid: errors.length === 0,
|
||||||
errors: errors
|
errors: errors
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validation d'email
|
|
||||||
private isValidEmail(email: string): boolean {
|
|
||||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
||||||
return emailRegex.test(email);
|
|
||||||
}
|
|
||||||
|
|
||||||
confirmDeleteMerchant(): void {
|
confirmDeleteMerchant(): void {
|
||||||
if (!this.selectedMerchantForDelete) {
|
if (!this.selectedMerchantForDelete) {
|
||||||
this.deleteMerchantError = 'Aucun marchand sélectionné pour suppression';
|
this.deleteMerchantError = 'Aucun marchand sélectionné pour suppression';
|
||||||
|
|||||||
@ -29,7 +29,7 @@ export class MerchantDataAdapter {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...apiMerchant,
|
...apiMerchant,
|
||||||
id: apiMerchant.id, //this.convertIdToString(apiMerchant.id),
|
id: apiMerchant.id,
|
||||||
configs: (apiMerchant.configs || []).map(config =>
|
configs: (apiMerchant.configs || []).map(config =>
|
||||||
this.convertApiConfigToFrontend(config)
|
this.convertApiConfigToFrontend(config)
|
||||||
),
|
),
|
||||||
@ -117,33 +117,6 @@ export class MerchantDataAdapter {
|
|||||||
if (dto.adresse !== undefined) updateData.adresse = dto.adresse?.trim();
|
if (dto.adresse !== undefined) updateData.adresse = dto.adresse?.trim();
|
||||||
if (dto.phone !== undefined) updateData.phone = dto.phone?.trim();
|
if (dto.phone !== undefined) updateData.phone = dto.phone?.trim();
|
||||||
|
|
||||||
// Configurations - seulement si présentes dans le DTO
|
|
||||||
if (dto.configs !== undefined) {
|
|
||||||
updateData.configs = (dto.configs || []).map(config => {
|
|
||||||
const apiConfig: any = {
|
|
||||||
name: config.name,
|
|
||||||
value: config.value?.trim(),
|
|
||||||
operatorId: this.validateOperatorId(config.operatorId)
|
|
||||||
};
|
|
||||||
|
|
||||||
return apiConfig;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Contacts techniques - seulement si présents dans le DTO
|
|
||||||
if (dto.technicalContacts !== undefined) {
|
|
||||||
updateData.technicalContacts = (dto.technicalContacts || []).map(contact => {
|
|
||||||
const apiContact: any = {
|
|
||||||
firstName: contact.firstName?.trim(),
|
|
||||||
lastName: contact.lastName?.trim(),
|
|
||||||
phone: contact.phone?.trim(),
|
|
||||||
email: contact.email?.trim()
|
|
||||||
};
|
|
||||||
|
|
||||||
return apiContact;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return updateData;
|
return updateData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,49 +223,6 @@ export class MerchantDataAdapter {
|
|||||||
errors.push('Le téléphone est requis');
|
errors.push('Le téléphone est requis');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validation des configurations si présentes
|
|
||||||
if (dto.configs !== undefined) {
|
|
||||||
if (dto.configs.length === 0) {
|
|
||||||
errors.push('Au moins une configuration est requise');
|
|
||||||
} else {
|
|
||||||
dto.configs.forEach((config, index) => {
|
|
||||||
if (!config.name?.trim()) {
|
|
||||||
errors.push(`Le type de configuration ${index + 1} est requis`);
|
|
||||||
}
|
|
||||||
if (!config.value?.trim()) {
|
|
||||||
errors.push(`La valeur de configuration ${index + 1} est requise`);
|
|
||||||
}
|
|
||||||
if (!config.operatorId) {
|
|
||||||
errors.push(`L'opérateur de configuration ${index + 1} est requis`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validation des contacts si présents
|
|
||||||
if (dto.technicalContacts !== undefined) {
|
|
||||||
if (dto.technicalContacts.length === 0) {
|
|
||||||
errors.push('Au moins un contact technique est requis');
|
|
||||||
} else {
|
|
||||||
dto.technicalContacts.forEach((contact, index) => {
|
|
||||||
if (!contact.firstName?.trim()) {
|
|
||||||
errors.push(`Le prénom du contact ${index + 1} est requis`);
|
|
||||||
}
|
|
||||||
if (!contact.lastName?.trim()) {
|
|
||||||
errors.push(`Le nom du contact ${index + 1} est requis`);
|
|
||||||
}
|
|
||||||
if (!contact.phone?.trim()) {
|
|
||||||
errors.push(`Le téléphone du contact ${index + 1} est requis`);
|
|
||||||
}
|
|
||||||
if (!contact.email?.trim()) {
|
|
||||||
errors.push(`L'email du contact ${index + 1} est requis`);
|
|
||||||
} else if (!this.isValidEmail(contact.email)) {
|
|
||||||
errors.push(`L'email du contact ${index + 1} est invalide`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
throw new Error(`Validation failed: ${errors.join(', ')}`);
|
throw new Error(`Validation failed: ${errors.join(', ')}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,15 +7,9 @@ import { MerchantUsersManagement } from '@modules/hub-users-management/merchant-
|
|||||||
|
|
||||||
// Composants principaux
|
// Composants principaux
|
||||||
import { DcbReportingDashboard } from '@modules/dcb-dashboard/dcb-reporting-dashboard';
|
import { DcbReportingDashboard } from '@modules/dcb-dashboard/dcb-reporting-dashboard';
|
||||||
import { Team } from '@modules/team/team';
|
|
||||||
import { Transactions } from '@modules/transactions/transactions';
|
import { Transactions } from '@modules/transactions/transactions';
|
||||||
import { OperatorsConfig } from '@modules/operators/config/config';
|
|
||||||
import { OperatorsStats } from '@modules/operators/stats/stats';
|
|
||||||
import { WebhooksHistory } from '@modules/webhooks/history/history';
|
|
||||||
import { WebhooksStatus } from '@modules/webhooks/status/status';
|
|
||||||
import { WebhooksRetry } from '@modules/webhooks/retry/retry';
|
|
||||||
import { Settings } from '@modules/settings/settings';
|
|
||||||
import { Integrations } from '@modules/integrations/integrations';
|
|
||||||
import { MyProfile } from '@modules/profile/profile';
|
import { MyProfile } from '@modules/profile/profile';
|
||||||
import { Documentation } from '@modules/documentation/documentation';
|
import { Documentation } from '@modules/documentation/documentation';
|
||||||
import { Help } from '@modules/help/help';
|
import { Help } from '@modules/help/help';
|
||||||
@ -38,18 +32,6 @@ const routes: Routes = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// ---------------------------
|
|
||||||
// Team
|
|
||||||
// ---------------------------
|
|
||||||
{
|
|
||||||
path: 'team',
|
|
||||||
component: Team,
|
|
||||||
canActivate: [authGuard, roleGuard],
|
|
||||||
data: {
|
|
||||||
title: 'Team',
|
|
||||||
module: 'team'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
// Transactions
|
// Transactions
|
||||||
@ -143,93 +125,6 @@ const routes: Routes = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// ---------------------------
|
|
||||||
// Operators (Admin seulement)
|
|
||||||
// ---------------------------
|
|
||||||
{
|
|
||||||
path: 'operators',
|
|
||||||
canActivate: [authGuard, roleGuard],
|
|
||||||
data: { module: 'operators' },
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'config',
|
|
||||||
component: OperatorsConfig,
|
|
||||||
data: {
|
|
||||||
title: 'Paramètres d\'Intégration',
|
|
||||||
module: 'operators/config'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'stats',
|
|
||||||
component: OperatorsStats,
|
|
||||||
data: {
|
|
||||||
title: 'Performance & Monitoring',
|
|
||||||
module: 'operators/stats'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
// ---------------------------
|
|
||||||
// Webhooks
|
|
||||||
// ---------------------------
|
|
||||||
{
|
|
||||||
path: 'webhooks',
|
|
||||||
canActivate: [authGuard, roleGuard],
|
|
||||||
data: { module: 'webhooks' },
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'history',
|
|
||||||
component: WebhooksHistory,
|
|
||||||
data: {
|
|
||||||
title: 'Historique',
|
|
||||||
module: 'webhooks/history'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'status',
|
|
||||||
component: WebhooksStatus,
|
|
||||||
data: {
|
|
||||||
title: 'Statut des Requêtes',
|
|
||||||
module: 'webhooks/status'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'retry',
|
|
||||||
component: WebhooksRetry,
|
|
||||||
data: {
|
|
||||||
title: 'Relancer Webhook',
|
|
||||||
module: 'webhooks/retry'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
// ---------------------------
|
|
||||||
// Settings
|
|
||||||
// ---------------------------
|
|
||||||
{
|
|
||||||
path: 'settings',
|
|
||||||
component: Settings,
|
|
||||||
canActivate: [authGuard, roleGuard],
|
|
||||||
data: {
|
|
||||||
title: 'Paramètres Système',
|
|
||||||
module: 'settings'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// ---------------------------
|
|
||||||
// Integrations (Admin seulement)
|
|
||||||
// ---------------------------
|
|
||||||
{
|
|
||||||
path: 'integrations',
|
|
||||||
component: Integrations,
|
|
||||||
canActivate: [authGuard, roleGuard],
|
|
||||||
data: {
|
|
||||||
title: 'Intégrations Externes',
|
|
||||||
module: 'integrations'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
// Profile (Tous les utilisateurs authentifiés)
|
// Profile (Tous les utilisateurs authentifiés)
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
<p>Notifications - Actions</p>
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
import { NotificationsActions } from './actions';
|
|
||||||
describe('NotificationsActions', () => {});
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-notifications-actions',
|
|
||||||
templateUrl: './actions.html',
|
|
||||||
})
|
|
||||||
export class NotificationsActions {}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
<p>Notifications - Filters</p>
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
import { NotificationsFilters } from './filters';
|
|
||||||
describe('NotificationsFilters', () => {});
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-notifications-filters',
|
|
||||||
templateUrl: './filters.html',
|
|
||||||
})
|
|
||||||
export class NotificationsFilters {}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
<p>Notifications - List</p>
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
import { NotificationsList } from './list';
|
|
||||||
describe('NotificationsList', () => {});
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-notifications-list',
|
|
||||||
templateUrl: './list.html',
|
|
||||||
})
|
|
||||||
export class NotificationsList {}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
<p>Notifications</p>
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
import { Notifications } from './notifications';
|
|
||||||
describe('Notifications', () => {});
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-notifications',
|
|
||||||
templateUrl: './notifications.html',
|
|
||||||
})
|
|
||||||
export class Notifications {}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class NotificationsActionsService {
|
|
||||||
constructor() {}
|
|
||||||
}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class NotificationsFiltersService {
|
|
||||||
constructor() {}
|
|
||||||
}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class NotificationsListService {
|
|
||||||
constructor() {}
|
|
||||||
}
|
|
||||||
@ -1,55 +0,0 @@
|
|||||||
import { Injectable, inject } from '@angular/core';
|
|
||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
import { environment } from '@environments/environment';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
|
|
||||||
export interface Notification {
|
|
||||||
id: string;
|
|
||||||
type: 'SMS' | 'EMAIL' | 'PUSH' | 'SYSTEM';
|
|
||||||
title: string;
|
|
||||||
message: string;
|
|
||||||
recipient: string;
|
|
||||||
status: 'SENT' | 'DELIVERED' | 'FAILED' | 'PENDING';
|
|
||||||
createdAt: Date;
|
|
||||||
sentAt?: Date;
|
|
||||||
errorMessage?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NotificationFilter {
|
|
||||||
type?: string;
|
|
||||||
status?: string;
|
|
||||||
startDate?: Date;
|
|
||||||
endDate?: Date;
|
|
||||||
recipient?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
|
||||||
export class NotificationService {
|
|
||||||
private http = inject(HttpClient);
|
|
||||||
private apiUrl = `${environment.localServiceTestApiUrl}/notifications`;
|
|
||||||
|
|
||||||
getNotifications(filters?: NotificationFilter): Observable<Notification[]> {
|
|
||||||
return this.http.post<Notification[]>(
|
|
||||||
`${this.apiUrl}/list`,
|
|
||||||
filters
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
sendNotification(notification: Partial<Notification>): Observable<Notification> {
|
|
||||||
return this.http.post<Notification>(
|
|
||||||
`${this.apiUrl}/send`,
|
|
||||||
notification
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getNotificationStats(): Observable<any> {
|
|
||||||
return this.http.get(`${this.apiUrl}/stats`);
|
|
||||||
}
|
|
||||||
|
|
||||||
retryNotification(notificationId: string): Observable<Notification> {
|
|
||||||
return this.http.post<Notification>(
|
|
||||||
`${this.apiUrl}/${notificationId}/retry`,
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
<p>Operators - Config</p>
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
import { OperatorsConfig } from './config';
|
|
||||||
describe('OperatorsConfig', () => {});
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
import { FormsModule } from '@angular/forms';
|
|
||||||
import { UiCard } from '@app/components/ui-card';
|
|
||||||
import { InputFields } from '@/app/modules/components/input-fields';
|
|
||||||
import { CheckboxesAndRadios } from '@/app/modules/components/checkboxes-and-radios';
|
|
||||||
import { InputTouchspin } from '@/app/modules/components/input-touchspin';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-operator-config',
|
|
||||||
//imports: [FormsModule, UiCard, InputFields, CheckboxesAndRadios, InputTouchspin],
|
|
||||||
templateUrl: './config.html',
|
|
||||||
})
|
|
||||||
export class OperatorsConfig {
|
|
||||||
operatorConfig = {
|
|
||||||
name: '',
|
|
||||||
apiEndpoint: '',
|
|
||||||
apiKey: '',
|
|
||||||
secretKey: '',
|
|
||||||
timeout: 30,
|
|
||||||
retryAttempts: 3,
|
|
||||||
webhookUrl: '',
|
|
||||||
isActive: true,
|
|
||||||
supportedCountries: [] as string[],
|
|
||||||
supportedServices: ['DCB', 'SMS', 'USSD']
|
|
||||||
};
|
|
||||||
|
|
||||||
countries = ['CIV', 'SEN', 'CMR', 'COD', 'TUN', 'BFA', 'MLI', 'GIN'];
|
|
||||||
|
|
||||||
saveConfig() {
|
|
||||||
console.log('Saving operator config:', this.operatorConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
testConnection() {
|
|
||||||
console.log('Testing connection to:', this.operatorConfig.apiEndpoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
<p>Operators</p>
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
import { Operators } from './operators';
|
|
||||||
describe('Operators', () => {});
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-operators',
|
|
||||||
templateUrl: './operators.html',
|
|
||||||
})
|
|
||||||
export class Operators {}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class OperatorsConfigService {
|
|
||||||
constructor() {}
|
|
||||||
}
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
import { Injectable, inject } from '@angular/core';
|
|
||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
import { environment } from '@environments/environment';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
|
|
||||||
export interface Operator {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
country: string;
|
|
||||||
status: 'ACTIVE' | 'INACTIVE';
|
|
||||||
config: OperatorConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OperatorConfig {
|
|
||||||
apiEndpoint: string;
|
|
||||||
apiKey: string;
|
|
||||||
secretKey: string;
|
|
||||||
timeout: number;
|
|
||||||
retryAttempts: number;
|
|
||||||
webhookUrl: string;
|
|
||||||
isActive: boolean;
|
|
||||||
supportedServices: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
|
||||||
export class OperatorService {
|
|
||||||
private http = inject(HttpClient);
|
|
||||||
private apiUrl = `${environment.localServiceTestApiUrl}/operators`;
|
|
||||||
|
|
||||||
getOperators(): Observable<Operator[]> {
|
|
||||||
return this.http.get<Operator[]>(`${this.apiUrl}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateOperatorConfig(operatorId: string, config: OperatorConfig): Observable<Operator> {
|
|
||||||
return this.http.put<Operator>(
|
|
||||||
`${this.apiUrl}/${operatorId}/config`,
|
|
||||||
config
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
testConnection(operatorId: string): Observable<{ success: boolean; latency: number }> {
|
|
||||||
return this.http.post<{ success: boolean; latency: number }>(
|
|
||||||
`${this.apiUrl}/${operatorId}/test-connection`,
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
import { Injectable, inject } from '@angular/core';
|
|
||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
import { environment } from '@environments/environment';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
|
|
||||||
export interface OperatorStats {
|
|
||||||
operatorId: string;
|
|
||||||
totalTransactions: number;
|
|
||||||
successRate: number;
|
|
||||||
totalRevenue: number;
|
|
||||||
averageLatency: number;
|
|
||||||
errorCount: number;
|
|
||||||
uptime: number;
|
|
||||||
dailyStats: DailyStat[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DailyStat {
|
|
||||||
date: string;
|
|
||||||
transactions: number;
|
|
||||||
successRate: number;
|
|
||||||
revenue: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
|
||||||
export class OperatorStatsService {
|
|
||||||
private http = inject(HttpClient);
|
|
||||||
private apiUrl = `${environment.localServiceTestApiUrl}/operators`;
|
|
||||||
|
|
||||||
getOperatorStats(operatorId: string): Observable<OperatorStats> {
|
|
||||||
return this.http.get<OperatorStats>(
|
|
||||||
`${this.apiUrl}/${operatorId}/stats`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getOperatorsComparison(): Observable<any[]> {
|
|
||||||
return this.http.get<any[]>(`${this.apiUrl}/comparison`);
|
|
||||||
}
|
|
||||||
|
|
||||||
getPerformanceMetrics(operatorId: string, period: string): Observable<any> {
|
|
||||||
return this.http.get(
|
|
||||||
`${this.apiUrl}/${operatorId}/metrics?period=${period}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
<p>Operators - Stats</p>
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
import { OperatorsStats } from './stats';
|
|
||||||
describe('OperatorsStats', () => {});
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-operators-stats',
|
|
||||||
templateUrl: './stats.html',
|
|
||||||
})
|
|
||||||
export class OperatorsStats {}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class SettingsService {
|
|
||||||
constructor() {}
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
<p>Settings</p>
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
import { Routes } from '@angular/router';
|
|
||||||
import { Settings } from './settings';
|
|
||||||
import { authGuard } from '../../core/guards/auth.guard';
|
|
||||||
import { roleGuard } from '../../core/guards/role.guard';
|
|
||||||
|
|
||||||
export const SETTINGS_ROUTES: Routes = [
|
|
||||||
{
|
|
||||||
path: 'settings',
|
|
||||||
canActivate: [authGuard, roleGuard],
|
|
||||||
component: Settings,
|
|
||||||
data: {
|
|
||||||
title: 'Configuration',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
import { Settings } from './settings';
|
|
||||||
describe('Settings', () => {});
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-settings',
|
|
||||||
templateUrl: './settings.html',
|
|
||||||
})
|
|
||||||
export class Settings {}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
<p>Team</p>
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
import { Team } from './team';
|
|
||||||
describe('Team', () => {});
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-team',
|
|
||||||
templateUrl: './team.html',
|
|
||||||
})
|
|
||||||
export class Team {}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
export type ContactType = {
|
|
||||||
name: string
|
|
||||||
avatar: string
|
|
||||||
country: {
|
|
||||||
name: string
|
|
||||||
flag: string
|
|
||||||
}
|
|
||||||
jobTitle: string
|
|
||||||
about: string
|
|
||||||
verified?: boolean
|
|
||||||
rating: number
|
|
||||||
campaigns: number
|
|
||||||
contacts: number
|
|
||||||
engagement: string
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
<p>Webhooks - History</p>
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
import { WebhooksHistory } from './history';
|
|
||||||
describe('WebhooksHistory', () => {});
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-webhooks-history',
|
|
||||||
templateUrl: './history.html',
|
|
||||||
})
|
|
||||||
export class WebhooksHistory {}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
<p>Webhooks - Retry</p>
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
import { WebhooksRetry } from './retry';
|
|
||||||
describe('WebhooksRetry', () => {});
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-webhooks-retry',
|
|
||||||
templateUrl: './retry.html',
|
|
||||||
})
|
|
||||||
export class WebhooksRetry {}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class WebhooksHistoryService {
|
|
||||||
constructor() {}
|
|
||||||
}
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
import { Injectable, inject } from '@angular/core';
|
|
||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
import { environment } from '@environments/environment';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
|
||||||
export class WebhookRetryService {
|
|
||||||
private http = inject(HttpClient);
|
|
||||||
private apiUrl = `${environment.localServiceTestApiUrl}/webhooks`;
|
|
||||||
|
|
||||||
retryWebhook(webhookId: string): Observable<{ success: boolean }> {
|
|
||||||
return this.http.post<{ success: boolean }>(
|
|
||||||
`${this.apiUrl}/${webhookId}/retry`,
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
bulkRetryWebhooks(webhookIds: string[]): Observable<{ success: number; failed: number }> {
|
|
||||||
return this.http.post<{ success: number; failed: number }>(
|
|
||||||
`${this.apiUrl}/bulk-retry`,
|
|
||||||
{ webhookIds }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getRetryConfig(): Observable<any> {
|
|
||||||
return this.http.get(`${this.apiUrl}/retry-config`);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateRetryConfig(config: any): Observable<any> {
|
|
||||||
return this.http.put(`${this.apiUrl}/retry-config`, config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class WebhooksStatusService {
|
|
||||||
constructor() {}
|
|
||||||
}
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
import { Injectable, inject } from '@angular/core';
|
|
||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
import { environment } from '@environments/environment';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
|
|
||||||
export interface WebhookEvent {
|
|
||||||
id: string;
|
|
||||||
url: string;
|
|
||||||
eventType: string;
|
|
||||||
payload: any;
|
|
||||||
status: 'SUCCESS' | 'FAILED' | 'PENDING';
|
|
||||||
retryCount: number;
|
|
||||||
createdAt: Date;
|
|
||||||
lastAttempt?: Date;
|
|
||||||
errorMessage?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WebhookFilter {
|
|
||||||
status?: string;
|
|
||||||
eventType?: string;
|
|
||||||
startDate?: Date;
|
|
||||||
endDate?: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
|
||||||
export class WebhookService {
|
|
||||||
private http = inject(HttpClient);
|
|
||||||
private apiUrl = `${environment.localServiceTestApiUrl}/webhooks`;
|
|
||||||
|
|
||||||
getWebhookHistory(filters?: WebhookFilter): Observable<WebhookEvent[]> {
|
|
||||||
return this.http.post<WebhookEvent[]>(
|
|
||||||
`${this.apiUrl}/history`,
|
|
||||||
filters
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getWebhookStatus(): Observable<{
|
|
||||||
total: number;
|
|
||||||
success: number;
|
|
||||||
failed: number;
|
|
||||||
pending: number;
|
|
||||||
}> {
|
|
||||||
return this.http.get<any>(`${this.apiUrl}/status`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
<p>Webhooks - Status</p>
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
import { WebhooksStatus } from './status';
|
|
||||||
describe('WebhooksStatus', () => {});
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-webhooks-status',
|
|
||||||
templateUrl: './status.html',
|
|
||||||
})
|
|
||||||
export class WebhooksStatus {}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
<p>Webhooks</p>
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
import { Webhooks } from './webhooks';
|
|
||||||
describe('Webhooks', () => {});
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-webhooks',
|
|
||||||
templateUrl: './webhooks.html',
|
|
||||||
})
|
|
||||||
export class Webhooks {}
|
|
||||||
Loading…
Reference in New Issue
Block a user