Files
Ronni Baslund 559348f6bc feat(portal): real Security & audit page (+ bundled Storage / per-tenant-roles WIP)
Security & audit (admin)
- Audit log: real, tenant-scoped — widened GET /tenants/:slug/audit with
  q/action/outcome/actorEmail/since/before; UI gains search, outcome + time
  filters, action chips, cursor pagination, and client-side CSV export.
- Security policy: new tenant.securityPolicy (mfaMode, session idle/absolute,
  allowedCountries, ipAllowlist) + PATCH /tenants/:slug/security-policy
  (membership-gated, audited). Editable, labelled by enforcement status.
- MFA: live enrollment overview via GET /tenants/:slug/mfa-status
  (Authentik countAuthenticators per member).
- SSO apps (Dezky as IdP): real Authentik OIDC provider + application CRUD,
  scoped to the tenant group. New AuthentikClient methods (provider/app/binding
  + flow/key/scope discovery), TenantSsoApp schema, TenantSsoService (rollback
  on partial failure; client secret never stored), GET/POST/DELETE
  /tenants/:slug/sso-apps. Validated end-to-end against live Authentik.
- Deferred: shared-flow MFA/geo/session enforcement (global auth-flow blast
  radius) — to be done as its own reviewed change.

Bundled in-progress work that shares the same files (kept together so the tree
stays green):
- Storage page: StorageService + GET /tenants/:slug/storage (OCIS-backed),
  storage.get proxy, storage.vue.
- Per-tenant roles: User.tenantRoles + MeProfile.tenantRoles plumbing.
2026-05-31 17:20:36 +02:00

22 lines
982 B
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Byte formatting for storage figures. Binary units (GiB/TiB) to match what
// OCIS reports. Auto-imported by Nuxt (utils/ is scanned by default).
const UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
// Human-readable size, e.g. 1610612736 → "1.5 GB". Picks the largest unit that
// keeps the number readable; trims trailing ".0".
export function formatBytes(bytes: number, decimals = 1): string {
if (!bytes || bytes < 0) return '0 GB'
const i = Math.min(UNITS.length - 1, Math.floor(Math.log(bytes) / Math.log(1024)))
const value = bytes / 1024 ** i
const fixed = value.toFixed(decimals)
return `${fixed.endsWith('.0') ? fixed.slice(0, -2) : fixed} ${UNITS[i]}`
}
// Integer percentage of used vs total, clamped to 0100. Returns 0 when total
// is 0 (unlimited) to avoid NaN in width styles.
export function percent(used: number, total: number): number {
if (!total || total <= 0) return 0
return Math.min(100, Math.max(0, Math.round((used / total) * 100)))
}