import { DeesElement, html, customElement, type TemplateResult, css, state, cssManager, } from '@design.estate/dees-element'; import * as appstate from '../appstate.js'; import * as interfaces from '../../dist_ts_interfaces/index.js'; import { viewHostCss } from './shared/css.js'; import { type IStatsTile } from '@design.estate/dees-catalog'; declare global { interface HTMLElementTagNameMap { 'ops-view-securityprofiles': OpsViewSecurityProfiles; } } @customElement('ops-view-securityprofiles') export class OpsViewSecurityProfiles extends DeesElement { @state() accessor profilesState: appstate.IProfilesTargetsState = appstate.profilesTargetsStatePart.getState()!; constructor() { super(); const sub = appstate.profilesTargetsStatePart.select().subscribe((newState) => { this.profilesState = newState; }); this.rxSubscriptions.push(sub); } async connectedCallback() { await super.connectedCallback(); await appstate.profilesTargetsStatePart.dispatchAction(appstate.fetchProfilesAndTargetsAction, null); } public static styles = [ cssManager.defaultStyles, viewHostCss, css` .profilesContainer { display: flex; flex-direction: column; gap: 24px; } `, ]; public render(): TemplateResult { const profiles = this.profilesState.profiles; const statsTiles: IStatsTile[] = [ { id: 'totalProfiles', title: 'Total Profiles', type: 'number', value: profiles.length, icon: 'lucide:shieldCheck', description: 'Reusable security profiles', color: '#3b82f6', }, ]; return html`
({ Name: profile.name, Description: profile.description || '-', 'IP Allow List': (profile.security?.ipAllowList || []).join(', ') || '-', 'IP Block List': (profile.security?.ipBlockList || []).join(', ') || '-', 'Max Connections': profile.security?.maxConnections ?? '-', 'Rate Limit': profile.security?.rateLimit?.enabled ? `${profile.security.rateLimit.maxRequests}/${profile.security.rateLimit.window}s` : '-', Extends: (profile.extendsProfiles || []).length > 0 ? profile.extendsProfiles!.map(id => { const p = profiles.find(pp => pp.id === id); return p ? p.name : id.slice(0, 8); }).join(', ') : '-', })} .dataActions=${[ { name: 'Create Profile', iconName: 'lucide:plus', type: ['header' as const], actionFunc: async (_: any, table: any) => { await this.showCreateProfileDialog(table); }, }, { name: 'Refresh', iconName: 'lucide:rotateCw', type: ['header' as const], actionFunc: async () => { await appstate.profilesTargetsStatePart.dispatchAction(appstate.fetchProfilesAndTargetsAction, null); }, }, { name: 'Edit', iconName: 'lucide:pencil', type: ['contextmenu' as const], actionFunc: async (profile: interfaces.data.ISecurityProfile, table: any) => { await this.showEditProfileDialog(profile, table); }, }, { name: 'Delete', iconName: 'lucide:trash2', type: ['contextmenu' as const], actionFunc: async (profile: interfaces.data.ISecurityProfile) => { await this.deleteProfile(profile); }, }, ]} >
`; } private async showCreateProfileDialog(table: any) { const { DeesModal } = await import('@design.estate/dees-catalog'); DeesModal.createAndShow({ heading: 'Create Security Profile', content: html` `, menuOptions: [ { name: 'Create', action: async (modalArg: any) => { const form = modalArg.shadowRoot!.querySelector('dees-form'); const data = await form.collectFormData(); const ipAllowList: string[] = Array.isArray(data.ipAllowList) ? data.ipAllowList : []; const ipBlockList: string[] = Array.isArray(data.ipBlockList) ? data.ipBlockList : []; const maxConnections = data.maxConnections ? parseInt(String(data.maxConnections)) : undefined; await appstate.profilesTargetsStatePart.dispatchAction(appstate.createProfileAction, { name: String(data.name), description: data.description ? String(data.description) : undefined, security: { ...(ipAllowList.length > 0 ? { ipAllowList } : {}), ...(ipBlockList.length > 0 ? { ipBlockList } : {}), ...(maxConnections ? { maxConnections } : {}), }, }); modalArg.destroy(); }, }, { name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() }, ], }); } private async showEditProfileDialog(profile: interfaces.data.ISecurityProfile, table: any) { const { DeesModal } = await import('@design.estate/dees-catalog'); DeesModal.createAndShow({ heading: `Edit Profile: ${profile.name}`, content: html` `, menuOptions: [ { name: 'Save', action: async (modalArg: any) => { const form = modalArg.shadowRoot!.querySelector('dees-form'); const data = await form.collectFormData(); const ipAllowList: string[] = Array.isArray(data.ipAllowList) ? data.ipAllowList : []; const ipBlockList: string[] = Array.isArray(data.ipBlockList) ? data.ipBlockList : []; const maxConnections = data.maxConnections ? parseInt(String(data.maxConnections)) : undefined; await appstate.profilesTargetsStatePart.dispatchAction(appstate.updateProfileAction, { id: profile.id, name: String(data.name), description: data.description ? String(data.description) : undefined, security: { ipAllowList, ipBlockList, ...(maxConnections ? { maxConnections } : {}), }, }); modalArg.destroy(); }, }, { name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() }, ], }); } private async deleteProfile(profile: interfaces.data.ISecurityProfile) { await appstate.profilesTargetsStatePart.dispatchAction(appstate.deleteProfileAction, { id: profile.id, force: false, }); const currentState = appstate.profilesTargetsStatePart.getState()!; if (currentState.error?.includes('in use')) { const { DeesModal } = await import('@design.estate/dees-catalog'); DeesModal.createAndShow({ heading: 'Profile In Use', content: html`

${currentState.error} Force delete?

`, menuOptions: [ { name: 'Force Delete', action: async (modalArg: any) => { await appstate.profilesTargetsStatePart.dispatchAction(appstate.deleteProfileAction, { id: profile.id, force: true, }); modalArg.destroy(); }, }, { name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() }, ], }); } } }