Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ type CreateEditIpsecTunnelPayload = {
rekeytime: string
}
ipcomp: string
closeaction?: string
dpdaction: 'restart' | 'none'
remote_subnet: string[]
local_subnet: string[]
Expand Down Expand Up @@ -104,6 +105,7 @@ const presharedKeyMode = ref<'generate' | 'import'>('generate')
const presharedKey = ref('')
const dpd = ref(false)
const enableCompression = ref(false)
const closeAction = ref('none')

// Step 3 fields
const ikeVersion = ref('')
Expand Down Expand Up @@ -144,6 +146,11 @@ const presharedKeyOptions = [
}
]
const wanOptions = ref<NeComboboxOption[]>([])
const closeActionOptions: NeComboboxOption[] = [
{ id: 'none', label: 'none' },
{ id: 'trap', label: 'trap' },
{ id: 'start', label: 'start' }
]
const encryptionOptions = ref<NeComboboxOption[]>([])
const integrityOptions = ref<NeComboboxOption[]>([])
const diffieHellmanOptions = ref<NeComboboxOption[]>([])
Expand Down Expand Up @@ -247,6 +254,7 @@ async function resetForm() {
remoteNetworks.value = tunnelData?.remote_subnet ?? ['']
dpd.value = tunnelData ? tunnelData.dpdaction == 'restart' : false
enableCompression.value = tunnelData ? tunnelData.ipcomp === 'true' : false
closeAction.value = tunnelData?.closeaction ?? 'none'
ikeVersion.value = tunnelData?.keyexchange ?? ikeVersionOptions[0].id
ikeEncryptionAlgorithm.value = tunnelData?.ike.encryption_algorithm ?? 'aes256'
ikeIntegrityAlgorithm.value = tunnelData?.ike.hash_algorithm ?? 'sha256'
Expand Down Expand Up @@ -416,6 +424,7 @@ async function createOrEditTunnel() {
rekeytime: espKeyLifetime.value
},
ipcomp: enableCompression.value ? 'true' : 'false',
closeaction: closeAction.value,
enabled: enabled.value ? '1' : '0',
dpdaction: dpd.value ? 'restart' : 'none',
keyexchange: ikeVersion.value,
Expand Down Expand Up @@ -627,6 +636,25 @@ watch(
"
/>
</div>
<NeCombobox
v-model="closeAction"
:label="t('standalone.ipsec_tunnel.close_action')"
:options="closeActionOptions"
:no-options-label="t('ne_combobox.no_options_label')"
:no-results-label="t('ne_combobox.no_results')"
:limited-options-label="t('ne_combobox.limited_options_label')"
:selected-label="t('ne_combobox.selected')"
:user-input-label="t('ne_combobox.user_input_label')"
:optional-label="t('common.optional')"
>
<template #tooltip>
<NeTooltip>
<template #content>
{{ t('standalone.ipsec_tunnel.close_action_tooltip') }}
</template>
</NeTooltip>
</template>
</NeCombobox>
</template>
<template v-else>
<NeHeading tag="h6" class="mb-1.5">{{
Expand Down
229 changes: 181 additions & 48 deletions src/components/standalone/ipsec_tunnel/TunnelTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,37 @@
-->

<script setup lang="ts">
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import NeTable from '../NeTable.vue'
import { NeDropdown } from '@nethesis/vue-components'
import { NeDropdown, NeModal, NeLink } from '@nethesis/vue-components'
import { NeButton } from '@nethesis/vue-components'
import type { IpsecTunnel } from '@/views/standalone/vpn/IPsecTunnelView.vue'
import { faCircleCheck, faCircleXmark, faTrash } from '@fortawesome/free-solid-svg-icons'
import {
faCircleCheck,
faCircleXmark,
faTrash,
faMagnifyingGlassPlus
} from '@fortawesome/free-solid-svg-icons'

const { t } = useI18n()

const showDetailsModal = ref(false)
const showRawOutput = ref(false)
const selectedTunnel = ref<IpsecTunnel | null>(null)

function openDetailsModal(tunnel: IpsecTunnel) {
selectedTunnel.value = tunnel
showRawOutput.value = false
showDetailsModal.value = true
}

function closeDetailsModal() {
showDetailsModal.value = false
showRawOutput.value = false
selectedTunnel.value = null
}

defineProps<{
tunnels: IpsecTunnel[]
}>()
Expand Down Expand Up @@ -47,39 +69,53 @@ const tableHeaders = [
]

function getDropdownItems(item: IpsecTunnel) {
return [
{
id: 'enable_disable',
label:
item.enabled === '1'
? t('standalone.ipsec_tunnel.disable')
: t('standalone.ipsec_tunnel.enable'),
icon: item.enabled === '1' ? faCircleXmark : faCircleCheck,
action: () => {
emit('tunnel-toggle-enable', item)
}
},
{
id: 'delete',
label: t('common.delete'),
icon: faTrash,
danger: true,
action: () => {
emit('tunnel-delete', item)
}
const items = []

items.push({
id: 'enable_disable',
label:
item.enabled === '1'
? t('standalone.ipsec_tunnel.disable')
: t('standalone.ipsec_tunnel.enable'),
icon: item.enabled === '1' ? faCircleXmark : faCircleCheck,
action: () => {
emit('tunnel-toggle-enable', item)
}
})

items.push({
id: 'delete',
label: t('common.delete'),
icon: faTrash,
danger: true,
action: () => {
emit('tunnel-delete', item)
}
]
})

return items
}

function getCellClasses(item: IpsecTunnel) {
return item.enabled === '0' ? ['text-gray-400', 'dark:text-gray-700'] : []
return item.enabled === '0' ? ['text-green-700', 'dark:text-green-400'] : []
}
</script>

<template>
<NeTable :data="tunnels" :headers="tableHeaders">
<template #name="{ item }: { item: IpsecTunnel }">
<p :class="[...getCellClasses(item)]">{{ item.name }}</p>
<div class="flex items-center gap-2">
<NeButton
v-if="item.enabled === '1'"
kind="tertiary"
size="sm"
@click="openDetailsModal(item)"
>
<font-awesome-icon :icon="faMagnifyingGlassPlus" class="h-4 w-4" aria-hidden="true" />
</NeButton>
<span v-else class="w-8"></span>
<p :class="[...getCellClasses(item)]">{{ item.name }}</p>
</div>
</template>
<template #local_networks="{ item }: { item: IpsecTunnel }">
<template v-if="item.local.length > 0">
Expand Down Expand Up @@ -109,7 +145,7 @@ function getCellClasses(item: IpsecTunnel) {
<div :class="['flex', 'flex-row', 'items-center', ...getCellClasses(item)]">
<font-awesome-icon
:icon="['fas', item.enabled === '1' ? 'circle-check' : 'circle-xmark']"
class="mr-2 h-5 w-5"
class="mr-2 h-5 w-5 text-green-700 dark:text-green-400"
aria-hidden="true"
/>
<p>
Expand All @@ -122,28 +158,46 @@ function getCellClasses(item: IpsecTunnel) {
</div>
</template>
<template #connection="{ item }: { item: IpsecTunnel }">
<div :class="['flex', 'flex-row', 'items-center', ...getCellClasses(item)]">
<font-awesome-icon
:icon="['fas', item.connected ? 'circle-check' : 'circle-xmark']"
:class="[
'mr-2',
'h-5',
'w-5',
item.enabled === '0'
? 'text-gray-400 dark:text-gray-700'
: item.connected
? 'text-green-500'
: 'text-rose-500'
]"
aria-hidden="true"
/>
<p>
{{
item.connected
? t('standalone.ipsec_tunnel.connected')
: t('standalone.ipsec_tunnel.not_connected')
}}
</p>
<div :class="['flex', 'flex-col', ...getCellClasses(item)]">
<div class="flex items-center">
<font-awesome-icon
:icon="[
'fas',
item.connected === 'yes'
? 'circle-check'
: item.connected === 'warning'
? 'triangle-exclamation'
: 'circle-xmark'
]"
:class="[
'mr-2',
'h-5',
'w-5',
item.enabled === '0'
? 'text-gray-400 dark:text-gray-700'
: item.connected === 'yes'
? 'text-green-700 dark:text-green-500'
: item.connected === 'warning'
? 'text-amber-500'
: 'text-red-600 dark:text-red-400'
]"
aria-hidden="true"
/>
<div>
<p>
{{
item.connected === 'yes'
? t('standalone.ipsec_tunnel.connected')
: item.connected === 'warning'
? t('standalone.ipsec_tunnel.warning')
: t('standalone.ipsec_tunnel.not_connected')
}}
</p>
<NeLink v-if="item.connected === 'warning'" @click="openDetailsModal(item)">
{{ t('standalone.ipsec_tunnel.more_info') }}
</NeLink>
</div>
</div>
</div>
</template>
<template #menu="{ item }: { item: IpsecTunnel }">
Expand All @@ -162,4 +216,83 @@ function getCellClasses(item: IpsecTunnel) {
</div>
</template>
</NeTable>

<!-- Details Modal -->
<NeModal
:visible="showDetailsModal"
kind="info"
size="xl"
:title="t('standalone.ipsec_tunnel.tunnel_details')"
:primary-label="t('common.close')"
:close-aria-label="t('common.close')"
@primary-click="closeDetailsModal"
@close="closeDetailsModal"
>
<template v-if="selectedTunnel">
<p class="mb-4">
{{ selectedTunnel.name }} {{ t('standalone.ipsec_tunnel.child_tunnels') }}:
</p>

<!-- Children list -->
<div class="mb-4">
<ul class="space-y-1">
<li
v-for="child in selectedTunnel.children"
:key="child.name"
class="flex items-center text-sm"
>
<font-awesome-icon
:icon="['fas', child.installed ? 'circle-check' : 'circle-xmark']"
:class="[
'mr-2',
'h-4',
'w-4',
child.installed
? 'text-green-700 dark:text-green-400'
: 'text-red-600 dark:text-red-400'
]"
aria-hidden="true"
/>
<span>{{ child.name }}</span>
<span class="ml-2 text-gray-500 dark:text-gray-400">
{{
child.installed
? t('standalone.ipsec_tunnel.installed')
: t('standalone.ipsec_tunnel.not_installed')
}},
</span>
<template v-if="child.local_subnet.length || child.remote_subnet.length">
<span class="ml-3 text-sm text-gray-500 dark:text-gray-400">
<template v-if="child.local_subnet.length">{{
child.local_subnet.join(', ')
}}</template>
<template v-if="child.local_subnet.length && child.remote_subnet.length">
</template>
<template v-if="child.remote_subnet.length">{{
child.remote_subnet.join(', ')
}}</template>
</span>
</template>
</li>
</ul>
</div>

<!-- Raw output toggle -->
<div>
<NeLink @click="showRawOutput = !showRawOutput">
{{
showRawOutput
? t('standalone.ipsec_tunnel.hide_full_status')
: t('standalone.ipsec_tunnel.show_full_status')
}}
</NeLink>
<pre
v-if="showRawOutput"
class="mt-2 max-h-96 overflow-auto rounded bg-gray-100 p-3 text-sm whitespace-pre-wrap dark:bg-gray-800"
>{{ selectedTunnel.raw_output }}</pre
>
</div>
</template>
</NeModal>
</template>
Loading
Loading