mirror of
https://github.com/vatesfr/xen-orchestra.git
synced 2026-11-09 17:06:13 -06:00
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:
@@ -772,6 +772,7 @@ export type RawTrafficRule = {
|
||||
ipRange: string
|
||||
direction: TrafficRuleDirection
|
||||
port?: string
|
||||
priority?: number
|
||||
}
|
||||
|
||||
type BaseTrafficRule = RawTrafficRule & {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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í",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 プラグインモードが必要です",
|
||||
|
||||
@@ -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 플러그인 모드 필요",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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ı",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "翻译工具",
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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: []
|
||||
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
@@ -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 ?? '',
|
||||
})
|
||||
|
||||
@@ -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 ?? '',
|
||||
})
|
||||
|
||||
19
@xen-orchestra/web/src/test/create-traffic-rule.ts
Normal file
19
@xen-orchestra/web/src/test/create-traffic-rule.ts
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user