-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
473 additions
and
30 deletions.
There are no files selected for viewing
99 changes: 99 additions & 0 deletions
99
src/components/standalone/security/ips/IpsDisableRuleDrawer.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
<script lang="ts" setup> | ||
import { NeButton, NeSideDrawer, NeTextInput } from '@nethesis/vue-components' | ||
import { ref, watchEffect } from 'vue' | ||
import { useI18n } from 'vue-i18n' | ||
import { MessageBag } from '@/lib/validation' | ||
import { ubusCall, ValidationError } from '@/lib/standalone/ubus' | ||
const { visible = false } = defineProps<{ | ||
visible: boolean | ||
}>() | ||
const emit = defineEmits(['close', 'save']) | ||
const { t } = useI18n() | ||
const gid = ref('') | ||
const sid = ref('') | ||
const description = ref('') | ||
const validationErrors = ref(new MessageBag()) | ||
const loading = ref(false) | ||
const error = ref<Error>() | ||
function closeHandler() { | ||
if (!loading.value) { | ||
emit('close') | ||
} | ||
} | ||
function save() { | ||
loading.value = true | ||
error.value = undefined | ||
validationErrors.value.clear() | ||
ubusCall('ns.snort', 'disable-rule', { | ||
gid: gid.value, | ||
sid: sid.value, | ||
description: description.value | ||
}) | ||
.then(() => { | ||
emit('save') | ||
}) | ||
.catch((reason) => { | ||
if (reason instanceof ValidationError) { | ||
validationErrors.value = reason.errorBag | ||
} else { | ||
error.value = reason | ||
} | ||
}) | ||
.finally(() => { | ||
loading.value = false | ||
}) | ||
} | ||
watchEffect(() => { | ||
if (visible) { | ||
gid.value = '' | ||
sid.value = '' | ||
description.value = '' | ||
} | ||
}) | ||
</script> | ||
|
||
<template> | ||
<NeSideDrawer :is-shown="visible" :title="t('standalone.ips.disable_rule')" @close="closeHandler"> | ||
<form class="space-y-8" @submit.prevent="save"> | ||
<NeTextInput | ||
v-model="gid" | ||
:disabled="loading" | ||
:invalid-message="t(validationErrors.getFirstI18nKeyFor('gid'))" | ||
:label="t('standalone.ips.rule_gid')" | ||
required | ||
type="number" | ||
/> | ||
<NeTextInput | ||
v-model="sid" | ||
:disabled="loading" | ||
:invalid-message="t(validationErrors.getFirstI18nKeyFor('sid'))" | ||
:label="t('standalone.ips.rule_sid')" | ||
required | ||
type="number" | ||
/> | ||
<NeTextInput | ||
v-model="description" | ||
:disabled="loading" | ||
:invalid-message="t(validationErrors.getFirstI18nKeyFor('description'))" | ||
:label="t('standalone.ips.rule_description')" | ||
required | ||
/> | ||
<hr /> | ||
<div class="flex flex-wrap justify-end gap-6"> | ||
<NeButton :disabled="loading" kind="tertiary" size="lg" @click="closeHandler"> | ||
{{ t('common.cancel') }} | ||
</NeButton> | ||
<NeButton :disabled="loading" :loading="loading" kind="primary" size="lg" type="submit"> | ||
{{ t('standalone.ips.disable_rule') }} | ||
</NeButton> | ||
</div> | ||
</form> | ||
</NeSideDrawer> | ||
</template> |
232 changes: 232 additions & 0 deletions
232
src/components/standalone/security/ips/IpsDisabledRules.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,232 @@ | ||
<script lang="ts" setup> | ||
import IpsEnabledBadge from '@/components/standalone/security/ips/IpsEnabledBadge.vue' | ||
import { useI18n } from 'vue-i18n' | ||
import { computed, onMounted, ref } from 'vue' | ||
import { ubusCall } from '@/lib/standalone/ubus' | ||
import { | ||
getAxiosErrorMessage, | ||
NeButton, | ||
NeDropdown, | ||
type NeDropdownItem, | ||
NeEmptyState, | ||
NeInlineNotification, | ||
NePaginator, | ||
NeSkeleton, | ||
NeTable, | ||
NeTableBody, | ||
NeTableCell, | ||
NeTableHead, | ||
NeTableHeadCell, | ||
NeTableRow, | ||
NeTextInput, | ||
useItemPagination, | ||
useSort | ||
} from '@nethesis/vue-components' | ||
import type { AxiosResponse } from 'axios' | ||
import { faCircleInfo, faShield, faXmarkCircle } from '@fortawesome/free-solid-svg-icons' | ||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' | ||
import IpsDisableRuleDrawer from '@/components/standalone/security/ips/IpsDisableRuleDrawer.vue' | ||
import { useUciPendingChangesStore } from '@/stores/standalone/uciPendingChanges' | ||
import IpsEnableRuleModal from '@/components/standalone/security/ips/IpsEnableRuleModal.vue' | ||
export type Rule = { | ||
description: string | ||
id: string | ||
gid: string | ||
sid: string | ||
} | ||
type ListRuleResponse = AxiosResponse<{ | ||
rules: Rule[] | ||
}> | ||
const changes = useUciPendingChangesStore() | ||
const { t } = useI18n() | ||
const error = ref<Error>() | ||
const loading = ref(true) | ||
const rules = ref<Rule[]>([]) | ||
function listDisabledRules() { | ||
loading.value = true | ||
ubusCall('ns.snort', 'list-disabled-rules', {}) | ||
.then((response: ListRuleResponse) => { | ||
rules.value = response.data.rules | ||
}) | ||
.catch((reason) => { | ||
error.value = reason | ||
}) | ||
.finally(() => { | ||
loading.value = false | ||
}) | ||
} | ||
onMounted(() => { | ||
listDisabledRules() | ||
}) | ||
const filter = ref('') | ||
const filteredRules = computed((): Rule[] => { | ||
return rules.value.filter((rule) => { | ||
return rule.description.includes(filter.value) || rule.id.includes(filter.value) | ||
}) | ||
}) | ||
const sortKey = ref<keyof Rule>('id') | ||
const sortDescending = ref(false) | ||
const { sortedItems } = useSort(filteredRules, sortKey, sortDescending, {}) | ||
const pageSize = ref(10) | ||
const { currentPage, paginatedItems } = useItemPagination(sortedItems, { | ||
itemsPerPage: pageSize | ||
}) | ||
// FIXME: when types from library are fixed, use proper type | ||
const onSort = (payload: any) => { | ||
sortKey.value = payload.key | ||
sortDescending.value = payload.descending | ||
} | ||
const disablingRule = ref(false) | ||
function handleSave() { | ||
listDisabledRules() | ||
changes.getChanges() | ||
disablingRule.value = false | ||
} | ||
function dropDownActions(rule: Rule): NeDropdownItem[] { | ||
return [ | ||
{ | ||
id: 'delete', | ||
label: t('common.delete'), | ||
icon: 'trash', | ||
iconStyle: 'fas', | ||
danger: true, | ||
action: () => { | ||
ruleToEnable.value = rule | ||
} | ||
} | ||
] | ||
} | ||
const ruleToEnable = ref<Rule>() | ||
function handleEnabled() { | ||
listDisabledRules() | ||
changes.getChanges() | ||
ruleToEnable.value = undefined | ||
} | ||
</script> | ||
|
||
<template> | ||
<div class="space-y-8"> | ||
<div class="flex flex-wrap items-start justify-between gap-4"> | ||
<p class="max-w-lg">{{ t('standalone.ips.disabled_rules_description') }}</p> | ||
<IpsEnabledBadge /> | ||
</div> | ||
|
||
<NeInlineNotification | ||
v-if="error" | ||
:description="t(getAxiosErrorMessage(error))" | ||
:title="t('standalone.ips.error_loading_disabled_rules')" | ||
kind="error" | ||
/> | ||
<NeSkeleton v-if="loading" :lines="10" /> | ||
<template v-else-if="rules.length > 0"> | ||
<div class="flex flex-col flex-wrap justify-between gap-4 md:flex-row"> | ||
<NeTextInput v-model.trim="filter" :placeholder="t('common.filter')" is-search /> | ||
<NeButton v-if="rules.length > 0" kind="secondary" size="lg" @click="disablingRule = true"> | ||
<template #prefix> | ||
<FontAwesomeIcon :icon="faXmarkCircle" aria-hidden="true" class="h-4 w-4" /> | ||
</template> | ||
{{ t('standalone.ips.disable_rule') }} | ||
</NeButton> | ||
</div> | ||
<NeTable | ||
:ariaLabel="t('standalone.ips.rules_table')" | ||
:skeleton-columns="7" | ||
:skeleton-rows="5" | ||
:sortDescending="sortDescending" | ||
:sortKey="sortKey" | ||
card-breakpoint="xl" | ||
> | ||
<NeTableHead> | ||
<NeTableHeadCell column-key="description" sortable @sort="onSort"> | ||
{{ t('standalone.ips.rule_description') }} | ||
</NeTableHeadCell> | ||
<NeTableHeadCell column-key="id" sortable @sort="onSort"> | ||
{{ t('standalone.ips.rule_id') }} | ||
</NeTableHeadCell> | ||
<NeTableHeadCell> | ||
<!-- no header for actions --> | ||
</NeTableHeadCell> | ||
</NeTableHead> | ||
<NeTableBody> | ||
<NeTableRow v-if="filteredRules.length < 1"> | ||
<NeTableCell colspan="4"> | ||
<NeEmptyState | ||
:description="t('common.try_changing_search_filters')" | ||
:icon="faCircleInfo" | ||
:title="t('standalone.ips.no_rules_found')" | ||
class="bg-white dark:bg-gray-950" | ||
> | ||
<NeButton kind="tertiary" @click="filter = ''"> | ||
{{ t('common.clear_filters') }} | ||
</NeButton> | ||
</NeEmptyState> | ||
</NeTableCell> | ||
</NeTableRow> | ||
<NeTableRow v-for="item in paginatedItems" v-else :key="`${item.id}`"> | ||
<NeTableCell :data-label="t('standalone.ips.rule_description')"> | ||
{{ item.description }} | ||
</NeTableCell> | ||
<NeTableCell :data-label="t('standalone.ips.rule_id')"> | ||
{{ item.id }} | ||
</NeTableCell> | ||
<NeTableCell :data-label="t('common.actions')"> | ||
<div class="flex justify-end"> | ||
<NeDropdown :align-to-right="true" :items="dropDownActions(item)" /> | ||
</div> | ||
</NeTableCell> | ||
</NeTableRow> | ||
</NeTableBody> | ||
<template #paginator> | ||
<NePaginator | ||
:current-page="currentPage" | ||
:nav-pagination-label="t('ne_table.pagination')" | ||
:next-label="t('ne_table.go_to_next_page')" | ||
:page-size="pageSize" | ||
:page-size-label="t('ne_table.show')" | ||
:previous-label="t('ne_table.go_to_previous_page')" | ||
:range-of-total-label="t('ne_table.of')" | ||
:total-rows="rules.length" | ||
@selectPageSize="(size: number) => { pageSize = size }" | ||
@select-page="(page: number) => { currentPage = page }" | ||
/> | ||
</template> | ||
</NeTable> | ||
</template> | ||
<NeEmptyState | ||
v-else | ||
:description="t('standalone.ips.no_disabled_rules_description')" | ||
:icon="faShield" | ||
:title="t('standalone.ips.no_disabled_rules')" | ||
> | ||
<NeButton kind="primary" size="lg" @click="disablingRule = true"> | ||
<template #prefix> | ||
<FontAwesomeIcon :icon="faXmarkCircle" aria-hidden="true" class="h-4 w-4" /> | ||
</template> | ||
{{ t('standalone.ips.disable_rule') }} | ||
</NeButton> | ||
</NeEmptyState> | ||
<IpsDisableRuleDrawer | ||
@save="handleSave" | ||
:visible="disablingRule" | ||
@close="disablingRule = false" | ||
/> | ||
<IpsEnableRuleModal | ||
:rule="ruleToEnable" | ||
@enabled="handleEnabled" | ||
@close="ruleToEnable = undefined" | ||
/> | ||
</div> | ||
</template> |
Oops, something went wrong.