feat(web): crypto provider picker UI (CP-003/004/005)

- Add crypto provider panel to Platform Settings at /setup/crypto-providers
- Show provider health status with status dots, latency, and last-checked time
- Collapsible start commands with copy-to-clipboard for stopped providers
- Auto-refresh every 30s using interval+switchMap, stopped on destroy
- Provider selection with confirmation dialog via existing ConfirmDialogComponent
- Priority ordering via number input per provider preference
- Active provider banner and per-card active badge
- Disabled "Set as Active" for stopped/unreachable providers with tooltip
- Algorithm scope mapping table for configured preferences
- Backward-compatible redirect from /settings/crypto-providers
- Setup overview card added for Crypto Providers
- Sprint CP-003/CP-004 marked DONE with execution log

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
master
2026-04-08 15:36:10 +03:00
parent bc9eec511b
commit 87eac86fb9
7 changed files with 1185 additions and 13 deletions

View File

@@ -0,0 +1,58 @@
/**
* Crypto Provider API Client
* Sprint: SPRINT_20260408_001_FE_crypto_provider_picker (CP-003/004/005)
*
* Calls the Platform admin endpoints for crypto provider health probing
* and tenant preference CRUD.
*/
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import {
CryptoProviderHealthResponse,
CryptoProviderPreference,
CryptoProviderPreferenceUpdate,
} from './crypto-provider.models';
@Injectable({ providedIn: 'root' })
export class CryptoProviderClient {
private readonly http = inject(HttpClient);
private readonly baseUrl = '/api/v1/admin/crypto-providers';
// ─────────────────────────────────────────────────────────────────────────
// Health (CP-001)
// ─────────────────────────────────────────────────────────────────────────
/**
* Probe all configured crypto providers and return aggregated health.
*/
getHealth(): Observable<CryptoProviderHealthResponse> {
return this.http.get<CryptoProviderHealthResponse>(`${this.baseUrl}/health`);
}
// ─────────────────────────────────────────────────────────────────────────
// Preferences (CP-002)
// ─────────────────────────────────────────────────────────────────────────
/**
* List current tenant's crypto provider preferences.
*/
getPreferences(): Observable<CryptoProviderPreference[]> {
return this.http.get<CryptoProviderPreference[]>(`${this.baseUrl}/preferences`);
}
/**
* Create or update a provider preference for the current tenant.
*/
updatePreference(body: CryptoProviderPreferenceUpdate): Observable<CryptoProviderPreference> {
return this.http.put<CryptoProviderPreference>(`${this.baseUrl}/preferences`, body);
}
/**
* Remove a provider preference.
*/
deletePreference(id: string): Observable<void> {
return this.http.delete<void>(`${this.baseUrl}/preferences/${id}`);
}
}

View File

@@ -0,0 +1,73 @@
/**
* Crypto Provider Models
* Sprint: SPRINT_20260408_001_FE_crypto_provider_picker (CP-003/004/005)
*/
// ─────────────────────────────────────────────────────────────────────────────
// Health probe models (CP-001 response)
// ─────────────────────────────────────────────────────────────────────────────
export type CryptoProviderStatus = 'running' | 'stopped' | 'unreachable' | 'degraded';
export interface CryptoProviderHealth {
id: string;
name: string;
status: CryptoProviderStatus;
healthEndpoint: string;
responseTimeMs: number | null;
composeOverlay: string;
startCommand: string;
}
export interface CryptoProviderHealthResponse {
providers: CryptoProviderHealth[];
}
// ─────────────────────────────────────────────────────────────────────────────
// Tenant preference models (CP-002 response)
// ─────────────────────────────────────────────────────────────────────────────
export interface CryptoProviderPreference {
id: string;
tenantId: string;
providerId: string;
algorithmScope: string;
priority: number;
isActive: boolean;
createdAt: string;
updatedAt: string;
}
export interface CryptoProviderPreferenceUpdate {
providerId: string;
algorithmScope: string;
priority: number;
isActive: boolean;
}
// ─────────────────────────────────────────────────────────────────────────────
// UI helpers
// ─────────────────────────────────────────────────────────────────────────────
export const PROVIDER_STATUS_COLORS: Record<CryptoProviderStatus, string> = {
running: 'status-dot--running',
stopped: 'status-dot--stopped',
unreachable: 'status-dot--unreachable',
degraded: 'status-dot--degraded',
};
export const PROVIDER_STATUS_LABELS: Record<CryptoProviderStatus, string> = {
running: 'Running',
stopped: 'Stopped',
unreachable: 'Unreachable',
degraded: 'Degraded',
};
export const ALGORITHM_SCOPES = [
{ value: '*', label: 'All Algorithms (Global)' },
{ value: 'SM', label: 'SM (SM2/SM3/SM4)' },
{ value: 'GOST', label: 'GOST (R 34.10/34.11/34.12)' },
{ value: 'RSA', label: 'RSA' },
{ value: 'ECDSA', label: 'ECDSA' },
{ value: 'EdDSA', label: 'EdDSA (Ed25519/Ed448)' },
] as const;

View File

@@ -28,7 +28,7 @@ interface SetupCard {
</p>
</div>
<div class="admin-overview__meta">
<span class="admin-overview__meta-chip">7 setup domains</span>
<span class="admin-overview__meta-chip">8 setup domains</span>
<span class="admin-overview__meta-chip">3 operational drilldowns</span>
<span class="admin-overview__meta-chip">Offline-first safe</span>
</div>
@@ -309,6 +309,13 @@ export class AdministrationOverviewComponent {
route: '/setup/usage',
icon: 'QTA',
},
{
id: 'crypto-providers',
title: 'Crypto Providers',
description: 'Discover, monitor, and select cryptographic providers per tenant.',
route: '/setup/crypto-providers',
icon: 'KEY',
},
{
id: 'system',
title: 'System Settings',

View File

@@ -132,6 +132,12 @@ export const SETTINGS_ROUTES: Routes = [
// -----------------------------------------------------------------------
// Operations config redirects -> canonical ops/setup owners
// -----------------------------------------------------------------------
{
path: 'crypto-providers',
title: 'Crypto Providers',
redirectTo: redirectToCanonical('/setup/crypto-providers'),
pathMatch: 'full' as const,
},
{
path: 'integrations',
title: 'Integrations',

View File

@@ -60,6 +60,15 @@ export const SETUP_ROUTES: Routes = [
data: { breadcrumb: 'Environments' },
loadChildren: () => import('./topology.routes').then((m) => m.TOPOLOGY_ROUTES),
},
{
path: 'crypto-providers',
title: 'Crypto Providers',
data: { breadcrumb: 'Crypto Providers' },
loadComponent: () =>
import('../features/settings/crypto-providers/crypto-provider-panel.component').then(
(m) => m.CryptoProviderPanelComponent,
),
},
{
path: 'trust-signing',
title: 'Certificates',