import { DeesElement, customElement, html, css, cssManager, property, type TemplateResult, } from '@design.estate/dees-element'; import type { ISgAuthProviderDetail } from '../interfaces.js'; declare global { interface HTMLElementTagNameMap { 'sg-admin-provider-form-view': SgAdminProviderFormView; } } interface IProviderFormData { name: string; displayName: string; type: 'oidc' | 'ldap'; status: 'active' | 'disabled' | 'testing'; priority: number; // OIDC fields clientId?: string; clientSecret?: string; issuerUrl?: string; authorizationUrl?: string; tokenUrl?: string; userInfoUrl?: string; scopes?: string; // LDAP fields ldapUrl?: string; bindDn?: string; bindPassword?: string; baseDn?: string; userFilter?: string; // Attribute mapping usernameAttr?: string; emailAttr?: string; displayNameAttr?: string; // Provisioning autoCreateUsers?: boolean; defaultRole?: string; } @customElement('sg-admin-provider-form-view') export class SgAdminProviderFormView extends DeesElement { public static demo = () => html`
`; public static demoGroups = ['Admin']; @property({ type: Object }) public accessor provider: ISgAuthProviderDetail | null = null; private formData: IProviderFormData = { name: '', displayName: '', type: 'oidc', status: 'testing', priority: 10, clientId: '', clientSecret: '', issuerUrl: '', authorizationUrl: '', tokenUrl: '', userInfoUrl: '', scopes: 'openid profile email', ldapUrl: '', bindDn: '', bindPassword: '', baseDn: '', userFilter: '(uid={{username}})', usernameAttr: 'preferred_username', emailAttr: 'email', displayNameAttr: 'name', autoCreateUsers: true, defaultRole: 'member', }; async connectedCallback() { await super.connectedCallback(); if (this.provider) { this.formData = { ...this.formData, name: this.provider.name, displayName: this.provider.displayName, type: this.provider.type, status: this.provider.status, priority: this.provider.priority, }; } } public static styles = [ cssManager.defaultStyles, css` :host { display: block; color: ${cssManager.bdTheme('#111', '#fff')}; } .container { display: flex; flex-direction: column; gap: 24px; } .header { display: flex; justify-content: space-between; align-items: center; } .page-title { font-size: 24px; font-weight: 700; letter-spacing: -0.02em; } .cancel-btn { padding: 8px 16px; background: transparent; border: 1px solid ${cssManager.bdTheme('#ddd', '#333')}; font-size: 13px; color: ${cssManager.bdTheme('#666', '#999')}; cursor: pointer; transition: all 150ms ease; } .cancel-btn:hover { border-color: ${cssManager.bdTheme('#999', '#666')}; color: ${cssManager.bdTheme('#111', '#fff')}; } /* Section */ .section { background: ${cssManager.bdTheme('#fff', '#111')}; border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#333')}; padding: 24px; display: flex; flex-direction: column; gap: 16px; } .section-title { font-size: 16px; font-weight: 600; margin-bottom: 4px; } .section-subtitle { font-size: 13px; color: ${cssManager.bdTheme('#888', '#777')}; margin-top: -12px; } /* Form elements */ .form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; } .form-group { display: flex; flex-direction: column; gap: 6px; } .form-group.full { grid-column: 1 / -1; } .form-label { font-size: 13px; font-weight: 600; color: ${cssManager.bdTheme('#111', '#ddd')}; text-transform: uppercase; letter-spacing: 0.04em; } .form-input { padding: 10px 12px; background: ${cssManager.bdTheme('#fff', '#0a0a0a')}; border: 1px solid ${cssManager.bdTheme('#ddd', '#333')}; font-size: 14px; color: ${cssManager.bdTheme('#111', '#fff')}; outline: none; font-family: inherit; box-sizing: border-box; } .form-input:focus { border-color: ${cssManager.bdTheme('#111', '#fff')}; } .form-input.mono { font-family: 'JetBrains Mono', monospace; font-size: 13px; } .form-hint { font-size: 12px; color: ${cssManager.bdTheme('#aaa', '#666')}; } .form-select { padding: 10px 12px; background: ${cssManager.bdTheme('#fff', '#0a0a0a')}; border: 1px solid ${cssManager.bdTheme('#ddd', '#333')}; font-size: 14px; color: ${cssManager.bdTheme('#111', '#fff')}; outline: none; font-family: inherit; } /* Type selector */ .type-selector { display: flex; gap: 0; border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#333')}; width: fit-content; } .type-btn { padding: 10px 24px; background: transparent; border: none; border-right: 1px solid ${cssManager.bdTheme('#e5e5e5', '#333')}; font-size: 14px; font-weight: 500; color: ${cssManager.bdTheme('#666', '#999')}; cursor: pointer; transition: all 150ms ease; } .type-btn:last-child { border-right: none; } .type-btn.active { background: ${cssManager.bdTheme('#111', '#fff')}; color: ${cssManager.bdTheme('#fff', '#111')}; } .type-btn:hover:not(.active) { background: ${cssManager.bdTheme('#f5f5f5', '#1a1a1a')}; } /* Toggle */ .toggle-row { display: flex; align-items: center; justify-content: space-between; padding: 4px 0; } .toggle-switch { width: 44px; height: 24px; background: ${cssManager.bdTheme('#ddd', '#333')}; cursor: pointer; position: relative; transition: background 150ms ease; flex-shrink: 0; } .toggle-switch.on { background: #22c55e; } .toggle-switch::after { content: ''; position: absolute; width: 18px; height: 18px; background: #fff; top: 3px; left: 3px; transition: transform 150ms ease; } .toggle-switch.on::after { transform: translateX(20px); } /* Footer */ .form-footer { display: flex; gap: 8px; } .save-btn { padding: 10px 24px; background: ${cssManager.bdTheme('#111', '#fff')}; border: none; font-size: 14px; font-weight: 600; color: ${cssManager.bdTheme('#fff', '#111')}; cursor: pointer; transition: opacity 150ms ease; } .save-btn:hover { opacity: 0.85; } .footer-cancel-btn { padding: 10px 24px; background: transparent; border: 1px solid ${cssManager.bdTheme('#ddd', '#333')}; font-size: 14px; color: ${cssManager.bdTheme('#666', '#999')}; cursor: pointer; transition: all 150ms ease; } .footer-cancel-btn:hover { border-color: ${cssManager.bdTheme('#999', '#666')}; color: ${cssManager.bdTheme('#111', '#fff')}; } `, ]; public render(): TemplateResult { const isNew = !this.provider; return html`
${isNew ? 'Add Authentication Provider' : 'Edit Provider'}
Basic Information
this.updateField('name', (e.target as HTMLInputElement).value)} placeholder="github-sso" > Unique identifier (lowercase, no spaces)
this.updateField('displayName', (e.target as HTMLInputElement).value)} placeholder="GitHub SSO" >
this.updateField('priority', parseInt((e.target as HTMLInputElement).value) || 0)} min="0" > Lower number = higher priority
${this.formData.type === 'oidc' ? this.renderOidcFields() : this.renderLdapFields()} ${this.renderAttributeMapping()} ${this.renderProvisioning()}
`; } private renderOidcFields(): TemplateResult { return html`
OpenID Connect Configuration
Configure your OIDC provider endpoints and credentials
this.updateField('clientId', (e.target as HTMLInputElement).value)} placeholder="your-client-id" >
this.updateField('clientSecret', (e.target as HTMLInputElement).value)} placeholder="your-client-secret" >
this.updateField('issuerUrl', (e.target as HTMLInputElement).value)} placeholder="https://accounts.google.com" > The OIDC discovery endpoint base URL
this.updateField('authorizationUrl', (e.target as HTMLInputElement).value)} placeholder="Auto-discovered from issuer" >
this.updateField('tokenUrl', (e.target as HTMLInputElement).value)} placeholder="Auto-discovered from issuer" >
this.updateField('userInfoUrl', (e.target as HTMLInputElement).value)} placeholder="Auto-discovered from issuer" >
this.updateField('scopes', (e.target as HTMLInputElement).value)} placeholder="openid profile email" >
`; } private renderLdapFields(): TemplateResult { return html`
LDAP Configuration
Configure your LDAP/Active Directory server connection
this.updateField('ldapUrl', (e.target as HTMLInputElement).value)} placeholder="ldaps://ldap.example.com:636" >
this.updateField('bindDn', (e.target as HTMLInputElement).value)} placeholder="cn=admin,dc=example,dc=com" >
this.updateField('bindPassword', (e.target as HTMLInputElement).value)} placeholder="Bind password" >
this.updateField('baseDn', (e.target as HTMLInputElement).value)} placeholder="ou=users,dc=example,dc=com" >
this.updateField('userFilter', (e.target as HTMLInputElement).value)} placeholder="(uid={{username}})" > Use {{username}} as placeholder for the login username
`; } private renderAttributeMapping(): TemplateResult { return html`
Attribute Mapping
Map provider attributes to registry user fields
this.updateField('usernameAttr', (e.target as HTMLInputElement).value)} placeholder="preferred_username" >
this.updateField('emailAttr', (e.target as HTMLInputElement).value)} placeholder="email" >
this.updateField('displayNameAttr', (e.target as HTMLInputElement).value)} placeholder="name" >
`; } private renderProvisioning(): TemplateResult { return html`
Provisioning
Control automatic user creation and default roles
Auto-create Users
Automatically create accounts for new users who authenticate via this provider
{ this.formData.autoCreateUsers = !this.formData.autoCreateUsers; this.requestUpdate(); }} >
Role assigned to newly provisioned users
`; } private updateField(field: string, value: unknown) { (this.formData as unknown as Record)[field] = value; this.requestUpdate(); } private handleSave() { this.dispatchEvent( new CustomEvent('save', { detail: { providerData: { ...this.formData } }, bubbles: true, composed: true, }) ); } private emitEvent(name: string, detail: Record) { this.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true })); } }