Skip to content
Draft
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
20 changes: 0 additions & 20 deletions eslint-suppressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -254,31 +254,11 @@
"count": 1
}
},
"src/components/standalone/dpi/DeleteDpiRuleModal.vue": {
"@typescript-eslint/no-explicit-any": {
"count": 1
}
},
"src/components/standalone/dpi/DpiExceptions.vue": {
"@typescript-eslint/no-explicit-any": {
"count": 1
}
},
"src/components/standalone/dpi/DpiRuleCard.vue": {
"@typescript-eslint/no-explicit-any": {
"count": 1
}
},
"src/components/standalone/dpi/DpiRules.vue": {
"@typescript-eslint/no-explicit-any": {
"count": 1
}
},
"src/components/standalone/dpi/ManageDpiRuleModal.vue": {
"@typescript-eslint/no-explicit-any": {
"count": 7
}
},
"src/components/standalone/factory_reset/FactoryResetContent.vue": {
"@typescript-eslint/no-explicit-any": {
"count": 2
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --build",
"lint": "eslint . --max-warnings 60",
"lint": "eslint . --max-warnings 58",
"lint-fix": "npm run lint -- --fix",
"format": "prettier --list-different src/",
"format-fix": "prettier --write src/",
Expand Down
37 changes: 23 additions & 14 deletions src/components/standalone/NeMultiTextInput.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<!--
Copyright (C) 2024 Nethesis S.r.l.
Copyright (C) 2026 Nethesis S.r.l.
SPDX-License-Identifier: GPL-3.0-or-later
-->

<script setup lang="ts">
import { onMounted } from 'vue'
import { computed, onMounted } from 'vue'
import { ref } from 'vue'
import { NeCombobox, type NeComboboxOption, NeButton, NeTextInput } from '@nethesis/vue-components'
import { watch } from 'vue'
Expand All @@ -23,21 +23,22 @@
const props = withDefaults(
defineProps<{
useKeyInput?: boolean
keyInputType?: 'combobox' | 'text'

Check warning on line 26 in src/components/standalone/NeMultiTextInput.vue

View workflow job for this annotation

GitHub Actions / Eslint

Prop 'keyInputType' requires default value to be set
required?: boolean
modelValue: string[] | KeyValueItem[]
addItemLabel: string
invalidMessages?: string[]

Check warning on line 30 in src/components/standalone/NeMultiTextInput.vue

View workflow job for this annotation

GitHub Actions / Eslint

Prop 'invalidMessages' requires default value to be set
generalInvalidMessage?: string

Check warning on line 31 in src/components/standalone/NeMultiTextInput.vue

View workflow job for this annotation

GitHub Actions / Eslint

Prop 'generalInvalidMessage' requires default value to be set
invalidKeyMessages?: string[]

Check warning on line 32 in src/components/standalone/NeMultiTextInput.vue

View workflow job for this annotation

GitHub Actions / Eslint

Prop 'invalidKeyMessages' requires default value to be set
keyOptions?: NeComboboxOption[]
title?: string

Check warning on line 34 in src/components/standalone/NeMultiTextInput.vue

View workflow job for this annotation

GitHub Actions / Eslint

Prop 'title' requires default value to be set
optional?: boolean
disableInputs?: boolean
disableAddButton?: boolean
optionalLabel?: string

Check warning on line 38 in src/components/standalone/NeMultiTextInput.vue

View workflow job for this annotation

GitHub Actions / Eslint

Prop 'optionalLabel' requires default value to be set
keyInputPlaceholder?: string

Check warning on line 39 in src/components/standalone/NeMultiTextInput.vue

View workflow job for this annotation

GitHub Actions / Eslint

Prop 'keyInputPlaceholder' requires default value to be set
placeholder?: string
helperText?: string
}>(),
{ useKeyInput: false, required: false, keyOptions: () => [] }
)
Expand All @@ -51,6 +52,10 @@
const keys = ref<string[]>([])
const items = ref<string[]>([])

const addButtonMargin = computed(() =>
!props.helperText && (items.value.length > 0 || props.generalInvalidMessage) ? 'mt-8' : 'mt-4'
)

const inputRef = ref()

function refreshKeysAndItems() {
Expand Down Expand Up @@ -187,18 +192,22 @@
<p v-if="generalInvalidMessage" :class="'mt-2 text-sm text-rose-700 dark:text-rose-400'">
{{ generalInvalidMessage }}
</p>
<NeButton
class="mt-4 -ml-2.5"
size="md"
:disabled="disableAddButton"
kind="tertiary"
@click="addItem"
>
<template #prefix>
<FontAwesomeIcon :icon="faCirclePlus" class="h-4 w-4" aria-hidden="true" />
</template>
{{ addItemLabel }}
</NeButton>
</div>
<p v-if="helperText" class="mt-2 text-xs leading-4 text-tertiary-neutral">
{{ helperText }}
</p>
<NeButton
class="-ml-2.5"
:class="addButtonMargin"
size="md"
:disabled="disableAddButton"
kind="tertiary"
@click="addItem"
>
<template #prefix>
<FontAwesomeIcon :icon="faCirclePlus" class="h-4 w-4" aria-hidden="true" />
</template>
{{ addItemLabel }}
</NeButton>
</div>
</template>
23 changes: 19 additions & 4 deletions src/components/standalone/NeStepper.vue
Original file line number Diff line number Diff line change
@@ -1,25 +1,40 @@
<!--
Copyright (C) 2024 Nethesis S.r.l.
Copyright (C) 2026 Nethesis S.r.l.
SPDX-License-Identifier: GPL-3.0-or-later
-->

<script setup lang="ts">
import { NeProgressBar } from '@nethesis/vue-components'
import { range } from 'lodash-es'

defineProps<{
const {
totalSteps,
currentStep,
stepLabel,
barSize = 'sm',
labelColorClasses = 'text-primary-600 dark:text-primary-500',
barColorClasses = 'bg-primary-600 dark:bg-primary-500'
} = defineProps<{
totalSteps: number
currentStep: number
stepLabel: string
barSize?: 'sm' | 'md' | 'lg' | 'xl'
labelColorClasses?: string
barColorClasses?: string
}>()
</script>

<template>
<div>
<NeProgressBar :progress="(currentStep / totalSteps) * 100" size="sm" />
<NeProgressBar
:progress="(currentStep / totalSteps) * 100"
:size="barSize"
color="custom"
:custom-color-classes="barColorClasses"
/>
<div class="mt-2 flex flex-row">
<div v-for="i in range(1, totalSteps + 1)" :key="i" class="flex grow basis-0 justify-center">
<p v-if="i == currentStep" class="text-xs font-semibold text-primary-400">
<p v-if="i == currentStep" class="text-xs font-semibold" :class="labelColorClasses">
{{ stepLabel }} {{ currentStep }}/{{ totalSteps }}
</p>
</div>
Expand Down
184 changes: 184 additions & 0 deletions src/components/standalone/dpi/ApplicationGroups.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
<!--
Copyright (C) 2026 Nethesis S.r.l.
SPDX-License-Identifier: GPL-3.0-or-later
-->

<script setup lang="ts">
import {
getAxiosErrorMessage,
NeButton,
NeEmptyState,
NeInlineNotification,
NeTextInput
} from '@nethesis/vue-components'
import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { refDebounced } from '@vueuse/core'
import { faCirclePlus, faCubes, faMagnifyingGlass } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import {
useApplicationGroupsPage,
type DpiApplicationGroup
} from '@/composables/useApplicationGroups'
import { useDpiRules } from '@/composables/useDpiRules'
import ApplicationGroupsTable from '@/components/standalone/dpi/ApplicationGroupsTable.vue'
import CreateApplicationGroupModal from '@/components/standalone/dpi/CreateApplicationGroupModal.vue'
import DeleteApplicationGroupModal from '@/components/standalone/dpi/DeleteApplicationGroupModal.vue'

const { t } = useI18n()

const nameFilter = ref('')
const search = refDebounced(nameFilter, 400)
const page = ref(1)
const pageSize = ref(10)

const { data, isLoading, isPlaceholderData, isError, error } = useApplicationGroupsPage({
search,
page,
pageSize
})
const { data: allRules } = useDpiRules()

const groups = computed(() => data.value?.data ?? [])
const totalRows = computed(() => data.value?.meta.total ?? 0)
const isFiltered = computed(() => search.value.trim() !== '')
const hasGroups = computed(() => groups.value.length > 0)

const groupToEdit = ref<DpiApplicationGroup>()
const isDuplicating = ref(false)
const isShownSaveModal = ref(false)
const groupToDelete = ref<DpiApplicationGroup>()
const isShownDeleteModal = ref(false)

watch(search, () => {
page.value = 1
})

function createGroup() {
groupToEdit.value = undefined
isDuplicating.value = false
isShownSaveModal.value = true
}

function editGroup(group: DpiApplicationGroup) {
groupToEdit.value = group
isDuplicating.value = false
isShownSaveModal.value = true
}

function duplicateGroup(group: DpiApplicationGroup) {
groupToEdit.value = group
isDuplicating.value = true
isShownSaveModal.value = true
}

function deleteGroup(group: DpiApplicationGroup) {
groupToDelete.value = group
isShownDeleteModal.value = true
}

function resetFilters() {
nameFilter.value = ''
}
</script>

<template>
<div>
<NeInlineNotification
v-if="isError"
kind="error"
:title="t('error.cannot_retrieve_application_groups')"
:description="t(getAxiosErrorMessage(error))"
class="mb-8"
/>
<ApplicationGroupsTable
v-if="isLoading"
:groups="[]"
:rules="allRules ?? []"
loading
:current-page="page"
:page-size="pageSize"
:total-rows="0"
/>
<template v-else-if="!hasGroups && !isFiltered">
<NeEmptyState
:title="t('standalone.dpi.no_application_groups_configured')"
:description="t('standalone.dpi.no_application_groups_configured_description')"
:icon="faCubes"
class="text-center [&>div>div]:max-w-2xl"
>
<NeButton kind="primary" size="lg" @click="createGroup">
<template #prefix>
<FontAwesomeIcon :icon="faCirclePlus" aria-hidden="true" />
</template>
{{ t('standalone.dpi.create_application_group') }}
</NeButton>
</NeEmptyState>
</template>
<template v-else>
<div class="mb-6 flex flex-wrap items-center justify-between gap-4">
<p class="max-w-2xl text-sm leading-5 text-tertiary-neutral">
{{ t('standalone.dpi.create_application_group_description') }}
</p>
<NeButton kind="primary" size="lg" @click="createGroup">
<template #prefix>
<FontAwesomeIcon :icon="faCirclePlus" aria-hidden="true" />
</template>
{{ t('standalone.dpi.create_application_group') }}
</NeButton>
</div>
<div class="mb-6 flex flex-wrap items-center gap-3">
<NeTextInput
v-model="nameFilter"
is-search
:clear-search-label="t('common.clear_filter')"
:placeholder="t('standalone.dpi.filter_groups')"
class="w-80"
/>
</div>
<NeEmptyState
v-if="!hasGroups"
:title="t('standalone.dpi.no_application_groups_found')"
:description="t('standalone.dpi.no_application_groups_found_description')"
:icon="faMagnifyingGlass"
class="text-center [&>div>div]:max-w-2xl"
>
<NeButton kind="tertiary" size="lg" @click="resetFilters">
{{ t('common.reset_filters') }}
</NeButton>
</NeEmptyState>
<ApplicationGroupsTable
v-else
:groups="groups"
:rules="allRules ?? []"
:loading="isPlaceholderData"
:current-page="page"
:page-size="pageSize"
:total-rows="totalRows"
@edit="editGroup"
@duplicate="duplicateGroup"
@delete="deleteGroup"
@select-page="(selected) => (page = selected)"
@select-page-size="
(size) => {
pageSize = size
page = 1
}
"
/>
</template>
<CreateApplicationGroupModal
:visible="isShownSaveModal"
:group="groupToEdit"
:duplicate="isDuplicating"
@close="isShownSaveModal = false"
@save="isShownSaveModal = false"
/>
<DeleteApplicationGroupModal
:visible="isShownDeleteModal"
:group="groupToDelete"
@close="isShownDeleteModal = false"
@deleted="isShownDeleteModal = false"
/>
</div>
</template>
Loading
Loading