diff --git a/public/i18n/en/translation.json b/public/i18n/en/translation.json index b09253f9f..565d4a9a3 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 1 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", @@ -54,7 +56,16 @@ "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", + "cannot_retrieve_protocols": "Cannot retrieve protocols", + "cannot_retrieve_zones": "Cannot retrieve zones", + "cannot_retrieve_wan_interfaces": "Cannot retrieve WAN interfaces" }, "ne_text_input": { "show_password": "Show password", @@ -459,6 +470,45 @@ "enable_logging": "Enable logging on this zone", "delete_zone": "Delete \"{0}\"?" }, + "port_forward": { + "title": "Port forward", + "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + "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", + "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": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + "restrict_access_to_tooltip": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + "no_port_forward_configured": "No port forward configured", + "no_port_forward_found": "No port forward found", + "filter_change_suggestion": "Try changing filter value", + "copy": "Copy" + }, "firewall": { "title": "Firewall" }, diff --git a/src/components/standalone/NeMultiTextInput.vue b/src/components/standalone/NeMultiTextInput.vue new file mode 100644 index 000000000..8c95712d1 --- /dev/null +++ b/src/components/standalone/NeMultiTextInput.vue @@ -0,0 +1,83 @@ + + + diff --git a/src/components/standalone/NeTable.vue b/src/components/standalone/NeTable.vue index 1a5b7bdc9..47f855824 100644 --- a/src/components/standalone/NeTable.vue +++ b/src/components/standalone/NeTable.vue @@ -133,7 +133,7 @@ table { /* Base style CSS. */ .table-basic { - @apply -mx-4 overflow-hidden overflow-x-auto border-y border-gray-300 dark:border-gray-600 sm:mx-0 sm:rounded-lg sm:border-x; + @apply -mx-4 overflow-x-auto border-y border-gray-300 dark:border-gray-600 sm:mx-0 sm:rounded-lg sm:border-x; } .table-basic.ghost { 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..dcf19e98c --- /dev/null +++ b/src/components/standalone/firewall/CreateOrEditPortForwardDrawer.vue @@ -0,0 +1,404 @@ + + + diff --git a/src/components/standalone/firewall/DeletePortForwardModal.vue b/src/components/standalone/firewall/DeletePortForwardModal.vue new file mode 100644 index 000000000..6a5e458ac --- /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..39dc6d343 --- /dev/null +++ b/src/components/standalone/firewall/PortForwardTable.vue @@ -0,0 +1,179 @@ + + + diff --git a/src/components/standalone/system_settings/TimeSynchronization.vue b/src/components/standalone/system_settings/TimeSynchronization.vue index 339c029ff..fa2c08d45 100644 --- a/src/components/standalone/system_settings/TimeSynchronization.vue +++ b/src/components/standalone/system_settings/TimeSynchronization.vue @@ -14,7 +14,6 @@ import { NeSkeleton, NeCombobox, NeInlineNotification, - NeTextInput, getAxiosErrorMessage } from '@nethserver/vue-tailwind-lib' import type { NeComboboxOption } from '@nethserver/vue-tailwind-lib' @@ -22,6 +21,7 @@ import { isEmpty, uniq } from 'lodash' import { computed, onMounted, ref, type Ref, watch } from 'vue' import { useI18n } from 'vue-i18n' import FormLayout from '@/components/standalone/FormLayout.vue' +import NeMultiTextInput from '../NeMultiTextInput.vue' const { t } = useI18n() const uciChangesStore = useUciPendingChangesStore() @@ -253,15 +253,9 @@ async function save() { } } -function deleteNtpServer(ntpServerName: string) { +function resetNtpServerErrors() { // reset errors to prevent validation errors mismatch error.value.ntpServerCandidate = [] - - ntpServerCandidates.value = ntpServerCandidates.value.filter((elem) => elem !== ntpServerName) -} - -function addNtpServer() { - ntpServerCandidates.value.push('') } @@ -321,41 +315,14 @@ function addNtpServer() {

-
-
-
- - - -
- - - - {{ t('standalone.system_settings.add_ntp_server') }} - -
-
+
diff --git a/src/lib/validation.ts b/src/lib/validation.ts index ffacc917b..f0f3b4f23 100644 --- a/src/lib/validation.ts +++ b/src/lib/validation.ts @@ -1,7 +1,9 @@ // Copyright (C) 2023 Nethesis S.r.l. // SPDX-License-Identifier: GPL-3.0-or-later -interface validationOutput { +import type { NeComboboxOption } from '@nethserver/vue-tailwind-lib' + +export interface validationOutput { valid: Boolean errMessage?: String i18Params?: Object @@ -198,7 +200,7 @@ export const validateVlanId = (value: String): validationOutput => { return { valid: true } } -export function validatePort(value: string, minPort = 1, maxPort = 65565): validationOutput { +export function validatePort(value: string, minPort = 1, maxPort = 65535): validationOutput { const port = Number.parseInt(value) if (Number.isNaN(port) || port < minPort || port > maxPort) { @@ -207,7 +209,7 @@ export function validatePort(value: string, minPort = 1, maxPort = 65565): valid return { valid: true } } -export function validatePortRange(value: string, minRange = 1, maxRange = 65565): validationOutput { +export function validatePortRange(value: string, minRange = 1, maxRange = 65535): validationOutput { let strings: string[] if (value.indexOf(',')) { strings = value.split(',') @@ -225,6 +227,13 @@ export function validatePortRange(value: string, minRange = 1, maxRange = 65565) return { valid: true } } +export const validateRequiredOption = (value: NeComboboxOption[]): validationOutput => { + if (value.length == 0) { + return { valid: false, errMessage: 'error.required_option' } + } + return { valid: true } +} + /** * Extends Map class to provide a name-array for errors */ 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..dd18de564 --- /dev/null +++ b/src/views/standalone/firewall/PortForward.vue @@ -0,0 +1,260 @@ + + +