New CFCFRMS > I4C Live section backed by /v1/i4c/admin and /v1/i4c/cases (the demo mock interceptor does not match these paths): - Dashboard, complaints from I4C, holds register, approvals, responses and API log, bank code master, intimation settings. - Hold popup: overview, hold transactions, customer intimation (automatic and manual), maker request (full / partial release, freeze, unfreeze), GRM / MRM and documents, request history. - GRM grievances, MRM restorations and MRM balance report pages; shared date range filter (today, yesterday, week, month, custom), server-side paging and filters on every list; document upload / download. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
127 lines
3.7 KiB
TypeScript
127 lines
3.7 KiB
TypeScript
/**
|
|
* Copyright since 2025 Mifos Initiative
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
import { Component, OnInit, inject } from '@angular/core';
|
|
import { DecimalPipe } from '@angular/common';
|
|
import { RouterLink } from '@angular/router';
|
|
import { MatButtonModule } from '@angular/material/button';
|
|
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
|
import { I4cService, i4cErrorMessage } from '../i4c.service';
|
|
|
|
/**
|
|
* Integration health at a glance, and the API performance figures the RBI letter (05 Dec 2025, para 4) asks to be
|
|
* reviewed periodically: calls, failures and response times in each direction.
|
|
*/
|
|
@Component({
|
|
selector: 'mifosx-i4c-dashboard',
|
|
templateUrl: './i4c-dashboard.component.html',
|
|
styleUrls: ['./i4c-dashboard.component.scss'],
|
|
imports: [
|
|
DecimalPipe,
|
|
RouterLink,
|
|
MatButtonModule,
|
|
MatProgressBarModule,
|
|
FaIconComponent
|
|
]
|
|
})
|
|
export class I4cDashboardComponent implements OnInit {
|
|
private i4c = inject(I4cService);
|
|
|
|
data: any = null;
|
|
loading = false;
|
|
error = '';
|
|
showConfigModal = false;
|
|
copiedKey = '';
|
|
|
|
ngOnInit(): void {
|
|
this.load();
|
|
}
|
|
|
|
load(): void {
|
|
this.loading = true;
|
|
this.error = '';
|
|
this.i4c.dashboard().subscribe({
|
|
next: (d) => {
|
|
this.data = d;
|
|
this.loading = false;
|
|
},
|
|
error: (e) => {
|
|
this.error = i4cErrorMessage(e);
|
|
this.loading = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
copyText(text: string, key: string): void {
|
|
if (!text) {
|
|
return;
|
|
}
|
|
navigator.clipboard.writeText(text);
|
|
this.copiedKey = key;
|
|
setTimeout(() => {
|
|
if (this.copiedKey === key) {
|
|
this.copiedKey = '';
|
|
}
|
|
}, 2000);
|
|
}
|
|
|
|
statusClass(status: string): string {
|
|
switch (status) {
|
|
case 'PROCESSED':
|
|
return 'i4c-badge--green';
|
|
case 'RETRY':
|
|
return 'i4c-badge--red';
|
|
case 'RECEIVED':
|
|
case 'PROCESSING':
|
|
return 'i4c-badge--amber';
|
|
default:
|
|
return '';
|
|
}
|
|
}
|
|
|
|
/** Success rate of calls in one direction, as a percentage; null when there were none. */
|
|
successRate(row: any): number | null {
|
|
const calls = Number(row?.calls || 0);
|
|
return calls === 0 ? null : (Number(row?.succeeded || 0) * 100) / calls;
|
|
}
|
|
|
|
/** What still stands between this bank and live traffic, worked out from the configuration. */
|
|
get setupGaps(): string[] {
|
|
const c = this.data?.configuration;
|
|
if (!c) {
|
|
return [];
|
|
}
|
|
const gaps: string[] = [];
|
|
if (!c.enabled) {
|
|
gaps.push('Integration is switched off (fineract.i4c.enabled=false): I4C calls are refused with 503.');
|
|
}
|
|
if (!c.inboundCredentialsConfigured) {
|
|
gaps.push('No inbound username/password set for I4C to obtain a token.');
|
|
} else if (!c.inboundPasswordHashed) {
|
|
gaps.push('Inbound password is configured in plain text; set the bcrypt hash for production.');
|
|
}
|
|
if (!c.allowedIpCount) {
|
|
gaps.push('No I4C source IP allowlist: any address may call (UAT only).');
|
|
}
|
|
if (c.cryptoMode !== 'NONE' && !c.cryptoPassphraseConfigured) {
|
|
gaps.push('Encryption mode is ' + c.cryptoMode + ' but no passphrase is configured.');
|
|
}
|
|
if (!c.outboundConfigured) {
|
|
gaps.push('I4C response URL is not configured: replies wait in the outbox.');
|
|
}
|
|
if (!c.bankCode) {
|
|
gaps.push("This bank's I4C bank code is not set (fineract.i4c.bank-code); root_bankid is sent as 0.");
|
|
}
|
|
if (c.simulatorEnabled) {
|
|
gaps.push('The local I4C simulator is enabled; switch it off in production.');
|
|
}
|
|
return gaps;
|
|
}
|
|
}
|