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() },
],
});
}
}
}