2026-04-02 15:44:36 +00:00
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 `
< div class = "profilesContainer" >
< dees - statsgrid .tiles = $ { statsTiles } > < / d e e s - s t a t s g r i d >
< dees - table
2026-04-02 16:10:35 +00:00
. heading1 = $ { 'Security Profiles' }
. heading2 = $ { 'Reusable security configurations for routes' }
2026-04-02 15:44:36 +00:00
. data = $ { profiles }
. displayFunction = $ { ( profile : interfaces.data.ISecurityProfile ) = > ( {
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 ] ,
2026-04-02 16:10:35 +00:00
actionFunc : async ( _ : any , table : any ) = > {
2026-04-02 15:44:36 +00:00
await this . showCreateProfileDialog ( table ) ;
} ,
} ,
{
name : 'Refresh' ,
iconName : 'lucide:rotateCw' ,
type : [ 'header' as const ] ,
2026-04-02 16:10:35 +00:00
actionFunc : async ( ) = > {
2026-04-02 15:44:36 +00:00
await appstate . profilesTargetsStatePart . dispatchAction ( appstate . fetchProfilesAndTargetsAction , null ) ;
} ,
} ,
{
name : 'Edit' ,
iconName : 'lucide:pencil' ,
type : [ 'contextmenu' as const ] ,
2026-04-02 16:10:35 +00:00
actionFunc : async ( profile : interfaces.data.ISecurityProfile , table : any ) = > {
2026-04-02 15:44:36 +00:00
await this . showEditProfileDialog ( profile , table ) ;
} ,
} ,
{
name : 'Delete' ,
iconName : 'lucide:trash2' ,
type : [ 'contextmenu' as const ] ,
2026-04-02 16:10:35 +00:00
actionFunc : async ( profile : interfaces.data.ISecurityProfile ) = > {
2026-04-02 15:44:36 +00:00
await this . deleteProfile ( profile ) ;
} ,
} ,
] }
> < / d e e s - t a b l e >
< / div >
` ;
}
private async showCreateProfileDialog ( table : any ) {
const { DeesModal } = await import ( '@design.estate/dees-catalog' ) ;
DeesModal . createAndShow ( {
heading : 'Create Security Profile' ,
content : html `
< dees - form >
< dees - input - text .key = $ { 'name' } .label = $ { 'Name' } .required = $ { true } > < / d e e s - i n p u t - t e x t >
< dees - input - text .key = $ { 'description' } .label = $ { 'Description' } > < / d e e s - i n p u t - t e x t >
< dees - input - text .key = $ { 'ipAllowList' } .label = $ { 'IP Allow List (comma-separated)' } > < / d e e s - i n p u t - t e x t >
< dees - input - text .key = $ { 'ipBlockList' } .label = $ { 'IP Block List (comma-separated)' } > < / d e e s - i n p u t - t e x t >
< dees - input - text .key = $ { 'maxConnections' } .label = $ { 'Max Connections' } > < / d e e s - i n p u t - t e x t >
< / d e e s - f o r m >
` ,
menuOptions : [
{
name : 'Create' ,
action : async ( modalArg : any ) = > {
const form = modalArg . shadowRoot ! . querySelector ( 'dees-form' ) ;
const data = await form . collectFormData ( ) ;
const ipAllowList = data . ipAllowList
? String ( data . ipAllowList ) . split ( ',' ) . map ( ( s : string ) = > s . trim ( ) ) . filter ( Boolean )
: undefined ;
const ipBlockList = data . ipBlockList
? String ( data . ipBlockList ) . split ( ',' ) . map ( ( s : string ) = > s . trim ( ) ) . filter ( Boolean )
: undefined ;
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 ? { ipAllowList } : { } ) ,
. . . ( ipBlockList ? { 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 `
< dees - form >
< dees - input - text .key = $ { 'name' } .label = $ { 'Name' } .value = $ { profile.name } > < / d e e s - i n p u t - t e x t >
< dees - input - text .key = $ { 'description' } .label = $ { 'Description' } .value = $ { profile.description | | '' } > < / d e e s - i n p u t - t e x t >
< dees - input - text .key = $ { 'ipAllowList' } .label = $ { 'IP Allow List (comma-separated)' } .value = $ { ( profile.security ? .ipAllowList | | [ ] ) .join ( ', ' ) } > < / d e e s - i n p u t - t e x t >
< dees - input - text .key = $ { 'ipBlockList' } .label = $ { 'IP Block List (comma-separated)' } .value = $ { ( profile.security ? .ipBlockList | | [ ] ) .join ( ', ' ) } > < / d e e s - i n p u t - t e x t >
< dees - input - text .key = $ { 'maxConnections' } .label = $ { 'Max Connections' } .value = $ { String ( profile.security ? .maxConnections | | '' ) } > < / d e e s - i n p u t - t e x t >
< / d e e s - f o r m >
` ,
menuOptions : [
{
name : 'Save' ,
action : async ( modalArg : any ) = > {
const form = modalArg . shadowRoot ! . querySelector ( 'dees-form' ) ;
const data = await form . collectFormData ( ) ;
const ipAllowList = data . ipAllowList
? String ( data . ipAllowList ) . split ( ',' ) . map ( ( s : string ) = > s . trim ( ) ) . filter ( Boolean )
: [ ] ;
const ipBlockList = data . ipBlockList
? String ( data . ipBlockList ) . split ( ',' ) . map ( ( s : string ) = > s . trim ( ) ) . filter ( Boolean )
: [ ] ;
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 ` <p> ${ currentState . error } Force delete?</p> ` ,
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 ( ) } ,
] ,
} ) ;
}
}
}