@@ -14,15 +14,19 @@ This document describes the technical structure of VaultCertsViewer (vcv), a sin
1414## Directory layout (app/)
1515
1616- ` cmd/server/main.go ` — entrypoint, router, middleware, static file serving, graceful shutdown.
17- - ` web/ ` — ` index.html ` , ` assets/app-htmx.js ` , ` assets/styles.css ` , ` templates/ ` (UI fragments + Admin templates).
18- - ` config/ ` — environment-backed configuration loading with expiration threshold support.
1917- ` internal/cache/ ` — simple in-memory TTL cache (used by Vault client).
18+ - ` internal/config/ ` — environment-backed configuration loading with expiration threshold support.
19+ - ` internal/errors/ ` — custom error types and helpers.
2020- ` internal/handlers/ ` — HTTP handlers (` certs ` , ` i18n ` , ` health ` , ` ready ` , ` ui ` , ` admin ` routes).
21- - ` internal/metrics/ ` — Prometheus collectors.
21+ - ` internal/httputil/ ` - HTTP utility functions for client IP extraction, used by rate limiting middleware.
22+ - ` internal/i18n/ ` - Internationalization support.
2223- ` internal/logger/ ` — zerolog initialization and structured helpers (HTTP events, panic).
24+ - ` internal/metrics/ ` — Prometheus collectors.
25+ - ` internal/middleware/ ` — request ID, HTTP logging, panic recovery, CORS, security headers, rate limiting, CSRF protection, body limit.
26+ - ` internal/validation/ ` - Validation helpers.
2327- ` internal/vault/ ` — Vault client implementations with graceful shutdown support.
2428- ` internal/version/ ` — build version info (injected via ldflags).
25- - ` middleware /` — request ID, HTTP logging, panic recovery, CORS, security headers, rate limiting, CSRF protection, body limit .
29+ - ` web /` — ` index.html ` , ` assets/app-htmx.js ` , ` assets/styles.css ` , ` templates/ ` (UI fragments + Admin templates) .
2630
2731## API surface
2832
@@ -45,7 +49,8 @@ This document describes the technical structure of VaultCertsViewer (vcv), a sin
4549| ` /ui/certs/refresh ` | POST | HTMX fragment: refresh certificates |
4650| ` /ui/certs/{id}/details ` | GET | HTMX fragment: certificate details |
4751| ` /ui/theme/toggle ` | POST | Toggle dark/light theme |
48- | ` /ui/status ` | GET | Real-time Vault connection status |
52+ | ` /ui/vaults/status ` | GET | HTMX fragment: vault status |
53+ | ` /ui/vaults/refresh ` | POST | HTMX fragment: refresh vaults |
4954| ` /admin ` | GET | Admin page (enabled only if admin password is configured in settings.json) |
5055| ` /admin/panel ` | GET | Admin panel fragment (HTMX) |
5156| ` /admin/login ` | POST | Admin login (HTMX) |
@@ -148,7 +153,7 @@ Precedence rules:
148153- Responsive design with sticky header
149154- Dark/light theme persistence
150155- Modal mount selector for multi-PKI support
151- - Configurable pagination (25/50/75/ 100/all)
156+ - Configurable pagination (25/50/100/all)
152157- Sortable columns with visual indicators
153158
154159## Metrics
@@ -181,158 +186,26 @@ Metrics are exposed at `/metrics`.
181186 - Uses the configured expiration thresholds.
182187- ` vcv_vaults_configured `
183188- ` vcv_pki_mounts_configured{vault_id} `
189+ - ` vcv_expiration_threshold_critical_days ` — Configured critical threshold.
190+ - ` vcv_expiration_threshold_warning_days ` — Configured warning threshold.
184191
185- ### Optional metric (disabled by default)
192+ ### Enhanced metrics
186193
187- - ` vcv_certificate_expiry_timestamp_seconds{certificate_id, common_name, status, vault_id, pki} `
194+ - ` vcv_certificates_expiry_bucket{vault_id, pki, bucket} ` — Certificate distribution by time bucket.
195+ - Buckets: ` 0-7d ` , ` 7-30d ` , ` 30-90d ` , ` 90d+ ` , ` expired ` , ` revoked ` .
188196
189- Enable it with:
197+ ### Optional metrics (disabled by default)
190198
191- ``` bash
192- VCV_METRICS_PER_CERTIFICATE=true
193- ```
199+ - ` vcv_certificate_expiry_timestamp_seconds{certificate_id, common_name, status, vault_id, pki} `
200+ - ` vcv_certificate_days_until_expiry{certificate_id, common_name, status, vault_id, pki} ` — Days until expiration (negative if expired).
194201
195- Example :
202+ Enable with :
196203
197- ``` bash
198- # HELP vcv_cache_size Number of items currently cached
199- # TYPE vcv_cache_size gauge
200- vcv_cache_size 0
201- # HELP vcv_certificate_exporter_last_scrape_duration_seconds Duration of the last certificate scrape in seconds
202- # TYPE vcv_certificate_exporter_last_scrape_duration_seconds gauge
203- vcv_certificate_exporter_last_scrape_duration_seconds 0.000118208
204- # HELP vcv_certificate_exporter_last_scrape_success Whether the last scrape succeeded (1) or failed (0)
205- # TYPE vcv_certificate_exporter_last_scrape_success gauge
206- vcv_certificate_exporter_last_scrape_success 1
207- # HELP vcv_certificates_expired_count Number of expired certificates
208- # TYPE vcv_certificates_expired_count gauge
209- vcv_certificates_expired_count 30
210- # HELP vcv_certificates_expiring_soon_count Number of certificates expiring soon within threshold window
211- # TYPE vcv_certificates_expiring_soon_count gauge
212- vcv_certificates_expiring_soon_count{level=" critical" ,pki=" __all__" ,vault_id=" __all__" } 17
213- vcv_certificates_expiring_soon_count{level=" critical" ,pki=" pki" ,vault_id=" vault-main" } 3
214- vcv_certificates_expiring_soon_count{level=" critical" ,pki=" pki_blockchain" ,vault_id=" vault-dev-3" } 0
215- vcv_certificates_expiring_soon_count{level=" critical" ,pki=" pki_cloud" ,vault_id=" vault-dev-3" } 0
216- vcv_certificates_expiring_soon_count{level=" critical" ,pki=" pki_corporate" ,vault_id=" vault-dev-2" } 0
217- vcv_certificates_expiring_soon_count{level=" critical" ,pki=" pki_dev" ,vault_id=" vault-main" } 1
218- vcv_certificates_expiring_soon_count{level=" critical" ,pki=" pki_dmz" ,vault_id=" vault-dev-5" } 0
219- vcv_certificates_expiring_soon_count{level=" critical" ,pki=" pki_edge" ,vault_id=" vault-dev-3" } 0
220- vcv_certificates_expiring_soon_count{level=" critical" ,pki=" pki_external" ,vault_id=" vault-dev-2" } 0
221- vcv_certificates_expiring_soon_count{level=" critical" ,pki=" pki_internal" ,vault_id=" vault-dev-5" } 0
222- vcv_certificates_expiring_soon_count{level=" critical" ,pki=" pki_iot" ,vault_id=" vault-dev-3" } 0
223- vcv_certificates_expiring_soon_count{level=" critical" ,pki=" pki_lab" ,vault_id=" vault-dev-4" } 0
224- vcv_certificates_expiring_soon_count{level=" critical" ,pki=" pki_partners" ,vault_id=" vault-dev-2" } 0
225- vcv_certificates_expiring_soon_count{level=" critical" ,pki=" pki_perf" ,vault_id=" vault-dev-4" } 0
226- vcv_certificates_expiring_soon_count{level=" critical" ,pki=" pki_production" ,vault_id=" vault-main" } 0
227- vcv_certificates_expiring_soon_count{level=" critical" ,pki=" pki_qa" ,vault_id=" vault-dev-4" } 0
228- vcv_certificates_expiring_soon_count{level=" critical" ,pki=" pki_shared" ,vault_id=" vault-dev-5" } 0
229- vcv_certificates_expiring_soon_count{level=" critical" ,pki=" pki_stage" ,vault_id=" vault-main" } 1
230- vcv_certificates_expiring_soon_count{level=" critical" ,pki=" pki_vault2" ,vault_id=" vault-dev-2" } 2
231- vcv_certificates_expiring_soon_count{level=" critical" ,pki=" pki_vault3" ,vault_id=" vault-dev-3" } 2
232- vcv_certificates_expiring_soon_count{level=" critical" ,pki=" pki_vault4" ,vault_id=" vault-dev-4" } 4
233- vcv_certificates_expiring_soon_count{level=" critical" ,pki=" pki_vault5" ,vault_id=" vault-dev-5" } 4
234- vcv_certificates_expiring_soon_count{level=" warning" ,pki=" __all__" ,vault_id=" __all__" } 45
235- vcv_certificates_expiring_soon_count{level=" warning" ,pki=" pki" ,vault_id=" vault-main" } 7
236- vcv_certificates_expiring_soon_count{level=" warning" ,pki=" pki_blockchain" ,vault_id=" vault-dev-3" } 0
237- vcv_certificates_expiring_soon_count{level=" warning" ,pki=" pki_cloud" ,vault_id=" vault-dev-3" } 0
238- vcv_certificates_expiring_soon_count{level=" warning" ,pki=" pki_corporate" ,vault_id=" vault-dev-2" } 0
239- vcv_certificates_expiring_soon_count{level=" warning" ,pki=" pki_dev" ,vault_id=" vault-main" } 2
240- vcv_certificates_expiring_soon_count{level=" warning" ,pki=" pki_dmz" ,vault_id=" vault-dev-5" } 5
241- vcv_certificates_expiring_soon_count{level=" warning" ,pki=" pki_edge" ,vault_id=" vault-dev-3" } 0
242- vcv_certificates_expiring_soon_count{level=" warning" ,pki=" pki_external" ,vault_id=" vault-dev-2" } 0
243- vcv_certificates_expiring_soon_count{level=" warning" ,pki=" pki_internal" ,vault_id=" vault-dev-5" } 5
244- vcv_certificates_expiring_soon_count{level=" warning" ,pki=" pki_iot" ,vault_id=" vault-dev-3" } 0
245- vcv_certificates_expiring_soon_count{level=" warning" ,pki=" pki_lab" ,vault_id=" vault-dev-4" } 0
246- vcv_certificates_expiring_soon_count{level=" warning" ,pki=" pki_partners" ,vault_id=" vault-dev-2" } 0
247- vcv_certificates_expiring_soon_count{level=" warning" ,pki=" pki_perf" ,vault_id=" vault-dev-4" } 0
248- vcv_certificates_expiring_soon_count{level=" warning" ,pki=" pki_production" ,vault_id=" vault-main" } 0
249- vcv_certificates_expiring_soon_count{level=" warning" ,pki=" pki_qa" ,vault_id=" vault-dev-4" } 6
250- vcv_certificates_expiring_soon_count{level=" warning" ,pki=" pki_shared" ,vault_id=" vault-dev-5" } 0
251- vcv_certificates_expiring_soon_count{level=" warning" ,pki=" pki_stage" ,vault_id=" vault-main" } 2
252- vcv_certificates_expiring_soon_count{level=" warning" ,pki=" pki_vault2" ,vault_id=" vault-dev-2" } 5
253- vcv_certificates_expiring_soon_count{level=" warning" ,pki=" pki_vault3" ,vault_id=" vault-dev-3" } 5
254- vcv_certificates_expiring_soon_count{level=" warning" ,pki=" pki_vault4" ,vault_id=" vault-dev-4" } 4
255- vcv_certificates_expiring_soon_count{level=" warning" ,pki=" pki_vault5" ,vault_id=" vault-dev-5" } 4
256- # HELP vcv_certificates_last_fetch_timestamp_seconds Timestamp of last successful certificates fetch
257- # TYPE vcv_certificates_last_fetch_timestamp_seconds gauge
258- vcv_certificates_last_fetch_timestamp_seconds 1.765985686e+09
259- # HELP vcv_certificates_total Total certificates grouped by status
260- # TYPE vcv_certificates_total gauge
261- vcv_certificates_total{pki=" __all__" ,status=" expired" ,vault_id=" __all__" } 30
262- vcv_certificates_total{pki=" __all__" ,status=" revoked" ,vault_id=" __all__" } 14
263- vcv_certificates_total{pki=" __all__" ,status=" valid" ,vault_id=" __all__" } 85
264- vcv_certificates_total{pki=" pki" ,status=" expired" ,vault_id=" vault-main" } 3
265- vcv_certificates_total{pki=" pki" ,status=" revoked" ,vault_id=" vault-main" } 0
266- vcv_certificates_total{pki=" pki" ,status=" valid" ,vault_id=" vault-main" } 12
267- vcv_certificates_total{pki=" pki_blockchain" ,status=" expired" ,vault_id=" vault-dev-3" } 0
268- vcv_certificates_total{pki=" pki_blockchain" ,status=" revoked" ,vault_id=" vault-dev-3" } 1
269- vcv_certificates_total{pki=" pki_blockchain" ,status=" valid" ,vault_id=" vault-dev-3" } 1
270- vcv_certificates_total{pki=" pki_cloud" ,status=" expired" ,vault_id=" vault-dev-3" } 0
271- vcv_certificates_total{pki=" pki_cloud" ,status=" revoked" ,vault_id=" vault-dev-3" } 1
272- vcv_certificates_total{pki=" pki_cloud" ,status=" valid" ,vault_id=" vault-dev-3" } 1
273- vcv_certificates_total{pki=" pki_corporate" ,status=" expired" ,vault_id=" vault-dev-2" } 0
274- vcv_certificates_total{pki=" pki_corporate" ,status=" revoked" ,vault_id=" vault-dev-2" } 1
275- vcv_certificates_total{pki=" pki_corporate" ,status=" valid" ,vault_id=" vault-dev-2" } 1
276- vcv_certificates_total{pki=" pki_dev" ,status=" expired" ,vault_id=" vault-main" } 1
277- vcv_certificates_total{pki=" pki_dev" ,status=" revoked" ,vault_id=" vault-main" } 2
278- vcv_certificates_total{pki=" pki_dev" ,status=" valid" ,vault_id=" vault-main" } 5
279- vcv_certificates_total{pki=" pki_dmz" ,status=" expired" ,vault_id=" vault-dev-5" } 0
280- vcv_certificates_total{pki=" pki_dmz" ,status=" revoked" ,vault_id=" vault-dev-5" } 0
281- vcv_certificates_total{pki=" pki_dmz" ,status=" valid" ,vault_id=" vault-dev-5" } 6
282- vcv_certificates_total{pki=" pki_edge" ,status=" expired" ,vault_id=" vault-dev-3" } 0
283- vcv_certificates_total{pki=" pki_edge" ,status=" revoked" ,vault_id=" vault-dev-3" } 1
284- vcv_certificates_total{pki=" pki_edge" ,status=" valid" ,vault_id=" vault-dev-3" } 1
285- vcv_certificates_total{pki=" pki_external" ,status=" expired" ,vault_id=" vault-dev-2" } 0
286- vcv_certificates_total{pki=" pki_external" ,status=" revoked" ,vault_id=" vault-dev-2" } 1
287- vcv_certificates_total{pki=" pki_external" ,status=" valid" ,vault_id=" vault-dev-2" } 1
288- vcv_certificates_total{pki=" pki_internal" ,status=" expired" ,vault_id=" vault-dev-5" } 0
289- vcv_certificates_total{pki=" pki_internal" ,status=" revoked" ,vault_id=" vault-dev-5" } 1
290- vcv_certificates_total{pki=" pki_internal" ,status=" valid" ,vault_id=" vault-dev-5" } 6
291- vcv_certificates_total{pki=" pki_iot" ,status=" expired" ,vault_id=" vault-dev-3" } 0
292- vcv_certificates_total{pki=" pki_iot" ,status=" revoked" ,vault_id=" vault-dev-3" } 1
293- vcv_certificates_total{pki=" pki_iot" ,status=" valid" ,vault_id=" vault-dev-3" } 1
294- vcv_certificates_total{pki=" pki_lab" ,status=" expired" ,vault_id=" vault-dev-4" } 0
295- vcv_certificates_total{pki=" pki_lab" ,status=" revoked" ,vault_id=" vault-dev-4" } 0
296- vcv_certificates_total{pki=" pki_lab" ,status=" valid" ,vault_id=" vault-dev-4" } 7
297- vcv_certificates_total{pki=" pki_partners" ,status=" expired" ,vault_id=" vault-dev-2" } 0
298- vcv_certificates_total{pki=" pki_partners" ,status=" revoked" ,vault_id=" vault-dev-2" } 1
299- vcv_certificates_total{pki=" pki_partners" ,status=" valid" ,vault_id=" vault-dev-2" } 1
300- vcv_certificates_total{pki=" pki_perf" ,status=" expired" ,vault_id=" vault-dev-4" } 0
301- vcv_certificates_total{pki=" pki_perf" ,status=" revoked" ,vault_id=" vault-dev-4" } 0
302- vcv_certificates_total{pki=" pki_perf" ,status=" valid" ,vault_id=" vault-dev-4" } 1
303- vcv_certificates_total{pki=" pki_production" ,status=" expired" ,vault_id=" vault-main" } 0
304- vcv_certificates_total{pki=" pki_production" ,status=" revoked" ,vault_id=" vault-main" } 0
305- vcv_certificates_total{pki=" pki_production" ,status=" valid" ,vault_id=" vault-main" } 1
306- vcv_certificates_total{pki=" pki_qa" ,status=" expired" ,vault_id=" vault-dev-4" } 0
307- vcv_certificates_total{pki=" pki_qa" ,status=" revoked" ,vault_id=" vault-dev-4" } 0
308- vcv_certificates_total{pki=" pki_qa" ,status=" valid" ,vault_id=" vault-dev-4" } 7
309- vcv_certificates_total{pki=" pki_shared" ,status=" expired" ,vault_id=" vault-dev-5" } 0
310- vcv_certificates_total{pki=" pki_shared" ,status=" revoked" ,vault_id=" vault-dev-5" } 0
311- vcv_certificates_total{pki=" pki_shared" ,status=" valid" ,vault_id=" vault-dev-5" } 6
312- vcv_certificates_total{pki=" pki_stage" ,status=" expired" ,vault_id=" vault-main" } 1
313- vcv_certificates_total{pki=" pki_stage" ,status=" revoked" ,vault_id=" vault-main" } 0
314- vcv_certificates_total{pki=" pki_stage" ,status=" valid" ,vault_id=" vault-main" } 5
315- vcv_certificates_total{pki=" pki_vault2" ,status=" expired" ,vault_id=" vault-dev-2" } 5
316- vcv_certificates_total{pki=" pki_vault2" ,status=" revoked" ,vault_id=" vault-dev-2" } 1
317- vcv_certificates_total{pki=" pki_vault2" ,status=" valid" ,vault_id=" vault-dev-2" } 6
318- vcv_certificates_total{pki=" pki_vault3" ,status=" expired" ,vault_id=" vault-dev-3" } 5
319- vcv_certificates_total{pki=" pki_vault3" ,status=" revoked" ,vault_id=" vault-dev-3" } 1
320- vcv_certificates_total{pki=" pki_vault3" ,status=" valid" ,vault_id=" vault-dev-3" } 6
321- vcv_certificates_total{pki=" pki_vault4" ,status=" expired" ,vault_id=" vault-dev-4" } 7
322- vcv_certificates_total{pki=" pki_vault4" ,status=" revoked" ,vault_id=" vault-dev-4" } 1
323- vcv_certificates_total{pki=" pki_vault4" ,status=" valid" ,vault_id=" vault-dev-4" } 5
324- vcv_certificates_total{pki=" pki_vault5" ,status=" expired" ,vault_id=" vault-dev-5" } 8
325- vcv_certificates_total{pki=" pki_vault5" ,status=" revoked" ,vault_id=" vault-dev-5" } 1
326- vcv_certificates_total{pki=" pki_vault5" ,status=" valid" ,vault_id=" vault-dev-5" } 5
327- # HELP vcv_vault_connected Vault connection status (1=connected,0=disconnected)
328- # TYPE vcv_vault_connected gauge
329- vcv_vault_connected{vault_id=" __all__" } 0
330- vcv_vault_connected{vault_id=" vault-dev-2" } 1
331- vcv_vault_connected{vault_id=" vault-dev-3" } 1
332- vcv_vault_connected{vault_id=" vault-dev-4" } 1
333- vcv_vault_connected{vault_id=" vault-dev-5" } 1
334- vcv_vault_connected{vault_id=" vault-dev-6" } 0
335- vcv_vault_connected{vault_id=" vault-main" } 1
204+ ``` json
205+ "metrics" : {
206+ "per_certificate" : true ,
207+ "enhanced_metrics" : false
208+ }
336209```
337210
338211## Build & run
@@ -346,7 +219,7 @@ See README.md on the root path for production deployment instructions.
346219A Hashicorp Vault or OpenBao server is required to run the application in development mode. Thus, a container with an init script is provided in ` docker-compose.dev.yml ` . It will initialize a Vault server with a PKI mount and some certs.
347220
348221``` bash
349- make dev
222+ task dev
350223```
351224
352225Binary serves UI and API at ` http://localhost:52000 ` .
@@ -367,23 +240,5 @@ cd app && go test ./...
367240
368241Test targets:
369242
370- - ` make test-offline ` : Run tests without Vault dependency
371- - ` make test-dev ` : Run tests against dev Vault instance
372-
373- ## Development notes
374-
375- - No external frontend toolchain; edit ` app-htmx.js ` /` styles.css ` directly.
376- - Request IDs are added to all responses; include them when correlating logs.
377- - Use ` VAULT_TLS_INSECURE=true ` only in development environments.
378- - HTMX partial templates are in ` web/templates/ ` .
379- - JavaScript uses modern ES6+ features with browser-native APIs.
380- - CSS uses custom properties for theming and responsive design.
381-
382- ## Performance Considerations
383-
384- - In-memory caching with configurable TTL (default 5 minutes)
385- - Request deduplication for concurrent identical requests
386- - Efficient DOM updates via HTMX partial swapping
387- - Lazy loading of certificate details
388- - Optimized search with client-side filtering
389- - Rate limiting to prevent abuse (production only)
243+ - ` task test-offline ` : Run tests without Vault dependency
244+ - ` task test-dev ` : Run tests against dev Vault instance
0 commit comments