feat(xo6): displaying traffic rules by priority within network (#10688)

* feat(xo6): displaying traffic rules by priority within network

* update changelog

* clean code
This commit is contained in:
Elise-F
2026-11-09 09:51:51 +01:00
committed by GitHub
parent 8cc3384850
commit 8df236a512
38 changed files with 550 additions and 70 deletions

View File

@@ -772,6 +772,7 @@ export type RawTrafficRule = {
ipRange: string
direction: TrafficRuleDirection
port?: string
priority?: number
}
type BaseTrafficRule = RawTrafficRule & {

View File

@@ -103,9 +103,11 @@ export const locales: Locales = {
},
}
export const FALLBACK_LOCALE = 'en'
export default createI18n({
locale: localStorage.getItem('lang') ?? 'en',
fallbackLocale: 'en',
fallbackLocale: FALLBACK_LOCALE,
messages,
missing: (locale, key, vm) => {
if (!import.meta.env.DEV) {

View File

@@ -1241,7 +1241,6 @@
"traffic-rules:error-plugin:check-doc": "وثائق وحدة تحكم SDN",
"traffic-rules:format-warning": "خطأ في التكوين: قواعد حركة المرور ليست بالتنسيق المتوقع. يرجى {check-doc} حول كيفية ترحيلها.",
"traffic-rules:format-warning:check-doc": "مراجعة أحدث الوثائق",
"traffic-rules:info-message": "ترتيب قواعد حركة المرور قيد التنفيذ. قد يكون السلوك غير مضمون.",
"traffic-rules:no-traffic-rules-detected": "لم يتم اكتشاف أي قواعد حركة مرور",
"traffic-rules:target-on": "@.capitalize:direction-on",
"traffic-rules:xapi-plugin-required": "يتطلب وضع إضافة XAPI",

View File

@@ -1204,7 +1204,6 @@
"traffic-rules:error-plugin:check-doc": "dokumentace k řadiči SDN sítě",
"traffic-rules:format-warning": "Chyba nastavení: Pravidla pro provoz nejsou v očekávaném formátu. {check-doc} ohledně toho, jak je převést.",
"traffic-rules:format-warning:check-doc": "Podívejte se do neaktuálnější dokumentace",
"traffic-rules:info-message": "Implementování pořadí pravidel pro provoz teprve probíhá. Chování může být nebezpečné.",
"traffic-rules:no-traffic-rules-detected": "Nezjištěna žádná pravidla pro provoz",
"traffic-rules:xapi-plugin-required": "Vyžaduje režim XAPI zásuvného modulu",
"translation-tool": "Nástroj pro překládání",

View File

@@ -1241,7 +1241,6 @@
"traffic-rules:error-plugin:check-doc": "dokumentationen til SDN-controlleren",
"traffic-rules:format-warning": "Konfigurationsfejl: Trafikreglerne er ikke i det forventede format. Du bedes {check-doc} for, hvordan de migreres.",
"traffic-rules:format-warning:check-doc": "tjekke den seneste dokumentation",
"traffic-rules:info-message": "Rækkefølge for trafikregler er under implementering. Adfærden kan være uforudsigelig.",
"traffic-rules:no-traffic-rules-detected": "Ingen trafikregler registreret",
"traffic-rules:target-on": "@.capitalize:direction-on",
"traffic-rules:xapi-plugin-required": "Kræver XAPI-plugin-tilstand",

View File

@@ -887,7 +887,6 @@
"total-used:": "Gesamt genutzt:",
"traffic-rule": "Traffic Regel",
"traffic-rules": "Traffic Regeln",
"traffic-rules:info-message": "Reihenfolge der Traffic Regeln wird implementiert. Es kann zu unvorgesehenen Verhalten führen.",
"traffic-rules:no-traffic-rules-detected": "Keine Traffic Regeln erkannt",
"translation-tool": "Übersetzungstool",
"trigger": "Trigger",

View File

@@ -1131,6 +1131,8 @@
"power-on-mode": "Power on mode",
"power-on-vm-for-console": "Power on your VM to access its console",
"power-state": "Power state",
"priority": "Priority",
"priority-within": "Priority within {name}",
"pro-support": "{name} pro support",
"professional-support": "Professional support",
"progress": "Progress",
@@ -1368,6 +1370,7 @@
"theme-solarized-description": "Precision colors for reduced eye strain",
"third-parties": "Third party apps",
"this-host": "This host",
"this-rule": "This rule",
"this-vm-cant-be-migrated": "This VM can't be migrated",
"timeout": "Timeout",
"timezone": "Timezone",
@@ -1395,8 +1398,9 @@
"traffic-rules:error-plugin:check-doc": "the SDN controller documentation",
"traffic-rules:format-warning": "Configuration error: Traffic rules are not in the expected format. Please {check-doc} on how to migrate them.",
"traffic-rules:format-warning:check-doc": "check the latest documentation",
"traffic-rules:info-message": "Traffic rules order is being implemented. Behavior may be hazardous.",
"traffic-rules:no-traffic-rules-detected": "No traffic rules detected",
"traffic-rules:position-on-network": "{position} rule on this network.",
"traffic-rules:rule-ordinal-suffixes": "one:st, two:nd, few:rd, other:th",
"traffic-rules:target-on": "@.capitalize:direction-on",
"traffic-rules:xapi-plugin-required": "Requires XAPI plugin mode",
"translation-tool": "Translation tool",

View File

@@ -1241,7 +1241,6 @@
"traffic-rules:error-plugin:check-doc": "مستندات کنترلر SDN",
"traffic-rules:format-warning": "خطای پیکربندی: قوانین ترافیک در قالب مورد انتظار نیستند. لطفاً برای نحوه مهاجرت آن‌ها {check-doc}.",
"traffic-rules:format-warning:check-doc": "آخرین مستندات را بررسی کنید",
"traffic-rules:info-message": "ترتیب قوانین ترافیک در حال پیاده‌سازی است. رفتار ممکن است مخاطره‌آمیز باشد.",
"traffic-rules:no-traffic-rules-detected": "هیچ قانون ترافیکی شناسایی نشد",
"traffic-rules:target-on": "@.capitalize:direction-on",
"traffic-rules:xapi-plugin-required": "نیازمند حالت افزونه XAPI",

View File

@@ -1241,7 +1241,6 @@
"traffic-rules:error-plugin:check-doc": "SDN-ohjaimen dokumentaatio",
"traffic-rules:format-warning": "Määritysvirhe: liikennesäännöt eivät ole odotetussa muodossa. {check-doc}, miten ne siirretään.",
"traffic-rules:format-warning:check-doc": "Tarkista uusimmasta dokumentaatiosta",
"traffic-rules:info-message": "Liikennesääntöjen järjestystä ollaan toteuttamassa. Toiminta voi olla arvaamatonta.",
"traffic-rules:no-traffic-rules-detected": "Liikennesääntöjä ei havaittu",
"traffic-rules:target-on": "@.capitalize:direction-on",
"traffic-rules:xapi-plugin-required": "Vaatii XAPI-lisäosatilan",

View File

@@ -1131,6 +1131,8 @@
"power-on-mode": "Mode d'alimentation",
"power-on-vm-for-console": "Allumez votre VM pour accéder à sa console",
"power-state": "État d'alimentation",
"priority": "Priorité",
"priority-within": "Priorité dans {name}",
"pro-support": "Support pro {name}",
"professional-support": "Support professionnel",
"progress": "Progression",
@@ -1368,6 +1370,7 @@
"theme-solarized-description": "Couleurs précises pour réduire la fatigue oculaire",
"third-parties": "Apps tierces",
"this-host": "Cet hôte",
"this-rule": "Cette règle",
"this-vm-cant-be-migrated": "Cette VM ne peut pas être migrée",
"timeout": "Délai d'attente",
"timezone": "Fuseau horaire",
@@ -1395,8 +1398,9 @@
"traffic-rules:error-plugin:check-doc": "la documentation du contrôleur SDN",
"traffic-rules:format-warning": "Erreur de configuration : les règles de trafic ne sont pas au format attendu. Veuillez {check-doc} la plus récente pour savoir comment les migrer.",
"traffic-rules:format-warning:check-doc": "consulter la documentation",
"traffic-rules:info-message": "Les règles de trafic sont en cours d'application. Certains comportements peuvent être hasardeux.",
"traffic-rules:no-traffic-rules-detected": "Aucune règle de trafic détectée",
"traffic-rules:position-on-network": "{position} règle sur ce réseau.",
"traffic-rules:rule-ordinal-suffixes": "one:re, other:e",
"traffic-rules:target-on": "@.capitalize:direction-on",
"traffic-rules:xapi-plugin-required": "Nécessite le mode plugin XAPI",
"translation-tool": "Outil de traduction",

View File

@@ -1241,7 +1241,6 @@
"traffic-rules:error-plugin:check-doc": "la documentazione del controller SDN",
"traffic-rules:format-warning": "Errore di configurazione: le regole di traffico non sono nel formato previsto. {check-doc} su come migrarle.",
"traffic-rules:format-warning:check-doc": "Consulta la documentazione più recente",
"traffic-rules:info-message": "L'ordine delle regole di traffico è in fase di implementazione. Il comportamento potrebbe essere imprevedibile.",
"traffic-rules:no-traffic-rules-detected": "Nessuna regola di traffico rilevata",
"traffic-rules:target-on": "@.capitalize:direction-on",
"traffic-rules:xapi-plugin-required": "Richiede la modalità plugin XAPI",

View File

@@ -1241,7 +1241,6 @@
"traffic-rules:error-plugin:check-doc": "SDN コントローラーのドキュメント",
"traffic-rules:format-warning": "設定エラー: トラフィックルールが想定された形式ではありません。移行方法については{check-doc}。",
"traffic-rules:format-warning:check-doc": "最新のドキュメントを確認してください",
"traffic-rules:info-message": "トラフィックルールの順序は実装中です。動作が不安定な場合があります。",
"traffic-rules:no-traffic-rules-detected": "トラフィックルールは検出されませんでした",
"traffic-rules:target-on": "@.capitalize:direction-on",
"traffic-rules:xapi-plugin-required": "XAPI プラグインモードが必要です",

View File

@@ -1241,7 +1241,6 @@
"traffic-rules:error-plugin:check-doc": "SDN 컨트롤러 문서",
"traffic-rules:format-warning": "구성 오류: 트래픽 규칙이 예상된 형식이 아닙니다. 마이그레이션 방법은 {check-doc}.",
"traffic-rules:format-warning:check-doc": "최신 문서를 확인하세요",
"traffic-rules:info-message": "트래픽 규칙 순서 기능을 구현 중입니다. 동작이 불안정할 수 있습니다.",
"traffic-rules:no-traffic-rules-detected": "감지된 트래픽 규칙 없음",
"traffic-rules:target-on": "@.capitalize:direction-on",
"traffic-rules:xapi-plugin-required": "XAPI 플러그인 모드 필요",

View File

@@ -1241,7 +1241,6 @@
"traffic-rules:error-plugin:check-doc": "dokumentasjonen for SDN-kontrolleren",
"traffic-rules:format-warning": "Konfigurasjonsfeil: Trafikkreglene har ikke forventet format. Vennligst {check-doc} om hvordan du migrerer dem.",
"traffic-rules:format-warning:check-doc": "se den nyeste dokumentasjonen",
"traffic-rules:info-message": "Rekkefølge for trafikkregler er under implementering. Oppførselen kan være uforutsigbar.",
"traffic-rules:no-traffic-rules-detected": "Ingen trafikkregler oppdaget",
"traffic-rules:target-on": "@.capitalize:direction-on",
"traffic-rules:xapi-plugin-required": "Krever XAPI-plugin-modus",

View File

@@ -1171,7 +1171,6 @@
"traffic-rules:error-plugin:check-doc": "de documentatie van de SDN-controller",
"traffic-rules:format-warning": "Configuratiefout: De verkeersregels hebben niet de verwachte indeling. Raadpleeg {check-doc} voor informatie over hoe je ze kunt migreren.",
"traffic-rules:format-warning:check-doc": "raadpleeg de meest recente documentatie",
"traffic-rules:info-message": "De verkeersregels worden gehandhaafd. Het gedrag kan gevaarlijk zijn.",
"traffic-rules:no-traffic-rules-detected": "Er zijn geen verkeersregels gevonden",
"traffic-rules:xapi-plugin-required": "Vereist de XAPI-plug-in modus",
"translation-tool": "Vertaalprogramma",

View File

@@ -1241,7 +1241,6 @@
"traffic-rules:error-plugin:check-doc": "dokumentację kontrolera SDN",
"traffic-rules:format-warning": "Błąd konfiguracji: reguły ruchu nie są w oczekiwanym formacie. {check-doc}, jak je zmigrować.",
"traffic-rules:format-warning:check-doc": "Sprawdź w najnowszej dokumentacji",
"traffic-rules:info-message": "Kolejność reguł ruchu jest w trakcie implementacji. Działanie może być nieprzewidywalne.",
"traffic-rules:no-traffic-rules-detected": "Nie wykryto reguł ruchu",
"traffic-rules:target-on": "@.capitalize:direction-on",
"traffic-rules:xapi-plugin-required": "Wymaga trybu wtyczki XAPI",

View File

@@ -1241,7 +1241,6 @@
"traffic-rules:error-plugin:check-doc": "a documentação do controlador SDN",
"traffic-rules:format-warning": "Erro de configuração: as regras de tráfego não estão no formato esperado. Por favor, {check-doc} sobre como migrá-las.",
"traffic-rules:format-warning:check-doc": "consulte a documentação mais recente",
"traffic-rules:info-message": "A ordem das regras de tráfego está sendo implementada. O comportamento pode ser imprevisível.",
"traffic-rules:no-traffic-rules-detected": "Nenhuma regra de tráfego detectada",
"traffic-rules:target-on": "@.capitalize:direction-on",
"traffic-rules:xapi-plugin-required": "Requer o modo de plugin XAPI",

View File

@@ -1241,7 +1241,6 @@
"traffic-rules:error-plugin:check-doc": "документацию SDN-контроллера",
"traffic-rules:format-warning": "Ошибка конфигурации: правила для трафика имеют неожиданный формат. Пожалуйста, {check-doc} о том, как их мигрировать.",
"traffic-rules:format-warning:check-doc": "ознакомьтесь с последней документацией",
"traffic-rules:info-message": "Порядок правил для трафика находится в разработке. Поведение может быть непредсказуемым.",
"traffic-rules:no-traffic-rules-detected": "Правила для трафика не обнаружены",
"traffic-rules:target-on": "@.capitalize:direction-on",
"traffic-rules:xapi-plugin-required": "Требуется режим плагина XAPI",

View File

@@ -1171,7 +1171,6 @@
"traffic-rules:error-plugin:check-doc": "dokumentácia k radiču SDN siete",
"traffic-rules:format-warning": "Chyba nastavenia: Pravidlá pre prevádzku nie sú v očakávanom formáte. {check-doc} ohľadom toho, ako ich previesť.",
"traffic-rules:format-warning:check-doc": "Pozrite sa do najaktuálnejšej dokumentácie",
"traffic-rules:info-message": "Implementácia poradia pravidiel pre prevádzku ešte prebieha. Chovanie môže byť nebezpečné.",
"traffic-rules:no-traffic-rules-detected": "Nezjištěná žiadna pravidla pre prevádzku",
"traffic-rules:xapi-plugin-required": "Vyžaduje režim XAPI zásuvného modulu",
"translation-tool": "Prekladový nástroj",

View File

@@ -1090,7 +1090,6 @@
"total-used:": "Totalt använt:",
"traffic-rule": "Trafikregel",
"traffic-rules": "Trafikregler",
"traffic-rules:info-message": "Trafikreglernas ordning införs. Beteendet kan vara farligt.",
"traffic-rules:no-traffic-rules-detected": "Inga trafikregler upptäckta",
"translation-tool": "Översättningsverktyg",
"trigger": "Utlösare",

View File

@@ -1161,7 +1161,6 @@
"traffic-rules:error-plugin:check-doc": "SDN denetleyicisi belgelerine",
"traffic-rules:format-warning": "Yapılandırma hatası: Trafik kuralları beklenen biçimde değil. Bunları nasıl taşıyacağınızı öğrenmek için lütfen {check-doc}.",
"traffic-rules:format-warning:check-doc": "en son belgelere bakın",
"traffic-rules:info-message": "Trafik kurallarının sırası henüz uygulanıyor. Davranış riskli olabilir.",
"traffic-rules:no-traffic-rules-detected": "Trafik kuralı bulunamadı",
"traffic-rules:xapi-plugin-required": "XAPI eklenti modu gerekir",
"translation-tool": "Çeviri aracı",

View File

@@ -1241,7 +1241,6 @@
"traffic-rules:error-plugin:check-doc": "документацію контролера SDN",
"traffic-rules:format-warning": "Помилка конфігурації: правила трафіку мають неочікуваний формат. Будь ласка, {check-doc}, щоб дізнатися, як їх мігрувати.",
"traffic-rules:format-warning:check-doc": "перегляньте найновішу документацію",
"traffic-rules:info-message": "Порядок правил трафіку ще впроваджується. Поведінка може бути непередбачуваною.",
"traffic-rules:no-traffic-rules-detected": "Правил трафіку не виявлено",
"traffic-rules:target-on": "@.capitalize:direction-on",
"traffic-rules:xapi-plugin-required": "Потрібен режим плагіна XAPI",

View File

@@ -1171,7 +1171,6 @@
"traffic-rules:error-plugin:check-doc": "SDN控制器文档",
"traffic-rules:format-warning": "配置错误:流量规则格式异常,请参考{check-doc}完成迁移。",
"traffic-rules:format-warning:check-doc": "最新文档",
"traffic-rules:info-message": "流量规则顺序正在生效,行为可能存在异常。",
"traffic-rules:no-traffic-rules-detected": "未检测到流量规则",
"traffic-rules:xapi-plugin-required": "需要XAPI插件模式",
"translation-tool": "翻译工具",

View File

@@ -1,7 +1,7 @@
import { defineColumns } from '@core/packages/table/define-columns.ts'
import { useActionColumn } from '@core/tables/column-definitions/action-column.ts'
import { useDoubleLinkColumn } from '@core/tables/column-definitions/double-link-column.ts'
import { useNumberColumn } from '@core/tables/column-definitions/number-column.ts'
import { useLinkColumn } from '@core/tables/column-definitions/link-column.ts'
import { useTagColumn } from '@core/tables/column-definitions/tag-column.ts'
import { useTextColumn } from '@core/tables/column-definitions/text-column.ts'
import { useTruncatedTextColumn } from '@core/tables/column-definitions/truncated-text-column.ts'
@@ -11,7 +11,8 @@ export const useTrafficRulesColumns = defineColumns(() => {
const { t } = useI18n()
return {
order: useNumberColumn(),
network: useLinkColumn({ headerLabel: () => t('network') }),
priority: useTextColumn({ headerLabel: () => t('priority') }),
policy: useTagColumn({ headerLabel: () => t('policy') }),
protocol: useTextColumn({
headerLabel: () => t('protocol:port'),

View File

@@ -6,11 +6,10 @@
<slot name="title-action" />
</template>
</UiTitle>
<UiAlert accent="info">{{ t('traffic-rules:info-message') }}</UiAlert>
<UiAlert v-if="showRulesFormatWarning" accent="warning">
<I18nT keypath="traffic-rules:format-warning" scope="global">
<template #check-doc>
<UiLink size="small" :href="XO_LINKS.DOC_SDN_CONTROLLER_MIGRATION">
<UiLink :href="XO_LINKS.DOC_SDN_CONTROLLER_MIGRATION" size="small">
{{ t('traffic-rules:format-warning:check-doc') }}
</UiLink>
</template>
@@ -19,7 +18,7 @@
<UiAlert v-if="showXapiPluginRequiredError" accent="danger">
<I18nT keypath="traffic-rules:error-plugin" scope="global">
<template #check-doc>
<UiLink size="small" :href="XO_LINKS.DOC_SDN_CONTROLLER_XAPI_PLUGIN">
<UiLink :href="XO_LINKS.DOC_SDN_CONTROLLER_XAPI_PLUGIN" size="small">
{{ t('traffic-rules:error-plugin:check-doc') }}
</UiLink>
</template>
@@ -27,18 +26,14 @@
</UiAlert>
<VtsQueryBuilder :model-value="filter" :schema @search="filter = $event" />
<div class="container">
<VtsTable :state :pagination-bindings sticky="right">
<VtsTable :pagination-bindings :state sticky="right">
<thead>
<tr>
<HeadCells />
</tr>
</thead>
<tbody>
<VtsRow
v-for="rule of paginatedRules"
:key="`${rule.id}:${rule.order}`"
:selected="selectedRuleId === rule.id"
>
<VtsRow v-for="rule of paginatedRules" :key="rule.id" :selected="selectedRuleId === rule.id">
<BodyCells :item="rule" />
</VtsRow>
</tbody>
@@ -47,13 +42,16 @@
</div>
</template>
<script setup lang="ts">
<script lang="ts" setup>
import { useXoNetworkCollection } from '@/modules/network/remote-resources/use-xo-network-collection.ts'
import { getPoolNetworkRoute } from '@/modules/network/utils/xo-network.util.ts'
import type { FrontXoPool } from '@/modules/pool/remote-resources/use-xo-pool-collection.ts'
import { useDirectionLabels } from '@/modules/traffic-rules/composables/direction-labels.composable.ts'
import { useTrafficRuleTarget } from '@/modules/traffic-rules/composables/traffic-rule-target.composable.ts'
import { useTrafficRuleDelete } from '@/modules/traffic-rules/composables/use-traffic-rule-delete.composable.ts'
import { useTrafficRuleEdit } from '@/modules/traffic-rules/composables/use-traffic-rule-edit.composable.ts'
import type { EnrichedTrafficRule } from '@/modules/traffic-rules/types.ts'
import type { EnrichedTrafficRule, RankedTrafficRule } from '@/modules/traffic-rules/types.ts'
import { getNetworkRules, getTrafficRuleProtocolLabel } from '@/modules/traffic-rules/utils/xo-traffic-rule.util.ts'
import { XO_LINKS } from '@/shared/constants.ts'
import VtsQueryBuilder from '@core/components/query-builder/VtsQueryBuilder.vue'
import VtsRow from '@core/components/table/VtsRow.vue'
@@ -70,7 +68,7 @@ import { useTrafficRulesColumns } from '@core/tables/column-sets/traffic-rules-c
import { useBooleanSchema } from '@core/utils/query-builder/use-boolean-schema.ts'
import { useNumberSchema } from '@core/utils/query-builder/use-number-schema.ts'
import { useStringSchema } from '@core/utils/query-builder/use-string-schema.ts'
import { SDN_CONTROLLER_OF_FORMAT_KEY, SDN_CONTROLLER_OF_METHOD_KEY, type TrafficRule } from '@vates/types'
import { SDN_CONTROLLER_OF_FORMAT_KEY, SDN_CONTROLLER_OF_METHOD_KEY } from '@vates/types'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
@@ -81,12 +79,14 @@ const {
busy,
error,
showXapiPluginRequiredError,
showPriority,
} = defineProps<{
rules: TrafficRule[]
rules: RankedTrafficRule[]
pool?: FrontXoPool
busy?: boolean
error?: boolean
showXapiPluginRequiredError?: boolean
showPriority?: boolean
}>()
defineSlots<{
@@ -101,6 +101,8 @@ const getTarget = useTrafficRuleTarget()
const getDirectionLabels = useDirectionLabels()
const { getNetworkById, useGetNetworkById } = useXoNetworkCollection()
const showRulesFormatWarning = computed(() => {
const ofMethod = pool?.otherConfig[SDN_CONTROLLER_OF_METHOD_KEY]
const ofFormat = pool?.otherConfig[SDN_CONTROLLER_OF_FORMAT_KEY]
@@ -108,13 +110,16 @@ const showRulesFormatWarning = computed(() => {
})
const enrichedRules = computed(() =>
rawRules.map((rule, index) => {
rawRules.map(rule => {
const target = getTarget(rule)
const [directionA, directionB] = getDirectionLabels(rule)
const network = getNetworkById(rule.networkId)
const networkRules = getNetworkRules(rawRules, rule.networkId)
return {
...rule,
order: index + 1,
networkLabel: network?.name_label ?? '',
networkRuleCount: networkRules.length,
directionA,
directionB,
directionLabel: `${directionA} ${directionB}`,
@@ -128,6 +133,8 @@ const { pageRecords: paginatedRules, paginationBindings } = usePagination('traff
const schema = useQueryBuilderSchema<EnrichedTrafficRule>({
'': useStringSchema(t('any-property')),
networkLabel: useStringSchema(t('network')),
rank: showPriority ? useNumberSchema(t('priority')) : undefined,
allow: useBooleanSchema(t('policy'), {
true: t('allow'),
false: t('drop'),
@@ -151,16 +158,29 @@ const state = useTableState({
})
const { HeadCells, BodyCells } = useTrafficRulesColumns({
exclude: showPriority ? [] : ['priority'],
body: (rule: EnrichedTrafficRule) => {
const network = useGetNetworkById(() => rule.networkId)
const networkTo = computed(() =>
network.value ? getPoolNetworkRoute(network.value.$pool, network.value.id) : undefined
)
const { deleteTrafficRules, canDeleteTrafficRules, isDeletingTrafficRules, deleteTrafficRulesErrorMessage } =
useTrafficRuleDelete(() => [rule])
const { editTrafficRule, canEditTrafficRule, isEditingTrafficRule } = useTrafficRuleEdit(() => rule)
return {
order: r => r(rule.order),
network: r =>
r({
label: rule.networkLabel,
icon: 'object:network',
to: networkTo.value,
}),
priority: r => r(`${rule.rank}/${rule.networkRuleCount}`),
policy: r => r(t(rule.allow ? 'allow' : 'drop'), rule.allow ? 'success' : 'danger'),
protocol: r => r(rule.port ? `${rule.protocol}:${rule.port}` : rule.protocol),
protocol: r => r(getTrafficRuleProtocolLabel(rule)),
directionA: r => r(rule.directionA),
target: r => r(rule.ipRange),
directionB: r => r(rule.directionB),
@@ -192,7 +212,7 @@ const { HeadCells, BodyCells } = useTrafficRulesColumns({
})
</script>
<style scoped lang="postcss">
<style lang="postcss" scoped>
.traffic-rules-table,
.container {
display: flex;

View File

@@ -5,7 +5,7 @@
</template>
<template v-if="rule" #default>
<VtsStateHero v-if="!isReady" format="panel" type="busy" size="medium" />
<VtsStateHero v-if="!isReady" format="panel" size="medium" type="busy" />
<template v-else>
<TrafficRuleSummaryCard :rule />
@@ -15,30 +15,37 @@
</template>
<template v-else-if="network">
<TrafficRuleNetworkInfosCard :rule :network />
<TrafficRuleNetworkPifsCard :rule :network />
<TrafficRuleNetworkInfosCard :network :rule />
<TrafficRuleNetworkPifsCard :network :rule />
</template>
<TrafficRulePriorityCard v-if="showPriority && network" :network :rule :rules />
</template>
</template>
</VtsSidePanel>
</template>
<script setup lang="ts">
<script lang="ts" setup>
import { useXoNetworkCollection } from '@/modules/network/remote-resources/use-xo-network-collection.ts'
import TrafficRuleActions from '@/modules/traffic-rules/components/actions/TrafficRuleActions.vue'
import TrafficRuleNetworkInfosCard from '@/modules/traffic-rules/components/list/panel/cards/TrafficRuleNetworkInfosCard.vue'
import TrafficRuleNetworkPifsCard from '@/modules/traffic-rules/components/list/panel/cards/TrafficRuleNetworkPifsCard.vue'
import TrafficRulePriorityCard from '@/modules/traffic-rules/components/list/panel/cards/TrafficRulePriorityCard.vue'
import TrafficRuleSummaryCard from '@/modules/traffic-rules/components/list/panel/cards/TrafficRuleSummaryCard.vue'
import TrafficRuleVifInfosCard from '@/modules/traffic-rules/components/list/panel/cards/TrafficRuleVifInfosCard.vue'
import TrafficRuleVifNetworkInfoCard from '@/modules/traffic-rules/components/list/panel/cards/TrafficRuleVifNetworkInfoCard.vue'
import type { RankedTrafficRule } from '@/modules/traffic-rules/types.ts'
import { useXoVifCollection } from '@/modules/vif/remote-resources/use-xo-vif-collection.ts'
import VtsSidePanel from '@core/components/panel/VtsSidePanel.vue'
import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
import type { TrafficRule } from '@vates/types'
import { logicAnd } from '@vueuse/math'
import { computed } from 'vue'
const { rule } = defineProps<{ rule?: TrafficRule }>()
const { rule, rules, showPriority } = defineProps<{
rule?: RankedTrafficRule
rules: RankedTrafficRule[]
showPriority?: boolean
}>()
const emit = defineEmits<{
close: []

View File

@@ -0,0 +1,177 @@
<template>
<UiPanelCard class="traffic-rule-priority-card">
<UiPanelCardTitle size="medium">
<template #label>
<I18nT class="title" keypath="priority-within" scope="global" tag="span">
<template #name>
<UiLink :to="networkTo" icon="object:network" size="small">
{{ network.name_label }}
</UiLink>
</template>
</I18nT>
</template>
</UiPanelCardTitle>
<I18nT class="position typo-body-regular-small" keypath="traffic-rules:position-on-network" scope="global" tag="p">
<template #position>
<span class="typo-body-bold-small"
>{{ rule.rank }}<sup v-if="ordinalSuffix">{{ ordinalSuffix }}</sup></span
>
</template>
</I18nT>
<ul class="rules">
<li
v-for="{ networkRule, protocolLabel } of visibleNetworkRuleItems"
:key="networkRule.id"
:class="{ current: networkRule.id === rule.id }"
class="rule typo-body-regular-small"
>
<span class="index typo-body-bold-small">{{ networkRule.rank }}</span>
<UiTag :accent="networkRule.allow ? 'success' : 'danger'" variant="secondary">
{{ networkRule.allow ? t('allow') : t('drop') }}
</UiTag>
<span class="typo-body-bold-small">{{ protocolLabel }}</span>
<span>{{ networkRule.ipRange }}</span>
<span v-if="networkRule.id === rule.id" class="this-rule typo-body-bold-small">{{ t('this-rule') }}</span>
</li>
</ul>
<UiButton
v-if="hasHiddenRules"
accent="brand"
class="toggle"
size="small"
variant="tertiary"
@click="isExpanded = !isExpanded"
>
{{ isExpanded ? t('action:see-less') : t('action:see-all') }}
</UiButton>
</UiPanelCard>
</template>
<script lang="ts" setup>
import type { FrontXoNetwork } from '@/modules/network/remote-resources/use-xo-network-collection.ts'
import { getPoolNetworkRoute } from '@/modules/network/utils/xo-network.util.ts'
import type { RankedTrafficRule } from '@/modules/traffic-rules/types.ts'
import {
getNetworkRules,
getOrdinalSuffix,
getTrafficRuleProtocolLabel,
} from '@/modules/traffic-rules/utils/xo-traffic-rule.util.ts'
import UiButton from '@core/components/ui/button/UiButton.vue'
import UiLink from '@core/components/ui/link/UiLink.vue'
import UiPanelCard from '@core/components/ui/panel-card/UiPanelCard.vue'
import UiPanelCardTitle from '@core/components/ui/panel-card-title/UiPanelCardTitle.vue'
import UiTag from '@core/components/ui/tag/UiTag.vue'
import { FALLBACK_LOCALE } from '@core/i18n.ts'
import { computed, ref, watch } from 'vue'
import { I18nT, useI18n } from 'vue-i18n'
const { rule, rules, network } = defineProps<{
rule: RankedTrafficRule
rules: RankedTrafficRule[]
network: FrontXoNetwork
}>()
const VISIBLE_RULES_COUNT = 5
const { t, te, locale } = useI18n()
const networkTo = computed(() => getPoolNetworkRoute(network.$pool, network.id))
const ordinalSuffix = computed(() => {
const suffixesKey = 'traffic-rules:rule-ordinal-suffixes'
let messageLocale = FALLBACK_LOCALE
if (te('traffic-rules:position-on-network', locale.value)) {
messageLocale = locale.value
}
if (!te(suffixesKey, messageLocale)) {
return undefined
}
return getOrdinalSuffix(t(suffixesKey, {}, { locale: messageLocale }), messageLocale, rule.rank)
})
const networkRules = computed(() => getNetworkRules(rules, rule.networkId))
const networkRuleItems = computed(() =>
networkRules.value.map(networkRule => ({
networkRule,
protocolLabel: getTrafficRuleProtocolLabel(networkRule),
}))
)
const isExpanded = ref(false)
watch(
() => rule.id,
() => {
isExpanded.value = false
}
)
const hasHiddenRules = computed(() => networkRules.value.length > VISIBLE_RULES_COUNT)
const visibleNetworkRuleItems = computed(() => {
if (isExpanded.value || !hasHiddenRules.value) {
return networkRuleItems.value
}
const lastStart = networkRules.value.length - VISIBLE_RULES_COUNT
const centeredStart = rule.rank - 1 - Math.floor(VISIBLE_RULES_COUNT / 2)
const start = Math.min(Math.max(centeredStart, 0), lastStart)
return networkRuleItems.value.slice(start, start + VISIBLE_RULES_COUNT)
})
</script>
<style lang="postcss" scoped>
.traffic-rule-priority-card {
.title {
display: inline-flex;
align-items: center;
gap: 1.6rem;
}
.position {
color: var(--color-neutral-txt-secondary);
}
.rules {
display: flex;
flex-direction: column;
gap: 0.8rem;
}
.rule {
line-height: 1;
display: flex;
align-items: center;
gap: 1.2rem;
margin-inline: -1.6rem;
padding: 0.4rem 1.6rem;
&.current {
background-color: var(--color-brand-background-selected);
}
}
.index {
color: var(--color-brand-txt-base);
}
.this-rule {
margin-inline-start: auto;
text-align: right;
color: var(--color-brand-txt-base);
}
.toggle {
align-self: start;
}
}
</style>

View File

@@ -17,7 +17,7 @@
<template #value>
<div class="value-content">
<VtsStatus :status="policy" icon-only />
{{ rule.port ? `${rule.protocol}:${rule.port}` : rule.protocol }}
{{ protocolLabel }}
{{ direction.labelA }} {{ rule.ipRange }} {{ direction.labelB }}
<template v-if="vif">
<UiLink
@@ -53,6 +53,7 @@ import { useXoNetworkCollection } from '@/modules/network/remote-resources/use-x
import { getPoolNetworkRoute } from '@/modules/network/utils/xo-network.util.ts'
import { useDirectionLabels } from '@/modules/traffic-rules/composables/direction-labels.composable.ts'
import { useTrafficRuleTarget } from '@/modules/traffic-rules/composables/traffic-rule-target.composable.ts'
import { getTrafficRuleProtocolLabel } from '@/modules/traffic-rules/utils/xo-traffic-rule.util.ts'
import { useXoVifCollection } from '@/modules/vif/remote-resources/use-xo-vif-collection.ts'
import { useXoVmCollection } from '@/modules/vm/remote-resources/use-xo-vm-collection.ts'
import { RULE_STATUS } from '@/shared/constants.ts'
@@ -107,11 +108,18 @@ const direction = computed(() => {
return { labelA, labelB }
})
const protocolLabel = computed(() => getTrafficRuleProtocolLabel(rule))
const description = computed(() => {
const policyLabel = rule.allow ? t('allow') : t('drop')
const protocol = rule.port ? `${rule.protocol}:${rule.port}` : rule.protocol
const { label, suffix } = target.value
const descriptionParts = [policyLabel, protocol, direction.value.labelA, rule.ipRange, direction.value.labelB]
const descriptionParts = [
policyLabel,
protocolLabel.value,
direction.value.labelA,
rule.ipRange,
direction.value.labelB,
]
if (label) {
descriptionParts.push(label)

View File

@@ -1,4 +1,9 @@
import type { FrontXoNetwork } from '@/modules/network/remote-resources/use-xo-network-collection.ts'
import {
type FrontXoNetwork,
useXoNetworkCollection,
} from '@/modules/network/remote-resources/use-xo-network-collection.ts'
import type { RankedTrafficRule } from '@/modules/traffic-rules/types.ts'
import { rankTrafficRules } from '@/modules/traffic-rules/utils/xo-traffic-rule.util.ts'
import type { FrontXoVif } from '@/modules/vif/remote-resources/use-xo-vif-collection.ts'
import { useXoVmCollection } from '@/modules/vm/remote-resources/use-xo-vm-collection.ts'
import { toComputed } from '@core/utils/to-computed.util.ts'
@@ -22,12 +27,10 @@ export function useTrafficRules(
const { getVmById } = useXoVmCollection()
const trafficRules = computed<TrafficRule[]>(() => {
const sortedNetworks = [...networks.value].sort((networkA, networkB) =>
(networkA.name_label ?? '').localeCompare(networkB.name_label ?? '')
)
const { getNetworkById } = useXoNetworkCollection()
const sortedVifs = [...vifs.value]
const trafficRules = computed<RankedTrafficRule[]>(() => {
const sortedVifs = vifs.value
.filter(vif => getVmById(vif.$VM))
.sort((vifA, vifB) => {
const vmNameLabelA = getVmById(vifA.$VM)?.name_label ?? ''
@@ -41,8 +44,8 @@ export function useTrafficRules(
return Number(vifA.device) - Number(vifB.device)
})
return [
...sortedNetworks.flatMap(network =>
const rules: TrafficRule[] = [
...networks.value.flatMap(network =>
parseRules(network.other_config?.[SDN_CONTROLLER_OF_RULES_KEY]).map((rule, index) => ({
...rule,
id: `network:${network.id}:${index}`,
@@ -61,6 +64,8 @@ export function useTrafficRules(
}))
),
]
return rankTrafficRules(rules, networkId => getNetworkById(networkId)?.name_label ?? '')
})
return { trafficRules }

View File

@@ -29,6 +29,10 @@ export const useXoTrafficRuleDeleteJob = defineJob('traffic-rule.delete', [xoTra
body.port = rule.port
}
if (rule.priority !== undefined) {
body.priority = rule.priority
}
if (rule.type === 'VIF') {
body.vifId = rule.sourceId
} else {

View File

@@ -29,6 +29,7 @@ export const useXoTrafficRuleEditJob = defineJob(
ipRange: oldRule.ipRange,
protocol: oldRule.protocol,
port: oldRule.port !== undefined ? Number(oldRule.port) : undefined,
priority: oldRule.priority,
},
newRule,
})

View File

@@ -1,7 +1,12 @@
import type { TrafficRule } from '@vates/types'
export type EnrichedTrafficRule = TrafficRule & {
order: number
export type RankedTrafficRule = TrafficRule & {
rank: number
}
export type EnrichedTrafficRule = RankedTrafficRule & {
networkLabel: string
networkRuleCount: number
directionA: string
directionB: string
directionLabel: string

View File

@@ -1,8 +1,81 @@
import type { FrontXoNetwork } from '@/modules/network/remote-resources/use-xo-network-collection.ts'
import type { FrontXoPool } from '@/modules/pool/remote-resources/use-xo-pool-collection.ts'
import { SDN_CONTROLLER_OF_METHOD_KEY } from '@vates/types'
import type { RankedTrafficRule } from '@/modules/traffic-rules/types.ts'
import { SDN_CONTROLLER_OF_METHOD_KEY, type TrafficRule } from '@vates/types'
// OpenFlow default, applied to rules stored without a priority
const DEFAULT_TRAFFIC_RULE_PRIORITY = 32768
export function isNetworkRuleSupported(pool: FrontXoPool | undefined): boolean {
const ofMethod = pool?.otherConfig[SDN_CONTROLLER_OF_METHOD_KEY]
return ofMethod === 'xapi-plugin'
}
export function getTrafficRulePriority(rule: TrafficRule): number {
if (rule.priority === undefined) {
return DEFAULT_TRAFFIC_RULE_PRIORITY
}
return rule.priority
}
export function getTrafficRuleProtocolLabel(rule: TrafficRule): string {
if (rule.port === undefined) {
return rule.protocol
}
return `${rule.protocol}:${rule.port}`
}
export function getNetworkRules<TRule extends TrafficRule>(rules: TRule[], networkId: FrontXoNetwork['id']): TRule[] {
return rules.filter(rule => rule.networkId === networkId)
}
export function rankTrafficRules(
rules: TrafficRule[],
getNetworkLabel: (networkId: FrontXoNetwork['id']) => string
): RankedTrafficRule[] {
const sortedRules = [...rules].sort((ruleA, ruleB) => {
const networkComparison = getNetworkLabel(ruleA.networkId).localeCompare(getNetworkLabel(ruleB.networkId))
if (networkComparison !== 0) {
return networkComparison
}
if (ruleA.networkId !== ruleB.networkId) {
return ruleA.networkId.localeCompare(ruleB.networkId)
}
return getTrafficRulePriority(ruleB) - getTrafficRulePriority(ruleA)
})
const lastRankByNetworkId = new Map<FrontXoNetwork['id'], number>()
return sortedRules.map(rule => {
const lastRank = lastRankByNetworkId.get(rule.networkId)
let rank = 1
if (lastRank !== undefined) {
rank = lastRank + 1
}
lastRankByNetworkId.set(rule.networkId, rank)
return { ...rule, rank }
})
}
export function getOrdinalSuffix(suffixesMessage: string, locale: string, position: number): string | undefined {
const suffixes = new Map<string, string>()
for (const entry of suffixesMessage.split(',')) {
const [category, suffix] = entry.trim().split(':')
suffixes.set(category, suffix)
}
const category = new Intl.PluralRules(locale, { type: 'ordinal' }).select(position)
return suffixes.get(category)
}

View File

@@ -0,0 +1,167 @@
import type { FrontXoNetwork } from '@/modules/network/remote-resources/use-xo-network-collection.ts'
import {
getNetworkRules,
getOrdinalSuffix,
getTrafficRulePriority,
getTrafficRuleProtocolLabel,
rankTrafficRules,
} from '@/modules/traffic-rules/utils/xo-traffic-rule.util.ts'
import type { FrontXoVif } from '@/modules/vif/remote-resources/use-xo-vif-collection.ts'
import { createTrafficRule } from '@/test/create-traffic-rule.ts'
const EN_SUFFIXES = 'one:st, two:nd, few:rd, other:th'
const FR_SUFFIXES = 'one:re, other:e'
describe('getTrafficRulePriority', () => {
it('returns the priority of the rule', () => {
expect(getTrafficRulePriority(createTrafficRule({ priority: 65000 }))).toBe(65000)
})
it('keeps a priority of 0', () => {
expect(getTrafficRulePriority(createTrafficRule({ priority: 0 }))).toBe(0)
})
it('defaults to the OpenFlow priority when the rule has none', () => {
expect(getTrafficRulePriority(createTrafficRule({ priority: undefined }))).toBe(32768)
})
})
describe('getTrafficRuleProtocolLabel', () => {
it('returns the protocol alone when the rule has no port', () => {
expect(getTrafficRuleProtocolLabel(createTrafficRule({ protocol: 'ICMP', port: undefined }))).toBe('ICMP')
})
it('appends the port to the protocol', () => {
expect(getTrafficRuleProtocolLabel(createTrafficRule({ protocol: 'TCP', port: '443' }))).toBe('TCP:443')
})
})
describe('getNetworkRules', () => {
const networkA = 'network-a' as FrontXoNetwork['id']
const networkB = 'network-b' as FrontXoNetwork['id']
const ruleA1 = createTrafficRule({ id: 'a1', networkId: networkA })
const ruleB1 = createTrafficRule({ id: 'b1', networkId: networkB })
const ruleA2 = createTrafficRule({ id: 'a2', networkId: networkA })
it('keeps only the rules of the given network, in their order', () => {
expect(getNetworkRules([ruleA1, ruleB1, ruleA2], networkA)).toEqual([ruleA1, ruleA2])
})
it('returns an empty list when the network has no rule', () => {
expect(getNetworkRules([ruleA1, ruleA2], networkB)).toEqual([])
})
})
describe('rankTrafficRules', () => {
const lanNetwork = 'network-1' as FrontXoNetwork['id']
const dmzNetwork = 'network-2' as FrontXoNetwork['id']
const otherLanNetwork = 'network-3' as FrontXoNetwork['id']
const networkLabels = new Map<FrontXoNetwork['id'], string>([
[lanNetwork, 'LAN'],
[dmzNetwork, 'DMZ'],
[otherLanNetwork, 'LAN'],
])
const getNetworkLabel = (networkId: FrontXoNetwork['id']) => networkLabels.get(networkId) ?? ''
it('sorts by descending priority within a network', () => {
const low = createTrafficRule({ id: 'low', priority: 100 })
const high = createTrafficRule({ id: 'high', priority: 200 })
expect(rankTrafficRules([low, high], getNetworkLabel).map(rule => rule.id)).toEqual(['high', 'low'])
})
it('places rules without priority at the default priority', () => {
const above = createTrafficRule({ id: 'above', priority: 40000 })
const none = createTrafficRule({ id: 'none', priority: undefined })
const below = createTrafficRule({ id: 'below', priority: 100 })
expect(rankTrafficRules([below, none, above], getNetworkLabel).map(rule => rule.id)).toEqual([
'above',
'none',
'below',
])
})
it('keeps input order for rules with equal priority', () => {
const networkRule = createTrafficRule({ id: 'network' })
const vifRuleA = createTrafficRule({ id: 'vif-a', type: 'VIF', sourceId: 'vif-a' as FrontXoVif['id'] })
const vifRuleB = createTrafficRule({ id: 'vif-b', type: 'VIF', sourceId: 'vif-b' as FrontXoVif['id'] })
expect(rankTrafficRules([networkRule, vifRuleA, vifRuleB], getNetworkLabel).map(rule => rule.id)).toEqual([
'network',
'vif-a',
'vif-b',
])
})
it('gives distinct ranks to rules with equal priority', () => {
const rules = [createTrafficRule({ id: 'a' }), createTrafficRule({ id: 'b' })]
expect(rankTrafficRules(rules, getNetworkLabel).map(rule => rule.rank)).toEqual([1, 2])
})
it('groups rules by network label and restarts ranks per network', () => {
const lan = createTrafficRule({ id: 'lan', networkId: lanNetwork })
const dmz = createTrafficRule({ id: 'dmz', networkId: dmzNetwork })
expect(rankTrafficRules([lan, dmz], getNetworkLabel).map(rule => ({ id: rule.id, rank: rule.rank }))).toEqual([
{ id: 'dmz', rank: 1 },
{ id: 'lan', rank: 1 },
])
})
it('does not interleave two networks with the same label', () => {
const rules = [
createTrafficRule({ id: 'n1-low', networkId: lanNetwork, priority: 100 }),
createTrafficRule({ id: 'n3-high', networkId: otherLanNetwork, priority: 300 }),
createTrafficRule({ id: 'n1-high', networkId: lanNetwork, priority: 200 }),
]
expect(rankTrafficRules(rules, getNetworkLabel).map(rule => rule.id)).toEqual(['n1-high', 'n1-low', 'n3-high'])
})
it('does not mutate the input array', () => {
const rules = [createTrafficRule({ id: 'low', priority: 100 }), createTrafficRule({ id: 'high', priority: 200 })]
rankTrafficRules(rules, getNetworkLabel)
expect(rules.map(rule => rule.id)).toEqual(['low', 'high'])
})
})
describe('getOrdinalSuffix', () => {
it.each([
[1, 'st'],
[2, 'nd'],
[3, 'rd'],
[4, 'th'],
[11, 'th'],
[12, 'th'],
[13, 'th'],
[21, 'st'],
[22, 'nd'],
[23, 'rd'],
[101, 'st'],
])('returns the English suffix of %i', (position, suffix) => {
expect(getOrdinalSuffix(EN_SUFFIXES, 'en', position)).toBe(suffix)
})
it.each([
[1, 're'],
[2, 'e'],
[21, 'e'],
])('returns the French suffix of %i', (position, suffix) => {
expect(getOrdinalSuffix(FR_SUFFIXES, 'fr', position)).toBe(suffix)
})
it('ignores the spaces around the entries', () => {
expect(getOrdinalSuffix(' one:st ,other:th ', 'en', 1)).toBe('st')
})
it('returns undefined when the message has no suffix for the plural category', () => {
expect(getOrdinalSuffix('other:th', 'en', 1)).toBeUndefined()
})
})

View File

@@ -2,7 +2,7 @@
<VtsContentSidePanel class="traffic-rules">
<UiCard class="container">
<VtsStateHero v-if="!isReady" format="page" type="busy" size="medium" />
<TrafficRulesTable v-else :rules="trafficRules" :pool :show-xapi-plugin-required-error>
<TrafficRulesTable v-else :rules="trafficRules" :pool :show-xapi-plugin-required-error show-priority>
<template #title-action>
<UiLink :to="{ name: '/traffic-rule/new', query: { poolid: pool.id } }" icon="fa:plus" size="medium">
{{ t('new') }}
@@ -10,7 +10,7 @@
</template>
</TrafficRulesTable>
</UiCard>
<TrafficRulesSidePanel :rule="selectedRule" @close="selectedRule = undefined" />
<TrafficRulesSidePanel :rule="selectedRule" :rules="trafficRules" show-priority @close="selectedRule = undefined" />
</VtsContentSidePanel>
</template>
@@ -20,6 +20,7 @@ import type { FrontXoPool } from '@/modules/pool/remote-resources/use-xo-pool-co
import TrafficRulesSidePanel from '@/modules/traffic-rules/components/list/panel/TrafficRulesSidePanel.vue'
import TrafficRulesTable from '@/modules/traffic-rules/components/TrafficRulesTable.vue'
import { useTrafficRules } from '@/modules/traffic-rules/composables/traffic-rules.composable.ts'
import type { RankedTrafficRule } from '@/modules/traffic-rules/types.ts'
import { isNetworkRuleSupported } from '@/modules/traffic-rules/utils/xo-traffic-rule.util.ts'
import { useXoVifCollection } from '@/modules/vif/remote-resources/use-xo-vif-collection.ts'
import { useXoVmCollection } from '@/modules/vm/remote-resources/use-xo-vm-collection.ts'
@@ -28,7 +29,6 @@ import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
import UiCard from '@core/components/ui/card/UiCard.vue'
import UiLink from '@core/components/ui/link/UiLink.vue'
import { useRouteQuery } from '@core/composables/route-query.composable.ts'
import type { TrafficRule } from '@vates/types'
import { logicAnd } from '@vueuse/math'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
@@ -55,7 +55,7 @@ const poolNetworks = computed(() => networks.value.filter(network => network.$po
const { trafficRules } = useTrafficRules(poolVifs, poolNetworks)
const selectedRule = useRouteQuery<TrafficRule | undefined>('id', {
const selectedRule = useRouteQuery<RankedTrafficRule | undefined>('id', {
toData: id => trafficRules.value.find(rule => rule.id === id),
toQuery: rule => rule?.id ?? '',
})

View File

@@ -10,7 +10,7 @@
</template>
</TrafficRulesTable>
</UiCard>
<TrafficRulesSidePanel :rule="selectedRule" @close="selectedRule = undefined" />
<TrafficRulesSidePanel :rule="selectedRule" :rules="trafficRules" @close="selectedRule = undefined" />
</VtsContentSidePanel>
</template>
@@ -19,6 +19,7 @@ import { useXoPoolCollection } from '@/modules/pool/remote-resources/use-xo-pool
import TrafficRulesSidePanel from '@/modules/traffic-rules/components/list/panel/TrafficRulesSidePanel.vue'
import TrafficRulesTable from '@/modules/traffic-rules/components/TrafficRulesTable.vue'
import { useTrafficRules } from '@/modules/traffic-rules/composables/traffic-rules.composable.ts'
import type { RankedTrafficRule } from '@/modules/traffic-rules/types.ts'
import { type FrontXoVif } from '@/modules/vif/remote-resources/use-xo-vif-collection.ts'
import { useXoVmCollection } from '@/modules/vm/remote-resources/use-xo-vm-collection.ts'
import VtsContentSidePanel from '@core/components/layout/VtsContentSidePanel.vue'
@@ -26,7 +27,6 @@ import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
import UiCard from '@core/components/ui/card/UiCard.vue'
import UiLink from '@core/components/ui/link/UiLink.vue'
import { useRouteQuery } from '@core/composables/route-query.composable.ts'
import type { TrafficRule } from '@vates/types'
import { useI18n } from 'vue-i18n'
const { vif } = defineProps<{ vif: FrontXoVif }>()
@@ -40,7 +40,7 @@ const { areVmsReady } = useXoVmCollection()
const { trafficRules } = useTrafficRules(() => [vif], [])
const selectedRule = useRouteQuery<TrafficRule | undefined>('id', {
const selectedRule = useRouteQuery<RankedTrafficRule | undefined>('id', {
toData: id => trafficRules.value.find(rule => rule.id === id),
toQuery: rule => rule?.id ?? '',
})

View File

@@ -0,0 +1,19 @@
import type { TrafficRule, XoNetwork } from '@vates/types'
/**
* Builds a fully-populated network `TrafficRule` for use in tests. Pass `overrides` to
* tweak only the fields relevant to the case under test.
*/
export function createTrafficRule(overrides: Partial<TrafficRule> = {}): TrafficRule {
return {
id: 'network:network-1:0',
type: 'network',
sourceId: 'network-1' as XoNetwork['id'],
networkId: 'network-1' as XoNetwork['id'],
allow: true,
protocol: 'TCP',
ipRange: '192.168.1.0/24',
direction: 'from/to',
...overrides,
} as TrafficRule
}

View File

@@ -15,12 +15,13 @@
- [Side panels] Values stay next to their label when they fit and move below it otherwise, fields with several values (IP addresses, report recipients…) get a copy button per value and a "Copy all" action, and rows, links and statuses are more evenly spaced and sized (PR [#10733](https://github.com/vatesfr/xen-orchestra/pull/10733))
- [Treeview] A more compact tree, so that names get more room: lower rows running edge to edge and a smaller indent per level; hovering a quick actions button no longer highlights its whole row (PR [#10727](https://github.com/vatesfr/xen-orchestra/pull/10727))
- [Pool/Network] Add network dedicated page, with General and PIFs tabs (PR [#10704](https://github.com/vatesfr/xen-orchestra/pull/10704))
- [Pool/Traffic rules] Display traffic rules by priority within their network (PR [#10688](https://github.com/vatesfr/xen-orchestra/pull/10688))
### Bug fixes
> Users must be able to say: "I had this issue, happy to know it's fixed"
- [XO6/Pools] Pool side panel now shows the same pool name, UUID and icon as the pools table, and keeps the pool card when the pool is disconnected or unreachable (PR [#10714](https://github.com/vatesfr/xen-orchestra/pull/10714))
- [Pools] Pool side panel now shows the same pool name, UUID and icon as the pools table, and keeps the pool card when the pool is disconnected or unreachable (PR [#10714](https://github.com/vatesfr/xen-orchestra/pull/10714))
### Packages to release