@syndew/oluso-admin-ui 0.0.0-stage → 0.0.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.
- package/README.md +6 -2
- package/fesm2022/syndew-oluso-admin-ui-entitlements-page.component-BeZtIUnk.mjs +149 -0
- package/fesm2022/syndew-oluso-admin-ui-entitlements-page.component-BeZtIUnk.mjs.map +1 -0
- package/fesm2022/syndew-oluso-admin-ui-license-status-page.component-JQmj_qhz.mjs +205 -0
- package/fesm2022/syndew-oluso-admin-ui-license-status-page.component-JQmj_qhz.mjs.map +1 -0
- package/fesm2022/syndew-oluso-admin-ui-platform-clients-page.component-ab3WDRsI.mjs +80 -0
- package/fesm2022/syndew-oluso-admin-ui-platform-clients-page.component-ab3WDRsI.mjs.map +1 -0
- package/fesm2022/syndew-oluso-admin-ui-search-results-page.component-EH82yuW3.mjs +543 -0
- package/fesm2022/syndew-oluso-admin-ui-search-results-page.component-EH82yuW3.mjs.map +1 -0
- package/fesm2022/syndew-oluso-admin-ui-syndew-oluso-admin-ui-BVs_bAxm.mjs +22987 -0
- package/fesm2022/syndew-oluso-admin-ui-syndew-oluso-admin-ui-BVs_bAxm.mjs.map +1 -0
- package/fesm2022/syndew-oluso-admin-ui-team-detail-page.component-CjrQ89qU.mjs +566 -0
- package/fesm2022/syndew-oluso-admin-ui-team-detail-page.component-CjrQ89qU.mjs.map +1 -0
- package/fesm2022/syndew-oluso-admin-ui-unauthorized-page.component-CWYFN6mp.mjs +73 -0
- package/fesm2022/syndew-oluso-admin-ui-unauthorized-page.component-CWYFN6mp.mjs.map +1 -0
- package/fesm2022/syndew-oluso-admin-ui-webhook-detail-page.component-Bh5dZwt-.mjs +728 -0
- package/fesm2022/syndew-oluso-admin-ui-webhook-detail-page.component-Bh5dZwt-.mjs.map +1 -0
- package/fesm2022/syndew-oluso-admin-ui.mjs +3 -0
- package/fesm2022/syndew-oluso-admin-ui.mjs.map +1 -0
- package/help/en/admin.api-scopes.detail.md +43 -0
- package/help/en/admin.api-scopes.md +37 -0
- package/help/en/admin.audit-logs.md +44 -0
- package/help/en/admin.clients.create.md +60 -0
- package/help/en/admin.clients.detail.md +61 -0
- package/help/en/admin.clients.md +48 -0
- package/help/en/admin.dashboard.md +28 -0
- package/help/en/admin.experience.journeys.builder.md +115 -0
- package/help/en/admin.experience.journeys.md +54 -0
- package/help/en/admin.experience.plugins.md +83 -0
- package/help/en/admin.experience.submissions.md +71 -0
- package/help/en/admin.feedback.detail.md +36 -0
- package/help/en/admin.feedback.md +42 -0
- package/help/en/admin.grants.md +42 -0
- package/help/en/admin.identity-providers.detail.md +46 -0
- package/help/en/admin.identity-providers.md +39 -0
- package/help/en/admin.identity-resources.md +47 -0
- package/help/en/admin.module-seats.md +45 -0
- package/help/en/admin.organizations.detail.md +51 -0
- package/help/en/admin.organizations.md +38 -0
- package/help/en/admin.platform.clients.create.md +34 -0
- package/help/en/admin.platform.clients.detail.md +28 -0
- package/help/en/admin.platform.clients.md +38 -0
- package/help/en/admin.resources.detail.md +44 -0
- package/help/en/admin.resources.md +43 -0
- package/help/en/admin.roles.md +33 -0
- package/help/en/admin.settings.license.md +39 -0
- package/help/en/admin.settings.md +70 -0
- package/help/en/admin.signing-keys.md +47 -0
- package/help/en/admin.tenants.detail.md +58 -0
- package/help/en/admin.tenants.md +46 -0
- package/help/en/admin.users.detail.md +41 -0
- package/help/en/admin.users.md +56 -0
- package/help/en/admin.webhooks.detail.md +41 -0
- package/help/en/admin.webhooks.md +37 -0
- package/package.json +32 -3
- 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=<hex-signature>.\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":";;;;;;;;;;AAiCA;;;AAGG;MAEU,oBAAoB,CAAA;IACvB,OAAO,GAAkD,IAAI;IAErE,GAAG,CAAC,UAAkB,EAAE,MAAc,EAAA;QACpC,IAAI,CAAC,OAAO,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE;IACvC;AAEA,IAAA,IAAI,CAAC,UAAkB,EAAA;AACrB,QAAA,IAAI,IAAI,CAAC,OAAO,EAAE,UAAU,KAAK,UAAU;AAAE,YAAA,OAAO,IAAI;AACxD,QAAA,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM;AAClC,QAAA,IAAI,CAAC,OAAO,GAAG,IAAI;AACnB,QAAA,OAAO,MAAM;IACf;wGAZW,oBAAoB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA;AAApB,IAAA,OAAA,KAAA,GAAA,EAAA,CAAA,qBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,oBAAoB,cADP,MAAM,EAAA,CAAA;;4FACnB,oBAAoB,EAAA,UAAA,EAAA,CAAA;kBADhC,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE;;AA+ClC;AACA,MAAM,WAAW,GAAG,gCAAgC;AAEpD;AACA,MAAM,gBAAgB,GAAG,CAAC,eAAe,EAAE,cAAc,EAAE,WAAW,CAAC;AAEvE,SAAS,SAAS,GAAA;IAChB,OAAO;AACL,QAAA,IAAI,EAAE,EAAE;AACR,QAAA,WAAW,EAAE,EAAE;AACf,QAAA,GAAG,EAAE,EAAE;AACP,QAAA,OAAO,EAAE,IAAI;AACb,QAAA,UAAU,EAAE,EAAE;QACd,QAAQ,EAAE,eAAe,CAAC,IAAI;AAC9B,QAAA,UAAU,EAAE,EAAE;AACd,QAAA,aAAa,EAAE,EAAE;AACjB,QAAA,aAAa,EAAE,EAAE;AACjB,QAAA,QAAQ,EAAE,EAAE;AACZ,QAAA,mBAAmB,EAAE,IAAI;AACzB,QAAA,sBAAsB,EAAE,KAAK;AAC7B,QAAA,OAAO,EAAE,EAAE;KACZ;AACH;AAEA;AACM,SAAU,mBAAmB,CAAC,IAAqB,EAAE,gBAAyB,EAAA;IAClF,MAAM,MAAM,GAAsB,EAAE;AAEpC,IAAA,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;AAAE,QAAA,MAAM,CAAC,IAAI,GAAG,eAAe;IAEpD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE;IAC3B,IAAI,CAAC,GAAG,EAAE;AACR,QAAA,MAAM,CAAC,GAAG,GAAG,yCAAyC;IACxD;SAAO;AACL,QAAA,IAAI;AACF,YAAA,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC;YAC3B,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO;AAAE,gBAAA,MAAM,CAAC,GAAG,GAAG,2BAA2B;QAC3G;AAAE,QAAA,MAAM;AACN,YAAA,MAAM,CAAC,GAAG,GAAG,iEAAiE;QAChF;IACF;AAEA,IAAA,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC;AAAE,QAAA,MAAM,CAAC,UAAU,GAAG,4BAA4B;AAElF,IAAA,IAAI,yBAAyB,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,gBAAgB,EAAE;QAC1E,IAAI,IAAI,CAAC,QAAQ,KAAK,eAAe,CAAC,SAAS,EAAE;YAC/C,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,aAAa;AAAE,gBAAA,MAAM,CAAC,UAAU,GAAG,gCAAgC;AACtG,iBAAA,IAAI,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,GAAG,CAAC;AAAE,gBAAA,MAAM,CAAC,UAAU,GAAG,sCAAsC;QACvG;aAAO,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE;AAClC,YAAA,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC,QAAQ,KAAK,eAAe,CAAC,WAAW,GAAG,kBAAkB,GAAG,oBAAoB;QAC/G;IACF;AAAO,SAAA,IAAI,IAAI,CAAC,QAAQ,KAAK,eAAe,CAAC,SAAS,KAAK,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,aAAa,CAAC,EAAE;;QAEpG,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,aAAa;AAAE,YAAA,MAAM,CAAC,UAAU,GAAG,2DAA2D;AACjI,aAAA,IAAI,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,GAAG,CAAC;AAAE,YAAA,MAAM,CAAC,UAAU,GAAG,sCAAsC;IACvG;AAEA,IAAA,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU;AAC9B,IAAA,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,OAAO,EAAE;QAC9B,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE;AAC5B,QAAA,IAAI,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK;YAAE;QACzB,IAAI,CAAC,IAAI,EAAE;AACT,YAAA,MAAM,CAAC,OAAO,GAAG,4BAA4B;YAC7C;QACF;QACA,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;AAC3B,YAAA,MAAM,CAAC,OAAO,GAAG,CAAA,CAAA,EAAI,IAAI,+BAA+B;YACxD;QACF;AACA,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE;AAChC,QAAA,IAAI,gBAAgB,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE;AACtE,YAAA,MAAM,CAAC,OAAO,GAAG,CAAA,EAAG,IAAI,iCAAiC;YACzD;QACF;AACA,QAAA,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE;AACnB,YAAA,MAAM,CAAC,OAAO,GAAG,CAAA,EAAG,IAAI,mBAAmB;YAC3C;QACF;AACA,QAAA,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;IACjB;AAEA,IAAA,OAAO,MAAM;AACf;MAmBa,0BAA0B,CAAA;AACpB,IAAA,KAAK,GAAG,MAAM,CAAC,cAAc,CAAC;AAC9B,IAAA,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;AACvB,IAAA,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC;AACzB,IAAA,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC;AACxB,IAAA,cAAc,GAAG,MAAM,CAAC,cAAc,CAAC;AACvC,IAAA,aAAa,GAAG,MAAM,CAAC,aAAa,CAAC;AACrC,IAAA,KAAK,GAAG,MAAM,CAAC,YAAY,CAAC;AAC5B,IAAA,aAAa,GAAG,MAAM,CAAC,oBAAoB,CAAC;IAEpD,QAAQ,GAAG,eAAe;IAC1B,iBAAiB,GAAG,2BAA2B;AAC/C,IAAA,iBAAiB,GAA6D;AACrF,QAAA,EAAE,IAAI,EAAE,eAAe,CAAC,WAAW,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,uCAAuC,EAAE;AAC3G,QAAA,EAAE,IAAI,EAAE,eAAe,CAAC,SAAS,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,kDAAkD,EAAE;AAClH,QAAA,EAAE,IAAI,EAAE,eAAe,CAAC,YAAY,EAAE,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,0BAA0B,EAAE;KAClG;AAEQ,IAAA,QAAQ,GAAG;AAClB,QAAA,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE;AACjC,QAAA,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE;AACjC,QAAA,EAAE,EAAE,EAAE,gBAAgB,EAAE,KAAK,EAAE,gBAAgB,EAAE;AACjD,QAAA,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,eAAe,EAAE;AACxC,QAAA,EAAE,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,gBAAgB,EAAE;AAC5C,QAAA,EAAE,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,qBAAqB,EAAE;KACjD;AAEQ,IAAA,KAAK,GAAG,MAAM,CAAC,IAAI,4EAAC;AACpB,IAAA,OAAO,GAAG,MAAM,CAAC,IAAI,8EAAC;AACtB,IAAA,MAAM,GAAG,MAAM,CAAC,KAAK,6EAAC;AACtB,IAAA,SAAS,GAAG,MAAM,CAAC,KAAK,gFAAC;AACzB,IAAA,QAAQ,GAAG,MAAM,CAAyB,IAAI,+EAAC;AAC/C,IAAA,OAAO,GAAG,MAAM,CAAW,EAAE,8EAAC;AAC9B,IAAA,aAAa,GAAG,MAAM,CAAqC,EAAE,oFAAC;AAC9D,IAAA,SAAS,GAAG,MAAM,CAAa,UAAU,gFAAC;AAE1C,IAAA,UAAU,GAAG,MAAM,CAAoB,EAAE,iFAAC;AAC1C,IAAA,iBAAiB,GAAG,MAAM,CAAC,KAAK,wFAAC;AACjC,IAAA,gBAAgB,GAAG,MAAM,CAAC,KAAK,uFAAC;AAChC,IAAA,UAAU,GAAG,MAAM,CAAgB,IAAI,iFAAC;AACxC,IAAA,OAAO,GAAG,MAAM,CAAC,KAAK,8EAAC;AAEvB,IAAA,iBAAiB,GAAG,MAAM,CAAC,KAAK,wFAAC;AACjC,IAAA,QAAQ,GAAG,MAAM,CAAC,KAAK,+EAAC;AACxB,IAAA,iBAAiB,GAAG,MAAM,CAAC,KAAK,wFAAC;AACjC,IAAA,QAAQ,GAAG,MAAM,CAAC,KAAK,+EAAC;AACxB,IAAA,eAAe,GAAG,MAAM,CAAC,KAAK,sFAAC;AAC/B,IAAA,SAAS,GAAG,MAAM,CAAC,EAAE,gFAAC;AACtB,IAAA,YAAY,GAAG,MAAM,CAAC,KAAK,mFAAC;AAC5B,IAAA,wBAAwB,GAAG,MAAM,CAAC,KAAK,+FAAC;;AAGxC,IAAA,OAAO,GAAG,MAAM,CAAgB,IAAI,8EAAC;AACrC,IAAA,iBAAiB,GAAG,MAAM,CAAC,KAAK,wFAAC;IAClC,aAAa,GAAG,KAAK;AAEpB,IAAA,eAAe,GAAG,MAAM,CAAc,IAAI,GAAG,EAAE,sFAAC;IACzD,eAAe,GAAG,EAAE;IAEpB,QAAQ,GAAoB,SAAS,EAAE;AAE9B,IAAA,YAAY,GAAG,QAAQ,CAAC,MAAK;AACpC,QAAA,MAAM,MAAM,GAAG,IAAI,GAAG,EAA4B;AAClD,QAAA,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,EAAE;YACxD,KAAK,MAAM,KAAK,IAAI,MAAM;gBAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC;QAChE;AACA,QAAA,OAAO,MAAM;AACf,IAAA,CAAC,mFAAC;IAEO,IAAI,GAAG,QAAQ,CAAC,MACvB,IAAI,CAAC,KAAK;AACR,UAAE;AACF,UAAE;AACE,YAAA,EAAE,EAAE,EAAE,UAAwB,EAAE,KAAK,EAAE,UAAU,EAAE;AACnD,YAAA,EAAE,EAAE,EAAE,YAA0B,EAAE,KAAK,EAAE,YAAY,EAAE;AACxD,SAAA,EAAA,IAAA,SAAA,GAAA,CAAA,EAAA,SAAA,EAAA,MAAA,EAAA,CAAA,8BAAA,EAAA,CAAA,CACN;IAED,QAAQ,GAAA;AACN,QAAA,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;QACjD,IAAI,CAAC,UAAU,EAAE;QACjB,IAAI,CAAC,WAAW,EAAE;AAElB,QAAA,IAAI,EAAE,IAAI,EAAE,KAAK,KAAK,EAAE;AACtB,YAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC;AACrB,YAAA,IAAI,CAAC,YAAY,CAAC,EAAE,EAAE,IAAI,CAAC;QAC7B;aAAO;AACL,YAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;AACpB,YAAA,IAAI,CAAC,QAAQ,GAAG,SAAS,EAAE;AAC3B,YAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;QACzB;IACF;;AAIQ,IAAA,YAAY,CAAC,EAAU,EAAE,KAAK,GAAG,KAAK,EAAA;AAC5C,QAAA,IAAI,KAAK;AAAE,YAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;QACjC,IAAI,CAAC,cAAc,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;AAC5C,YAAA,IAAI,EAAE,CAAC,QAAQ,KAAI;AACjB,gBAAA,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC;gBAC3B,IAAI,KAAK,EAAE;AACT,oBAAA,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC;AACxB,oBAAA,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;oBACnD,IAAI,MAAM,EAAE;AACV,wBAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC;AAC1B,wBAAA,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC;oBAChC;gBACF;AACA,gBAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;YACzB,CAAC;AACD,YAAA,KAAK,EAAE,CAAC,KAAK,KAAI;AACf,gBAAA,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,KAAK,CAAC;AAC/C,gBAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;YACzB,CAAC;AACF,SAAA,CAAC;IACJ;IAEQ,UAAU,GAAA;AAChB,QAAA,IAAI,CAAC,cAAc,CAAC,gBAAgB,EAAE,CAAC,SAAS,CAAC;AAC/C,YAAA,IAAI,EAAE,CAAC,OAAO,KAAK,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC;AAClD,YAAA,KAAK,EAAE,CAAC,KAAK,KAAK,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,KAAK,CAAC;AACjE,SAAA,CAAC;IACJ;IAEQ,WAAW,GAAA;AACjB,QAAA,IAAI,CAAC,aAAa,CAAC,UAAU,EAAE,CAAC,SAAS,CAAC;AACxC,YAAA,IAAI,EAAE,CAAC,OAAO,KAAK,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;AAC5C,YAAA,KAAK,EAAE,CAAC,KAAK,KAAK,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,KAAK,CAAC;AAClE,SAAA,CAAC;IACJ;AAEQ,IAAA,SAAS,CAAC,QAAyB,EAAA;QACzC,IAAI,CAAC,QAAQ,GAAG;AACd,YAAA,GAAG,SAAS,EAAE;YACd,IAAI,EAAE,QAAQ,CAAC,IAAI;AACnB,YAAA,WAAW,EAAE,QAAQ,CAAC,WAAW,IAAI,EAAE;YACvC,GAAG,EAAE,QAAQ,CAAC,GAAG;YACjB,OAAO,EAAE,QAAQ,CAAC,OAAO;AACzB,YAAA,UAAU,EAAE,CAAC,GAAG,QAAQ,CAAC,UAAU,CAAC;AACpC,YAAA,QAAQ,EAAE,QAAQ,CAAC,QAAQ,IAAI,eAAe,CAAC,IAAI;AACnD,YAAA,QAAQ,EAAE,QAAQ,CAAC,QAAQ,IAAI,EAAE;YACjC,mBAAmB,EAAE,QAAQ,CAAC,mBAAmB;AACjD,YAAA,sBAAsB,EAAE,CAAC,CAAC,QAAQ,CAAC,yBAAyB;AAC5D,YAAA,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;SAC1F;QACD,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,KAAK,eAAe,CAAC,YAAY;YAAE,IAAI,CAAC,WAAW,EAAE;IACjF;;IAIA,SAAS,GAAA;AACP,QAAA,OAAO,IAAI,CAAC,KAAK,EAAE,GAAG,aAAa,GAAG,IAAI,CAAC,QAAQ,EAAE,EAAE,IAAI,IAAI,SAAS;IAC1E;AAEA,IAAA,YAAY,CAAC,GAAe,EAAA;AAC1B,QAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC;QACvB,IAAI,GAAG,KAAK,YAAY,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE;YAAE,IAAI,CAAC,cAAc,EAAE;IAC7E;AAEA,IAAA,eAAe,CAAC,EAAU,EAAA;QACxB,IAAI,OAAO,QAAQ,KAAK,WAAW;YAAE;QACrC,QAAQ,CAAC,cAAc,CAAC,CAAA,QAAA,EAAW,EAAE,CAAA,CAAE,CAAC,EAAE,cAAc,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;IACpG;IAEA,QAAQ,GAAA;QACN,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,WAAW,CAAC,CAAC;IACrC;;;IAKA,gBAAgB,GAAA;AACd,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE;QAChC,OAAO,CAAC,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ,KAAK,IAAI,CAAC,QAAQ,CAAC,QAAQ,IAAI,yBAAyB,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;IACjI;IAEA,MAAM,GAAA;QACJ,OAAO,mBAAmB,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,gBAAgB,EAAE,CAAC;IACpE;;AAGA,IAAA,UAAU,CAAC,KAA8B,EAAA;AACvC,QAAA,OAAO,IAAI,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,SAAS;IAC5D;IAEA,MAAM,GAAA;AACJ,QAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC;AACxB,QAAA,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE;QAC5B,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAC3C,IAAI,YAAY,EAAE;AAChB,YAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC;AAC9B,YAAA,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,+BAA+B,CAAC;YACjD,MAAM,OAAO,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,gBAAgB,EAAE,OAAO,EAAE,UAAU,EAAE,CACxH,YAAuC,CACxC;YACD,UAAU,CAAC,MAAM,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;YAC/C;QACF;AAEA,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE;AAChC,QAAA,IAAI,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ;YAAE,IAAI,CAAC,MAAM,EAAE;;AACvC,YAAA,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;IAC5B;IAEQ,UAAU,GAAA;QAChB,MAAM,GAAG,GAA2B,EAAE;QACtC,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE;AACvC,YAAA,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE;AAAE,gBAAA,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,GAAG,CAAC,KAAK;QACvD;AACA,QAAA,OAAO,GAAG;IACZ;;IAGQ,eAAe,GAAA;AACrB,QAAA,QAAQ,IAAI,CAAC,QAAQ,CAAC,QAAQ;YAC5B,KAAK,eAAe,CAAC,WAAW;YAChC,KAAK,eAAe,CAAC,YAAY;gBAC/B,OAAO,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,SAAS;YACrD,KAAK,eAAe,CAAC,SAAS;AAC5B,gBAAA,OAAO,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC,QAAQ,CAAC;AACzD,sBAAE,CAAA,EAAG,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAA;sBACpE,SAAS;AACf,YAAA;AACE,gBAAA,OAAO,SAAS;;IAEtB;IAEA,kBAAkB,GAAA;AAChB,QAAA,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE;QACjC,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE;YAC/B,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,SAAS;YAC1D,GAAG,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE;AAC7B,YAAA,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,OAAO;AAC9B,YAAA,UAAU,EAAE,IAAI,CAAC,QAAQ,CAAC,UAAU;AACpC,YAAA,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,OAAO,GAAG,SAAS;AAC1D,YAAA,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ;AAChC,YAAA,cAAc,EAAE,IAAI,CAAC,eAAe,EAAE;AACtC,YAAA,UAAU,EAAE,2BAA2B;AACvC,YAAA,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ,IAAI,SAAS;AAC7C,YAAA,mBAAmB,EAAE,IAAI,CAAC,QAAQ,CAAC,mBAAmB;AACtD,YAAA,sBAAsB,EAAE,IAAI,CAAC,QAAQ,CAAC,sBAAsB;SAC7D;IACH;AAEA,IAAA,kBAAkB,CAAC,QAAyB,EAAA;AAC1C,QAAA,MAAM,OAAO,GAAiC;YAC5C,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE;YAC/B,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,EAAE;YAC7C,GAAG,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE;AAC7B,YAAA,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,OAAO;AAC9B,YAAA,UAAU,EAAE,IAAI,CAAC,QAAQ,CAAC,UAAU;AACpC,YAAA,OAAO,EAAE,IAAI,CAAC,UAAU,EAAE;AAC1B,YAAA,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ;AAChC,YAAA,mBAAmB,EAAE,IAAI,CAAC,QAAQ,CAAC,mBAAmB;AACtD,YAAA,sBAAsB,EAAE,IAAI,CAAC,QAAQ,CAAC,sBAAsB;SAC7D;;AAED,QAAA,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,MAAM,QAAQ,CAAC,QAAQ,IAAI,eAAe,CAAC,IAAI,CAAC,EAAE;YAC1E,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ;QAC3C;AACA,QAAA,MAAM,UAAU,GAAG,IAAI,CAAC,eAAe,EAAE;QACzC,IAAI,UAAU,KAAK,SAAS;AAAE,YAAA,OAAO,CAAC,cAAc,GAAG,UAAU;AACjE,QAAA,OAAO,OAAO;IAChB;IAEQ,MAAM,GAAA;AACZ,QAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;AACrB,QAAA,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC,CAAC,SAAS,CAAC;AACtE,YAAA,IAAI,EAAE,CAAC,OAAO,KAAI;AAChB,gBAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;AACtB,gBAAA,IAAI,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,KAAK,eAAe,CAAC,YAAY,EAAE;AAC7E,oBAAA,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,MAAM,CAAC;gBACpD;AACA,gBAAA,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,iBAAiB,CAAC;AACrC,gBAAA,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;YACjD,CAAC;AACD,YAAA,KAAK,EAAE,CAAC,KAAK,KAAI;AACf,gBAAA,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,mBAAmB,CAAC,KAAK,EAAE,0BAA0B,CAAC,CAAC;AACxE,gBAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;YACxB,CAAC;AACF,SAAA,CAAC;IACJ;AAEQ,IAAA,MAAM,CAAC,QAAyB,EAAA;AACtC,QAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;AACrB,QAAA,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;AAC3F,YAAA,IAAI,EAAE,CAAC,OAAO,KAAI;AAChB,gBAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;AACtB,gBAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC;;gBAEzB,MAAM,MAAM,GAAG,EAAE,GAAG,QAAQ,EAAE,GAAG,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE;AACjG,gBAAA,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC;AACzB,gBAAA,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;AACtB,gBAAA,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC;YACrC,CAAC;AACD,YAAA,KAAK,EAAE,CAAC,KAAK,KAAI;AACf,gBAAA,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,mBAAmB,CAAC,KAAK,EAAE,0BAA0B,CAAC,CAAC;AACxE,gBAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;YACxB,CAAC;AACF,SAAA,CAAC;IACJ;;IAIA,aAAa,GAAA;AACX,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE;AAChC,QAAA,IAAI,CAAC,QAAQ;YAAE;AACf,QAAA,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;QACvB,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;YACxD,IAAI,EAAE,MAAK;AACT,gBAAA,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;AACxB,gBAAA,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC;AACjC,gBAAA,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,iBAAiB,CAAC;gBACrC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,WAAW,CAAC,CAAC;YACrC,CAAC;AACD,YAAA,KAAK,EAAE,CAAC,KAAK,KAAI;AACf,gBAAA,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,mBAAmB,CAAC,KAAK,EAAE,0BAA0B,CAAC,CAAC;AACxE,gBAAA,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;YAC1B,CAAC;AACF,SAAA,CAAC;IACJ;;AAIA,IAAA,YAAY,CAAC,IAAA,GAAwB,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAA;AACzD,QAAA,OAAO,yBAAyB,CAAC,QAAQ,CAAC,IAAI,CAAC;IACjD;AAEA,IAAA,cAAc,CAAC,IAAkC,EAAA;QAC/C,IAAI,IAAI,KAAK,MAAM;YAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ,GAAG,eAAe,CAAC,IAAI;AAC7D,aAAA,IAAI,IAAI,KAAK,QAAQ,EAAE;YAC1B,IAAI,CAAC,QAAQ,CAAC,QAAQ,GAAG,eAAe,CAAC,YAAY;YACrD,IAAI,CAAC,WAAW,EAAE;QACpB;AAAO,aAAA,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE;YAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE,EAAE,QAAQ;YACxC,IAAI,CAAC,QAAQ,CAAC,QAAQ,GAAG,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,eAAe,CAAC,WAAW;QACnH;IACF;IAEA,QAAQ,GAAA;QACN,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,KAAK,eAAe,CAAC,YAAY;AAAE,YAAA,OAAO,QAAQ;AAC5E,QAAA,OAAO,IAAI,CAAC,YAAY,EAAE,GAAG,QAAQ,GAAG,MAAM;IAChD;;IAGA,iBAAiB,GAAA;QACf,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,KAAK,eAAe,CAAC,YAAY;IAChE;;IAGA,mBAAmB,GAAA;AACjB,QAAA,OAAO,IAAI,CAAC,QAAQ,EAAE,EAAE,QAAQ,KAAK,eAAe,CAAC,YAAY,IAAI,IAAI,CAAC,iBAAiB,EAAE;IAC/F;AAEA;;;AAGG;IACH,WAAW,GAAA;QACT,IAAI,IAAI,CAAC,aAAa;YAAE;AACxB,QAAA,IAAI,CAAC,aAAa,GAAG,IAAI;AACzB,QAAA,MAAM,KAAK,GAAG,EAAE,OAAO,EAAE,IAAI,WAAW,EAAE,CAAC,GAAG,CAAC,gBAAgB,EAAE,IAAI,CAAC,EAAE;AACxE,QAAA,MAAM,WAAW,GAAG,CAAC,MAAc,KAAK,CAAA,EAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,mBAAmB;AAEvF,QAAA,IAAI,CAAC,IAAI,CAAC,GAAG,CAAyB,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAA,+BAAA,CAAiC,EAAE,KAAK,CAAC,CAAC,SAAS,CAAC;AAC3G,YAAA,IAAI,EAAE,CAAC,IAAI,KAAI;gBACb,MAAM,MAAM,GAAG,CAAC,IAAI,EAAE,SAAS,IAAI,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;gBAC/E,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;AACrC,gBAAA,IAAI,CAAC,IAAI,CAAC,GAAG,CAAwB,CAAA,EAAG,MAAM,CAAA,iCAAA,CAAmC,EAAE,KAAK,CAAC,CAAC,SAAS,CAAC;AAClG,oBAAA,IAAI,EAAE,CAAC,SAAS,KAAI;AAClB,wBAAA,IAAI,SAAS,EAAE,QAAQ,EAAE;4BACvB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC;AACpC,4BAAA,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC;wBAClC;oBACF,CAAC;AACD,oBAAA,KAAK,EAAE,MAAM,SAAS;AACvB,iBAAA,CAAC;YACJ,CAAC;YACD,KAAK,EAAE,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC;AACrE,SAAA,CAAC;IACJ;AAEA,IAAA,IAAI,CAAC,KAAgC,EAAA;AACnC,QAAA,IAAI,CAAC,KAAK;YAAE;AACZ,QAAA,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,IAAI,CACvC,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAClC,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAC1D;IACH;IAEA,mBAAmB,GAAA;AACjB,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE;AAChC,QAAA,IAAI,CAAC,QAAQ;YAAE;AACf,QAAA,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;QACvB,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;AACtD,YAAA,IAAI,EAAE,CAAC,QAAQ,KAAI;AACjB,gBAAA,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;AACxB,gBAAA,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC;gBACjC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC;AACnC,gBAAA,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC;YAChC,CAAC;AACD,YAAA,KAAK,EAAE,CAAC,KAAK,KAAI;AACf,gBAAA,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,mBAAmB,CAAC,KAAK,EAAE,6BAA6B,CAAC,CAAC;AAC3E,gBAAA,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;YAC1B,CAAC;AACF,SAAA,CAAC;IACJ;AAEA,IAAA,MAAM,UAAU,GAAA;QACd,MAAM,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;AACrD,QAAA,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC;AAC3B,QAAA,UAAU,CAAC,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC;IACtD;IAEA,gBAAgB,GAAA;AACd,QAAA,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC;AAC/B,QAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;IACxB;;AAIA,IAAA,WAAW,CAAC,QAAgB,EAAA;QAC1B,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC;AAClE,QAAA,OAAO,MAAM,EAAE,UAAU,GAAG,CAAA,EAAG,MAAM,CAAC,UAAU,KAAK,QAAQ,CAAA,CAAA,CAAG,GAAG,QAAQ;IAC7E;;IAIA,SAAS,GAAA;QACP,IAAI,CAAC,QAAQ,CAAC,OAAO,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IAC7E;AAEA,IAAA,YAAY,CAAC,KAAa,EAAA;QACxB,IAAI,CAAC,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,KAAK,CAAC;IAC7E;;AAIA,IAAA,eAAe,CAAC,SAAiB,EAAA;QAC/B,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE;YAChD,IAAI,CAAC,QAAQ,CAAC,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,SAAS,CAAC;QACpF;AAAO,aAAA,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE;AAC7D,YAAA,IAAI,CAAC,QAAQ,CAAC,UAAU,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;QACrE;IACF;AAEA,IAAA,UAAU,CAAC,SAAiB,EAAA;QAC1B,OAAO,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC;IACrD;IAEA,cAAc,GAAA;AACZ,QAAA,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,EAAE;AACpC,QAAA,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACpF,MAAM,IAAI,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE;AACtD,QAAA,IAAI,CAAC,IAAI;AAAE,YAAA,OAAO,MAAM;AACxB,QAAA,OAAO;AACJ,aAAA,GAAG,CAAC,CAAC,KAAK,MAAM;AACf,YAAA,GAAG,KAAK;YACR,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,CACzB,CAAC,CAAC,KACA,CAAC,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC;gBACxC,CAAC,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC;AAC1C,iBAAC,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC;gBACtD,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAC1C;AACF,SAAA,CAAC;AACD,aAAA,MAAM,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IAC/C;;AAGA,IAAA,eAAe,CAAC,IAAY,EAAA;AAC1B,QAAA,OAAO,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC;IAC3E;AAEA,IAAA,WAAW,CAAC,IAAY,EAAA;QACtB,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC;AAC5C,QAAA,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;AAAE,YAAA,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;;AAChC,YAAA,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;AACnB,QAAA,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC;IAChC;IAEA,eAAe,GAAA;QACb,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC;IACrC;IAEA,iBAAiB,GAAA;AACf,QAAA,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;IACtE;AAEA,IAAA,aAAa,CAAC,KAAiB,EAAA;QAC7B,OAAO,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM;IACxE;;AAGQ,IAAA,UAAU,CAAC,KAAiB,EAAA;AAClC,QAAA,OAAO,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC;IAC9G;AAEA,IAAA,oBAAoB,CAAC,KAAiB,EAAA;QACpC,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;QACpC,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IACnE;AAEA,IAAA,qBAAqB,CAAC,KAAiB,EAAA;QACrC,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC;QACvC,OAAO,KAAK,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC,KAAK,CAAC;IACvD;AAEA,IAAA,oBAAoB,CAAC,KAAiB,EAAA;QACpC,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;AACpC,QAAA,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE;YAC1C,IAAI,CAAC,QAAQ,CAAC,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QACvF;aAAO;YACL,IAAI,CAAC,QAAQ,CAAC,UAAU,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC;QAClF;IACF;IAEA,WAAW,GAAA;AACT,QAAA,IAAI,CAAC,QAAQ,CAAC,UAAU,GAAG,EAAE;IAC/B;;;AAKA,IAAA,SAAS,CAAC,KAAmC,EAAA;AAC3C,QAAA,OAAO,KAAK,EAAE,YAAY,KAAK,KAAK;IACtC;AAEA,IAAA,oBAAoB,CAAC,KAAmC,EAAA;QACtD,OAAO,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK,CAAC,UAAU,IAAI,CAAC,KAAK,CAAC,mBAAmB,EAAE,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;IACxF;AAEA,IAAA,iBAAiB,CAAC,UAAoB,EAAA;AACpC,QAAA,OAAO;AACJ,aAAA,GAAG,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,YAAY,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC;AAC3C,aAAA,MAAM,CAAC,CAAC,KAAK,KAAgC,IAAI,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC;IACnF;;IAGA,yBAAyB,GAAA;QACvB,OAAO,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,KAAK,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACjG;AAEA,IAAA,gBAAgB,CAAC,KAAuB,EAAA;AACtC,QAAA,MAAM,KAAK,GAAG;AACZ,YAAA,KAAK,CAAC,qBAAqB;AAC3B,YAAA,KAAK,CAAC,kBAAkB,GAAG,CAAA,aAAA,EAAgB,KAAK,CAAC,kBAAkB,CAAA,YAAA,CAAc,GAAG,SAAS;YAC7F,KAAK,CAAC,mBAAmB,EAAE,MAAM,GAAG,+BAA+B,KAAK,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,SAAS;SACtH;QACD,OAAO,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;IACxC;AAEA,IAAA,kBAAkB,CAAC,QAAyB,EAAA;QAC1C,IAAI,CAAC,QAAQ,CAAC,yBAAyB;AAAE,YAAA,OAAO,EAAE;QAClD,MAAM,EAAE,GAAG,QAAQ,CAAC,6BAA6B,IAAI,QAAQ,CAAC,yBAAyB;AACvF,QAAA,OAAO,CAAA,cAAA,EAAiB,EAAE,CAAA,IAAA,EAAO,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,yBAAyB,CAAC,CAAA,CAAE;IACxF;AAEA,IAAA,8BAA8B,CAAC,OAAgB,EAAA;AAC7C,QAAA,IAAI,CAAC,QAAQ,CAAC,sBAAsB,GAAG,KAAK;AAC5C,QAAA,IAAI,OAAO;AAAE,YAAA,IAAI,CAAC,wBAAwB,CAAC,GAAG,CAAC,IAAI,CAAC;IACtD;IAEA,oBAAoB,GAAA;AAClB,QAAA,IAAI,CAAC,QAAQ,CAAC,sBAAsB,GAAG,IAAI;AAC3C,QAAA,IAAI,CAAC,wBAAwB,CAAC,GAAG,CAAC,KAAK,CAAC;IAC1C;IAEA,mBAAmB,GAAA;AACjB,QAAA,IAAI,CAAC,QAAQ,CAAC,sBAAsB,GAAG,KAAK;AAC5C,QAAA,IAAI,CAAC,wBAAwB,CAAC,GAAG,CAAC,KAAK,CAAC;IAC1C;IAEA,2BAA2B,GAAA;QACzB,MAAM,KAAK,GAAG,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,WAAW,CAAC;AACxF,QAAA,QACE,CAAA,cAAA,EAAiB,IAAI,CAAC,QAAQ,CAAC,GAAG,IAAI,eAAe,CAAA,yCAAA,EAA4C,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA,EAAA,CAAI;YACrH,8EAA8E;YAC9E,mFAAmF;AACnF,YAAA,iEAAiE;IAErE;;IAIA,cAAc,GAAA;AACZ,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE;AAChC,QAAA,IAAI,CAAC,QAAQ;YAAE;AACf,QAAA,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC;QAChC,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;AACvD,YAAA,IAAI,EAAE,CAAC,UAAU,KAAI;AACnB,gBAAA,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,UAAU,CAAC;AAC/B,gBAAA,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC;AACjC,gBAAA,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC;YACjC,CAAC;AACD,YAAA,KAAK,EAAE,CAAC,KAAK,KAAI;AACf,gBAAA,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,KAAK,CAAC;AAClD,gBAAA,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC;YACnC,CAAC;AACF,SAAA,CAAC;IACJ;IAEA,YAAY,GAAA;AACV,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE;AAChC,QAAA,IAAI,CAAC,QAAQ;YAAE;AACf,QAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;AACtD,YAAA,IAAI,EAAE,CAAC,MAAM,KAAI;AACf,gBAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;AACvB,gBAAA,IAAI,MAAM,EAAE,OAAO,KAAK,KAAK;oBAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,IAAI,0BAA0B,CAAC;;AACtF,oBAAA,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,iBAAiB,CAAC;AAC1C,gBAAA,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC9B,IAAI,IAAI,CAAC,gBAAgB,EAAE,IAAI,IAAI,CAAC,SAAS,EAAE,KAAK,YAAY;oBAAE,IAAI,CAAC,cAAc,EAAE;YACzF,CAAC;AACD,YAAA,KAAK,EAAE,CAAC,KAAK,KAAI;AACf,gBAAA,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,mBAAmB,CAAC,KAAK,EAAE,+BAA+B,CAAC,CAAC;AAC7E,gBAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;YACzB,CAAC;AACF,SAAA,CAAC;IACJ;AAEA,IAAA,aAAa,CAAC,QAAyB,EAAA;QACrC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;QAChC,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;YACvD,IAAI,EAAE,MAAK;AACT,gBAAA,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC;gBACzB,IAAI,CAAC,cAAc,EAAE;YACvB,CAAC;AACD,YAAA,KAAK,EAAE,CAAC,KAAK,KAAI;AACf,gBAAA,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,mBAAmB,CAAC,KAAK,EAAE,8BAA8B,CAAC,CAAC;AAC5E,gBAAA,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC;YAC3B,CAAC;AACF,SAAA,CAAC;IACJ;AAEA,IAAA,QAAQ,CAAC,MAA6B,EAAA;AACpC,QAAA,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,WAAW;IACtD;AAEA,IAAA,gBAAgB,CAAC,MAA6B,EAAA;QAC5C,QAAQ,MAAM;AACZ,YAAA,KAAK,SAAS;AACZ,gBAAA,OAAO,SAAS;AAClB,YAAA,KAAK,QAAQ;AACX,gBAAA,OAAO,QAAQ;AACjB,YAAA,KAAK,SAAS;AACZ,gBAAA,OAAO,SAAS;AAClB,YAAA;AACE,gBAAA,OAAO,SAAS;;IAEtB;AAEA,IAAA,UAAU,CAAC,UAA8B,EAAA;AACvC,QAAA,OAAO,UAAU,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,cAAc,EAAE,GAAG,OAAO;IACrE;wGA/oBW,0BAA0B,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;4FAA1B,0BAA0B,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,2BAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,ECzLvC,230CAoxBA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EDxmBI,YAAY,8BACZ,WAAW,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,aAAA,EAAA,QAAA,EAAA,8CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,cAAA,EAAA,QAAA,EAAA,QAAA,EAAA,MAAA,EAAA,CAAA,SAAA,EAAA,OAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,uBAAA,EAAA,QAAA,EAAA,QAAA,EAAA,MAAA,EAAA,CAAA,SAAA,EAAA,OAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,oBAAA,EAAA,QAAA,EAAA,8MAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,4BAAA,EAAA,QAAA,EAAA,uGAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,0BAAA,EAAA,QAAA,EAAA,6GAAA,EAAA,MAAA,EAAA,CAAA,aAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,eAAA,EAAA,QAAA,EAAA,2CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,oBAAA,EAAA,QAAA,EAAA,sGAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,iBAAA,EAAA,QAAA,EAAA,wIAAA,EAAA,MAAA,EAAA,CAAA,UAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,OAAA,EAAA,QAAA,EAAA,qDAAA,EAAA,MAAA,EAAA,CAAA,MAAA,EAAA,UAAA,EAAA,SAAA,EAAA,gBAAA,CAAA,EAAA,OAAA,EAAA,CAAA,eAAA,CAAA,EAAA,QAAA,EAAA,CAAA,SAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,MAAA,EAAA,QAAA,EAAA,yEAAA,EAAA,MAAA,EAAA,CAAA,eAAA,CAAA,EAAA,OAAA,EAAA,CAAA,UAAA,CAAA,EAAA,QAAA,EAAA,CAAA,QAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACX,UAAU,EAAA,QAAA,EAAA,cAAA,EAAA,MAAA,EAAA,CAAA,QAAA,EAAA,aAAA,EAAA,UAAA,EAAA,qBAAA,EAAA,OAAA,EAAA,MAAA,EAAA,YAAA,EAAA,kBAAA,EAAA,oBAAA,EAAA,YAAA,EAAA,YAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACV,aAAa,wGACb,eAAe,EAAA,QAAA,EAAA,WAAA,EAAA,MAAA,EAAA,CAAA,SAAA,EAAA,MAAA,EAAA,UAAA,EAAA,SAAA,EAAA,MAAA,EAAA,WAAA,CAAA,EAAA,OAAA,EAAA,CAAA,SAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACf,cAAc,EAAA,QAAA,EAAA,UAAA,EAAA,MAAA,EAAA,CAAA,SAAA,EAAA,MAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACd,cAAc,kLACd,sBAAsB,EAAA,QAAA,EAAA,mBAAA,EAAA,MAAA,EAAA,CAAA,MAAA,EAAA,OAAA,EAAA,SAAA,EAAA,aAAA,EAAA,YAAA,EAAA,SAAA,EAAA,SAAA,EAAA,iBAAA,EAAA,eAAA,CAAA,EAAA,OAAA,EAAA,CAAA,WAAA,EAAA,WAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACtB,aAAa,EAAA,QAAA,EAAA,SAAA,EAAA,MAAA,EAAA,CAAA,MAAA,EAAA,aAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACb,cAAc,EAAA,QAAA,EAAA,UAAA,EAAA,MAAA,EAAA,CAAA,MAAA,EAAA,OAAA,EAAA,aAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,OAAA,EAAA,MAAA,EAAA,SAAA,EAAA,YAAA,EAAA,YAAA,CAAA,EAAA,OAAA,EAAA,CAAA,aAAA,CAAA,EAAA,CAAA,EAAA,CAAA;;4FAIL,0BAA0B,EAAA,UAAA,EAAA,CAAA;kBAjBtC,SAAS;+BACE,2BAA2B,EAAA,UAAA,EACzB,IAAI,EAAA,OAAA,EACP;wBACP,YAAY;wBACZ,WAAW;wBACX,UAAU;wBACV,aAAa;wBACb,eAAe;wBACf,cAAc;wBACd,cAAc;wBACd,sBAAsB;wBACtB,aAAa;wBACb,cAAc;AACf,qBAAA,EAAA,QAAA,EAAA,230CAAA,EAAA;;;;;"}
|
|
@@ -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.
|