diff --git a/src/components/standalone/multi-wan/MultiWanManager.vue b/src/components/standalone/multi-wan/MultiWanManager.vue index 77bc84b87..a73decceb 100644 --- a/src/components/standalone/multi-wan/MultiWanManager.vue +++ b/src/components/standalone/multi-wan/MultiWanManager.vue @@ -6,31 +6,31 @@ - - + - - + + {{ t('standalone.multi_wan.policy') }} @@ -133,9 +170,9 @@ function deletePolicyHandler() { @@ -144,62 +181,112 @@ function deletePolicyHandler() { {{ t('standalone.multi_wan.create_policy') }} - - - - (deletePolicy = toDeletePolicy)" - @edit="(toEditPolicy) => (editPolicy = toEditPolicy)" - /> + + + + {{ t('standalone.multi_wan.no_policy_found') }} + + + + + {{ t('standalone.multi_wan.create_default_policy') }} + + + + + + + + + {{ item.label ?? item.name }} + - - {{ t('standalone.multi_wan.no_policy_found') }} - - - - - {{ t('standalone.multi_wan.create_default_policy') }} - - - - - - - - - - {{ t('standalone.multi_wan.rules') }} - - - {{ t('standalone.multi_wan.rules_description') }} - + + + + {{ t(`standalone.multi_wan.modes.${item.type}`) }} - - - - - {{ t('standalone.multi_wan.create_rule') }} - - - - (deleteRule = toDeleteRule)" - @edit="(toEditRule) => (editRule = toEditRule)" - /> - - + + + + + + {{ t('standalone.multi_wan.priority', metricIndex + 1) }} + + + + + + + + + + + import type { NeComboboxOption } from '@nethserver/vue-tailwind-lib' import { - getAxiosErrorMessage, NeButton, NeCombobox, NeFormItemLabel, @@ -12,29 +11,16 @@ import { NeTextInput } from '@nethserver/vue-tailwind-lib' import { useI18n } from 'vue-i18n' -import { computed, onMounted, reactive, ref, watch } from 'vue' -import { ubusCall } from '@/lib/standalone/ubus' -import type { AxiosError, AxiosResponse } from 'axios' +import { computed, onMounted, ref, watch } from 'vue' import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' import { faPlus } from '@fortawesome/free-solid-svg-icons' -import { useMwanConfig } from '@/composables/useMwanConfig' import { MessageBag, validateRequired, validateUciName } from '@/lib/validation' -import { useMwanDefaults } from '@/composables/useMwanDefaults' +import { useFirewallStore, Zone } from '@/stores/standalone/useFirewallStore' +import { ubusCall, ValidationError } from '@/lib/standalone/ubus' +import type { AxiosError } from 'axios' const { t } = useI18n() -/** - * Interface describing the get firewall response - */ -interface FirewallResponse { - values: { - [name: string]: { - name: string - network: Array - } - } -} - /** * Gateway definition. */ @@ -75,6 +61,8 @@ const policyOptions = [ } ] +const firewall = useFirewallStore() + const props = defineProps({ isShown: { type: Boolean, @@ -86,19 +74,11 @@ const props = defineProps({ } }) -const mwanConfig = reactive(useMwanConfig()) -const mwanDefaults = reactive(useMwanDefaults()) +const form = ref(initForm()) -const form = ref({ - label: '', - selection: policyOptions[0].id, - priorities: [[new Gateway(), new Gateway()]] -}) -const gateways = ref>() -const error = ref() -const loading = ref(false) const saving = ref(false) const messageBag = ref(new MessageBag()) +const error = ref() const labelElement = ref(null) @@ -114,6 +94,19 @@ const isTrashButtonDisabled = computed(() => { return false }) +const availableGateways = computed((): Array => { + return firewall.zones + .filter((zone: Zone) => zone.configName == 'ns_wan') + .map((zone: Zone) => zone.interfaces) + .flat() + .map((name: string) => { + return { + id: name, + label: name + } + }) +}) + /** * Map transformation of priorities and gateways between behaviours, allows to keep previous values set. */ @@ -142,55 +135,25 @@ watch( watch( () => props.isShown, () => { - if (props.createDefault) { - form.value.label = 'Default' - } + form.value = initForm() if (props.isShown && !props.createDefault) { setTimeout(() => labelElement.value?.focus(), 50) } } ) -/** - * Get error from mwanDefaults if any - */ -watch( - () => mwanDefaults.error, - () => (error.value = mwanDefaults.error) -) +const emit = defineEmits(['success', 'close']) -/** - * Get error from mwanConfig if any - */ -watch( - () => mwanConfig.error, - () => (error.value = mwanConfig.error) -) - -const emit = defineEmits(['policyCreated', 'abortCreation']) - -onMounted(() => fetchGateways()) +onMounted(() => { + firewall.fetch() +}) -/** - * Get gateways from the 'wan' firewall zone. - */ -function fetchGateways() { - loading.value = true - ubusCall('uci', 'get', { config: 'firewall', type: 'zone' }) - .then( - (response: AxiosResponse) => - (gateways.value = Object.entries(response.data.values) - .filter((value) => value[1].name == 'wan') - .flatMap((value) => value[1].network) - .map((value) => { - return { - id: value, - label: value - } - })) - ) - .catch((exception: AxiosError) => (error.value = new Error(t(getAxiosErrorMessage(exception))))) - .finally(() => (loading.value = false)) +function initForm(): Form { + return { + label: props.createDefault ? 'Default' : '', + selection: policyOptions[0].id, + priorities: [[new Gateway(), new Gateway()]] + } } /** @@ -215,134 +178,65 @@ function validate(): boolean { messageBag.value.set('label', [t(errMessage.valueOf())]) labelElement.value?.focus() } - // TODO: implement priority/gateway validation + form.value.priorities.flat().forEach((priority, index) => { + errMessage = validateRequired(priority.id).errMessage + if (errMessage) { + messageBag.value.set(`interfaces.${index}.name`, [t(errMessage.valueOf())]) + } + }) return !(messageBag.value.size > 0) } /** * Create every entity needed for the policy to work. Interfaces, Members, Policies and Default Rule (if missing) */ -function createPolicy() { +function create() { if (validate()) { saving.value = true - // group all calls in one big promise - let calls: Promise[] = [] - // interface creation - calls.push( - // start with organizing the interfaces to create - ...form.value.priorities - // flatten all priorities, get only the gateways picked - .map((priorities) => priorities.map((gateway) => gateway)) - .flat(1) - // gateways must not already exist, this filters them out - .filter((gateway) => !mwanConfig.interfaces.some((iface) => iface.name == gateway.id)) - // map all in multiple calls - .map((gateway) => - ubusCall('uci', 'add', { - config: 'mwan3', - name: gateway.id, - type: 'interface', - values: { - enabled: '1', - initial_state: mwanDefaults.data!.values.initial_state, - family: mwanDefaults.data!.values.protocol, - track_ip: mwanDefaults.data!.values.track_ip, - track_method: mwanDefaults.data!.values.tracking_method, - reliability: mwanDefaults.data!.values.tracking_reliability, - count: mwanDefaults.data!.values.ping_count, - size: mwanDefaults.data!.values.ping_size, - max_ttl: mwanDefaults.data!.values.ping_max_ttl, - timeout: mwanDefaults.data!.values.ping_timeout, - interval: mwanDefaults.data!.values.ping_interval, - failure_interval: mwanDefaults.data!.values.ping_failure_interval, - recovery_interval: mwanDefaults.data!.values.ping_recovery_interval, - down: mwanDefaults.data!.values.interface_down_threshold, - up: mwanDefaults.data!.values.interface_up_threshold + ubusCall('ns.mwan', 'store_policy', { + name: form.value.label, + interfaces: form.value.priorities + .map((gateways, index) => + gateways.map((gateway) => { + return { + name: gateway.id, + metric: (index + 1) * 10, + weight: gateway.weight } }) ) - ) - // member creation - calls.push( - // for each priority we'll have +10 metric applied - ...form.value.priorities - .map((priority, index) => - // call 'add' to each gateway - priority.map((gateway) => - ubusCall('uci', 'add', { - config: 'mwan3', - name: `${gateway.id}_M${(index + 1) * 10}_W${gateway.weight ?? 100}`, - type: 'member', - values: { - interface: gateway.id, - metric: (index + 1) * 10, - weight: gateway.weight ?? 100 - } - }) - ) - ) - // flatten array, we have nested promises here - .flat(1) - ) - calls.push( - // create policy - ubusCall('uci', 'add', { - config: 'mwan3', - name: form.value.label, - type: 'policy', - values: { - // map gateways to members name - // TODO: reuse name generation logic from above - use_member: form.value.priorities - .map((priority, index) => - priority.map( - (gateway) => `${gateway.id}_M${(index + 1) * 10}_W${gateway.weight ?? 100}` - ) - ) - .flat(1) - } - }) - ) - // if this is the default policy, create a default rule too - if (props.createDefault) { - calls.push( - ubusCall('uci', 'add', { - config: 'mwan3', - name: 'DefaultRule', - type: 'rule', - values: { - proto: 'all', - src_ip: '0.0.0.0/0', - dest_ip: '0.0.0.0/0', - sticky: '0', - use_policy: form.value.label - } - }) - ) - } - // If all calls are successful, go forth and reload the UI - // TODO: implement error catching, with rollback. - Promise.all(calls).then(() => { - saving.value = false - emit('policyCreated') + .flat() }) + .then(() => { + emit('success') + form.value = initForm() + }) + .catch((reason: ValidationError) => { + messageBag.value = reason.errorBag + }) + .catch((reason: AxiosError) => { + error.value = reason + }) + .finally(() => { + saving.value = false + }) } } - - + - - - {{ t('standalone.multi_wan.priority', index + 1) }} - - - - - - - - - - - - {{ t('standalone.multi_wan.add_gateway') }} - - + + + + + {{ t('standalone.multi_wan.priority', index + 1) }} + + + + + + + + + + + + {{ t('standalone.multi_wan.add_gateway') }} + + + @@ -392,10 +291,10 @@ function createPolicy() { - + {{ t('common.cancel') }} - + {{ t('common.save') }} diff --git a/src/lib/fontawesome.ts b/src/lib/fontawesome.ts index 090856629..8991c28e0 100644 --- a/src/lib/fontawesome.ts +++ b/src/lib/fontawesome.ts @@ -1,6 +1,6 @@ import { library } from '@fortawesome/fontawesome-svg-core' import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' -import { faArrowRight, faHouse as fasHouse } from '@fortawesome/free-solid-svg-icons' +import { faArrowRight, faHouse as fasHouse, faUserGear } from '@fortawesome/free-solid-svg-icons' import { faHouse as falHouse } from '@nethesis/nethesis-light-svg-icons' import { faServer as fasServer } from '@fortawesome/free-solid-svg-icons' import { faServer as falServer } from '@nethesis/nethesis-light-svg-icons' @@ -116,4 +116,5 @@ export async function loadFontAwesome(app: any) { library.add(fasCircleInfo) library.add(faArrowRight) library.add(faEmptySet) + library.add(faUserGear) }
{{ t('standalone.multi_wan.no_policy_found') }}
{{ item.label ?? item.name }}
- {{ t('standalone.multi_wan.rules_description') }} -
{{ t(`standalone.multi_wan.modes.${item.type}`) }}