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
48 changes: 38 additions & 10 deletions src/components/standalone/firewall/rules/FirewallRulesContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
NeButton,
getAxiosErrorMessage,
NeEmptyState,
NeTextInput
NeTextInput,
NeDropdownFilter
} from '@nethesis/vue-components'
import { onMounted, ref, type PropType, watch, computed } from 'vue'
import CreateOrEditFirewallRuleDrawer from './CreateOrEditFirewallRuleDrawer.vue'
Expand Down Expand Up @@ -39,6 +40,7 @@ const isShownCreateOrEditRuleDrawer = ref(false)
const isShownDeleteRuleModal = ref(false)
const knownTags = ref<NeComboboxOption[]>([])
const textFilter = ref('')
const tagFilter = ref<string[]>([])

const loading = ref({
listRules: false,
Expand Down Expand Up @@ -99,13 +101,20 @@ const emptyStateTitle = computed(() => {
}
})

const isFilterActive = computed(() => !!textFilter.value || tagFilter.value.length > 0)

const filteredRules = computed(() => {
if (!textFilter.value) {
if (!isFilterActive.value) {
// no filter
return rules.value
} else {
// filter rules
return rules.value.filter((rule) => searchStringInRule(rule, textFilter.value))
return rules.value.filter((rule) => {
const matchesTextFilter = !textFilter.value || searchStringInRule(rule, textFilter.value)
const matchesTagFilter =
!tagFilter.value.length || rule.ns_tag.some((tag) => tagFilter.value.includes(tag))
return matchesTextFilter && matchesTagFilter
})
}
})

Expand Down Expand Up @@ -136,13 +145,14 @@ async function listRules() {
const res = await ubusCall('ns.firewall', listRulesMethod.value)
rules.value = res.data.rules

const tags = uniq(rules.value.map((rule) => rule.ns_tag).flat())
const tags = uniq(rules.value.map((rule) => rule.ns_tag).flat()).sort()
knownTags.value = tags.map((tag) => {
return {
id: tag,
label: tag
}
})
tagFilter.value = tagFilter.value.filter((tag) => tags.includes(tag))
} catch (err: any) {
console.error(err)
error.value.listRules = t(getAxiosErrorMessage(err))
Expand Down Expand Up @@ -217,6 +227,11 @@ function showDeleteRuleModal(rule: FirewallRule) {
isShownDeleteRuleModal.value = true
}

function clearFilters() {
textFilter.value = ''
tagFilter.value = []
}

function hideCreateOrEditRuleDrawer() {
isShownCreateOrEditRuleDrawer.value = false
}
Expand Down Expand Up @@ -331,20 +346,32 @@ function searchStringInRule(rule: FirewallRule, queryText: string) {
</NeInlineNotification>
<template v-else>
<!-- no error -->
<!-- text filter -->
<!-- filters -->
<div class="mb-5 flex items-center gap-4">
<NeTextInput
v-model.trim="textFilter"
:placeholder="t('standalone.firewall_rules.filter_rules')"
:disabled="loading.listRules"
class="max-w-xs"
/>
<NeDropdownFilter
v-if="knownTags.length"
v-model="tagFilter"
kind="checkbox"
:label="t('standalone.firewall_rules.tags')"
:options="knownTags"
:clear-search-label="t('ne_dropdown_filter.clear_search')"
:clear-filter-label="t('ne_dropdown_filter.clear_filter')"
:open-menu-aria-label="t('ne_dropdown_filter.open_filter')"
:no-options-label="t('ne_dropdown_filter.no_options')"
:more-options-hidden-label="t('ne_dropdown_filter.more_options_hidden')"
/>
<NeButton
kind="tertiary"
:disabled="loading.listRules || !textFilter"
@click="textFilter = ''"
:disabled="loading.listRules || !isFilterActive"
@click="clearFilters"
>
{{ t('standalone.firewall_rules.clear_filter') }}
{{ t('common.clear_filters') }}
</NeButton>
</div>
<!-- empty state -->
Expand All @@ -366,8 +393,8 @@ function searchStringInRule(rule: FirewallRule, queryText: string) {
:icon="['fas', 'circle-info']"
class="mt-4"
>
<NeButton kind="tertiary" @click="textFilter = ''">
{{ t('standalone.firewall_rules.clear_filter') }}</NeButton
<NeButton kind="tertiary" @click="clearFilters">
{{ t('common.clear_filters') }}</NeButton
>
</NeEmptyState>
</template>
Expand All @@ -376,6 +403,7 @@ function searchStringInRule(rule: FirewallRule, queryText: string) {
:rules-type="rulesType"
:rules="rules"
:text-filter="textFilter"
:tag-filter="tagFilter"
:loading-rules="loading.listRules"
@reload-rules="loadData"
@edit-rule="showEditRuleDrawer"
Expand Down
23 changes: 17 additions & 6 deletions src/components/standalone/firewall/rules/FirewallRulesTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ const props = defineProps({
type: String,
required: true
},
tagFilter: {
type: Array as PropType<string[]>,
required: true
},
loadingRules: {
type: Boolean,
required: true
Expand Down Expand Up @@ -96,13 +100,20 @@ const ruleDragged = ref<number>()
const indexOver = ref<number>()
const disabledRuleClasses = 'bg-[#fcfdfd]! dark:bg-[#18212f]!'

const isFilterActive = computed(() => !!props.textFilter || props.tagFilter.length > 0)

const filteredRules = computed(() => {
if (!props.textFilter) {
if (!isFilterActive.value) {
// no filter
return internalRules.value
} else {
// filter rules
return internalRules.value.filter((rule) => searchStringInRule(rule, props.textFilter))
return internalRules.value.filter((rule) => {
const matchesTextFilter = !props.textFilter || searchStringInRule(rule, props.textFilter)
const matchesTagFilter =
!props.tagFilter.length || rule.ns_tag.some((tag) => props.tagFilter.includes(tag))
return matchesTextFilter && matchesTagFilter
})
}
})

Expand Down Expand Up @@ -309,7 +320,7 @@ function searchStringInRule(rule: FirewallRule, queryText: string) {
<template v-for="(rule, index) in filteredRules" :key="rule.id">
<!-- drop target -->
<tr
v-if="!textFilter"
v-if="!isFilterActive"
:class="[
indexOver == index ? 'drop-over drop-target' : '',
ruleDragged != undefined ? 'drop-active' : ''
Expand All @@ -329,21 +340,21 @@ function searchStringInRule(rule: FirewallRule, queryText: string) {
<!-- rule -->
<tr
:class="{ 'opacity-30': ruleDragged == index }"
:draggable="!textFilter"
:draggable="!isFilterActive"
@dragend="ruleDragged = undefined"
@dragstart="ruleDragged = index"
>
<!-- vertical grip -->
<td
v-if="!textFilter"
v-if="!isFilterActive"
:class="[
'cursor-move text-center hover:bg-gray-100 hover:dark:bg-gray-700',
!isEnabled(rule) ? disabledRuleClasses : ''
]"
>
<FontAwesomeIcon :icon="faGripVertical" />
</td>
<!-- cannot drag & drop rules with active text filter -->
<!-- cannot drag & drop rules with active filter -->
<td v-else :class="!isEnabled(rule) ? disabledRuleClasses : ''">
<NeTooltip trigger-event="mouseenter focus" placement="top-start">
<template #trigger>
Expand Down
Loading