feat(vpn,target-profiles,migrations): add startup data migrations, support scoped VPN route allow entries, and rename target profile hosts to ips
This commit is contained in:
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/dcrouter',
|
||||
version: '13.0.11',
|
||||
version: '13.1.0',
|
||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||
}
|
||||
|
||||
@@ -1015,7 +1015,7 @@ export const createVpnClientAction = vpnStatePart.createAction<{
|
||||
clientId: string;
|
||||
targetProfileIds?: string[];
|
||||
description?: string;
|
||||
forceDestinationSmartproxy?: boolean;
|
||||
|
||||
destinationAllowList?: string[];
|
||||
destinationBlockList?: string[];
|
||||
useHostIp?: boolean;
|
||||
@@ -1037,7 +1037,7 @@ export const createVpnClientAction = vpnStatePart.createAction<{
|
||||
clientId: dataArg.clientId,
|
||||
targetProfileIds: dataArg.targetProfileIds,
|
||||
description: dataArg.description,
|
||||
forceDestinationSmartproxy: dataArg.forceDestinationSmartproxy,
|
||||
|
||||
destinationAllowList: dataArg.destinationAllowList,
|
||||
destinationBlockList: dataArg.destinationBlockList,
|
||||
useHostIp: dataArg.useHostIp,
|
||||
@@ -1113,7 +1113,7 @@ export const updateVpnClientAction = vpnStatePart.createAction<{
|
||||
clientId: string;
|
||||
description?: string;
|
||||
targetProfileIds?: string[];
|
||||
forceDestinationSmartproxy?: boolean;
|
||||
|
||||
destinationAllowList?: string[];
|
||||
destinationBlockList?: string[];
|
||||
useHostIp?: boolean;
|
||||
@@ -1135,7 +1135,7 @@ export const updateVpnClientAction = vpnStatePart.createAction<{
|
||||
clientId: dataArg.clientId,
|
||||
description: dataArg.description,
|
||||
targetProfileIds: dataArg.targetProfileIds,
|
||||
forceDestinationSmartproxy: dataArg.forceDestinationSmartproxy,
|
||||
|
||||
destinationAllowList: dataArg.destinationAllowList,
|
||||
destinationBlockList: dataArg.destinationBlockList,
|
||||
useHostIp: dataArg.useHostIp,
|
||||
@@ -1223,7 +1223,7 @@ export const createTargetProfileAction = targetProfilesStatePart.createAction<{
|
||||
name: string;
|
||||
description?: string;
|
||||
domains?: string[];
|
||||
targets?: Array<{ host: string; port: number }>;
|
||||
targets?: Array<{ ip: string; port: number }>;
|
||||
routeRefs?: string[];
|
||||
}>(async (statePartArg, dataArg, actionContext): Promise<ITargetProfilesState> => {
|
||||
const context = getActionContext();
|
||||
@@ -1259,7 +1259,7 @@ export const updateTargetProfileAction = targetProfilesStatePart.createAction<{
|
||||
name?: string;
|
||||
description?: string;
|
||||
domains?: string[];
|
||||
targets?: Array<{ host: string; port: number }>;
|
||||
targets?: Array<{ ip: string; port: number }>;
|
||||
routeRefs?: string[];
|
||||
}>(async (statePartArg, dataArg, actionContext): Promise<ITargetProfilesState> => {
|
||||
const context = getActionContext();
|
||||
|
||||
@@ -30,6 +30,20 @@ export class OpsViewSecurity extends DeesElement {
|
||||
@state()
|
||||
accessor selectedTab: 'overview' | 'blocked' | 'authentication' | 'email-security' = 'overview';
|
||||
|
||||
private tabLabelMap: Record<string, string> = {
|
||||
'overview': 'Overview',
|
||||
'blocked': 'Blocked IPs',
|
||||
'authentication': 'Authentication',
|
||||
'email-security': 'Email Security',
|
||||
};
|
||||
|
||||
private labelToTab: Record<string, 'overview' | 'blocked' | 'authentication' | 'email-security'> = {
|
||||
'Overview': 'overview',
|
||||
'Blocked IPs': 'blocked',
|
||||
'Authentication': 'authentication',
|
||||
'Email Security': 'email-security',
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const subscription = appstate.statsStatePart
|
||||
@@ -40,35 +54,23 @@ export class OpsViewSecurity extends DeesElement {
|
||||
this.rxSubscriptions.push(subscription);
|
||||
}
|
||||
|
||||
async firstUpdated() {
|
||||
const toggle = this.shadowRoot!.querySelector('dees-input-multitoggle') as any;
|
||||
if (toggle) {
|
||||
const sub = toggle.changeSubject.subscribe(() => {
|
||||
const tab = this.labelToTab[toggle.selectedOption];
|
||||
if (tab) this.selectedTab = tab;
|
||||
});
|
||||
this.rxSubscriptions.push(sub);
|
||||
}
|
||||
}
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
shared.viewHostCss,
|
||||
css`
|
||||
.tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
dees-input-multitoggle {
|
||||
margin-bottom: 24px;
|
||||
border-bottom: 2px solid ${cssManager.bdTheme('#e9ecef', '#333')};
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 12px 24px;
|
||||
background: none;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
color: ${cssManager.bdTheme('#666', '#999')};
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.tab:hover {
|
||||
color: ${cssManager.bdTheme('#333', '#ccc')};
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
color: ${cssManager.bdTheme('#2196F3', '#4a90e2')};
|
||||
border-bottom-color: ${cssManager.bdTheme('#2196F3', '#4a90e2')};
|
||||
}
|
||||
|
||||
h2 {
|
||||
@@ -91,135 +93,22 @@ export class OpsViewSecurity extends DeesElement {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.securityCard.alert {
|
||||
border-color: ${cssManager.bdTheme('#f44336', '#ff6666')};
|
||||
background: ${cssManager.bdTheme('#ffebee', '#4a1f1f')};
|
||||
}
|
||||
|
||||
.securityCard.warning {
|
||||
border-color: ${cssManager.bdTheme('#ff9800', '#ffaa33')};
|
||||
background: ${cssManager.bdTheme('#fff3e0', '#4a3a1f')};
|
||||
}
|
||||
|
||||
.securityCard.success {
|
||||
border-color: ${cssManager.bdTheme('#4caf50', '#66cc66')};
|
||||
background: ${cssManager.bdTheme('#e8f5e9', '#1f3f1f')};
|
||||
}
|
||||
|
||||
.cardHeader {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.cardTitle {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: ${cssManager.bdTheme('#333', '#ccc')};
|
||||
}
|
||||
|
||||
.cardStatus {
|
||||
font-size: 14px;
|
||||
padding: 4px 12px;
|
||||
border-radius: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-critical {
|
||||
background: ${cssManager.bdTheme('#f44336', '#ff6666')};
|
||||
color: ${cssManager.bdTheme('#fff', '#fff')};
|
||||
}
|
||||
|
||||
.status-warning {
|
||||
background: ${cssManager.bdTheme('#ff9800', '#ffaa33')};
|
||||
color: ${cssManager.bdTheme('#fff', '#fff')};
|
||||
}
|
||||
|
||||
.status-good {
|
||||
background: ${cssManager.bdTheme('#4caf50', '#66cc66')};
|
||||
color: ${cssManager.bdTheme('#fff', '#fff')};
|
||||
}
|
||||
|
||||
.metricValue {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.metricLabel {
|
||||
font-size: 14px;
|
||||
color: ${cssManager.bdTheme('#666', '#999')};
|
||||
}
|
||||
|
||||
.actionButton {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.blockedIpList {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.blockedIpItem {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid ${cssManager.bdTheme('#e9ecef', '#333')};
|
||||
}
|
||||
|
||||
.blockedIpItem:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.ipAddress {
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.blockReason {
|
||||
font-size: 14px;
|
||||
color: ${cssManager.bdTheme('#666', '#999')};
|
||||
}
|
||||
|
||||
.blockTime {
|
||||
font-size: 12px;
|
||||
color: ${cssManager.bdTheme('#999', '#666')};
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public render() {
|
||||
return html`
|
||||
<dees-heading level="2">Security</dees-heading>
|
||||
|
||||
<div class="tabs">
|
||||
<button
|
||||
class="tab ${this.selectedTab === 'overview' ? 'active' : ''}"
|
||||
@click=${() => this.selectedTab = 'overview'}
|
||||
>
|
||||
Overview
|
||||
</button>
|
||||
<button
|
||||
class="tab ${this.selectedTab === 'blocked' ? 'active' : ''}"
|
||||
@click=${() => this.selectedTab = 'blocked'}
|
||||
>
|
||||
Blocked IPs
|
||||
</button>
|
||||
<button
|
||||
class="tab ${this.selectedTab === 'authentication' ? 'active' : ''}"
|
||||
@click=${() => this.selectedTab = 'authentication'}
|
||||
>
|
||||
Authentication
|
||||
</button>
|
||||
<button
|
||||
class="tab ${this.selectedTab === 'email-security' ? 'active' : ''}"
|
||||
@click=${() => this.selectedTab = 'email-security'}
|
||||
>
|
||||
Email Security
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<dees-input-multitoggle
|
||||
.type=${'single'}
|
||||
.options=${['Overview', 'Blocked IPs', 'Authentication', 'Email Security']}
|
||||
.selectedOption=${this.tabLabelMap[this.selectedTab]}
|
||||
></dees-input-multitoggle>
|
||||
|
||||
${this.renderTabContent()}
|
||||
`;
|
||||
@@ -328,32 +217,53 @@ export class OpsViewSecurity extends DeesElement {
|
||||
}
|
||||
|
||||
private renderBlockedIPs(metrics: any) {
|
||||
const blockedIPs: string[] = metrics.blockedIPs || [];
|
||||
|
||||
const tiles: IStatsTile[] = [
|
||||
{
|
||||
id: 'totalBlocked',
|
||||
title: 'Blocked IPs',
|
||||
value: blockedIPs.length,
|
||||
type: 'number',
|
||||
icon: 'lucide:ShieldBan',
|
||||
color: blockedIPs.length > 0 ? '#ef4444' : '#22c55e',
|
||||
description: 'Currently blocked addresses',
|
||||
},
|
||||
];
|
||||
|
||||
return html`
|
||||
<div class="securityCard">
|
||||
<div class="cardHeader">
|
||||
<h3 class="cardTitle">Blocked IP Addresses</h3>
|
||||
<dees-button @click=${() => this.clearBlockedIPs()}>
|
||||
Clear All
|
||||
</dees-button>
|
||||
</div>
|
||||
|
||||
<div class="blockedIpList">
|
||||
${metrics.blockedIPs && metrics.blockedIPs.length > 0 ? metrics.blockedIPs.map((ipAddress, index) => html`
|
||||
<div class="blockedIpItem">
|
||||
<div>
|
||||
<div class="ipAddress">${ipAddress}</div>
|
||||
<div class="blockReason">Suspicious activity</div>
|
||||
<div class="blockTime">Blocked</div>
|
||||
</div>
|
||||
<dees-button @click=${() => this.unblockIP(ipAddress)}>
|
||||
Unblock
|
||||
</dees-button>
|
||||
</div>
|
||||
`) : html`
|
||||
<p>No blocked IPs</p>
|
||||
`}
|
||||
</div>
|
||||
</div>
|
||||
<dees-statsgrid
|
||||
.tiles=${tiles}
|
||||
.minTileWidth=${200}
|
||||
></dees-statsgrid>
|
||||
|
||||
<dees-table
|
||||
.heading1=${'Blocked IP Addresses'}
|
||||
.heading2=${'IPs blocked due to suspicious activity'}
|
||||
.data=${blockedIPs.map((ip) => ({ ip }))}
|
||||
.displayFunction=${(item) => ({
|
||||
'IP Address': item.ip,
|
||||
'Reason': 'Suspicious activity',
|
||||
})}
|
||||
.dataActions=${[
|
||||
{
|
||||
name: 'Unblock',
|
||||
iconName: 'lucide:shield-off',
|
||||
type: ['contextmenu' as const],
|
||||
actionFunc: async (item) => {
|
||||
await this.unblockIP(item.ip);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Clear All',
|
||||
iconName: 'lucide:trash-2',
|
||||
type: ['header' as const],
|
||||
actionFunc: async () => {
|
||||
await this.clearBlockedIPs();
|
||||
},
|
||||
},
|
||||
]}
|
||||
></dees-table>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ export class OpsViewTargetProfiles extends DeesElement {
|
||||
? html`${profile.domains.map(d => html`<span class="tagBadge">${d}</span>`)}`
|
||||
: '-',
|
||||
Targets: profile.targets?.length
|
||||
? html`${profile.targets.map(t => html`<span class="tagBadge">${t.host}:${t.port}</span>`)}`
|
||||
? html`${profile.targets.map(t => html`<span class="tagBadge">${t.ip}:${t.port}</span>`)}`
|
||||
: '-',
|
||||
'Route Refs': profile.routeRefs?.length
|
||||
? html`${profile.routeRefs.map(r => html`<span class="tagBadge">${r}</span>`)}`
|
||||
@@ -175,7 +175,7 @@ export class OpsViewTargetProfiles extends DeesElement {
|
||||
<dees-input-text .key=${'name'} .label=${'Name'} .required=${true}></dees-input-text>
|
||||
<dees-input-text .key=${'description'} .label=${'Description'}></dees-input-text>
|
||||
<dees-input-list .key=${'domains'} .label=${'Domains'} .placeholder=${'e.g. *.example.com'} .allowFreeform=${true}></dees-input-list>
|
||||
<dees-input-list .key=${'targets'} .label=${'Targets (host:port)'} .placeholder=${'e.g. 10.0.0.1:443'} .allowFreeform=${true}></dees-input-list>
|
||||
<dees-input-list .key=${'targets'} .label=${'Targets (ip:port)'} .placeholder=${'e.g. 10.0.0.1:443'} .allowFreeform=${true}></dees-input-list>
|
||||
<dees-input-list .key=${'routeRefs'} .label=${'Route Refs'} .placeholder=${'Type to search routes...'} .candidates=${routeCandidates} .allowFreeform=${true}></dees-input-list>
|
||||
</dees-form>
|
||||
`,
|
||||
@@ -197,11 +197,11 @@ export class OpsViewTargetProfiles extends DeesElement {
|
||||
const lastColon = s.lastIndexOf(':');
|
||||
if (lastColon === -1) return null;
|
||||
return {
|
||||
host: s.substring(0, lastColon),
|
||||
ip: s.substring(0, lastColon),
|
||||
port: parseInt(s.substring(lastColon + 1), 10),
|
||||
};
|
||||
})
|
||||
.filter((t): t is { host: string; port: number } => t !== null && !isNaN(t.port));
|
||||
.filter((t): t is { ip: string; port: number } => t !== null && !isNaN(t.port));
|
||||
const routeRefs: string[] = Array.isArray(data.routeRefs) ? data.routeRefs : [];
|
||||
|
||||
await appstate.targetProfilesStatePart.dispatchAction(appstate.createTargetProfileAction, {
|
||||
@@ -220,7 +220,7 @@ export class OpsViewTargetProfiles extends DeesElement {
|
||||
|
||||
private async showEditProfileDialog(profile: interfaces.data.ITargetProfile) {
|
||||
const currentDomains = profile.domains || [];
|
||||
const currentTargets = profile.targets?.map(t => `${t.host}:${t.port}`) || [];
|
||||
const currentTargets = profile.targets?.map(t => `${t.ip}:${t.port}`) || [];
|
||||
const currentRouteRefs = profile.routeRefs || [];
|
||||
|
||||
const { DeesModal } = await import('@design.estate/dees-catalog');
|
||||
@@ -234,7 +234,7 @@ export class OpsViewTargetProfiles extends DeesElement {
|
||||
<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>
|
||||
<dees-input-list .key=${'domains'} .label=${'Domains'} .placeholder=${'e.g. *.example.com'} .allowFreeform=${true} .value=${currentDomains}></dees-input-list>
|
||||
<dees-input-list .key=${'targets'} .label=${'Targets (host:port)'} .placeholder=${'e.g. 10.0.0.1:443'} .allowFreeform=${true} .value=${currentTargets}></dees-input-list>
|
||||
<dees-input-list .key=${'targets'} .label=${'Targets (ip:port)'} .placeholder=${'e.g. 10.0.0.1:443'} .allowFreeform=${true} .value=${currentTargets}></dees-input-list>
|
||||
<dees-input-list .key=${'routeRefs'} .label=${'Route Refs'} .placeholder=${'Type to search routes...'} .candidates=${routeCandidates} .allowFreeform=${true} .value=${currentRouteRefs}></dees-input-list>
|
||||
</dees-form>
|
||||
`,
|
||||
@@ -255,11 +255,11 @@ export class OpsViewTargetProfiles extends DeesElement {
|
||||
const lastColon = s.lastIndexOf(':');
|
||||
if (lastColon === -1) return null;
|
||||
return {
|
||||
host: s.substring(0, lastColon),
|
||||
ip: s.substring(0, lastColon),
|
||||
port: parseInt(s.substring(lastColon + 1), 10),
|
||||
};
|
||||
})
|
||||
.filter((t): t is { host: string; port: number } => t !== null && !isNaN(t.port));
|
||||
.filter((t): t is { ip: string; port: number } => t !== null && !isNaN(t.port));
|
||||
const routeRefs: string[] = Array.isArray(data.routeRefs) ? data.routeRefs : [];
|
||||
|
||||
await appstate.targetProfilesStatePart.dispatchAction(appstate.updateTargetProfileAction, {
|
||||
@@ -327,7 +327,7 @@ export class OpsViewTargetProfiles extends DeesElement {
|
||||
<div style="font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};">Targets</div>
|
||||
<div style="font-size: 14px; margin-top: 4px;">
|
||||
${profile.targets?.length
|
||||
? profile.targets.map(t => html`<span class="tagBadge">${t.host}:${t.port}</span>`)
|
||||
? profile.targets.map(t => html`<span class="tagBadge">${t.ip}:${t.port}</span>`)
|
||||
: '-'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -28,7 +28,7 @@ function setupFormVisibility(formEl: any) {
|
||||
const staticIpGroup = contentEl.querySelector('.staticIpGroup') as HTMLElement;
|
||||
const vlanIdGroup = contentEl.querySelector('.vlanIdGroup') as HTMLElement;
|
||||
const aclGroup = contentEl.querySelector('.aclGroup') as HTMLElement;
|
||||
if (hostIpGroup) hostIpGroup.style.display = data.forceDestinationSmartproxy ? 'none' : show;
|
||||
if (hostIpGroup) hostIpGroup.style.display = show; // always show (forceTarget is always on)
|
||||
if (hostIpDetails) hostIpDetails.style.display = data.useHostIp ? show : 'none';
|
||||
if (staticIpGroup) staticIpGroup.style.display = data.useDhcp ? 'none' : show;
|
||||
if (vlanIdGroup) vlanIdGroup.style.display = data.forceVlan ? show : 'none';
|
||||
@@ -317,9 +317,7 @@ export class OpsViewVpn extends DeesElement {
|
||||
statusHtml = html`<span class="statusBadge enabled" style="background: ${cssManager.bdTheme('#eff6ff', '#172554')}; color: ${cssManager.bdTheme('#1e40af', '#60a5fa')};">offline</span>`;
|
||||
}
|
||||
let routingHtml;
|
||||
if (client.forceDestinationSmartproxy !== false) {
|
||||
routingHtml = html`<span class="statusBadge enabled">SmartProxy</span>`;
|
||||
} else if (client.useHostIp) {
|
||||
if (client.useHostIp) {
|
||||
routingHtml = html`<span class="statusBadge" style="background: ${cssManager.bdTheme('#f3e8ff', '#3b0764')}; color: ${cssManager.bdTheme('#7c3aed', '#c084fc')};">Host IP</span>`;
|
||||
} else {
|
||||
routingHtml = html`<span class="statusBadge" style="background: ${cssManager.bdTheme('#eff6ff', '#172554')}; color: ${cssManager.bdTheme('#1e40af', '#60a5fa')};">Direct</span>`;
|
||||
@@ -355,8 +353,7 @@ export class OpsViewVpn extends DeesElement {
|
||||
<dees-input-text .key=${'clientId'} .label=${'Client ID'} .required=${true}></dees-input-text>
|
||||
<dees-input-text .key=${'description'} .label=${'Description'}></dees-input-text>
|
||||
<dees-input-list .key=${'targetProfileNames'} .label=${'Target Profiles'} .placeholder=${'Type to search profiles...'} .candidates=${profileCandidates} .allowFreeform=${false}></dees-input-list>
|
||||
<dees-input-checkbox .key=${'forceDestinationSmartproxy'} .label=${'Force traffic through SmartProxy'} .value=${true}></dees-input-checkbox>
|
||||
<div class="hostIpGroup" style="display: none; flex-direction: column; gap: 16px;">
|
||||
<div class="hostIpGroup" style="display: flex; flex-direction: column; gap: 16px;">
|
||||
<dees-input-checkbox .key=${'useHostIp'} .label=${'Get Host IP'} .value=${false}></dees-input-checkbox>
|
||||
<div class="hostIpDetails" style="display: none; flex-direction: column; gap: 16px;">
|
||||
<dees-input-checkbox .key=${'useDhcp'} .label=${'Get IP through DHCP'} .value=${false}></dees-input-checkbox>
|
||||
@@ -395,8 +392,7 @@ export class OpsViewVpn extends DeesElement {
|
||||
);
|
||||
|
||||
// Apply conditional logic based on checkbox states
|
||||
const forceSmartproxy = data.forceDestinationSmartproxy ?? true;
|
||||
const useHostIp = !forceSmartproxy && (data.useHostIp ?? false);
|
||||
const useHostIp = data.useHostIp ?? false;
|
||||
const useDhcp = useHostIp && (data.useDhcp ?? false);
|
||||
const staticIp = useHostIp && !useDhcp && data.staticIp ? data.staticIp : undefined;
|
||||
const forceVlan = useHostIp && (data.forceVlan ?? false);
|
||||
@@ -414,7 +410,7 @@ export class OpsViewVpn extends DeesElement {
|
||||
clientId: data.clientId,
|
||||
description: data.description || undefined,
|
||||
targetProfileIds,
|
||||
forceDestinationSmartproxy: forceSmartproxy,
|
||||
|
||||
useHostIp: useHostIp || undefined,
|
||||
useDhcp: useDhcp || undefined,
|
||||
staticIp,
|
||||
@@ -487,7 +483,7 @@ export class OpsViewVpn extends DeesElement {
|
||||
` : ''}
|
||||
<div class="infoItem"><span class="infoLabel">Description</span><span class="infoValue">${client.description || '-'}</span></div>
|
||||
<div class="infoItem"><span class="infoLabel">Target Profiles</span><span class="infoValue">${this.resolveProfileIdsToNames(client.targetProfileIds)?.join(', ') || '-'}</span></div>
|
||||
<div class="infoItem"><span class="infoLabel">Routing</span><span class="infoValue">${client.forceDestinationSmartproxy !== false ? 'SmartProxy' : client.useHostIp ? 'Host IP' : 'Direct'}</span></div>
|
||||
<div class="infoItem"><span class="infoLabel">Routing</span><span class="infoValue">${client.useHostIp ? 'Host IP' : 'SmartProxy'}</span></div>
|
||||
${client.useHostIp ? html`
|
||||
<div class="infoItem"><span class="infoLabel">Host IP</span><span class="infoValue">${client.useDhcp ? 'DHCP' : client.staticIp ? `Static: ${client.staticIp}` : 'Not configured'}</span></div>
|
||||
<div class="infoItem"><span class="infoLabel">VLAN</span><span class="infoValue">${client.forceVlan && client.vlanId != null ? `VLAN ${client.vlanId}` : 'No VLAN'}</span></div>
|
||||
@@ -652,7 +648,6 @@ export class OpsViewVpn extends DeesElement {
|
||||
const currentDescription = client.description ?? '';
|
||||
const currentTargetProfileNames = this.resolveProfileIdsToNames(client.targetProfileIds) || [];
|
||||
const profileCandidates = this.getTargetProfileCandidates();
|
||||
const currentForceSmartproxy = client.forceDestinationSmartproxy ?? true;
|
||||
const currentUseHostIp = client.useHostIp ?? false;
|
||||
const currentUseDhcp = client.useDhcp ?? false;
|
||||
const currentStaticIp = client.staticIp ?? '';
|
||||
@@ -668,8 +663,7 @@ export class OpsViewVpn extends DeesElement {
|
||||
<dees-form>
|
||||
<dees-input-text .key=${'description'} .label=${'Description'} .value=${currentDescription}></dees-input-text>
|
||||
<dees-input-list .key=${'targetProfileNames'} .label=${'Target Profiles'} .placeholder=${'Type to search profiles...'} .candidates=${profileCandidates} .allowFreeform=${false} .value=${currentTargetProfileNames}></dees-input-list>
|
||||
<dees-input-checkbox .key=${'forceDestinationSmartproxy'} .label=${'Force traffic through SmartProxy'} .value=${currentForceSmartproxy}></dees-input-checkbox>
|
||||
<div class="hostIpGroup" style="display: ${currentForceSmartproxy ? 'none' : 'flex'}; flex-direction: column; gap: 16px;">
|
||||
<div class="hostIpGroup" style="display: flex; flex-direction: column; gap: 16px;">
|
||||
<dees-input-checkbox .key=${'useHostIp'} .label=${'Get Host IP'} .value=${currentUseHostIp}></dees-input-checkbox>
|
||||
<div class="hostIpDetails" style="display: ${currentUseHostIp ? 'flex' : 'none'}; flex-direction: column; gap: 16px;">
|
||||
<dees-input-checkbox .key=${'useDhcp'} .label=${'Get IP through DHCP'} .value=${currentUseDhcp}></dees-input-checkbox>
|
||||
@@ -703,8 +697,7 @@ export class OpsViewVpn extends DeesElement {
|
||||
);
|
||||
|
||||
// Apply conditional logic based on checkbox states
|
||||
const forceSmartproxy = data.forceDestinationSmartproxy ?? true;
|
||||
const useHostIp = !forceSmartproxy && (data.useHostIp ?? false);
|
||||
const useHostIp = data.useHostIp ?? false;
|
||||
const useDhcp = useHostIp && (data.useDhcp ?? false);
|
||||
const staticIp = useHostIp && !useDhcp && data.staticIp ? data.staticIp : undefined;
|
||||
const forceVlan = useHostIp && (data.forceVlan ?? false);
|
||||
@@ -722,7 +715,7 @@ export class OpsViewVpn extends DeesElement {
|
||||
clientId: client.clientId,
|
||||
description: data.description || undefined,
|
||||
targetProfileIds,
|
||||
forceDestinationSmartproxy: forceSmartproxy,
|
||||
|
||||
useHostIp: useHostIp || undefined,
|
||||
useDhcp: useDhcp || undefined,
|
||||
staticIp,
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"order": 3
|
||||
"order": 4
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user