From d4f827bf65fb466169ef9ddbe35c5ccc0d436559 Mon Sep 17 00:00:00 2001 From: Kevin Cela Date: Mon, 2 Oct 2023 15:49:08 +0000 Subject: [PATCH 01/13] feat: add port forward page - add port forward entry in sidemenu and router - add port forward page - add port forward table list - add create/edit port forward drawer - add fa icons - add related translations --- public/i18n/en/translation.json | 44 ++- src/components/standalone/SideMenu.vue | 4 + .../CreateOrEditPortForwardDrawer.vue | 341 ++++++++++++++++++ .../firewall/DeletePortForwardModal.vue | 61 ++++ .../standalone/firewall/PortForwardTable.vue | 166 +++++++++ src/lib/fontawesome.ts | 4 + src/router/index.ts | 5 + src/views/standalone/firewall/PortForward.vue | 230 ++++++++++++ 8 files changed, 854 insertions(+), 1 deletion(-) create mode 100644 src/components/standalone/firewall/CreateOrEditPortForwardDrawer.vue create mode 100644 src/components/standalone/firewall/DeletePortForwardModal.vue create mode 100644 src/components/standalone/firewall/PortForwardTable.vue create mode 100644 src/views/standalone/firewall/PortForward.vue diff --git a/public/i18n/en/translation.json b/public/i18n/en/translation.json index 4eef096b5..32b8256f0 100644 --- a/public/i18n/en/translation.json +++ b/public/i18n/en/translation.json @@ -54,7 +54,13 @@ "cannot_retrieve_wan_list": "Cannot retrieve WAN list", "cannot_retrieve_wan_traffic": "Cannot retrieve WAN traffic", "register_unit_error": "Cannot register unit", - "cancel_registration_error": "Cannot cancel registration" + "cancel_registration_error": "Cannot cancel registration", + "cannot_delete_port_forward": "Cannot delete port forward", + "cannot_create_port_forward": "Cannot create port forward", + "cannot_edit_port_forward": "Cannot edit port forward", + "cannot_duplicate_port_forward": "Cannot duplicate port forward", + "cannot_enable_port_forward": "Cannot enable port forward", + "cannot_disable_port_forward": "Cannot disable port forward" }, "ne_text_input": { "show_password": "Show password", @@ -452,6 +458,42 @@ "enable_logging": "Enable logging on this zone", "delete_zone": "Delete \"{0}\"?" }, + "port_forward": { + "title": "Port forward", + "description": "Nunc quam dui, hendrerit id euismod nec, cursus nec magna. Fusce porta finibus ipsum sit amet faucibus.", + "add_port_forward": "Add port forward", + "name": "Name", + "source_port": "Source port", + "destination_port": "Destination port", + "protocols": "Protocols", + "wan_ip": "WAN IP", + "restrict_access_to": "Restrict Access to", + "status": "Status", + "enabled": "Enabled", + "disabled": "Disabled", + "filter": "Filter", + "enable": "Enable", + "disable": "Disable", + "duplicate": "Duplicate", + "delete_port_forward": "Delete port forward", + "delete_port_forward_message": "You are about to delete port forward {name}", + "destination": "Destination", + "edit_port_forward": "Edit port forward", + "source_zone": "Source zone", + "destination_address": "Destination address", + "destination_zone": "Destination zone", + "advanced_settings": "Advanced settings", + "add_ip_address": "Add IP address", + "log": "Log", + "hairpin_nat": "Hairpin NAT", + "hairpin_nat_zones": "Hairpin NAT enabled on following zones", + "choose_zone": "Choose zone", + "choose_protocol": "Choose protocol", + "any_zone": "Any zone", + "any": "Any", + "destination_address_tooltip": "Nunc quam dui, hendrerit id euismod nec, cursus nec magna.", + "restrict_access_to_tooltip": "Nunc quam dui, hendrerit id euismod nec, cursus nec magna." + }, "firewall": { "title": "Firewall" }, diff --git a/src/components/standalone/SideMenu.vue b/src/components/standalone/SideMenu.vue index 50fc18916..9cac3c929 100644 --- a/src/components/standalone/SideMenu.vue +++ b/src/components/standalone/SideMenu.vue @@ -64,6 +64,10 @@ const navigation: Ref = ref([ to: 'firewall', icon: 'block-brick-fire', children: [ + { + name: t('standalone.port_forward.title'), + to: 'firewall/port-forward' + }, { name: t('standalone.zones_and_policies.title'), to: 'firewall/zones-and-policies' diff --git a/src/components/standalone/firewall/CreateOrEditPortForwardDrawer.vue b/src/components/standalone/firewall/CreateOrEditPortForwardDrawer.vue new file mode 100644 index 000000000..f61744c6a --- /dev/null +++ b/src/components/standalone/firewall/CreateOrEditPortForwardDrawer.vue @@ -0,0 +1,341 @@ + + + diff --git a/src/components/standalone/firewall/DeletePortForwardModal.vue b/src/components/standalone/firewall/DeletePortForwardModal.vue new file mode 100644 index 000000000..532d98240 --- /dev/null +++ b/src/components/standalone/firewall/DeletePortForwardModal.vue @@ -0,0 +1,61 @@ + + + diff --git a/src/components/standalone/firewall/PortForwardTable.vue b/src/components/standalone/firewall/PortForwardTable.vue new file mode 100644 index 000000000..110462458 --- /dev/null +++ b/src/components/standalone/firewall/PortForwardTable.vue @@ -0,0 +1,166 @@ + + + + + diff --git a/src/lib/fontawesome.ts b/src/lib/fontawesome.ts index a1f0d2673..c52994211 100644 --- a/src/lib/fontawesome.ts +++ b/src/lib/fontawesome.ts @@ -61,6 +61,8 @@ import { faShield as fasShield } from '@fortawesome/free-solid-svg-icons' import { faCircleInfo as fasCircleInfo } from '@fortawesome/free-solid-svg-icons' import { faEmptySet } from '@nethesis/nethesis-solid-svg-icons' import { faClone as faClone } from '@fortawesome/free-solid-svg-icons' +import { faAngleUp as faAngleUp } from '@fortawesome/free-solid-svg-icons' +import { faAngleDown as faAngleDown } from '@fortawesome/free-solid-svg-icons' export async function loadFontAwesome(app: any) { app.component('FontAwesomeIcon', FontAwesomeIcon) @@ -123,4 +125,6 @@ export async function loadFontAwesome(app: any) { library.add(faArrowRight) library.add(faEmptySet) library.add(faClone) + library.add(faAngleUp) + library.add(faAngleDown) } diff --git a/src/router/index.ts b/src/router/index.ts index 7e46c528e..34a027774 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -54,6 +54,11 @@ const standaloneRoutes = [ path: 'firewall/zones-and-policies', name: 'ZonesAndPolicies', component: () => import('../views/standalone/firewall/ZonesAndPolicies.vue') + }, + { + path: 'firewall/port-forward', + name: 'PortForward', + component: () => import('../views/standalone/firewall/PortForward.vue') } ] diff --git a/src/views/standalone/firewall/PortForward.vue b/src/views/standalone/firewall/PortForward.vue new file mode 100644 index 000000000..52ba15f07 --- /dev/null +++ b/src/views/standalone/firewall/PortForward.vue @@ -0,0 +1,230 @@ + + + From 74d387ad4d06df53566f4716018860773fa12a9f Mon Sep 17 00:00:00 2001 From: Kevin Cela Date: Tue, 3 Oct 2023 09:49:03 +0000 Subject: [PATCH 02/13] feat: add validation for create/edit port forward - add new validation functions - add related translations - add validation for fields in the create/edit port forward drawer form - fix port forward table display for multiple items --- public/i18n/en/translation.json | 2 + .../CreateOrEditPortForwardDrawer.vue | 97 +++++++++++++++++-- .../standalone/firewall/PortForwardTable.vue | 11 ++- src/lib/validation.ts | 20 +++- 4 files changed, 119 insertions(+), 11 deletions(-) diff --git a/public/i18n/en/translation.json b/public/i18n/en/translation.json index 32b8256f0..e164a8b06 100644 --- a/public/i18n/en/translation.json +++ b/public/i18n/en/translation.json @@ -19,6 +19,7 @@ "http_404": "Resource not found", "http_500": "Server error", "required": "Required", + "required_option": "Select at least one option", "validation_failed": "Validation failed", "invalid_hostname": "Invalid hostname", "hostname_is_too_long": "Hostname has too many characters", @@ -34,6 +35,7 @@ "invalid_ip_v6_address": "Invalid IPv6 address", "invalid_cidr_v4_address": "Enter a valid IPv4 address with CIDR notation", "invalid_cidr_v6_address": "Enter a valid IPv6 address with CIDR notation", + "invalid_port": "Enter an integer number between 0 and 65535", "invalid_uci_name": "Only letters, numbers and underscore (_) are allowed", "cannot_apply_configuration_changes": "Cannot apply configuration changes", "cannot_revert_configuration_changes": "Cannot revert configuration changes", diff --git a/src/components/standalone/firewall/CreateOrEditPortForwardDrawer.vue b/src/components/standalone/firewall/CreateOrEditPortForwardDrawer.vue index f61744c6a..694ddaa4c 100644 --- a/src/components/standalone/firewall/CreateOrEditPortForwardDrawer.vue +++ b/src/components/standalone/firewall/CreateOrEditPortForwardDrawer.vue @@ -21,6 +21,14 @@ import { watchEffect } from 'vue' import { ubusCall } from '@/lib/standalone/ubus' import { ZoneType, useFirewallStore } from '@/stores/standalone/useFirewallStore' import { computed } from 'vue' +import { + MessageBag, + validateIpAddress, + validateRequired, + validatePort, + validateRequiredOption, + type validationOutput +} from '@/lib/validation' const props = defineProps<{ isShown: boolean @@ -39,6 +47,8 @@ const error = ref({ notificationTitle: '', notificationDescription: '' }) +const validationErrorBag = ref(new MessageBag()) +const restrictIPValidationErrors = ref([]) // Options const supportedProtocols = ref([]) @@ -75,6 +85,8 @@ const reflectionZones = ref([]) const destinationZone = ref('') function resetForm() { + validationErrorBag.value.clear() + id.value = props.initialItem?.id ?? '' name.value = props.initialItem?.name ?? '' sourcePort.value = props.initialItem?.source_port ?? '' @@ -82,7 +94,7 @@ function resetForm() { destinationPort.value = props.initialItem?.destination_port ?? '' wan.value = props.initialItem?.wan ?? 'any' enabled.value = props.initialItem?.enabled ?? false - restrict.value = props.initialItem?.restrict ?? [] + restrict.value = props.initialItem?.restrict.map((x) => x) ?? [] protocols.value = props.initialItem?.protocol.map((proto: string) => ({ id: proto, @@ -153,9 +165,59 @@ function addRestrictedIP() { restrict.value.push('') } +function runValidators(validators: validationOutput[], label: string): boolean { + for (let validator of validators) { + if (!validator.valid) { + validationErrorBag.value.set(label, [t(validator.errMessage as string)]) + } + } + + return validators.every((validator) => validator.valid) +} + function validate(): boolean { - //TODO: implement validation - return true + validationErrorBag.value.clear() + + restrictIPValidationErrors.value = [] + restrict.value.forEach(() => { + restrictIPValidationErrors.value.push('') + }) + + let validRestrict = true + for (let [index, restrictValue] of restrict.value.entries()) { + for (let validator of [validateRequired(restrictValue), validateIpAddress(restrictValue)]) { + if (!validator.valid) { + restrictIPValidationErrors.value[index] = t(validator.errMessage as string) + validRestrict = false + break + } + } + } + + if (!validRestrict) { + showAdvancedSettings.value = true + } + + let validators: [validationOutput[], string][] = [ + [[validateRequired(name.value)], 'name'], + [[validateRequired(sourcePort.value), validatePort(sourcePort.value)], 'sourcePort'], + [[validateRequiredOption(protocols.value)], 'protocols'], + [ + [validateRequired(destinationIP.value), validateIpAddress(destinationIP.value)], + 'destinationIP' + ], + [ + [validateRequired(destinationPort.value), validatePort(destinationPort.value)], + 'destinationPort' + ], + [reflection.value ? [validateRequiredOption(reflectionZones.value)] : [], 'reflectionZones'] + ] + + return ( + validators + .map(([validator, label]) => runValidators(validator, label)) + .every((result) => result) && validRestrict + ) } async function performRequest() { @@ -189,8 +251,8 @@ async function performRequest() { } } catch (err: any) { error.value.notificationTitle = isEditing - ? t('standalone.port_forward.cannot_edit_port_forward') - : t('standalone.port_forward.cannot_add_port_forward') + ? t('error.cannot_edit_port_forward') + : t('error.cannot_create_port_forward') error.value.notificationDescription = t(getAxiosErrorMessage(err)) } finally { @@ -220,6 +282,11 @@ async function performRequest() {
+

{{ t('standalone.port_forward.source_zone') }}

WAN

@@ -230,11 +297,18 @@ async function performRequest() { :multiple="true" :options="supportedProtocols" v-model="protocols" + :invalid-message="validationErrorBag.getFirstFor('protocols')" + /> + -
diff --git a/src/components/standalone/firewall/PortForwardTable.vue b/src/components/standalone/firewall/PortForwardTable.vue index 110462458..9634dfb4f 100644 --- a/src/components/standalone/firewall/PortForwardTable.vue +++ b/src/components/standalone/firewall/PortForwardTable.vue @@ -111,10 +111,17 @@ function getDropdownItems(item: PortForward) { {{ item.source_port }} {{ item.source_port_name ? `(${item.source_port_name})` : '' }}