refactor: rework async ToolFirewall

This commit is contained in:
axolotle 2024-08-13 00:29:27 +02:00
parent b9154908b4
commit 732578156a
2 changed files with 99 additions and 77 deletions

View file

@ -229,6 +229,19 @@ export type Diagnosis = {
}[] }[]
} }
// FIREWALL
type Protocols = { TCP: number[]; UDP: number[] }
export type Firewall = {
ipv4: Protocols
ipv6: Protocols
uPnP: Protocols & {
TCP_TO_CLOSE: number[]
UDP_TO_CLOSE: number[]
enabled: boolean
}
}
// DOMAINS // DOMAINS
export type DNSRecord = { export type DNSRecord = {

View file

@ -1,21 +1,63 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import api from '@/api' import api from '@/api'
import { APIBadRequestError, type APIError } from '@/api/errors' import { APIBadRequestError, type APIError } from '@/api/errors'
import { useForm } from '@/composables/form' import { useForm } from '@/composables/form'
import { useAutoModal } from '@/composables/useAutoModal' import { useAutoModal } from '@/composables/useAutoModal'
import { useInitialQueries } from '@/composables/useInitialQueries'
import { toEntries } from '@/helpers/commons'
import { between, integer, required } from '@/helpers/validators' import { between, integer, required } from '@/helpers/validators'
import type { Firewall } from '@/types/core/api'
import type { FieldProps, FormFieldDict } from '@/types/form' import type { FieldProps, FormFieldDict } from '@/types/form'
const { t } = useI18n() const { t } = useI18n()
const modalConfirm = useAutoModal() const modalConfirm = useAutoModal()
const { loading, refetch } = useInitialQueries([{ uri: '/firewall?raw' }], { const { protocols, upnpEnabled } = await api
onQueriesResponse, .fetch<Firewall>({ uri: 'firewall?raw' })
}) .then((firewall) => {
const portTypes = ['TCP', 'UDP'] as const
const protocolsTypes = ['ipv4', 'ipv6', 'uPnP'] as const
const ports = Object.values(firewall).reduce(
(ports, protocols) => {
for (const type of portTypes) {
for (const port of protocols[type]) {
ports[type].add(port)
}
}
return ports
},
{ TCP: new Set<number>(), UDP: new Set<number>() },
)
type Row = { port: number } & Record<keyof Firewall, boolean>
const tables = {
TCP: [] as Row[],
UDP: [] as Row[],
}
for (const protocol of portTypes) {
for (const port of ports[protocol]) {
const row = { port } as Row
for (const connection of protocolsTypes) {
row[connection] = firewall[connection][protocol].includes(port)
}
tables[protocol].push(row)
}
tables[protocol].sort((a, b) => (a.port < b.port ? -1 : 1))
}
return {
protocols: reactive(tables),
upnpEnabled: ref(firewall.uPnP.enabled),
}
})
const upnpError = ref('')
const tableFields = [
{ key: 'port', label: t('port') },
{ key: 'ipv4', label: t('ipv4') },
{ key: 'ipv6', label: t('ipv6') },
{ key: 'uPnP', label: t('upnp') },
]
type Form = { type Form = {
action: 'allow' | 'disallow' action: 'allow' | 'disallow'
@ -84,50 +126,12 @@ const fields = {
const { v } = useForm(form, fields) const { v } = useForm(form, fields)
// Ports tables data async function togglePort({
const protocols = ref() action,
const protocolsFields = toEntries(fields).map(([key, { label }]) => ({ port,
key, protocol,
label, connection,
})) }: Form): Promise<boolean> {
// uPnP
const upnpEnabled = ref()
const upnpError = ref('')
function onQueriesResponse(data: any) {
const ports = Object.values(data).reduce(
(ports_, protocols_) => {
for (const type of ['TCP', 'UDP']) {
for (const port of protocols_[type]) {
ports_[type].add(port)
}
}
return ports
},
{ TCP: new Set(), UDP: new Set() },
)
const tables = {
TCP: [],
UDP: [],
}
for (const protocol of ['TCP', 'UDP']) {
for (const port of ports[protocol]) {
const row = { port }
for (const connection of ['ipv4', 'ipv6', 'uPnP']) {
row[connection] = data[connection][protocol].includes(port)
}
tables[protocol].push(row)
}
tables[protocol].sort((a, b) => (a.port < b.port ? -1 : 1))
}
protocols.value = tables
upnpEnabled.value = data.uPnP.enabled
}
async function togglePort({ action, port, protocol, connection }) {
const confirmed = await modalConfirm( const confirmed = await modalConfirm(
t('confirm_firewall_' + action, { t('confirm_firewall_' + action, {
port, port,
@ -135,9 +139,7 @@ async function togglePort({ action, port, protocol, connection }) {
connection, connection,
}), }),
) )
if (!confirmed) { if (!confirmed) return false
return Promise.resolve(confirmed)
}
const actionTrad = t({ allow: 'open', disallow: 'close' }[action]) const actionTrad = t({ allow: 'open', disallow: 'close' }[action])
return api return api
@ -152,10 +154,10 @@ async function togglePort({ action, port, protocol, connection }) {
}, },
showModal: false, showModal: false,
}) })
.then(() => confirmed) .then(() => true)
} }
async function toggleUpnp(value) { async function toggleUpnp() {
const action = upnpEnabled.value ? 'disable' : 'enable' const action = upnpEnabled.value ? 'disable' : 'enable'
const confirmed = await modalConfirm(t('confirm_upnp_' + action)) const confirmed = await modalConfirm(t('confirm_upnp_' + action))
if (!confirmed) return if (!confirmed) return
@ -167,7 +169,7 @@ async function toggleUpnp(value) {
}) })
.then(() => { .then(() => {
// FIXME Couldn't test when it works. // FIXME Couldn't test when it works.
refetch(false) api.refetch()
}) })
.catch((err: APIError) => { .catch((err: APIError) => {
if (!(err instanceof APIBadRequestError)) throw err if (!(err instanceof APIBadRequestError)) throw err
@ -175,38 +177,43 @@ async function toggleUpnp(value) {
}) })
} }
function onTablePortToggling(port, protocol, connection, index, value) { function onTablePortToggling(
protocols.value[protocol][index][connection] = value { port, protocol, connection }: Omit<Form, 'action'>,
index: number,
value: boolean,
) {
const protocols_ =
protocol === 'Both' ? (['TCP', 'UDP'] as const) : [protocol]
protocols_.forEach((protocol) => {
protocols[protocol][index][connection] = value
})
const action = value ? 'allow' : 'disallow' const action = value ? 'allow' : 'disallow'
togglePort({ action, port, protocol, connection }).then((toggled) => { togglePort({ action, port, protocol, connection }).then((confirmed) => {
// Revert change on cancel // Revert change on cancel
if (!toggled) { if (!confirmed) {
protocols.value[protocol][index][connection] = !value protocols_.forEach((protocol) => {
protocols[protocol][index][connection] = !value
})
} }
}) })
} }
function onFormPortToggling() { function onFormPortToggling() {
togglePort(form).then((toggled) => { togglePort(form.value).then((confirmed) => {
if (toggled) refetch(false) // TODO: update data instead of refetch?
if (confirmed) api.refetch()
}) })
} }
</script> </script>
<template> <template>
<ViewBase :loading="loading" skeleton="CardFormSkeleton"> <div>
<!-- PORTS --> <!-- PORTS -->
<YCard :title="$t('ports')" icon="shield"> <YCard :title="$t('ports')" icon="shield">
<div v-for="(items, protocol) in protocols" :key="protocol"> <div v-for="(items, protocol) in protocols" :key="protocol">
<h5>{{ $t(protocol) }}</h5> <h5>{{ $t(protocol) }}</h5>
<BTable <BTable :fields="tableFields" :items="items" small striped responsive>
:fields="protocolsFields"
:items="items"
small
striped
responsive
>
<!-- PORT CELL --> <!-- PORT CELL -->
<template #cell(port)="data"> <template #cell(port)="data">
{{ data.value }} {{ data.value }}
@ -216,13 +223,15 @@ function onFormPortToggling() {
<template #cell()="data"> <template #cell()="data">
<BFormCheckbox <BFormCheckbox
v-if="data.field.key !== 'uPnP'" v-if="data.field.key !== 'uPnP'"
:modelValue="data.value" :model-value="data.value as boolean"
switch switch
@update:modelValue=" @update:model-value="
onTablePortToggling( onTablePortToggling(
data.item.port, {
protocol, port: data.item.port,
data.field.key, protocol,
connection: data.field.key as Form['connection'],
},
data.index, data.index,
$event, $event,
) )
@ -278,7 +287,7 @@ function onFormPortToggling() {
</BButton> </BButton>
</template> </template>
</YCard> </YCard>
</ViewBase> </div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>