@syndew/oluso-admin-ui 0.0.0-stage → 1.30.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/LICENSE +76 -0
  2. package/README.md +29 -2
  3. package/fesm2022/syndew-oluso-admin-ui-entitlements-page.component-BeZtIUnk.mjs +149 -0
  4. package/fesm2022/syndew-oluso-admin-ui-entitlements-page.component-BeZtIUnk.mjs.map +1 -0
  5. package/fesm2022/syndew-oluso-admin-ui-license-status-page.component-JQmj_qhz.mjs +205 -0
  6. package/fesm2022/syndew-oluso-admin-ui-license-status-page.component-JQmj_qhz.mjs.map +1 -0
  7. package/fesm2022/syndew-oluso-admin-ui-platform-clients-page.component-ab3WDRsI.mjs +80 -0
  8. package/fesm2022/syndew-oluso-admin-ui-platform-clients-page.component-ab3WDRsI.mjs.map +1 -0
  9. package/fesm2022/syndew-oluso-admin-ui-search-results-page.component-EH82yuW3.mjs +543 -0
  10. package/fesm2022/syndew-oluso-admin-ui-search-results-page.component-EH82yuW3.mjs.map +1 -0
  11. package/fesm2022/syndew-oluso-admin-ui-syndew-oluso-admin-ui-BVs_bAxm.mjs +22987 -0
  12. package/fesm2022/syndew-oluso-admin-ui-syndew-oluso-admin-ui-BVs_bAxm.mjs.map +1 -0
  13. package/fesm2022/syndew-oluso-admin-ui-team-detail-page.component-CjrQ89qU.mjs +566 -0
  14. package/fesm2022/syndew-oluso-admin-ui-team-detail-page.component-CjrQ89qU.mjs.map +1 -0
  15. package/fesm2022/syndew-oluso-admin-ui-unauthorized-page.component-CWYFN6mp.mjs +73 -0
  16. package/fesm2022/syndew-oluso-admin-ui-unauthorized-page.component-CWYFN6mp.mjs.map +1 -0
  17. package/fesm2022/syndew-oluso-admin-ui-webhook-detail-page.component-Bh5dZwt-.mjs +728 -0
  18. package/fesm2022/syndew-oluso-admin-ui-webhook-detail-page.component-Bh5dZwt-.mjs.map +1 -0
  19. package/fesm2022/syndew-oluso-admin-ui.mjs +3 -0
  20. package/fesm2022/syndew-oluso-admin-ui.mjs.map +1 -0
  21. package/help/en/admin.api-scopes.detail.md +43 -0
  22. package/help/en/admin.api-scopes.md +37 -0
  23. package/help/en/admin.audit-logs.md +44 -0
  24. package/help/en/admin.clients.create.md +60 -0
  25. package/help/en/admin.clients.detail.md +61 -0
  26. package/help/en/admin.clients.md +48 -0
  27. package/help/en/admin.dashboard.md +28 -0
  28. package/help/en/admin.experience.journeys.builder.md +115 -0
  29. package/help/en/admin.experience.journeys.md +54 -0
  30. package/help/en/admin.experience.plugins.md +83 -0
  31. package/help/en/admin.experience.submissions.md +71 -0
  32. package/help/en/admin.feedback.detail.md +36 -0
  33. package/help/en/admin.feedback.md +42 -0
  34. package/help/en/admin.grants.md +42 -0
  35. package/help/en/admin.identity-providers.detail.md +46 -0
  36. package/help/en/admin.identity-providers.md +39 -0
  37. package/help/en/admin.identity-resources.md +47 -0
  38. package/help/en/admin.module-seats.md +45 -0
  39. package/help/en/admin.organizations.detail.md +51 -0
  40. package/help/en/admin.organizations.md +38 -0
  41. package/help/en/admin.platform.clients.create.md +34 -0
  42. package/help/en/admin.platform.clients.detail.md +28 -0
  43. package/help/en/admin.platform.clients.md +38 -0
  44. package/help/en/admin.resources.detail.md +44 -0
  45. package/help/en/admin.resources.md +43 -0
  46. package/help/en/admin.roles.md +33 -0
  47. package/help/en/admin.settings.license.md +39 -0
  48. package/help/en/admin.settings.md +70 -0
  49. package/help/en/admin.signing-keys.md +47 -0
  50. package/help/en/admin.tenants.detail.md +58 -0
  51. package/help/en/admin.tenants.md +46 -0
  52. package/help/en/admin.users.detail.md +41 -0
  53. package/help/en/admin.users.md +56 -0
  54. package/help/en/admin.webhooks.detail.md +41 -0
  55. package/help/en/admin.webhooks.md +37 -0
  56. package/package.json +51 -3
  57. package/types/syndew-oluso-admin-ui.d.ts +5786 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"syndew-oluso-admin-ui-webhook-detail-page.component-Bh5dZwt-.mjs","sources":["../../../../libs/admin-ui/src/lib/pages/webhooks/webhook-detail-page.component.ts","../../../../libs/admin-ui/src/lib/pages/webhooks/webhook-detail-page.component.html"],"sourcesContent":["import { Component, Injectable, OnInit, computed, inject, signal } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { HttpClient, HttpContext } from '@angular/common/http';\nimport { ActivatedRoute, Router, RouterLink } from '@angular/router';\nimport {\n CardComponent,\n ButtonComponent,\n BadgeComponent,\n ModalComponent,\n ConfirmDialogComponent,\n IconComponent,\n InputComponent,\n ToastService,\n SKIP_ERROR_TOAST,\n extractErrorMessage,\n} from '@syndew/oluso-ui-core';\nimport { WebhookService } from '../../services/webhook.service';\nimport { ClientService } from '../../services/client.service';\nimport { ApiService } from '../../services/api.service';\nimport { Client } from '../../models/client.model';\nimport {\n WebhookEndpoint,\n WebhookEventInfo,\n WebhookDelivery,\n WebhookDeliveryStatus,\n WebhookAuthType,\n STATIC_WEBHOOK_AUTH_TYPES,\n DEFAULT_WEBHOOK_API_VERSION,\n CreateWebhookEndpointRequest,\n UpdateWebhookEndpointRequest,\n} from '../../models/webhook.model';\n\n/**\n * Hands a newly created endpoint's signing secret to the page it navigates to, in memory only: the secret is\n * shown once and must not land in the URL or the browser history.\n */\n@Injectable({ providedIn: 'root' })\nexport class WebhookSecretHandoff {\n private pending: { endpointId: string; secret: string } | null = null;\n\n put(endpointId: string, secret: string): void {\n this.pending = { endpointId, secret };\n }\n\n take(endpointId: string): string | null {\n if (this.pending?.endpointId !== endpointId) return null;\n const secret = this.pending.secret;\n this.pending = null;\n return secret;\n }\n}\n\nexport interface HeaderRow {\n name: string;\n value: string;\n}\n\nexport interface WebhookFormData {\n name: string;\n description: string;\n url: string;\n enabled: boolean;\n eventTypes: string[];\n authType: WebhookAuthType;\n /** Bearer token or API key. */\n credential: string;\n basicUsername: string;\n basicPassword: string;\n clientId: string;\n skipOwnClientEvents: boolean;\n authorizeSensitiveData: boolean;\n headers: HeaderRow[];\n}\n\nexport type WebhookFormErrors = Partial<Record<'name' | 'url' | 'eventTypes' | 'credential' | 'headers', string>>;\n\nexport type WebhookTab = 'settings' | 'deliveries';\n\nexport interface EventGroup {\n name: string;\n events: WebhookEventInfo[];\n}\n\n/** A header name as HTTP allows it (RFC 9110 token). */\nconst HEADER_NAME = /^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$/;\n\n/** Headers the dispatcher sets itself; a custom header with one of these names would be overwritten or clash. */\nconst RESERVED_HEADERS = ['authorization', 'content-type', 'x-api-key'];\n\nfunction emptyForm(): WebhookFormData {\n return {\n name: '',\n description: '',\n url: '',\n enabled: true,\n eventTypes: [],\n authType: WebhookAuthType.None,\n credential: '',\n basicUsername: '',\n basicPassword: '',\n clientId: '',\n skipOwnClientEvents: true,\n authorizeSensitiveData: false,\n headers: [],\n };\n}\n\n/** Why the form can't be saved, per field; empty when it can. */\nexport function validateWebhookForm(form: WebhookFormData, credentialStored: boolean): WebhookFormErrors {\n const errors: WebhookFormErrors = {};\n\n if (!form.name.trim()) errors.name = 'Enter a name.';\n\n const url = form.url.trim();\n if (!url) {\n errors.url = 'Enter the URL that receives deliveries.';\n } else {\n try {\n const parsed = new URL(url);\n if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') errors.url = 'Use an http or https URL.';\n } catch {\n errors.url = 'Enter a full URL, for example https://api.example.com/webhooks.';\n }\n }\n\n if (form.eventTypes.length === 0) errors.eventTypes = 'Choose at least one event.';\n\n if (STATIC_WEBHOOK_AUTH_TYPES.includes(form.authType) && !credentialStored) {\n if (form.authType === WebhookAuthType.BasicAuth) {\n if (!form.basicUsername.trim() || !form.basicPassword) errors.credential = 'Enter a username and password.';\n else if (form.basicUsername.includes(':')) errors.credential = 'The username cannot contain a colon.';\n } else if (!form.credential.trim()) {\n errors.credential = form.authType === WebhookAuthType.BearerToken ? 'Enter the token.' : 'Enter the API key.';\n }\n } else if (form.authType === WebhookAuthType.BasicAuth && (form.basicUsername || form.basicPassword)) {\n // Replacing a stored Basic credential needs both halves.\n if (!form.basicUsername.trim() || !form.basicPassword) errors.credential = 'Enter both the username and the password to replace them.';\n else if (form.basicUsername.includes(':')) errors.credential = 'The username cannot contain a colon.';\n }\n\n const seen = new Set<string>();\n for (const row of form.headers) {\n const name = row.name.trim();\n if (!name && !row.value) continue;\n if (!name) {\n errors.headers = 'Every header needs a name.';\n break;\n }\n if (!HEADER_NAME.test(name)) {\n errors.headers = `\"${name}\" is not a valid header name.`;\n break;\n }\n const lower = name.toLowerCase();\n if (RESERVED_HEADERS.includes(lower) || lower.startsWith('x-webhook-')) {\n errors.headers = `${name} is set by the delivery itself.`;\n break;\n }\n if (seen.has(lower)) {\n errors.headers = `${name} is listed twice.`;\n break;\n }\n seen.add(lower);\n }\n\n return errors;\n}\n\n@Component({\n selector: 'admin-webhook-detail-page',\n standalone: true,\n imports: [\n CommonModule,\n FormsModule,\n RouterLink,\n CardComponent,\n ButtonComponent,\n BadgeComponent,\n ModalComponent,\n ConfirmDialogComponent,\n IconComponent,\n InputComponent,\n ],\n templateUrl: './webhook-detail-page.component.html',\n})\nexport class WebhookDetailPageComponent implements OnInit {\n private readonly route = inject(ActivatedRoute);\n private readonly router = inject(Router);\n private readonly http = inject(HttpClient);\n private readonly api = inject(ApiService);\n private readonly webhookService = inject(WebhookService);\n private readonly clientService = inject(ClientService);\n private readonly toast = inject(ToastService);\n private readonly secretHandoff = inject(WebhookSecretHandoff);\n\n readonly AuthType = WebhookAuthType;\n readonly defaultApiVersion = DEFAULT_WEBHOOK_API_VERSION;\n readonly staticAuthOptions: { type: WebhookAuthType; label: string; hint: string }[] = [\n { type: WebhookAuthType.BearerToken, label: 'Bearer token', hint: 'Sent as Authorization: Bearer <token>' },\n { type: WebhookAuthType.BasicAuth, label: 'Basic auth', hint: 'Sent as Authorization: Basic <username:password>' },\n { type: WebhookAuthType.ApiKeyHeader, label: 'API key header', hint: 'Sent as X-API-Key: <key>' },\n ];\n\n readonly sections = [\n { id: 'basics', label: 'Basics' },\n { id: 'events', label: 'Events' },\n { id: 'authentication', label: 'Authentication' },\n { id: 'client', label: 'Owning client' },\n { id: 'sensitive', label: 'Sensitive data' },\n { id: 'advanced', label: 'Headers and version' },\n ];\n\n readonly isNew = signal(true);\n readonly loading = signal(true);\n readonly saving = signal(false);\n readonly submitted = signal(false);\n readonly endpoint = signal<WebhookEndpoint | null>(null);\n readonly clients = signal<Client[]>([]);\n readonly eventsGrouped = signal<Record<string, WebhookEventInfo[]>>({});\n readonly activeTab = signal<WebhookTab>('settings');\n\n readonly deliveries = signal<WebhookDelivery[]>([]);\n readonly deliveriesLoading = signal(false);\n readonly deliveriesLoaded = signal(false);\n readonly retryingId = signal<string | null>(null);\n readonly testing = signal(false);\n\n readonly showDeleteConfirm = signal(false);\n readonly deleting = signal(false);\n readonly showRotateConfirm = signal(false);\n readonly rotating = signal(false);\n readonly showSecretModal = signal(false);\n readonly newSecret = signal('');\n readonly secretCopied = signal(false);\n readonly showSensitiveDataConfirm = signal(false);\n\n /** The tenant issuer's JWKS URL, for issuer-signed delivery. */\n readonly jwksUrl = signal<string | null>(null);\n readonly jwksFromDiscovery = signal(false);\n private jwksRequested = false;\n\n readonly collapsedGroups = signal<Set<string>>(new Set());\n eventSearchTerm = '';\n\n formData: WebhookFormData = emptyForm();\n\n readonly eventsByType = computed(() => {\n const byType = new Map<string, WebhookEventInfo>();\n for (const events of Object.values(this.eventsGrouped())) {\n for (const event of events) byType.set(event.eventType, event);\n }\n return byType;\n });\n\n readonly tabs = computed(() =>\n this.isNew()\n ? []\n : [\n { id: 'settings' as WebhookTab, label: 'Settings' },\n { id: 'deliveries' as WebhookTab, label: 'Deliveries' },\n ]\n );\n\n ngOnInit(): void {\n const id = this.route.snapshot.paramMap.get('id');\n this.loadEvents();\n this.loadClients();\n\n if (id && id !== 'new') {\n this.isNew.set(false);\n this.loadEndpoint(id, true);\n } else {\n this.isNew.set(true);\n this.formData = emptyForm();\n this.loading.set(false);\n }\n }\n\n // --- Loading ---\n\n private loadEndpoint(id: string, first = false): void {\n if (first) this.loading.set(true);\n this.webhookService.getEndpoint(id).subscribe({\n next: (endpoint) => {\n this.endpoint.set(endpoint);\n if (first) {\n this.resetForm(endpoint);\n const secret = this.secretHandoff.take(endpoint.id);\n if (secret) {\n this.newSecret.set(secret);\n this.showSecretModal.set(true);\n }\n }\n this.loading.set(false);\n },\n error: (error) => {\n console.error('Failed to load webhook:', error);\n this.loading.set(false);\n },\n });\n }\n\n private loadEvents(): void {\n this.webhookService.getEventsGrouped().subscribe({\n next: (grouped) => this.eventsGrouped.set(grouped),\n error: (error) => console.error('Failed to load events:', error),\n });\n }\n\n private loadClients(): void {\n this.clientService.getClients().subscribe({\n next: (clients) => this.clients.set(clients),\n error: (error) => console.error('Failed to load clients:', error),\n });\n }\n\n private resetForm(endpoint: WebhookEndpoint): void {\n this.formData = {\n ...emptyForm(),\n name: endpoint.name,\n description: endpoint.description ?? '',\n url: endpoint.url,\n enabled: endpoint.enabled,\n eventTypes: [...endpoint.eventTypes],\n authType: endpoint.authType ?? WebhookAuthType.None,\n clientId: endpoint.clientId ?? '',\n skipOwnClientEvents: endpoint.skipOwnClientEvents,\n authorizeSensitiveData: !!endpoint.sensitiveDataAuthorizedAt,\n headers: Object.entries(endpoint.headers ?? {}).map(([name, value]) => ({ name, value })),\n };\n if (this.formData.authType === WebhookAuthType.IssuerSigned) this.loadJwksUrl();\n }\n\n // --- Header, tabs, navigation ---\n\n pageTitle(): string {\n return this.isNew() ? 'New webhook' : this.endpoint()?.name || 'Webhook';\n }\n\n setActiveTab(tab: WebhookTab): void {\n this.activeTab.set(tab);\n if (tab === 'deliveries' && !this.deliveriesLoaded()) this.loadDeliveries();\n }\n\n scrollToSection(id: string): void {\n if (typeof document === 'undefined') return;\n document.getElementById(`webhook-${id}`)?.scrollIntoView?.({ behavior: 'smooth', block: 'start' });\n }\n\n onCancel(): void {\n this.router.navigate(['/webhooks']);\n }\n\n // --- Validation and saving ---\n\n /** A static credential of the selected type is already stored, so leaving the field blank keeps it. */\n credentialStored(): boolean {\n const endpoint = this.endpoint();\n return !!endpoint && endpoint.authType === this.formData.authType && STATIC_WEBHOOK_AUTH_TYPES.includes(this.formData.authType);\n }\n\n errors(): WebhookFormErrors {\n return validateWebhookForm(this.formData, this.credentialStored());\n }\n\n /** The error to show for a field, once the admin has tried to save. */\n fieldError(field: keyof WebhookFormErrors): string | undefined {\n return this.submitted() ? this.errors()[field] : undefined;\n }\n\n onSave(): void {\n this.submitted.set(true);\n const errors = this.errors();\n const firstInvalid = Object.keys(errors)[0];\n if (firstInvalid) {\n this.activeTab.set('settings');\n this.toast.error('Check the highlighted fields.');\n const section = { name: 'basics', url: 'basics', eventTypes: 'events', credential: 'authentication', headers: 'advanced' }[\n firstInvalid as keyof WebhookFormErrors\n ];\n setTimeout(() => this.scrollToSection(section));\n return;\n }\n\n const endpoint = this.endpoint();\n if (this.isNew() || !endpoint) this.create();\n else this.update(endpoint);\n }\n\n private headersMap(): Record<string, string> {\n const map: Record<string, string> = {};\n for (const row of this.formData.headers) {\n if (row.name.trim()) map[row.name.trim()] = row.value;\n }\n return map;\n }\n\n /** The credential the form holds for the selected static type, or undefined to keep (or not set) one. */\n private credentialValue(): string | undefined {\n switch (this.formData.authType) {\n case WebhookAuthType.BearerToken:\n case WebhookAuthType.ApiKeyHeader:\n return this.formData.credential.trim() || undefined;\n case WebhookAuthType.BasicAuth:\n return this.formData.basicUsername.trim() && this.formData.basicPassword\n ? `${this.formData.basicUsername.trim()}:${this.formData.basicPassword}`\n : undefined;\n default:\n return undefined;\n }\n }\n\n buildCreateRequest(): CreateWebhookEndpointRequest {\n const headers = this.headersMap();\n return {\n name: this.formData.name.trim(),\n description: this.formData.description.trim() || undefined,\n url: this.formData.url.trim(),\n enabled: this.formData.enabled,\n eventTypes: this.formData.eventTypes,\n headers: Object.keys(headers).length ? headers : undefined,\n authType: this.formData.authType,\n authCredential: this.credentialValue(),\n apiVersion: DEFAULT_WEBHOOK_API_VERSION,\n clientId: this.formData.clientId || undefined,\n skipOwnClientEvents: this.formData.skipOwnClientEvents,\n authorizeSensitiveData: this.formData.authorizeSensitiveData,\n };\n }\n\n buildUpdateRequest(endpoint: WebhookEndpoint): UpdateWebhookEndpointRequest {\n const request: UpdateWebhookEndpointRequest = {\n name: this.formData.name.trim(),\n description: this.formData.description.trim(),\n url: this.formData.url.trim(),\n enabled: this.formData.enabled,\n eventTypes: this.formData.eventTypes,\n headers: this.headersMap(),\n clientId: this.formData.clientId,\n skipOwnClientEvents: this.formData.skipOwnClientEvents,\n authorizeSensitiveData: this.formData.authorizeSensitiveData,\n };\n // The type is sent only when it changes, so an endpoint whose type the server didn't report keeps it.\n if (this.formData.authType !== (endpoint.authType ?? WebhookAuthType.None)) {\n request.authType = this.formData.authType;\n }\n const credential = this.credentialValue();\n if (credential !== undefined) request.authCredential = credential;\n return request;\n }\n\n private create(): void {\n this.saving.set(true);\n this.webhookService.createEndpoint(this.buildCreateRequest()).subscribe({\n next: (created) => {\n this.saving.set(false);\n if (created.secret && this.formData.authType !== WebhookAuthType.IssuerSigned) {\n this.secretHandoff.put(created.id, created.secret);\n }\n this.toast.success('Webhook created');\n this.router.navigate(['/webhooks', created.id]);\n },\n error: (error) => {\n this.toast.error(extractErrorMessage(error, 'Failed to create webhook'));\n this.saving.set(false);\n },\n });\n }\n\n private update(endpoint: WebhookEndpoint): void {\n this.saving.set(true);\n this.webhookService.updateEndpoint(endpoint.id, this.buildUpdateRequest(endpoint)).subscribe({\n next: (updated) => {\n this.saving.set(false);\n this.submitted.set(false);\n // Keep stats and anything the update response leaves out.\n const merged = { ...endpoint, ...updated, authType: updated?.authType ?? this.formData.authType };\n this.endpoint.set(merged);\n this.resetForm(merged);\n this.toast.success('Changes saved');\n },\n error: (error) => {\n this.toast.error(extractErrorMessage(error, 'Failed to update webhook'));\n this.saving.set(false);\n },\n });\n }\n\n // --- Delete ---\n\n confirmDelete(): void {\n const endpoint = this.endpoint();\n if (!endpoint) return;\n this.deleting.set(true);\n this.webhookService.deleteEndpoint(endpoint.id).subscribe({\n next: () => {\n this.deleting.set(false);\n this.showDeleteConfirm.set(false);\n this.toast.success('Webhook deleted');\n this.router.navigate(['/webhooks']);\n },\n error: (error) => {\n this.toast.error(extractErrorMessage(error, 'Failed to delete webhook'));\n this.deleting.set(false);\n },\n });\n }\n\n // --- Authentication ---\n\n isStaticAuth(type: WebhookAuthType = this.formData.authType): boolean {\n return STATIC_WEBHOOK_AUTH_TYPES.includes(type);\n }\n\n selectAuthMode(mode: 'hmac' | 'issuer' | 'static'): void {\n if (mode === 'hmac') this.formData.authType = WebhookAuthType.None;\n else if (mode === 'issuer') {\n this.formData.authType = WebhookAuthType.IssuerSigned;\n this.loadJwksUrl();\n } else if (!this.isStaticAuth()) {\n const stored = this.endpoint()?.authType;\n this.formData.authType = stored !== undefined && this.isStaticAuth(stored) ? stored : WebhookAuthType.BearerToken;\n }\n }\n\n authMode(): 'hmac' | 'issuer' | 'static' {\n if (this.formData.authType === WebhookAuthType.IssuerSigned) return 'issuer';\n return this.isStaticAuth() ? 'static' : 'hmac';\n }\n\n /** Deliveries carry the HMAC signature, so the secret exists and can be rotated. */\n usesSigningSecret(): boolean {\n return this.formData.authType !== WebhookAuthType.IssuerSigned;\n }\n\n /** The endpoint was saved as issuer-signed and is being switched to a secret-based type. */\n leavingIssuerSigned(): boolean {\n return this.endpoint()?.authType === WebhookAuthType.IssuerSigned && this.usesSigningSecret();\n }\n\n /**\n * Resolves the JWKS URL the tenant's discovery document advertises: the tenant's issuer comes from the admin\n * API's server info, then jwks_uri from that issuer's discovery document. Falls back to the default path.\n */\n loadJwksUrl(): void {\n if (this.jwksRequested) return;\n this.jwksRequested = true;\n const quiet = { context: new HttpContext().set(SKIP_ERROR_TOAST, true) };\n const fallbackFor = (issuer: string) => `${issuer.replace(/\\/$/, '')}/.well-known/jwks`;\n\n this.http.get<{ issuerUri?: string }>(`${this.api.baseUrl}/identity-providers/server-info`, quiet).subscribe({\n next: (info) => {\n const issuer = (info?.issuerUri || this.api.absoluteUrl('')).replace(/\\/$/, '');\n this.jwksUrl.set(fallbackFor(issuer));\n this.http.get<{ jwks_uri?: string }>(`${issuer}/.well-known/openid-configuration`, quiet).subscribe({\n next: (discovery) => {\n if (discovery?.jwks_uri) {\n this.jwksUrl.set(discovery.jwks_uri);\n this.jwksFromDiscovery.set(true);\n }\n },\n error: () => undefined,\n });\n },\n error: () => this.jwksUrl.set(fallbackFor(this.api.absoluteUrl(''))),\n });\n }\n\n copy(value: string | null | undefined): void {\n if (!value) return;\n navigator.clipboard.writeText(value).then(\n () => this.toast.success('Copied'),\n () => this.toast.error('Could not copy to the clipboard')\n );\n }\n\n confirmRotateSecret(): void {\n const endpoint = this.endpoint();\n if (!endpoint) return;\n this.rotating.set(true);\n this.webhookService.rotateSecret(endpoint.id).subscribe({\n next: (response) => {\n this.rotating.set(false);\n this.showRotateConfirm.set(false);\n this.newSecret.set(response.secret);\n this.showSecretModal.set(true);\n },\n error: (error) => {\n this.toast.error(extractErrorMessage(error, 'Failed to rotate the secret'));\n this.rotating.set(false);\n },\n });\n }\n\n async copySecret(): Promise<void> {\n await navigator.clipboard.writeText(this.newSecret());\n this.secretCopied.set(true);\n setTimeout(() => this.secretCopied.set(false), 2000);\n }\n\n closeSecretModal(): void {\n this.showSecretModal.set(false);\n this.newSecret.set('');\n }\n\n // --- Owning client ---\n\n clientLabel(clientId: string): string {\n const client = this.clients().find((c) => c.clientId === clientId);\n return client?.clientName ? `${client.clientName} (${clientId})` : clientId;\n }\n\n // --- Custom headers ---\n\n addHeader(): void {\n this.formData.headers = [...this.formData.headers, { name: '', value: '' }];\n }\n\n removeHeader(index: number): void {\n this.formData.headers = this.formData.headers.filter((_, i) => i !== index);\n }\n\n // --- Events ---\n\n toggleEventType(eventType: string): void {\n if (this.formData.eventTypes.includes(eventType)) {\n this.formData.eventTypes = this.formData.eventTypes.filter((e) => e !== eventType);\n } else if (this.canSelect(this.eventsByType().get(eventType))) {\n this.formData.eventTypes = [...this.formData.eventTypes, eventType];\n }\n }\n\n isSelected(eventType: string): boolean {\n return this.formData.eventTypes.includes(eventType);\n }\n\n filteredGroups(): EventGroup[] {\n const grouped = this.eventsGrouped();\n const groups = Object.keys(grouped).map((name) => ({ name, events: grouped[name] }));\n const term = this.eventSearchTerm.trim().toLowerCase();\n if (!term) return groups;\n return groups\n .map((group) => ({\n ...group,\n events: group.events.filter(\n (e) =>\n e.eventType.toLowerCase().includes(term) ||\n e.displayName.toLowerCase().includes(term) ||\n (e.description?.toLowerCase().includes(term) ?? false) ||\n group.name.toLowerCase().includes(term)\n ),\n }))\n .filter((group) => group.events.length > 0);\n }\n\n /** Groups start open; a search always shows its matches. */\n isGroupExpanded(name: string): boolean {\n return !!this.eventSearchTerm.trim() || !this.collapsedGroups().has(name);\n }\n\n toggleGroup(name: string): void {\n const next = new Set(this.collapsedGroups());\n if (next.has(name)) next.delete(name);\n else next.add(name);\n this.collapsedGroups.set(next);\n }\n\n expandAllGroups(): void {\n this.collapsedGroups.set(new Set());\n }\n\n collapseAllGroups(): void {\n this.collapsedGroups.set(new Set(Object.keys(this.eventsGrouped())));\n }\n\n selectedCount(group: EventGroup): number {\n return group.events.filter((e) => this.isSelected(e.eventType)).length;\n }\n\n /** The events in the group the admin can toggle: subscribable ones, plus ones already selected. */\n private toggleable(group: EventGroup): string[] {\n return group.events.filter((e) => this.canSelect(e) || this.isSelected(e.eventType)).map((e) => e.eventType);\n }\n\n isGroupFullySelected(group: EventGroup): boolean {\n const types = this.toggleable(group);\n return types.length > 0 && types.every((t) => this.isSelected(t));\n }\n\n isGroupPartlySelected(group: EventGroup): boolean {\n const count = this.selectedCount(group);\n return count > 0 && !this.isGroupFullySelected(group);\n }\n\n toggleGroupSelection(group: EventGroup): void {\n const types = this.toggleable(group);\n if (types.every((t) => this.isSelected(t))) {\n this.formData.eventTypes = this.formData.eventTypes.filter((e) => !types.includes(e));\n } else {\n this.formData.eventTypes = [...new Set([...this.formData.eventTypes, ...types])];\n }\n }\n\n clearEvents(): void {\n this.formData.eventTypes = [];\n }\n\n // --- Restricted events and sensitive data ---\n\n /** A restricted event can only be newly subscribed by an administrator holding its permission. */\n canSelect(event: WebhookEventInfo | undefined): boolean {\n return event?.canSubscribe !== false;\n }\n\n carriesSensitiveData(event: WebhookEventInfo | undefined): boolean {\n return !!event && (!!event.restricted || (event.sensitiveProperties?.length ?? 0) > 0);\n }\n\n sensitiveEventsOf(eventTypes: string[]): WebhookEventInfo[] {\n return eventTypes\n .map((type) => this.eventsByType().get(type))\n .filter((event): event is WebhookEventInfo => this.carriesSensitiveData(event));\n }\n\n /** Authorising needs the permission of every restricted event the endpoint will subscribe to. */\n canAuthorizeSensitiveData(): boolean {\n return this.sensitiveEventsOf(this.formData.eventTypes).every((event) => this.canSelect(event));\n }\n\n restrictionTitle(event: WebhookEventInfo): string {\n const parts = [\n event.classificationPurpose,\n event.requiredPermission ? `Requires the ${event.requiredPermission} permission.` : undefined,\n event.sensitiveProperties?.length ? `Withheld unless authorised: ${event.sensitiveProperties.join(', ')}` : undefined,\n ];\n return parts.filter(Boolean).join(' ');\n }\n\n authorizationTitle(endpoint: WebhookEndpoint): string {\n if (!endpoint.sensitiveDataAuthorizedAt) return '';\n const by = endpoint.sensitiveDataAuthorizedByName || endpoint.sensitiveDataAuthorizedBy;\n return `Authorised by ${by} on ${this.formatDate(endpoint.sensitiveDataAuthorizedAt)}`;\n }\n\n onAuthorizeSensitiveDataChange(checked: boolean): void {\n this.formData.authorizeSensitiveData = false;\n if (checked) this.showSensitiveDataConfirm.set(true);\n }\n\n confirmSensitiveData(): void {\n this.formData.authorizeSensitiveData = true;\n this.showSensitiveDataConfirm.set(false);\n }\n\n cancelSensitiveData(): void {\n this.formData.authorizeSensitiveData = false;\n this.showSensitiveDataConfirm.set(false);\n }\n\n sensitiveDataConfirmMessage(): string {\n const names = this.sensitiveEventsOf(this.formData.eventTypes).map((e) => e.displayName);\n return (\n `Deliveries to ${this.formData.url || 'this endpoint'} will include the confidential fields of ${names.join(', ')}. ` +\n 'This data leaves the platform and is then governed by the receiving system. ' +\n 'Only authorise endpoints your organisation is permitted to share this data with. ' +\n 'Your name and the time are recorded against this authorisation.'\n );\n }\n\n // --- Deliveries and testing ---\n\n loadDeliveries(): void {\n const endpoint = this.endpoint();\n if (!endpoint) return;\n this.deliveriesLoading.set(true);\n this.webhookService.getDeliveries(endpoint.id).subscribe({\n next: (deliveries) => {\n this.deliveries.set(deliveries);\n this.deliveriesLoading.set(false);\n this.deliveriesLoaded.set(true);\n },\n error: (error) => {\n console.error('Failed to load deliveries:', error);\n this.deliveriesLoading.set(false);\n },\n });\n }\n\n testEndpoint(): void {\n const endpoint = this.endpoint();\n if (!endpoint) return;\n this.testing.set(true);\n this.webhookService.testEndpoint(endpoint.id).subscribe({\n next: (result) => {\n this.testing.set(false);\n if (result?.success === false) this.toast.error(result.error || 'The test delivery failed');\n else this.toast.success('Test event sent');\n this.loadEndpoint(endpoint.id);\n if (this.deliveriesLoaded() || this.activeTab() === 'deliveries') this.loadDeliveries();\n },\n error: (error) => {\n this.toast.error(extractErrorMessage(error, 'Failed to send the test event'));\n this.testing.set(false);\n },\n });\n }\n\n retryDelivery(delivery: WebhookDelivery): void {\n this.retryingId.set(delivery.id);\n this.webhookService.retryDelivery(delivery.id).subscribe({\n next: () => {\n this.retryingId.set(null);\n this.loadDeliveries();\n },\n error: (error) => {\n this.toast.error(extractErrorMessage(error, 'Failed to retry the delivery'));\n this.retryingId.set(null);\n },\n });\n }\n\n canRetry(status: WebhookDeliveryStatus): boolean {\n return status === 'Failed' || status === 'Exhausted';\n }\n\n getStatusVariant(status: WebhookDeliveryStatus): 'default' | 'success' | 'warning' | 'danger' | 'info' {\n switch (status) {\n case 'Success':\n return 'success';\n case 'Failed':\n return 'danger';\n case 'Pending':\n return 'warning';\n default:\n return 'default';\n }\n }\n\n formatDate(dateString: string | undefined): string {\n return dateString ? new Date(dateString).toLocaleString() : 'Never';\n }\n}\n","@if (loading()) {\n <div class=\"flex justify-center py-12\" role=\"status\" aria-label=\"Loading\">\n <svg class=\"animate-spin h-8 w-8 text-primary-600\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\">\n <circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle>\n <path class=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"></path>\n </svg>\n </div>\n} @else if (!isNew() && !endpoint()) {\n <div class=\"flex flex-col items-center justify-center py-16 text-gray-500\">\n <ui-icon name=\"link\" class=\"h-12 w-12 mb-4\" />\n <p>This webhook could not be found.</p>\n <a routerLink=\"/webhooks\" class=\"mt-2 text-sm text-primary-600 hover:text-primary-700\">Back to webhooks</a>\n </div>\n} @else {\n <div class=\"space-y-6\">\n <!-- Breadcrumb -->\n <nav aria-label=\"Breadcrumb\" class=\"text-sm\">\n <ol class=\"flex items-center gap-1 text-gray-500\">\n <li><a routerLink=\"/webhooks\" class=\"hover:text-gray-700\">Webhooks</a></li>\n <li aria-hidden=\"true\"><ui-icon name=\"chevron-right\" class=\"h-4 w-4\" /></li>\n <li class=\"text-gray-900 truncate\" aria-current=\"page\">{{ pageTitle() }}</li>\n </ol>\n </nav>\n\n <!-- Page header -->\n <div class=\"flex flex-col gap-4 md:flex-row md:items-start md:justify-between\">\n <div class=\"min-w-0\">\n <h1 class=\"text-2xl font-bold text-gray-900 truncate\">{{ pageTitle() }}</h1>\n @if (isNew()) {\n <p class=\"mt-1 text-sm text-gray-500\">Send events from this tenant to a URL you control.</p>\n } @else if (endpoint(); as ep) {\n <div class=\"mt-1 flex flex-wrap items-center gap-2\">\n <span class=\"font-mono text-xs text-gray-600 break-all\">{{ ep.url }}</span>\n <ui-badge [variant]=\"ep.enabled ? 'success' : 'default'\">{{ ep.enabled ? 'Active' : 'Disabled' }}</ui-badge>\n </div>\n <div class=\"mt-2 flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-gray-500\">\n <span>Created: <span class=\"text-gray-700\">{{ formatDate(ep.createdAt) }}</span></span>\n @if (ep.updatedAt) {\n <span>Updated: <span class=\"text-gray-700\">{{ formatDate(ep.updatedAt) }}</span></span>\n }\n <span>Last delivery: <span class=\"text-gray-700\">{{ formatDate(ep.stats?.lastDeliveryAt) }}</span></span>\n </div>\n }\n </div>\n <div class=\"flex flex-wrap items-center gap-2 shrink-0\">\n @if (!isNew()) {\n <ui-button variant=\"secondary\" [loading]=\"testing()\" (clicked)=\"testEndpoint()\">\n <ui-icon name=\"beaker\" class=\"h-4 w-4 mr-1.5\" />\n Send test event\n </ui-button>\n <ui-button variant=\"danger\" (clicked)=\"showDeleteConfirm.set(true)\">Delete</ui-button>\n }\n <ui-button variant=\"secondary\" (clicked)=\"onCancel()\">Cancel</ui-button>\n <ui-button [loading]=\"saving()\" (clicked)=\"onSave()\">{{ isNew() ? 'Create webhook' : 'Save changes' }}</ui-button>\n </div>\n </div>\n\n <!-- Tabs (existing endpoints) -->\n @if (tabs().length) {\n <div class=\"border-b border-gray-200\">\n <nav class=\"-mb-px flex space-x-6 overflow-x-auto\" role=\"tablist\" aria-label=\"Webhook sections\">\n @for (tab of tabs(); track tab.id) {\n <button\n type=\"button\"\n role=\"tab\"\n [attr.aria-selected]=\"activeTab() === tab.id\"\n [class]=\"activeTab() === tab.id\n ? 'border-primary-500 text-primary-600'\n : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'\"\n class=\"whitespace-nowrap py-3 px-1 border-b-2 font-medium text-sm\"\n (click)=\"setActiveTab(tab.id)\"\n >\n {{ tab.label }}\n @if (tab.id === 'deliveries' && endpoint()?.stats; as stats) {\n <span class=\"ml-1 text-xs text-gray-400\">{{ stats.totalDeliveries }}</span>\n }\n </button>\n }\n </nav>\n </div>\n }\n\n @if (activeTab() === 'settings') {\n <form (ngSubmit)=\"onSave()\" novalidate class=\"grid gap-6 lg:grid-cols-[12rem_minmax(0,1fr)]\">\n <!-- Section index -->\n <aside class=\"hidden lg:block\" aria-label=\"On this page\">\n <ul class=\"sticky top-4 space-y-1 text-sm\">\n @for (s of sections; track s.id) {\n <li>\n <a\n [href]=\"'#webhook-' + s.id\"\n (click)=\"$event.preventDefault(); scrollToSection(s.id)\"\n class=\"block rounded px-2 py-1 text-gray-600 hover:bg-gray-100 hover:text-gray-900\"\n >{{ s.label }}</a>\n </li>\n }\n </ul>\n </aside>\n\n <div class=\"space-y-6 min-w-0 max-w-4xl\">\n <!-- 1. Basics -->\n <section id=\"webhook-basics\" class=\"scroll-mt-4\">\n <ui-card title=\"Basics\" subtitle=\"What this endpoint is and where deliveries go.\">\n <div class=\"grid gap-4 md:grid-cols-2\">\n <ui-input\n label=\"Name\"\n inputId=\"webhook-name\"\n [required]=\"true\"\n placeholder=\"Order sync\"\n [error]=\"fieldError('name')\"\n [(ngModel)]=\"formData.name\"\n name=\"name\"\n />\n <ui-input\n label=\"Description\"\n inputId=\"webhook-description\"\n placeholder=\"Optional\"\n [(ngModel)]=\"formData.description\"\n name=\"description\"\n />\n <div class=\"md:col-span-2\">\n <ui-input\n label=\"Endpoint URL\"\n inputId=\"webhook-url\"\n type=\"url\"\n [required]=\"true\"\n placeholder=\"https://api.example.com/webhooks\"\n hint=\"Deliveries are POST requests with a JSON body. Use HTTPS outside development.\"\n [error]=\"fieldError('url')\"\n [(ngModel)]=\"formData.url\"\n name=\"url\"\n />\n </div>\n <div class=\"md:col-span-2 flex items-start gap-3\">\n <input\n type=\"checkbox\"\n id=\"webhook-enabled\"\n name=\"enabled\"\n [(ngModel)]=\"formData.enabled\"\n class=\"mt-0.5 h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500\"\n />\n <label for=\"webhook-enabled\" class=\"text-sm text-gray-700\">\n Endpoint is active\n <span class=\"block text-xs text-gray-500\">Turn this off to pause deliveries without losing the configuration.</span>\n </label>\n </div>\n </div>\n </ui-card>\n </section>\n\n <!-- 2. Events -->\n <section id=\"webhook-events\" class=\"scroll-mt-4\">\n <ui-card title=\"Events\" subtitle=\"Choose which events are delivered to this endpoint, grouped by module.\">\n <div class=\"space-y-3\">\n <div class=\"flex flex-col gap-2 sm:flex-row sm:items-center\">\n <div class=\"relative flex-1\">\n <label for=\"webhook-event-search\" class=\"sr-only\">Search events</label>\n <ui-icon name=\"search\" class=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400\" />\n <input\n id=\"webhook-event-search\"\n type=\"search\"\n [(ngModel)]=\"eventSearchTerm\"\n [ngModelOptions]=\"{ standalone: true }\"\n placeholder=\"Search events\"\n class=\"pl-9 w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm\"\n />\n </div>\n <div class=\"flex items-center gap-1 text-sm\">\n <button type=\"button\" (click)=\"expandAllGroups()\" class=\"rounded px-2 py-1.5 text-gray-600 hover:bg-gray-100\">Expand all</button>\n <button type=\"button\" (click)=\"collapseAllGroups()\" class=\"rounded px-2 py-1.5 text-gray-600 hover:bg-gray-100\">Collapse all</button>\n @if (formData.eventTypes.length) {\n <button type=\"button\" (click)=\"clearEvents()\" class=\"rounded px-2 py-1.5 text-gray-600 hover:bg-gray-100\">Clear</button>\n }\n </div>\n </div>\n\n <p class=\"text-sm\" [class.text-gray-600]=\"!fieldError('eventTypes')\" [class.text-red-600]=\"!!fieldError('eventTypes')\" aria-live=\"polite\">\n @if (fieldError('eventTypes'); as err) {\n {{ err }}\n } @else {\n {{ formData.eventTypes.length }} {{ formData.eventTypes.length === 1 ? 'event' : 'events' }} selected\n }\n </p>\n\n <div class=\"rounded-md border border-gray-200 divide-y divide-gray-200\">\n @for (group of filteredGroups(); track group.name) {\n <div>\n <div class=\"flex items-center gap-3 bg-gray-50 px-3 py-2\">\n <button\n type=\"button\"\n class=\"flex items-center text-gray-500 hover:text-gray-700\"\n [attr.aria-expanded]=\"isGroupExpanded(group.name)\"\n [attr.aria-label]=\"(isGroupExpanded(group.name) ? 'Collapse ' : 'Expand ') + group.name\"\n (click)=\"toggleGroup(group.name)\"\n >\n <ui-icon [name]=\"isGroupExpanded(group.name) ? 'chevron-down' : 'chevron-right'\" class=\"h-4 w-4\" />\n </button>\n <input\n type=\"checkbox\"\n [id]=\"'group-' + group.name\"\n [checked]=\"isGroupFullySelected(group)\"\n [indeterminate]=\"isGroupPartlySelected(group)\"\n (change)=\"toggleGroupSelection(group)\"\n [attr.aria-label]=\"'Select all ' + group.name + ' events'\"\n class=\"h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500\"\n />\n <button type=\"button\" class=\"flex-1 text-left text-sm font-medium text-gray-800\" (click)=\"toggleGroup(group.name)\">\n {{ group.name }}\n </button>\n <span class=\"text-xs text-gray-500\">{{ selectedCount(group) }} of {{ group.events.length }}</span>\n </div>\n @if (isGroupExpanded(group.name)) {\n <div class=\"grid gap-1 p-2 md:grid-cols-2\">\n @for (event of group.events; track event.eventType) {\n <label\n class=\"flex items-start gap-2 rounded px-2 py-2 text-sm hover:bg-gray-50\"\n [class.cursor-pointer]=\"canSelect(event) || isSelected(event.eventType)\"\n [class.opacity-60]=\"!canSelect(event) && !isSelected(event.eventType)\"\n [title]=\"carriesSensitiveData(event) ? restrictionTitle(event) : ''\"\n >\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(event.eventType)\"\n [disabled]=\"!canSelect(event) && !isSelected(event.eventType)\"\n (change)=\"toggleEventType(event.eventType)\"\n class=\"mt-0.5 h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500\"\n />\n <span class=\"min-w-0 flex-1\">\n <span class=\"flex flex-wrap items-center gap-2 font-medium text-gray-900\">\n {{ event.displayName }}\n @if (event.restricted) {\n <ui-badge variant=\"danger\">Restricted</ui-badge>\n } @else if (carriesSensitiveData(event)) {\n <ui-badge variant=\"warning\">Sensitive fields</ui-badge>\n }\n </span>\n <span class=\"block font-mono text-xs text-gray-500 break-all\">{{ event.eventType }}</span>\n @if (!canSelect(event)) {\n <span class=\"block text-xs text-red-600\">Requires the {{ event.requiredPermission }} permission</span>\n }\n @if (event.description) {\n <span class=\"block text-xs text-gray-500\">{{ event.description }}</span>\n }\n </span>\n </label>\n }\n </div>\n }\n </div>\n } @empty {\n <div class=\"py-8 text-center text-sm text-gray-500\">\n @if (eventSearchTerm) {\n No events match \"{{ eventSearchTerm }}\"\n } @else {\n No events are available for this tenant.\n }\n </div>\n }\n </div>\n </div>\n </ui-card>\n </section>\n\n <!-- 3. Authentication -->\n <section id=\"webhook-authentication\" class=\"scroll-mt-4\">\n <ui-card title=\"Authentication\" subtitle=\"How each delivery proves it comes from Oluṣọ.\">\n <fieldset class=\"space-y-3\">\n <legend class=\"sr-only\">Delivery authentication</legend>\n\n <!-- HMAC -->\n <label\n class=\"block cursor-pointer rounded-lg border p-4\"\n [class.border-primary-500]=\"authMode() === 'hmac'\"\n [class.ring-1]=\"authMode() === 'hmac'\"\n [class.ring-primary-500]=\"authMode() === 'hmac'\"\n [class.border-gray-200]=\"authMode() !== 'hmac'\"\n >\n <span class=\"flex items-start gap-3\">\n <input\n type=\"radio\"\n name=\"authMode\"\n value=\"hmac\"\n [checked]=\"authMode() === 'hmac'\"\n (change)=\"selectAuthMode('hmac')\"\n class=\"mt-0.5 h-4 w-4 border-gray-300 text-primary-600 focus:ring-primary-500\"\n />\n <span class=\"flex-1\">\n <span class=\"block text-sm font-medium text-gray-900\">HMAC signature (shared secret)</span>\n <span class=\"block text-xs text-gray-500\">\n Each delivery is signed with a <code class=\"font-mono\">whsec_</code> secret that you give the receiver. The signature\n is sent in the X-Webhook-Signature header with the time in X-Webhook-Timestamp.\n </span>\n </span>\n </span>\n </label>\n\n <!-- Issuer-signed -->\n <label\n class=\"block cursor-pointer rounded-lg border p-4\"\n [class.border-primary-500]=\"authMode() === 'issuer'\"\n [class.ring-1]=\"authMode() === 'issuer'\"\n [class.ring-primary-500]=\"authMode() === 'issuer'\"\n [class.border-gray-200]=\"authMode() !== 'issuer'\"\n >\n <span class=\"flex items-start gap-3\">\n <input\n type=\"radio\"\n name=\"authMode\"\n value=\"issuer\"\n [checked]=\"authMode() === 'issuer'\"\n (change)=\"selectAuthMode('issuer')\"\n class=\"mt-0.5 h-4 w-4 border-gray-300 text-primary-600 focus:ring-primary-500\"\n />\n <span class=\"flex-1\">\n <span class=\"flex flex-wrap items-center gap-2 text-sm font-medium text-gray-900\">\n Signed by this tenant's issuer (no shared secret)\n <ui-badge variant=\"info\">New</ui-badge>\n </span>\n <span class=\"block text-xs text-gray-500\">\n Each delivery carries a short-lived token signed with this tenant's issuer key. The receiver checks it against the\n issuer's public keys (its JWKS); nothing secret is shared.\n </span>\n </span>\n </span>\n </label>\n\n <!-- Static credential -->\n <label\n class=\"block cursor-pointer rounded-lg border p-4\"\n [class.border-primary-500]=\"authMode() === 'static'\"\n [class.ring-1]=\"authMode() === 'static'\"\n [class.ring-primary-500]=\"authMode() === 'static'\"\n [class.border-gray-200]=\"authMode() !== 'static'\"\n >\n <span class=\"flex items-start gap-3\">\n <input\n type=\"radio\"\n name=\"authMode\"\n value=\"static\"\n [checked]=\"authMode() === 'static'\"\n (change)=\"selectAuthMode('static')\"\n class=\"mt-0.5 h-4 w-4 border-gray-300 text-primary-600 focus:ring-primary-500\"\n />\n <span class=\"flex-1\">\n <span class=\"block text-sm font-medium text-gray-900\">Static credential</span>\n <span class=\"block text-xs text-gray-500\">\n A fixed bearer token, username and password, or API key, sent with every delivery for receivers that only check a\n header. Deliveries are also HMAC signed.\n </span>\n </span>\n </span>\n </label>\n </fieldset>\n\n <!-- Details of the chosen option -->\n <div class=\"mt-4 rounded-md bg-gray-50 p-4 space-y-3\">\n @switch (authMode()) {\n @case ('hmac') {\n @if (isNew()) {\n <p class=\"text-sm text-gray-600\">\n A <code class=\"font-mono\">whsec_</code> secret is generated when you create the webhook. It is shown once, so copy it then.\n </p>\n } @else {\n <div class=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <p class=\"text-sm font-medium text-gray-700\">Signing secret</p>\n <p class=\"font-mono text-sm text-gray-500\">whsec_••••••••••••</p>\n <p class=\"text-xs text-gray-500\">Secrets are shown only when created or rotated.</p>\n </div>\n <ui-button variant=\"secondary\" (clicked)=\"showRotateConfirm.set(true)\">\n <ui-icon name=\"key\" class=\"h-4 w-4 mr-1.5\" />\n Rotate secret\n </ui-button>\n </div>\n }\n }\n @case ('issuer') {\n <div>\n <p class=\"text-sm font-medium text-gray-700\" id=\"webhook-jwks-label\">Issuer public keys (JWKS)</p>\n <div class=\"mt-1 flex items-center gap-2\">\n <code class=\"flex-1 min-w-0 break-all rounded border border-gray-200 bg-white px-2 py-1.5 font-mono text-xs text-gray-800\" aria-labelledby=\"webhook-jwks-label\">\n {{ jwksUrl() || 'Resolving the issuer...' }}\n </code>\n <button\n type=\"button\"\n class=\"rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 disabled:opacity-50\"\n [disabled]=\"!jwksUrl()\"\n (click)=\"copy(jwksUrl())\"\n aria-label=\"Copy the JWKS URL\"\n title=\"Copy\"\n >\n <ui-icon name=\"document-duplicate\" class=\"h-4 w-4\" />\n </button>\n </div>\n <p class=\"mt-1 text-xs text-gray-500\">\n @if (jwksFromDiscovery()) {\n From this tenant's discovery document.\n } @else {\n The default location for this tenant's issuer; check it against the issuer's discovery document if the receiver\n cannot fetch it.\n }\n Give the receiver this URL. Keys rotate with the tenant's signing keys, so the receiver should refetch it when it meets an\n unknown key.\n </p>\n </div>\n @if (!formData.clientId) {\n <p class=\"text-xs text-amber-700\">Choose an owning client below; the token names it as its subject.</p>\n }\n }\n @case ('static') {\n <div class=\"grid gap-4 md:grid-cols-2\">\n <div class=\"md:col-span-2\">\n <span class=\"block text-sm font-medium text-gray-700 mb-2\" id=\"webhook-static-type\">Credential type</span>\n <div class=\"flex flex-wrap gap-2\" role=\"radiogroup\" aria-labelledby=\"webhook-static-type\">\n @for (option of staticAuthOptions; track option.type) {\n <label\n class=\"flex cursor-pointer items-center gap-2 rounded-md border bg-white px-3 py-2 text-sm\"\n [class.border-primary-500]=\"formData.authType === option.type\"\n [class.border-gray-300]=\"formData.authType !== option.type\"\n >\n <input\n type=\"radio\"\n name=\"staticAuthType\"\n [checked]=\"formData.authType === option.type\"\n (change)=\"formData.authType = option.type\"\n class=\"h-4 w-4 border-gray-300 text-primary-600 focus:ring-primary-500\"\n />\n {{ option.label }}\n </label>\n }\n </div>\n @for (option of staticAuthOptions; track option.type) {\n @if (formData.authType === option.type) {\n <p class=\"mt-1 font-mono text-xs text-gray-500\">{{ option.hint }}</p>\n }\n }\n </div>\n\n @if (formData.authType === AuthType.BasicAuth) {\n <ui-input\n label=\"Username\"\n inputId=\"webhook-basic-username\"\n [placeholder]=\"credentialStored() ? 'Leave blank to keep the current one' : ''\"\n [(ngModel)]=\"formData.basicUsername\"\n name=\"basicUsername\"\n />\n <ui-input\n label=\"Password\"\n inputId=\"webhook-basic-password\"\n type=\"password\"\n [placeholder]=\"credentialStored() ? 'Leave blank to keep the current one' : ''\"\n [(ngModel)]=\"formData.basicPassword\"\n name=\"basicPassword\"\n />\n } @else {\n <div class=\"md:col-span-2\">\n <ui-input\n [label]=\"formData.authType === AuthType.BearerToken ? 'Token' : 'API key'\"\n inputId=\"webhook-credential\"\n type=\"password\"\n [placeholder]=\"credentialStored() ? 'Leave blank to keep the current one' : ''\"\n [(ngModel)]=\"formData.credential\"\n name=\"credential\"\n />\n </div>\n }\n <div class=\"md:col-span-2\">\n @if (fieldError('credential'); as err) {\n <p class=\"text-sm text-red-600\">{{ err }}</p>\n } @else {\n <p class=\"text-xs text-gray-500\">Stored encrypted and never shown again.</p>\n }\n </div>\n </div>\n @if (!isNew()) {\n <div class=\"flex flex-col gap-2 border-t border-gray-200 pt-3 sm:flex-row sm:items-center sm:justify-between\">\n <p class=\"text-xs text-gray-500\">The HMAC signing secret is also sent with each delivery.</p>\n <ui-button variant=\"secondary\" size=\"sm\" (clicked)=\"showRotateConfirm.set(true)\">\n <ui-icon name=\"key\" class=\"h-4 w-4 mr-1.5\" />\n Rotate secret\n </ui-button>\n </div>\n }\n }\n }\n @if (leavingIssuerSigned()) {\n <p class=\"text-xs text-amber-700\">After saving, rotate the secret to get a signing secret for the receiver.</p>\n }\n </div>\n </ui-card>\n </section>\n\n <!-- 4. Owning client -->\n <section id=\"webhook-client\" class=\"scroll-mt-4\">\n <ui-card\n title=\"Owning client\"\n subtitle=\"The client this endpoint belongs to. Events this client causes itself (for example comments it writes through the Service API) are not sent back to it. With issuer-signed delivery, the token names this client as its subject.\"\n >\n <div class=\"space-y-4\">\n <div class=\"max-w-xl\">\n <label for=\"webhook-client-id\" class=\"block text-sm font-medium text-gray-700 mb-1\">Client</label>\n <select\n id=\"webhook-client-id\"\n name=\"clientId\"\n [(ngModel)]=\"formData.clientId\"\n class=\"w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm\"\n >\n <option value=\"\">None</option>\n @for (client of clients(); track client.clientId) {\n <option [value]=\"client.clientId\">{{ clientLabel(client.clientId) }}</option>\n }\n </select>\n </div>\n\n @if (formData.clientId) {\n <div class=\"flex items-start gap-3\">\n <input\n type=\"checkbox\"\n id=\"webhook-skip-own\"\n name=\"skipOwnClientEvents\"\n [(ngModel)]=\"formData.skipOwnClientEvents\"\n class=\"mt-0.5 h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500\"\n />\n <label for=\"webhook-skip-own\" class=\"text-sm text-gray-700\">\n Skip own client events\n <span class=\"block text-xs text-gray-500\">\n Don't send changes this client made itself. This stops the receiver being notified about its own updates, which can\n otherwise loop.\n </span>\n </label>\n </div>\n }\n </div>\n </ui-card>\n </section>\n\n <!-- 5. Sensitive data -->\n <section id=\"webhook-sensitive\" class=\"scroll-mt-4\">\n <ui-card title=\"Sensitive data\" subtitle=\"Confidential fields of restricted events are withheld unless you authorise this endpoint.\">\n @if (sensitiveEventsOf(formData.eventTypes).length > 0) {\n <div class=\"rounded-md border border-amber-300 bg-amber-50 p-3\">\n <div class=\"flex items-start gap-3\">\n <input\n type=\"checkbox\"\n id=\"webhook-authorize-sensitive\"\n name=\"authorizeSensitiveData\"\n [checked]=\"formData.authorizeSensitiveData\"\n [disabled]=\"!canAuthorizeSensitiveData()\"\n (change)=\"onAuthorizeSensitiveDataChange($any($event.target).checked)\"\n class=\"mt-0.5 h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500\"\n />\n <label for=\"webhook-authorize-sensitive\" class=\"text-sm text-gray-800\">\n Send sensitive data to this endpoint\n <span class=\"block text-xs text-gray-600\">\n Without this, the confidential fields of restricted events are withheld from deliveries and listed in\n <code>redacted_fields</code>.\n @if (!canAuthorizeSensitiveData()) {\n You need the permission each restricted event declares to authorise this.\n }\n </span>\n @if (endpoint()?.sensitiveDataAuthorizedAt) {\n <span class=\"block text-xs text-gray-600\">{{ authorizationTitle(endpoint()!) }}</span>\n }\n </label>\n </div>\n <ul class=\"mt-2 ml-7 list-disc text-xs text-gray-600\">\n @for (event of sensitiveEventsOf(formData.eventTypes); track event.eventType) {\n <li>{{ event.displayName }}</li>\n }\n </ul>\n </div>\n } @else {\n <p class=\"text-sm text-gray-500\">None of the selected events carry sensitive fields.</p>\n }\n </ui-card>\n </section>\n\n <!-- 6. Headers and API version -->\n <section id=\"webhook-advanced\" class=\"scroll-mt-4\">\n <ui-card title=\"Headers and version\" subtitle=\"Extra headers sent with every delivery, and the payload format.\">\n <div class=\"space-y-4\">\n <div>\n <span class=\"block text-sm font-medium text-gray-700 mb-2\">Custom headers</span>\n @if (formData.headers.length) {\n <div class=\"space-y-2\">\n @for (row of formData.headers; track $index; let i = $index) {\n <div class=\"grid grid-cols-[minmax(0,1fr)_minmax(0,1fr)_auto] items-center gap-2\">\n <input\n type=\"text\"\n [(ngModel)]=\"row.name\"\n [name]=\"'headerName' + i\"\n placeholder=\"X-Header-Name\"\n [attr.aria-label]=\"'Header ' + (i + 1) + ' name'\"\n class=\"w-full rounded-md border-gray-300 shadow-sm font-mono focus:border-primary-500 focus:ring-primary-500 sm:text-sm\"\n />\n <input\n type=\"text\"\n [(ngModel)]=\"row.value\"\n [name]=\"'headerValue' + i\"\n placeholder=\"Value\"\n [attr.aria-label]=\"'Header ' + (i + 1) + ' value'\"\n class=\"w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm\"\n />\n <button\n type=\"button\"\n (click)=\"removeHeader(i)\"\n class=\"rounded p-1.5 text-gray-400 hover:bg-gray-100 hover:text-red-600\"\n [attr.aria-label]=\"'Remove header ' + (i + 1)\"\n >\n <ui-icon name=\"trash\" class=\"h-4 w-4\" />\n </button>\n </div>\n }\n </div>\n } @else {\n <p class=\"text-sm text-gray-500\">No custom headers.</p>\n }\n @if (fieldError('headers'); as err) {\n <p class=\"mt-2 text-sm text-red-600\">{{ err }}</p>\n }\n <button type=\"button\" (click)=\"addHeader()\" class=\"mt-2 inline-flex items-center gap-1 text-sm text-primary-600 hover:text-primary-700\">\n <ui-icon name=\"plus\" class=\"h-4 w-4\" />\n Add header\n </button>\n <p class=\"mt-1 text-xs text-gray-500\">Don't put secrets here; header values are stored and shown as plain text.</p>\n </div>\n <div>\n <span class=\"block text-sm font-medium text-gray-700\">API version</span>\n <p class=\"mt-1 font-mono text-sm text-gray-800\">{{ endpoint()?.apiVersion || defaultApiVersion }}</p>\n <p class=\"text-xs text-gray-500\">The payload format deliveries use.</p>\n </div>\n </div>\n </ui-card>\n </section>\n\n <!-- Save bar -->\n <div class=\"sticky bottom-0 z-10 -mx-4 flex justify-end gap-2 border-t border-gray-200 bg-white/95 px-4 py-3 backdrop-blur sm:mx-0 sm:rounded-lg sm:border sm:shadow-sm\">\n <ui-button variant=\"secondary\" (clicked)=\"onCancel()\">Cancel</ui-button>\n <ui-button type=\"submit\" [loading]=\"saving()\">{{ isNew() ? 'Create webhook' : 'Save changes' }}</ui-button>\n </div>\n </div>\n </form>\n }\n\n @if (activeTab() === 'deliveries' && endpoint(); as ep) {\n <div class=\"space-y-6\">\n <div class=\"grid gap-4 sm:grid-cols-3\">\n <ui-card>\n <p class=\"text-sm text-gray-500\">Total deliveries</p>\n <p class=\"mt-1 text-2xl font-semibold text-gray-900\">{{ ep.stats?.totalDeliveries || 0 }}</p>\n </ui-card>\n <ui-card>\n <p class=\"text-sm text-gray-500\">Succeeded</p>\n <p class=\"mt-1 text-2xl font-semibold text-green-600\">{{ ep.stats?.successfulDeliveries || 0 }}</p>\n <p class=\"text-xs text-gray-500\">Last: {{ formatDate(ep.stats?.lastSuccessAt) }}</p>\n </ui-card>\n <ui-card>\n <p class=\"text-sm text-gray-500\">Failed</p>\n <p class=\"mt-1 text-2xl font-semibold text-red-600\">{{ ep.stats?.failedDeliveries || 0 }}</p>\n <p class=\"text-xs text-gray-500\">Last: {{ formatDate(ep.stats?.lastFailureAt) }}</p>\n </ui-card>\n </div>\n\n <ui-card [padding]=\"false\">\n <div class=\"flex items-center justify-between border-b border-gray-200 px-4 py-3 sm:px-6\">\n <h2 class=\"text-lg font-medium text-gray-900\">Delivery history</h2>\n <ui-button variant=\"secondary\" size=\"sm\" [loading]=\"deliveriesLoading()\" (clicked)=\"loadDeliveries()\">\n <ui-icon name=\"arrow-path\" class=\"h-4 w-4 mr-1.5\" />\n Refresh\n </ui-button>\n </div>\n @if (deliveriesLoading() && !deliveriesLoaded()) {\n <div class=\"py-8 text-center text-gray-500\">Loading...</div>\n } @else if (deliveries().length > 0) {\n <div class=\"overflow-x-auto\">\n <table class=\"min-w-full divide-y divide-gray-200\">\n <thead class=\"bg-gray-50\">\n <tr>\n <th scope=\"col\" class=\"px-4 py-2 text-left text-xs font-medium uppercase text-gray-500\">Event</th>\n <th scope=\"col\" class=\"px-4 py-2 text-left text-xs font-medium uppercase text-gray-500\">Status</th>\n <th scope=\"col\" class=\"px-4 py-2 text-left text-xs font-medium uppercase text-gray-500\">HTTP</th>\n <th scope=\"col\" class=\"px-4 py-2 text-left text-xs font-medium uppercase text-gray-500\">Attempts</th>\n <th scope=\"col\" class=\"px-4 py-2 text-left text-xs font-medium uppercase text-gray-500\">Time</th>\n <th scope=\"col\" class=\"px-4 py-2\"><span class=\"sr-only\">Actions</span></th>\n </tr>\n </thead>\n <tbody class=\"divide-y divide-gray-200 bg-white\">\n @for (delivery of deliveries(); track delivery.id) {\n <tr>\n <td class=\"px-4 py-2 font-mono text-sm\">{{ delivery.eventType }}</td>\n <td class=\"px-4 py-2\">\n <ui-badge [variant]=\"getStatusVariant(delivery.status)\">{{ delivery.status }}</ui-badge>\n @if (delivery.errorMessage) {\n <span class=\"mt-0.5 block max-w-xs truncate text-xs text-red-600\" [title]=\"delivery.errorMessage\">{{ delivery.errorMessage }}</span>\n }\n </td>\n <td class=\"px-4 py-2 text-sm\">{{ delivery.httpStatus || '-' }}</td>\n <td class=\"px-4 py-2 text-sm\">{{ delivery.retryCount + 1 }}</td>\n <td class=\"px-4 py-2 text-sm text-gray-500 whitespace-nowrap\">{{ formatDate(delivery.createdAt) }}</td>\n <td class=\"px-4 py-2 text-right\">\n @if (canRetry(delivery.status)) {\n <button\n type=\"button\"\n (click)=\"retryDelivery(delivery)\"\n [disabled]=\"retryingId() === delivery.id\"\n class=\"rounded p-1 text-gray-400 hover:text-gray-600 disabled:opacity-50\"\n aria-label=\"Retry delivery\"\n title=\"Retry\"\n >\n <ui-icon name=\"refresh\" class=\"h-4 w-4\" />\n </button>\n }\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n } @else {\n <p class=\"py-8 text-center text-gray-500\">No deliveries yet. Send a test event to try the endpoint.</p>\n }\n </ui-card>\n </div>\n }\n </div>\n\n <!-- Dialogs -->\n <ui-confirm-dialog\n [open]=\"showSensitiveDataConfirm()\"\n title=\"Send sensitive data outside the platform?\"\n [message]=\"sensitiveDataConfirmMessage()\"\n confirmText=\"Authorise\"\n cancelText=\"Cancel\"\n variant=\"danger\"\n [closeOnBackdrop]=\"false\"\n (confirmed)=\"confirmSensitiveData()\"\n (cancelled)=\"cancelSensitiveData()\"\n />\n\n <ui-confirm-dialog\n [open]=\"showDeleteConfirm()\"\n title=\"Delete webhook\"\n [message]=\"'Delete the webhook \\'' + (endpoint()?.name || '') + '\\'? Deliveries stop immediately and this cannot be undone.'\"\n confirmText=\"Delete\"\n cancelText=\"Cancel\"\n variant=\"danger\"\n [loading]=\"deleting()\"\n [closeOnBackdrop]=\"false\"\n (confirmed)=\"confirmDelete()\"\n (cancelled)=\"showDeleteConfirm.set(false)\"\n />\n\n <ui-confirm-dialog\n [open]=\"showRotateConfirm()\"\n title=\"Rotate signing secret\"\n [message]=\"'Rotate the secret for \\'' + (endpoint()?.name || '') + '\\'? The current secret stops working immediately, so update the receiver with the new one.'\"\n confirmText=\"Rotate secret\"\n cancelText=\"Cancel\"\n variant=\"warning\"\n [loading]=\"rotating()\"\n [closeOnBackdrop]=\"false\"\n (confirmed)=\"confirmRotateSecret()\"\n (cancelled)=\"showRotateConfirm.set(false)\"\n />\n\n <ui-modal [open]=\"showSecretModal()\" title=\"Signing secret\" [closeOnBackdrop]=\"false\" (closed)=\"closeSecretModal()\">\n <div class=\"space-y-4\">\n <p class=\"text-sm text-amber-700\">This is the only time this secret is shown. Copy it now.</p>\n <div class=\"flex items-center gap-2\">\n <code class=\"flex-1 break-all rounded bg-gray-100 px-3 py-2 font-mono text-sm\">{{ newSecret() }}</code>\n <ui-button variant=\"secondary\" (clicked)=\"copySecret()\" [attr.aria-label]=\"'Copy secret'\">\n @if (secretCopied()) {\n <ui-icon name=\"check-circle\" class=\"h-5 w-5 text-green-500\" />\n } @else {\n <ui-icon name=\"document-duplicate\" class=\"h-5 w-5\" />\n }\n </ui-button>\n </div>\n <p class=\"text-xs text-gray-500\">\n Use this secret to verify webhook signatures. The signature is sent in the X-Webhook-Signature header as\n sha256=&lt;hex-signature&gt;.\n </p>\n <div class=\"flex justify-end\">\n <ui-button (clicked)=\"closeSecretModal()\">Done</ui-button>\n </div>\n </div>\n </ui-modal>\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { i as ADMIN_BRAND, j as ADMIN_LANDING_COMPONENT, k as ADMIN_NAV_MODES, l as ADMIN_ROUTES, n as ALGORITHM_OPTIONS, o as AUDIT_LOG_CATEGORIES, p as AUDIT_ORIGIN_KINDS, q as AUTH_TYPES, r as AccessTokenType, t as AdminAuthService, u as ApiScopeDetailPageComponent, v as ApiScopeService, x as ApiScopesPageComponent, f as ApiService, y as AuditLogService, z as AuditLogsPageComponent, B as BlueprintsPageComponent, F as BlueprintsService, G as CLAIM_SUGGESTIONS, H as CONDITION_OPERATORS, I as CONTEXT_SUGGESTIONS, J as CallbackPageComponent, K as ClientDetailPageComponent, C as ClientService, L as ClientsPageComponent, N as ConditionBuilderComponent, O as ContextService, Q as DEFAULT_ACCOUNT_EMAIL_SETTINGS, R as DEFAULT_BRANDING_SETTINGS, V as DEFAULT_FILE_FIELD_KINDS, X as DEFAULT_PASSWORD_POLICY, Y as DEFAULT_PREFIX_SCOPE_SUFFIX_MAX_LENGTH, Z as DEFAULT_PROTOCOL_CONFIGURATION, _ as DEFAULT_PROTOCOL_SETTINGS, $ as DEFAULT_SENDER_PROFILE, a0 as DEFAULT_SESSION_CONFIGURATION, a1 as DEFAULT_STEPS, a2 as DEFAULT_SUFFIX_MAX_LENGTH, a3 as DEFAULT_UI_CONFIGURATION, D as DEFAULT_WEBHOOK_API_VERSION, a4 as DashboardComponent, a5 as DefaultLandingPageComponent, a6 as ENVIRONMENT_COLORS, a7 as FEEDBACK_CATEGORIES, a8 as FEEDBACK_STATUSES, a9 as FIELD_TYPES, aa as FILE_FIELD_KINDS, ab as FeedbackAdminService, ac as FeedbackDetailPageComponent, ad as FeedbackPageComponent, ae as GrantService, af as GrantsPageComponent, ag as HTTP_METHODS, ah as IdentityProviderDetailPageComponent, ai as IdentityProviderService, aj as IdentityProvidersPageComponent, ak as IdentityResourceDetailPageComponent, al as IdentityResourceService, am as IdentityResourcesPageComponent, an as JOURNEY_DATA_SUGGESTIONS, ao as JourneyBuilderPageComponent, ap as JourneyService, aq as JourneysPageComponent, ar as KEY_SIZE_OPTIONS, as as KEY_TYPES, at as LandingPageComponent, au as LogoutPageComponent, av as MAX_PREFIX_SCOPE_SUFFIX_MAX_LENGTH, aw as MIN_PREFIX_SCOPE_SUFFIX_MAX_LENGTH, ax as MainLayoutComponent, ay as MappingBuilderComponent, az as ModuleSeatsPageComponent, aA as ModuleSeatsService, aB as NO_VALUE_OPERATORS, aC as OidcAuthProvider, aD as OrganizationDetailPageComponent, aE as OrganizationService, aF as OrganizationsPageComponent, aG as POLICY_SLUG_MAX_LENGTH, aH as POLICY_SLUG_PATTERN, aI as POLICY_TYPE_COLORS, aJ as PermissionService, aK as PluginService, aL as PluginsPageComponent, aM as ProviderTypes, aN as ResourceDetailPageComponent, aO as ResourceService, aP as ResourcesPageComponent, aQ as RoleService, aR as RolesPageComponent, aS as SOURCE_TYPES, S as STATIC_WEBHOOK_AUTH_TYPES, aT as STEP_CATEGORY_COLORS, aU as STEP_CATEGORY_ICONS, aV as STEP_CATEGORY_ORDER, aW as STRICT_SCOPES_PROPERTY, aX as SUBMISSION_STATUS_COLORS, aY as SUBMISSION_STATUS_OPTIONS, aZ as SenderProfilesClient, a_ as SenderProfilesService, a$ as SenderProfilesTabComponent, b0 as SettingsPageComponent, b1 as SettingsService, b2 as SigningKeyService, b3 as SigningKeysPageComponent, b4 as SubmissionService, b5 as SubmissionsPageComponent, c as TeamService, b6 as TeamsPageComponent, T as TenantService, b7 as TenantSettingsPageComponent, b8 as TenantSwitcherComponent, b9 as TenantsPageComponent, ba as TerminologyCenterPageComponent, bb as TerminologyService, bc as TokenExpiration, bd as TokenUsage, be as USER_SUGGESTIONS, bf as UserDetailPageComponent, U as UserService, bg as UsersPageComponent, bh as WEBHOOK_DELIVERY_STATUS_COLORS, bi as WEBHOOK_DELIVERY_STATUS_LABELS, W as WebhookAuthType, h as WebhookService, bj as WebhooksPageComponent, bk as WhatsAppService, bl as WhatsAppSettingsTabComponent, bm as accountUrl, bn as adminOidcConfig, bo as apiUrl, bp as config, bq as getEnvironmentColor, d as managedByLabel, br as policySlugConflict, bs as prefixScopeSuffixMaxLengthError, bt as provideAdminAuth, bu as serverUrl, bv as setConfig, bw as slugify, bx as suffixMaxLengthError, by as suffixPatternError, bz as workspaceUrl } from './syndew-oluso-admin-ui-syndew-oluso-admin-ui-BVs_bAxm.mjs';
2
+ export { DEFAULT_LOCALE_SETTINGS, LocaleService, ToastContainerComponent } from '@syndew/oluso-ui-core';
3
+ //# sourceMappingURL=syndew-oluso-admin-ui.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"syndew-oluso-admin-ui.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -0,0 +1,43 @@
1
+ ---
2
+ title: API Scope Details
3
+ relatedTopics:
4
+ - API Scopes | admin.api-scopes
5
+ - Resources | admin.resources
6
+ - Clients | admin.clients
7
+ ---
8
+
9
+ View and edit the full configuration of an API scope.
10
+
11
+ ## Basic Information
12
+
13
+ - **Name** — the unique scope identifier (read-only after creation)
14
+ - **Display Name** — label shown to users on the consent screen
15
+ - **Description** — additional explanation shown during consent
16
+ - **Status** — Enabled or Disabled
17
+
18
+ ## Settings
19
+
20
+ - **Required** — if enabled, this scope is always included in tokens regardless of whether the client explicitly requests it
21
+ - **Emphasize on Consent** — visually highlight this scope on the consent page to draw the user's attention
22
+ - **Show in Discovery Document** — include this scope in the `.well-known/openid-configuration` response so clients can discover it automatically
23
+ - **Prefix scope** — the scope matches any scope value that starts with its name, followed by a part the client chooses (the suffix). With the prefix scope `device:`, a client allowed it may request `device:abc123`, and that full value is granted and put in the token. The consent screen shows the scope's display name, not the suffix. When ticked, two optional fields appear:
24
+ - **Allowed suffix pattern** — a regular expression the whole suffix must match (it is anchored). Leave it empty to allow any characters a scope can contain: printable ASCII except space, `"` and `\`. A pattern can only narrow that set. It must compile; a broken pattern is refused on save.
25
+ - **Maximum suffix length** — the longest suffix accepted. Leave it empty for the default of 256 characters.
26
+
27
+ For example, Matrix clients choose their device id under `urn:matrix:client:device:`; the pattern `[\x21\x23-\x5B\x5D-\x7E]+` accepts any scope-token character, which Element X needs for its base64 device ids. See `docs/prefix-scopes.md`.
28
+
29
+ ## User Claims
30
+
31
+ Claims are pieces of user information included in access tokens when this scope is granted. For example, a `profile` scope might include the `name`, `family_name`, and `given_name` claims.
32
+
33
+ Click **Edit Claims** to:
34
+
35
+ - Add custom claim types by name
36
+ - Remove existing claims
37
+ - Pick from common suggestions: `email`, `sub`, `name`, `given_name`, `family_name`, `preferred_username`, `role`, etc.
38
+
39
+ ## Tips
40
+
41
+ - Adding a claim to a scope means any client granted that scope will receive the claim in the access token
42
+ - Keep scopes focused — prefer multiple narrow scopes over one broad scope
43
+ - The `openid` scope is required for all OpenID Connect flows and should not be disabled
@@ -0,0 +1,37 @@
1
+ ---
2
+ title: API Scopes
3
+ relatedTopics:
4
+ - Resources | admin.resources
5
+ - Clients | admin.clients
6
+ ---
7
+
8
+ API scopes define the permissions that clients can request when accessing protected resources. They appear on the user consent screen and control what data or operations an access token grants.
9
+
10
+ ## Scope List
11
+
12
+ The table shows all registered scopes with:
13
+
14
+ - **Name** — the scope identifier used in OAuth requests (e.g., `read:orders`, `profile`)
15
+ - **Display Name** — human-readable label shown on consent screens
16
+ - **Status** — Enabled or Disabled
17
+ - **Required** — whether the scope is always included, even if the client doesn't request it
18
+
19
+ Click a row to view or edit the scope's details.
20
+
21
+ ## Creating a Scope
22
+
23
+ Click **Add API Scope** and provide:
24
+
25
+ - **Name** (required) — the unique identifier used in `scope` parameters
26
+ - **Display Name** — what users see on the consent screen
27
+ - **Description** — additional context shown during consent
28
+ - **Enabled** — whether clients can request this scope
29
+ - **Required** — automatically include in every token
30
+ - **Emphasize** — highlight this scope on the consent screen
31
+ - **Show in Discovery** — include in the `.well-known/openid-configuration` document
32
+
33
+ ## Tips
34
+
35
+ - Use descriptive names like `read:users` or `manage:billing` rather than generic names
36
+ - Required scopes are always granted — use sparingly for essential permissions
37
+ - Disabled scopes are rejected if a client requests them
@@ -0,0 +1,44 @@
1
+ ---
2
+ title: Activity Logs
3
+ relatedTopics:
4
+ - Users | admin.users
5
+ - Webhooks | admin.webhooks
6
+ - Settings | admin.settings
7
+ ---
8
+
9
+ A searchable, filterable audit trail of all security and system events. Use activity logs for compliance reporting, troubleshooting failed logins, and monitoring administrative actions.
10
+
11
+ ## Log Table
12
+
13
+ Each entry includes:
14
+
15
+ - **Timestamp** — when the event occurred
16
+ - **Action** — the specific event (e.g., `UserLogin`, `ClientCreated`, `RoleAssigned`)
17
+ - **Category** — grouping such as Authentication, User, Security, Token, Client, Role, Resource, or Settings
18
+ - **User** — the user who triggered the event
19
+ - **Client** — the OAuth client involved, if applicable
20
+ - **Status** — Success or Failed
21
+ - **IP Address** — source IP of the request
22
+
23
+ ## Filtering
24
+
25
+ Narrow results using:
26
+
27
+ - **Search** — free-text search across user, action, and details
28
+ - **Category** — filter by event category
29
+ - **Date Range** — From and To date pickers
30
+ - **Action** — filter by a specific action name
31
+ - **User ID** — show events for a single user
32
+ - **Client ID** — show events for a single client
33
+
34
+ Click **Apply** to run the filters, or **Clear** to reset.
35
+
36
+ ## Exporting
37
+
38
+ Click **Export CSV** to download the current filtered view as a CSV file for offline analysis or compliance archives.
39
+
40
+ ## Tips
41
+
42
+ - Failed login events include the IP address — useful for detecting brute-force attempts
43
+ - Check the Client column to understand which application triggered an event
44
+ - Combine the User ID filter with a date range to audit a specific user's activity
@@ -0,0 +1,60 @@
1
+ ---
2
+ title: Create Client
3
+ relatedTopics:
4
+ - Clients | admin.clients
5
+ - API Scopes | admin.api-scopes
6
+ - Roles | admin.roles
7
+ ---
8
+
9
+ Register a new OAuth 2.0 / OpenID Connect client application. The creation form has five sections, each configuring a different aspect of the client.
10
+
11
+ ## 1. Basic Information
12
+
13
+ - **Client ID** (required) — a unique identifier used in OAuth requests. Cannot be changed after creation. Use lowercase with hyphens, e.g. `my-web-app`.
14
+ - **Client Name** — a display name shown in consent screens and the admin portal.
15
+ - **Description** — an internal note about the client's purpose.
16
+ - **Client URI** — the application's homepage URL.
17
+ - **Logo URI** — a URL to the client's logo, shown on consent screens.
18
+
19
+ ## 2. Authentication
20
+
21
+ **Grant Types** — select which OAuth 2.0 flows this client supports:
22
+
23
+ - **Authorization Code** — standard flow for web and mobile apps. Recommended for most use cases.
24
+ - **Client Credentials** — for server-to-server communication with no user context.
25
+ - **Refresh Token** — enables token renewal without re-authentication.
26
+ - **Device Code** — for input-constrained devices (TVs, CLIs).
27
+ - **Implicit** and **Resource Owner Password** — legacy flows, use only if required.
28
+
29
+ **Security Settings:**
30
+
31
+ - **Require Client Secret** — enable for confidential clients (server-side apps). Disable for public clients (SPAs, mobile).
32
+ - **Require PKCE** — strongly recommended for public clients. Prevents authorization code interception attacks.
33
+ - **Allow Plain Text PKCE** — not recommended. Only enable if clients cannot support S256 challenge method.
34
+
35
+ ## 3. URIs & CORS
36
+
37
+ - **Redirect URIs** — where the authorization server sends users after authentication. Must exactly match what the client sends in the request.
38
+ - **Post Logout Redirect URIs** — where users are redirected after signing out.
39
+ - **CORS Origins** — origins allowed to make cross-origin requests to the token endpoint. Required for browser-based apps.
40
+
41
+ ## 4. Scopes
42
+
43
+ Select which scopes the client can request:
44
+
45
+ - **openid** — required for OpenID Connect flows
46
+ - **profile** — access to user profile claims (name, picture, etc.)
47
+ - **email** — access to email and email_verified claims
48
+ - **offline_access** — enables refresh tokens
49
+ - Add custom scopes for API-specific permissions
50
+
51
+ ## 5. Access Control
52
+
53
+ Restrict who can authenticate with this client:
54
+
55
+ - **Required Permission** — users must have this permission (via a role) to use the client.
56
+ - **Allowed Roles** — only users with these roles can authenticate.
57
+ - **Allowed Users** — allow specific users regardless of role.
58
+ - **Identity Provider Restrictions** — limit which external providers (Google, Azure AD, etc.) can be used.
59
+
60
+ Leave access control empty to allow all authenticated users.
@@ -0,0 +1,61 @@
1
+ ---
2
+ title: Client Details
3
+ relatedTopics:
4
+ - Clients | admin.clients
5
+ - API Scopes | admin.api-scopes
6
+ - Identity Providers | admin.identity-providers
7
+ ---
8
+
9
+ View and edit the full configuration of an OAuth 2.0 / OpenID Connect client. The detail page has nine tabs covering all aspects of the client.
10
+
11
+ ## Basic
12
+
13
+ Core identity of the client — name, description, URIs, and enabled status. The **Client ID** is read-only after creation.
14
+
15
+ ## Authentication
16
+
17
+ Configure grant types and security settings. Manage **Client Secrets** here — secrets are hashed on creation and cannot be viewed again, so copy the value immediately when generating a new one. You cannot delete the last remaining secret.
18
+
19
+ ## Tokens
20
+
21
+ Control token lifetimes:
22
+
23
+ - **Access Token Lifetime** — how long access tokens are valid (default: 3600s / 1 hour)
24
+ - **Identity Token Lifetime** — how long ID tokens are valid (default: 3600s)
25
+ - **Authorization Code Lifetime** — how long auth codes can be exchanged (default: 300s / 5 minutes)
26
+
27
+ Shorter lifetimes improve security but increase token refresh frequency.
28
+
29
+ ## URIs & CORS
30
+
31
+ Manage redirect URIs, post-logout redirect URIs, and CORS origins. Redirect URIs must exactly match what the client sends — no wildcards.
32
+
33
+ ## Scopes
34
+
35
+ Toggle common scopes (openid, profile, email, etc.) and add custom scopes. Only scopes listed here can be requested by the client.
36
+
37
+ ## Logout
38
+
39
+ Configure front-channel and back-channel logout URIs for single sign-out support.
40
+
41
+ - **Front-Channel** — the server renders a hidden iframe to this URL during logout
42
+ - **Back-Channel** — the server makes a direct HTTP POST to this URL
43
+
44
+ ## Access Control
45
+
46
+ Restrict authentication to specific roles, users, or identity providers. Set a **Required Permission** to gate access via the role-based permission system.
47
+
48
+ ## Claims & Properties
49
+
50
+ - **Client Claims** — custom claims added to tokens issued for this client (e.g., `department: engineering`)
51
+ - **Client Properties** — key-value metadata stored with the client
52
+ - **Claims Prefix** — string prepended to client claim types (default: `client_`)
53
+ - **Always Send Client Claims** — include client claims in all token types, not just client credentials tokens
54
+
55
+ ## Advanced
56
+
57
+ - **SSO Settings** — control local login and SSO session lifetime
58
+ - **UI Flow** — choose the authentication UI flow (journey-based or standalone pages)
59
+ - **Device Flow** — configure user code type and device code lifetime
60
+ - **PAR** — require Pushed Authorization Requests for enhanced security
61
+ - **Security** — enable JAR (Request Objects), DPoP (Proof of Possession), and JWT ID claims
@@ -0,0 +1,48 @@
1
+ ---
2
+ title: Clients
3
+ relatedTopics:
4
+ - Create Client | admin.clients.create
5
+ - Resources | admin.resources
6
+ - API Scopes | admin.api-scopes
7
+ - Identity Providers | admin.identity-providers
8
+ ---
9
+
10
+ Clients represent OAuth 2.0 / OpenID Connect applications that authenticate users through your identity platform. Each client corresponds to an application — a web app, mobile app, API, or service — that needs to verify user identity or access protected resources.
11
+
12
+ ## Client List
13
+
14
+ The table shows all registered clients with their:
15
+
16
+ - **Client ID** — the unique identifier used in OAuth flows
17
+ - **Name** — a human-readable display name
18
+ - **Status** — whether the client is enabled or disabled
19
+ - **Grant Types** — which OAuth 2.0 flows the client supports
20
+ - **Created** — when the client was registered
21
+
22
+ Click any row to view and edit the client's full configuration.
23
+
24
+ ## Adding a Client
25
+
26
+ Click **Add Client** to register a new application. You will need to configure at minimum:
27
+
28
+ 1. A unique **Client ID**
29
+ 2. One or more **Grant Types** (e.g., Authorization Code for web apps)
30
+ 3. **Redirect URIs** where authorization responses are sent
31
+ 4. **Scopes** the client is allowed to request
32
+
33
+ ## Common Grant Types
34
+
35
+ | Grant Type | Use Case |
36
+ |---|---|
37
+ | **Authorization Code** | Web and mobile apps with a backend |
38
+ | **Client Credentials** | Server-to-server / machine-to-machine |
39
+ | **Refresh Token** | Long-lived sessions without re-authentication |
40
+ | **Device Code** | Smart TVs, CLI tools, IoT devices |
41
+ | **Implicit** | Legacy SPAs (prefer Authorization Code + PKCE) |
42
+
43
+ ## Tips
44
+
45
+ - Use **Authorization Code with PKCE** for single-page applications instead of the Implicit flow
46
+ - Enable **Require PKCE** for public clients (SPAs, mobile apps) to prevent authorization code interception
47
+ - Keep client secrets secure — they are hashed and cannot be retrieved after creation
48
+ - Use **Access Control** settings to restrict which users and roles can authenticate with a client
@@ -0,0 +1,28 @@
1
+ ---
2
+ title: Admin Dashboard
3
+ relatedTopics:
4
+ - Users | admin.users
5
+ - Organizations | admin.organizations
6
+ - Settings | admin.settings
7
+ ---
8
+
9
+ Welcome to the Admin Portal. This dashboard gives you an at-a-glance overview of your identity and access management environment.
10
+
11
+ ## Quick Actions
12
+
13
+ - **Add a Client** to register a new application
14
+ - **Invite a User** to grant someone access
15
+ - **Review Audit Logs** to see recent activity
16
+
17
+ ## Navigation
18
+
19
+ Use the sidebar on the left to navigate between sections. The sidebar groups related pages under expandable menus:
20
+
21
+ - **Applications** - Manage OAuth/OIDC clients, API resources, and scopes
22
+ - **Federation** - Configure external identity providers (SAML, OIDC)
23
+ - **User Management** - Users, roles, and grants
24
+ - **Experience** - User journeys, data collection, and plugins
25
+
26
+ ## Module Plugins
27
+
28
+ If your organization has enabled additional modules, they appear in the sidebar automatically. Each module adds its own admin pages for configuration.