2026-04-02 15:44:36 +00:00
import {
DeesElement ,
html ,
customElement ,
type TemplateResult ,
css ,
state ,
cssManager ,
} from '@design.estate/dees-element' ;
2026-04-08 07:45:26 +00:00
import * as appstate from '../../appstate.js' ;
import * as interfaces from '../../../dist_ts_interfaces/index.js' ;
2026-04-08 08:24:55 +00:00
import { viewHostCss } from '../shared/css.js' ;
2026-04-02 15:44:36 +00:00
import { type IStatsTile } from '@design.estate/dees-catalog' ;
declare global {
interface HTMLElementTagNameMap {
2026-04-05 00:37:37 +00:00
'ops-view-sourceprofiles' : OpsViewSourceProfiles ;
2026-04-02 15:44:36 +00:00
}
}
2026-04-05 00:37:37 +00:00
@customElement ( 'ops-view-sourceprofiles' )
export class OpsViewSourceProfiles extends DeesElement {
2026-04-02 15:44:36 +00:00
@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 ,
2026-04-08 08:24:55 +00:00
viewHostCss ,
2026-04-02 15:44:36 +00:00
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' ,
2026-04-05 00:37:37 +00:00
description : 'Reusable source profiles' ,
2026-04-02 15:44:36 +00:00
color : '#3b82f6' ,
} ,
] ;
return html `
2026-04-08 11:08:18 +00:00
<dees-heading level="3">Source Profiles</dees-heading>
2026-04-02 15:44:36 +00:00
<div class="profilesContainer">
<dees-statsgrid .tiles= ${ statsTiles } ></dees-statsgrid>
<dees-table
2026-04-05 00:37:37 +00:00
.heading1= ${ 'Source Profiles' }
.heading2= ${ 'Reusable source configurations for routes' }
2026-04-02 15:44:36 +00:00
.data= ${ profiles }
2026-04-08 07:11:21 +00:00
.showColumnFilters= ${ true }
2026-04-05 00:37:37 +00:00
.displayFunction= ${ ( profile : interfaces.data.ISourceProfile ) = > ( {
2026-04-02 15:44:36 +00:00
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 18:49:52 +00:00
actionFunc : async ( ) = > {
await this . showCreateProfileDialog ( ) ;
2026-04-02 15:44:36 +00:00
} ,
},
{
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',
2026-04-02 18:49:52 +00:00
type: ['inRow', 'contextmenu'] as any,
actionFunc: async (actionData: any) => {
2026-04-05 00:37:37 +00:00
const profile = actionData.item as interfaces.data.ISourceProfile;
2026-04-02 18:49:52 +00:00
await this.showEditProfileDialog(profile);
2026-04-02 15:44:36 +00:00
},
},
{
name: 'Delete',
iconName: 'lucide:trash2',
2026-04-02 18:49:52 +00:00
type: ['inRow', 'contextmenu'] as any,
actionFunc: async (actionData: any) => {
2026-04-05 00:37:37 +00:00
const profile = actionData.item as interfaces.data.ISourceProfile;
2026-04-02 15:44:36 +00:00
await this.deleteProfile(profile);
},
},
]}
></dees-table>
</div>
` ;
}
2026-04-02 18:49:52 +00:00
private async showCreateProfileDialog() {
2026-04-02 15:44:36 +00:00
const { DeesModal } = await import ( '@design.estate/dees-catalog' ) ;
DeesModal . createAndShow ( {
2026-04-05 00:37:37 +00:00
heading : 'Create Source Profile' ,
2026-04-02 15:44:36 +00:00
content : html `
<dees-form>
<dees-input-text .key= ${ 'name' } .label= ${ 'Name' } .required= ${ true } ></dees-input-text>
<dees-input-text .key= ${ 'description' } .label= ${ 'Description' } ></dees-input-text>
2026-04-02 17:27:05 +00:00
<dees-input-list .key= ${ 'ipAllowList' } .label= ${ 'IP Allow List' } .placeholder= ${ 'Add IP or CIDR...' } ></dees-input-list>
<dees-input-list .key= ${ 'ipBlockList' } .label= ${ 'IP Block List' } .placeholder= ${ 'Add IP or CIDR...' } ></dees-input-list>
2026-04-02 15:44:36 +00:00
<dees-input-text .key= ${ 'maxConnections' } .label= ${ 'Max Connections' } ></dees-input-text>
</dees-form>
` ,
menuOptions : [
2026-04-02 18:49:52 +00:00
{ name : 'Cancel' , action : async ( modalArg : any ) = > modalArg . destroy ( ) } ,
2026-04-02 15:44:36 +00:00
{
name : 'Create' ,
action : async ( modalArg : any ) = > {
2026-04-02 18:49:52 +00:00
const form = modalArg . shadowRoot ? . querySelector ( '.content' ) ? . querySelector ( 'dees-form' ) ;
if ( ! form ) return ;
2026-04-02 15:44:36 +00:00
const data = await form . collectFormData ( ) ;
2026-04-02 17:27:05 +00:00
const ipAllowList : string [ ] = Array . isArray ( data . ipAllowList ) ? data . ipAllowList : [ ] ;
const ipBlockList : string [ ] = Array . isArray ( data . ipBlockList ) ? data . ipBlockList : [ ] ;
2026-04-06 10:23:18 +00:00
const parsed = data . maxConnections ? parseInt ( String ( data . maxConnections ) , 10 ) : NaN ;
const maxConnections = Number . isNaN ( parsed ) ? undefined : parsed ;
2026-04-02 15:44:36 +00:00
await appstate . profilesTargetsStatePart . dispatchAction ( appstate . createProfileAction , {
name : String ( data . name ) ,
description : data.description ? String ( data . description ) : undefined ,
security : {
2026-04-02 17:27:05 +00:00
. . . ( ipAllowList . length > 0 ? { ipAllowList } : { } ) ,
. . . ( ipBlockList . length > 0 ? { ipBlockList } : { } ) ,
2026-04-02 15:44:36 +00:00
. . . ( maxConnections ? { maxConnections } : { } ) ,
} ,
} ) ;
modalArg . destroy ( ) ;
} ,
} ,
] ,
} ) ;
}
2026-04-05 00:37:37 +00:00
private async showEditProfileDialog ( profile : interfaces.data.ISourceProfile ) {
2026-04-02 15:44:36 +00:00
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 } ></dees-input-text>
<dees-input-text .key= ${ 'description' } .label= ${ 'Description' } .value= ${ profile . description || '' } ></dees-input-text>
2026-04-02 17:27:05 +00:00
<dees-input-list .key= ${ 'ipAllowList' } .label= ${ 'IP Allow List' } .placeholder= ${ 'Add IP or CIDR...' } .value= ${ profile . security ? . ipAllowList || [ ] } ></dees-input-list>
<dees-input-list .key= ${ 'ipBlockList' } .label= ${ 'IP Block List' } .placeholder= ${ 'Add IP or CIDR...' } .value= ${ profile . security ? . ipBlockList || [ ] } ></dees-input-list>
2026-04-02 15:44:36 +00:00
<dees-input-text .key= ${ 'maxConnections' } .label= ${ 'Max Connections' } .value= ${ String ( profile . security ? . maxConnections || '' ) } ></dees-input-text>
</dees-form>
` ,
menuOptions : [
2026-04-02 18:49:52 +00:00
{ name : 'Cancel' , action : async ( modalArg : any ) = > modalArg . destroy ( ) } ,
2026-04-02 15:44:36 +00:00
{
name : 'Save' ,
action : async ( modalArg : any ) = > {
2026-04-02 18:49:52 +00:00
const form = modalArg . shadowRoot ? . querySelector ( '.content' ) ? . querySelector ( 'dees-form' ) ;
if ( ! form ) return ;
2026-04-02 15:44:36 +00:00
const data = await form . collectFormData ( ) ;
2026-04-02 17:27:05 +00:00
const ipAllowList : string [ ] = Array . isArray ( data . ipAllowList ) ? data . ipAllowList : [ ] ;
const ipBlockList : string [ ] = Array . isArray ( data . ipBlockList ) ? data . ipBlockList : [ ] ;
2026-04-06 10:23:18 +00:00
const parsed = data . maxConnections ? parseInt ( String ( data . maxConnections ) , 10 ) : NaN ;
const maxConnections = Number . isNaN ( parsed ) ? undefined : parsed ;
2026-04-02 15:44:36 +00:00
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 ( ) ;
} ,
} ,
] ,
} ) ;
}
2026-04-05 00:37:37 +00:00
private async deleteProfile ( profile : interfaces.data.ISourceProfile ) {
2026-04-02 15:44:36 +00:00
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 ( ) } ,
] ,
} ) ;
}
}
}