Compare commits

..

2 Commits

Author SHA1 Message Date
f685ce9928 v11.18.0
Some checks failed
Docker (tags) / security (push) Failing after 2s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-03-30 17:08:57 +00:00
699aa8a8e1 feat(vpn-ui): add format selection for VPN client config exports 2026-03-30 17:08:57 +00:00
5 changed files with 73 additions and 31 deletions

View File

@@ -1,5 +1,11 @@
# Changelog # Changelog
## 2026-03-30 - 11.18.0 - feat(vpn-ui)
add format selection for VPN client config exports
- Show an export modal that lets operators choose between WireGuard (.conf) and SmartVPN (.json) client configs.
- Update VPN client row actions to read the selected item from actionData for toggle, export, rotate keys, and delete handlers.
## 2026-03-30 - 11.17.0 - feat(vpn) ## 2026-03-30 - 11.17.0 - feat(vpn)
expand VPN operations view with client management and config export actions expand VPN operations view with client management and config export actions

View File

@@ -1,7 +1,7 @@
{ {
"name": "@serve.zone/dcrouter", "name": "@serve.zone/dcrouter",
"private": false, "private": false,
"version": "11.17.0", "version": "11.18.0",
"description": "A multifaceted routing service handling mail and SMS delivery functions.", "description": "A multifaceted routing service handling mail and SMS delivery functions.",
"type": "module", "type": "module",
"exports": { "exports": {

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@serve.zone/dcrouter', name: '@serve.zone/dcrouter',
version: '11.17.0', version: '11.18.0',
description: 'A multifaceted routing service handling mail and SMS delivery functions.' description: 'A multifaceted routing service handling mail and SMS delivery functions.'
} }

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@serve.zone/dcrouter', name: '@serve.zone/dcrouter',
version: '11.17.0', version: '11.18.0',
description: 'A multifaceted routing service handling mail and SMS delivery functions.' description: 'A multifaceted routing service handling mail and SMS delivery functions.'
} }

View File

@@ -308,7 +308,8 @@ export class OpsViewVpn extends DeesElement {
name: 'Toggle', name: 'Toggle',
iconName: 'lucide:power', iconName: 'lucide:power',
type: ['contextmenu', 'inRow'], type: ['contextmenu', 'inRow'],
actionFunc: async (client: interfaces.data.IVpnClient) => { actionFunc: async (actionData: any) => {
const client = actionData.item as interfaces.data.IVpnClient;
await appstate.vpnStatePart.dispatchAction(appstate.toggleVpnClientAction, { await appstate.vpnStatePart.dispatchAction(appstate.toggleVpnClientAction, {
clientId: client.clientId, clientId: client.clientId,
enabled: !client.enabled, enabled: !client.enabled,
@@ -319,39 +320,73 @@ export class OpsViewVpn extends DeesElement {
name: 'Export Config', name: 'Export Config',
iconName: 'lucide:download', iconName: 'lucide:download',
type: ['contextmenu', 'inRow'], type: ['contextmenu', 'inRow'],
actionFunc: async (client: interfaces.data.IVpnClient) => { actionFunc: async (actionData: any) => {
const { DeesToast } = await import('@design.estate/dees-catalog'); const client = actionData.item as interfaces.data.IVpnClient;
try { const { DeesModal, DeesToast } = await import('@design.estate/dees-catalog');
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_ExportVpnClientConfig const exportConfig = async (format: 'wireguard' | 'smartvpn') => {
>('/typedrequest', 'exportVpnClientConfig'); try {
const response = await request.fire({ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
identity: appstate.loginStatePart.getState()!.identity!, interfaces.requests.IReq_ExportVpnClientConfig
clientId: client.clientId, >('/typedrequest', 'exportVpnClientConfig');
format: 'wireguard', const response = await request.fire({
}); identity: appstate.loginStatePart.getState()!.identity!,
if (response.success && response.config) { clientId: client.clientId,
const blob = new Blob([response.config], { type: 'text/plain' }); format,
const url = URL.createObjectURL(blob); });
const a = document.createElement('a'); if (response.success && response.config) {
a.href = url; const ext = format === 'wireguard' ? 'conf' : 'json';
a.download = `${client.clientId}.conf`; const blob = new Blob([response.config], { type: 'text/plain' });
a.click(); const url = URL.createObjectURL(blob);
URL.revokeObjectURL(url); const a = document.createElement('a');
DeesToast.createAndShow({ message: 'Config downloaded', type: 'success', duration: 3000 }); a.href = url;
} else { a.download = `${client.clientId}.${ext}`;
DeesToast.createAndShow({ message: response.message || 'Export failed', type: 'error', duration: 5000 }); a.click();
URL.revokeObjectURL(url);
DeesToast.createAndShow({ message: `${format} config downloaded`, type: 'success', duration: 3000 });
} else {
DeesToast.createAndShow({ message: response.message || 'Export failed', type: 'error', duration: 5000 });
}
} catch (err: any) {
DeesToast.createAndShow({ message: err.message || 'Export failed', type: 'error', duration: 5000 });
} }
} catch (err: any) { };
DeesToast.createAndShow({ message: err.message || 'Export failed', type: 'error', duration: 5000 });
} DeesModal.createAndShow({
heading: `Export Config: ${client.clientId}`,
content: html`<p>Choose a config format to download.</p>`,
menuOptions: [
{
name: 'WireGuard (.conf)',
iconName: 'lucide:shield',
action: async (modalArg: any) => {
await modalArg.destroy();
await exportConfig('wireguard');
},
},
{
name: 'SmartVPN (.json)',
iconName: 'lucide:braces',
action: async (modalArg: any) => {
await modalArg.destroy();
await exportConfig('smartvpn');
},
},
{
name: 'Cancel',
iconName: 'lucide:x',
action: async (modalArg: any) => await modalArg.destroy(),
},
],
});
}, },
}, },
{ {
name: 'Rotate Keys', name: 'Rotate Keys',
iconName: 'lucide:rotate-cw', iconName: 'lucide:rotate-cw',
type: ['contextmenu'], type: ['contextmenu'],
actionFunc: async (client: interfaces.data.IVpnClient) => { actionFunc: async (actionData: any) => {
const client = actionData.item as interfaces.data.IVpnClient;
const { DeesModal, DeesToast } = await import('@design.estate/dees-catalog'); const { DeesModal, DeesToast } = await import('@design.estate/dees-catalog');
DeesModal.createAndShow({ DeesModal.createAndShow({
heading: 'Rotate Client Keys', heading: 'Rotate Client Keys',
@@ -390,7 +425,8 @@ export class OpsViewVpn extends DeesElement {
name: 'Delete', name: 'Delete',
iconName: 'lucide:trash2', iconName: 'lucide:trash2',
type: ['contextmenu'], type: ['contextmenu'],
actionFunc: async (client: interfaces.data.IVpnClient) => { actionFunc: async (actionData: any) => {
const client = actionData.item as interfaces.data.IVpnClient;
const { DeesModal } = await import('@design.estate/dees-catalog'); const { DeesModal } = await import('@design.estate/dees-catalog');
DeesModal.createAndShow({ DeesModal.createAndShow({
heading: 'Delete VPN Client', heading: 'Delete VPN Client',