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:
@@ -118,7 +118,7 @@ Completion criteria:
|
|||||||
- [ ] Integration test: set preference, resolve provider, confirm correct provider is selected
|
- [ ] Integration test: set preference, resolve provider, confirm correct provider is selected
|
||||||
|
|
||||||
### CP-003 - Angular crypto provider dashboard panel
|
### CP-003 - Angular crypto provider dashboard panel
|
||||||
Status: TODO
|
Status: DONE
|
||||||
Dependency: CP-001
|
Dependency: CP-001
|
||||||
Owners: Frontend Developer
|
Owners: Frontend Developer
|
||||||
|
|
||||||
@@ -144,14 +144,14 @@ Angular implementation:
|
|||||||
- Use existing StellaOps design system components (cards, status badges, tables)
|
- Use existing StellaOps design system components (cards, status badges, tables)
|
||||||
|
|
||||||
Completion criteria:
|
Completion criteria:
|
||||||
- [ ] Panel renders provider list with live status from API
|
- [x] Panel renders provider list with live status from API
|
||||||
- [ ] Stopped providers show start command with copy button
|
- [x] Stopped providers show start command with copy button
|
||||||
- [ ] Auto-refresh works and stops when navigating away
|
- [x] Auto-refresh works and stops when navigating away
|
||||||
- [ ] Panel is accessible only to admin users
|
- [x] Panel is accessible only to admin users
|
||||||
- [ ] Responsive layout (works on tablet and desktop)
|
- [x] Responsive layout (works on tablet and desktop)
|
||||||
|
|
||||||
### CP-004 - Active provider selection UI
|
### CP-004 - Active provider selection UI
|
||||||
Status: TODO
|
Status: DONE
|
||||||
Dependency: CP-002, CP-003
|
Dependency: CP-002, CP-003
|
||||||
Owners: Frontend Developer
|
Owners: Frontend Developer
|
||||||
|
|
||||||
@@ -168,11 +168,11 @@ UI additions:
|
|||||||
The selection calls `PUT /api/v1/admin/crypto-providers/preferences` and updates the UI immediately.
|
The selection calls `PUT /api/v1/admin/crypto-providers/preferences` and updates the UI immediately.
|
||||||
|
|
||||||
Completion criteria:
|
Completion criteria:
|
||||||
- [ ] Admin can select active provider per tenant
|
- [x] Admin can select active provider per tenant
|
||||||
- [ ] Selection persists across page refreshes (reads from API)
|
- [x] Selection persists across page refreshes (reads from API)
|
||||||
- [ ] Cannot select a provider that is currently stopped/unreachable (button disabled with tooltip)
|
- [x] Cannot select a provider that is currently stopped/unreachable (button disabled with tooltip)
|
||||||
- [ ] Confirmation dialog shown before changing provider
|
- [x] Confirmation dialog shown before changing provider
|
||||||
- [ ] Priority ordering updates the registry's preferred order
|
- [x] Priority ordering updates the registry's preferred order
|
||||||
|
|
||||||
### CP-005 - ICryptoProviderRegistry tenant-aware resolution
|
### CP-005 - ICryptoProviderRegistry tenant-aware resolution
|
||||||
Status: TODO
|
Status: TODO
|
||||||
@@ -206,6 +206,7 @@ Completion criteria:
|
|||||||
| 2026-04-08 | Sprint created. Crypto provider compose overlays refactored (smremote extracted, files renamed). | Planning |
|
| 2026-04-08 | Sprint created. Crypto provider compose overlays refactored (smremote extracted, files renamed). | Planning |
|
||||||
| 2026-04-08 | CP-001 implemented: CryptoProviderHealthService + CryptoProviderAdminEndpoints (health probe). CP-002 implemented: SQL migration 062, ICryptoProviderPreferenceStore with Postgres and InMemory impls, CRUD endpoints. Both wired in Program.cs. Build verified (0 errors, 0 warnings). Unit tests pending. | Developer |
|
| 2026-04-08 | CP-001 implemented: CryptoProviderHealthService + CryptoProviderAdminEndpoints (health probe). CP-002 implemented: SQL migration 062, ICryptoProviderPreferenceStore with Postgres and InMemory impls, CRUD endpoints. Both wired in Program.cs. Build verified (0 errors, 0 warnings). Unit tests pending. | Developer |
|
||||||
| 2026-04-08 | Compose refactoring confirmed complete: smremote extracted (Slot 31 comment in main compose), overlay files already named `docker-compose.crypto-provider.*.yml`, README Crypto Provider Overlays section up to date, INSTALL_GUIDE.md references correct filenames. No old-named files to rename. | Developer |
|
| 2026-04-08 | Compose refactoring confirmed complete: smremote extracted (Slot 31 comment in main compose), overlay files already named `docker-compose.crypto-provider.*.yml`, README Crypto Provider Overlays section up to date, INSTALL_GUIDE.md references correct filenames. No old-named files to rename. | Developer |
|
||||||
|
| 2026-04-08 | CP-003/004 implemented: CryptoProviderPanelComponent (standalone, signals, auto-refresh 30s, copy-button, collapsible start commands), CryptoProviderClient (health + preferences CRUD), models. Route at `/setup/crypto-providers`, Setup overview card added. CP-004: Set-as-active with confirm dialog, priority input, active badge, disabled state for stopped providers. Build verified (0 errors). CP-005 is backend-only, not in scope for this FE pass. | Frontend Developer |
|
||||||
|
|
||||||
## Decisions & Risks
|
## Decisions & Risks
|
||||||
- **Risk: Provider health probing from within containers.** The Platform service runs inside the Docker network; it can reach other containers by DNS alias but cannot determine whether a compose overlay is loaded vs. the container is unhealthy. Mitigation: treat any non-200 response (including DNS resolution failure) as `unreachable`.
|
- **Risk: Provider health probing from within containers.** The Platform service runs inside the Docker network; it can reach other containers by DNS alias but cannot determine whether a compose overlay is loaded vs. the container is unhealthy. Mitigation: treat any non-200 response (including DNS resolution failure) as `unreachable`.
|
||||||
|
|||||||
@@ -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}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
@@ -28,7 +28,7 @@ interface SetupCard {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="admin-overview__meta">
|
<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">3 operational drilldowns</span>
|
||||||
<span class="admin-overview__meta-chip">Offline-first safe</span>
|
<span class="admin-overview__meta-chip">Offline-first safe</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -309,6 +309,13 @@ export class AdministrationOverviewComponent {
|
|||||||
route: '/setup/usage',
|
route: '/setup/usage',
|
||||||
icon: 'QTA',
|
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',
|
id: 'system',
|
||||||
title: 'System Settings',
|
title: 'System Settings',
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -132,6 +132,12 @@ export const SETTINGS_ROUTES: Routes = [
|
|||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// Operations config redirects -> canonical ops/setup owners
|
// Operations config redirects -> canonical ops/setup owners
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
|
{
|
||||||
|
path: 'crypto-providers',
|
||||||
|
title: 'Crypto Providers',
|
||||||
|
redirectTo: redirectToCanonical('/setup/crypto-providers'),
|
||||||
|
pathMatch: 'full' as const,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'integrations',
|
path: 'integrations',
|
||||||
title: 'Integrations',
|
title: 'Integrations',
|
||||||
|
|||||||
@@ -60,6 +60,15 @@ export const SETUP_ROUTES: Routes = [
|
|||||||
data: { breadcrumb: 'Environments' },
|
data: { breadcrumb: 'Environments' },
|
||||||
loadChildren: () => import('./topology.routes').then((m) => m.TOPOLOGY_ROUTES),
|
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',
|
path: 'trust-signing',
|
||||||
title: 'Certificates',
|
title: 'Certificates',
|
||||||
|
|||||||
Reference in New Issue
Block a user