@xen-orchestra/web-core 0.24.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -86,7 +86,6 @@ const open = (event: MouseEvent) => {
86
86
 
87
87
  <style lang="postcss" scoped>
88
88
  .menu-list {
89
- z-index: 1;
90
89
  display: inline-flex;
91
90
  flex-direction: column;
92
91
  padding: 0.4rem;
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div class="vts-stacked-bar-with-legend">
3
3
  <UiStackedBar :segments :max-value="maxValue" />
4
- <VtsLegendList class="list" :horizontal="uiStore.isDesktop">
4
+ <VtsLegendList class="list" :horizontal="!uiStore.isMobile">
5
5
  <UiLegend
6
6
  v-for="segment in segments"
7
7
  :key="segment.label"
@@ -0,0 +1,132 @@
1
+ <!-- v1 -->
2
+ <template>
3
+ <li class="ui-alarm-item" :class="className">
4
+ <div class="content">
5
+ <div class="label-percent typo-body-regular text-ellipsis">
6
+ <UiButtonIcon
7
+ v-if="description"
8
+ :icon="isDescriptionVisible ? faAngleDown : faAngleRight"
9
+ size="small"
10
+ accent="brand"
11
+ :target-scale="2"
12
+ @click="toggleDescription()"
13
+ />
14
+ <span v-tooltip class="text-ellipsis">
15
+ {{ label }}
16
+ </span>
17
+ <span class="percent">{{ t('n-percent', percent) }}</span>
18
+ </div>
19
+ <div class="typo-body-regular-small info">
20
+ <div v-if="slots.link" class="link-container">
21
+ {{ t('on-object') }}
22
+ <span class="object-link">
23
+ <slot name="link" />
24
+ </span>
25
+ <span class="interpunct" />
26
+ </div>
27
+ <span>{{ timeAgo }}</span>
28
+ </div>
29
+ </div>
30
+ <div v-if="isDescriptionVisible" class="description">
31
+ {{ description }}
32
+ </div>
33
+ </li>
34
+ </template>
35
+
36
+ <script lang="ts" setup>
37
+ import UiButtonIcon from '@core/components/ui/button-icon/UiButtonIcon.vue'
38
+ import { useTimeAgo } from '@core/composables/locale-time-ago.composable.ts'
39
+ import { vTooltip } from '@core/directives/tooltip.directive'
40
+ import { toVariants } from '@core/utils/to-variants.util'
41
+ import { faAngleDown, faAngleRight } from '@fortawesome/free-solid-svg-icons'
42
+ import { useToggle } from '@vueuse/core'
43
+ import { computed } from 'vue'
44
+ import { useI18n } from 'vue-i18n'
45
+
46
+ const { date, size } = defineProps<{
47
+ date: Date | number | string
48
+ label: string
49
+ size: 'small' | 'large'
50
+ percent: number
51
+ description?: string
52
+ }>()
53
+
54
+ const slots = defineSlots<{
55
+ link?(): any
56
+ }>()
57
+
58
+ const timeAgo = useTimeAgo(() => date)
59
+
60
+ const { t } = useI18n()
61
+
62
+ const className = computed(() => toVariants({ size }))
63
+
64
+ const [isDescriptionVisible, toggleDescription] = useToggle(false)
65
+ </script>
66
+
67
+ <style scoped lang="postcss">
68
+ .ui-alarm-item {
69
+ display: flex;
70
+ flex-direction: column;
71
+ padding: 0.8rem 1.2rem;
72
+ border-block: 0.1rem solid var(--color-neutral-border);
73
+ color: var(--color-neutral-txt-primary);
74
+
75
+ &:not(:first-child) {
76
+ border-block-start: none;
77
+ }
78
+
79
+ .content {
80
+ display: flex;
81
+ justify-content: space-between;
82
+ gap: 0.6rem;
83
+ }
84
+
85
+ .label-percent {
86
+ gap: 1.6rem;
87
+ }
88
+
89
+ .info,
90
+ .link-container,
91
+ .label-percent {
92
+ display: flex;
93
+ flex-direction: row;
94
+ align-items: center;
95
+ }
96
+
97
+ .percent {
98
+ color: var(--color-danger-txt-base);
99
+ }
100
+
101
+ .info,
102
+ .link-container {
103
+ gap: 0.8rem;
104
+ white-space: nowrap;
105
+ }
106
+
107
+ .interpunct::before {
108
+ content: '•';
109
+ }
110
+
111
+ .info > :not(.object-link),
112
+ .description {
113
+ color: var(--color-neutral-txt-secondary);
114
+ }
115
+
116
+ &.size--large {
117
+ gap: 0.8rem;
118
+
119
+ .content {
120
+ flex-direction: row;
121
+ }
122
+ }
123
+
124
+ &.size--small {
125
+ gap: 0.4rem;
126
+
127
+ .content {
128
+ flex-direction: column;
129
+ }
130
+ }
131
+ }
132
+ </style>
@@ -0,0 +1,20 @@
1
+ <!-- v1 -->
2
+ <template>
3
+ <ul class="ui-alarm-list">
4
+ <slot />
5
+ </ul>
6
+ </template>
7
+
8
+ <script setup lang="ts">
9
+ defineSlots<{
10
+ default(): any
11
+ }>()
12
+ </script>
13
+
14
+ <style scoped lang="postcss">
15
+ .ui-alarm-list {
16
+ overflow-y: auto;
17
+ overflow-x: hidden;
18
+ height: 100%;
19
+ }
20
+ </style>
@@ -0,0 +1,24 @@
1
+ <!-- v1 -->
2
+ <template>
3
+ <div class="ui-data-ruler typo-body-regular-small">
4
+ <span>{{ t('n-percent', 0) }}</span>
5
+ <span>{{ t('n-percent', 50) }}</span>
6
+ <span>{{ t('n-percent', 100) }}</span>
7
+ </div>
8
+ </template>
9
+
10
+ <script lang="ts" setup>
11
+ import { useI18n } from 'vue-i18n'
12
+
13
+ const { t } = useI18n()
14
+ </script>
15
+
16
+ <style lang="postcss" scoped>
17
+ .ui-data-ruler {
18
+ display: flex;
19
+ align-items: center;
20
+ justify-content: space-between;
21
+ gap: 1rem;
22
+ color: var(--color-neutral-txt-secondary);
23
+ }
24
+ </style>
@@ -112,6 +112,7 @@ defineExpose({ focus })
112
112
  color: var(--color-neutral-txt-primary);
113
113
  height: 4rem;
114
114
  width: 100%;
115
+ min-width: 15rem;
115
116
  padding-inline: 1.6rem;
116
117
 
117
118
  .left-icon,
@@ -19,20 +19,20 @@ export type LegendItemAccent = 'neutral' | 'info' | 'success' | 'warning' | 'dan
19
19
 
20
20
  export type LegendItemProps = {
21
21
  accent: LegendItemAccent
22
- value?: number
22
+ value?: number | string
23
23
  unit?: string
24
24
  tooltip?: string
25
25
  }
26
26
 
27
- const props = defineProps<LegendItemProps>()
27
+ const { value, unit, accent } = defineProps<LegendItemProps>()
28
28
 
29
29
  defineSlots<{
30
- default(): void
30
+ default(): any
31
31
  }>()
32
32
 
33
- const valueLabel = computed(() => [props.value, props.unit].join(' ').trim())
33
+ const valueLabel = computed(() => [value, unit].join(' ').trim())
34
34
 
35
- const classNames = computed(() => toVariants({ accent: props.accent }))
35
+ const classNames = computed(() => toVariants({ accent }))
36
36
  </script>
37
37
 
38
38
  <style lang="postcss" scoped>
@@ -9,7 +9,12 @@
9
9
  <span v-for="step in steps" :key="step">{{ n(step, 'percent') }}</span>
10
10
  </div>
11
11
  <VtsLegendList class="legend">
12
- <UiLegend :accent :value="Math.round(percentage)" unit="%">{{ legend }}</UiLegend>
12
+ <UiLegend v-if="displayMode === 'percent'" :accent :value="Math.round(percentage)" unit="%">
13
+ {{ legend }}
14
+ </UiLegend>
15
+ <UiLegend v-else :accent :value="legendValue">
16
+ {{ legend }}
17
+ </UiLegend>
13
18
  </VtsLegendList>
14
19
  </div>
15
20
  </template>
@@ -17,27 +22,35 @@
17
22
  <script lang="ts" setup>
18
23
  import VtsLegendList from '@core/components/legend-list/VtsLegendList.vue'
19
24
  import UiLegend from '@core/components/ui/legend/UiLegend.vue'
25
+ import { formatSizeRaw } from '@core/utils/size.util.ts'
20
26
  import { toVariants } from '@core/utils/to-variants.util'
21
27
  import { useClamp, useMax } from '@vueuse/math'
22
28
  import { computed } from 'vue'
23
29
  import { useI18n } from 'vue-i18n'
24
30
 
25
- const {
26
- value: _value,
27
- max = 100,
28
- showSteps,
29
- } = defineProps<{
31
+ interface Props {
30
32
  legend: string
31
33
  value: number
32
- max?: number
33
34
  showSteps?: boolean
34
- }>()
35
+ }
36
+ interface PercentageProps {
37
+ max?: number
38
+ displayMode: 'percent'
39
+ }
40
+ interface ValueProps {
41
+ max: number
42
+ displayMode: 'value'
43
+ }
44
+
45
+ const { value: _value, max: _max, showSteps, displayMode } = defineProps<Props & (PercentageProps | ValueProps)>()
35
46
 
36
47
  const { n } = useI18n()
37
48
 
38
49
  const value = useMax(0, () => _value)
39
50
 
40
- const percentage = computed(() => (max <= 0 ? 0 : (value.value / max) * 100))
51
+ const max = computed(() => _max ?? 100)
52
+
53
+ const percentage = computed(() => (max.value <= 0 ? 0 : (value.value / max.value) * 100))
41
54
  const maxPercentage = computed(() => Math.ceil(percentage.value / 100) * 100)
42
55
  const fillWidth = useClamp(() => (percentage.value / maxPercentage.value) * 100 || 0, 0, 100)
43
56
  const shouldShowSteps = computed(() => showSteps || percentage.value > 100)
@@ -56,6 +69,14 @@ const accent = computed(() => {
56
69
  })
57
70
 
58
71
  const className = computed(() => toVariants({ accent: accent.value }))
72
+
73
+ const formattedValue = computed(() => formatSizeRaw(value.value, 1))
74
+
75
+ const formattedMax = computed(() => formatSizeRaw(max.value, 0))
76
+
77
+ const legendValue = computed(() => {
78
+ return `${formattedValue.value.value} ${formattedValue.value.prefix} / ${formattedMax.value.value} ${formattedMax.value.prefix}`
79
+ })
59
80
  </script>
60
81
 
61
82
  <style lang="postcss" scoped>
@@ -1,7 +1,7 @@
1
1
  <!-- v3 -->
2
2
  <template>
3
3
  <form class="ui-query-search-bar" @submit.prevent="emit('search', value)">
4
- <label v-if="uiStore.isDesktop" :for="id" class="typo-body-regular-small label">
4
+ <label v-if="uiStore.isDesktopLarge" :for="id" class="typo-body-regular-small label">
5
5
  {{ t('core.query-search-bar.label') }}
6
6
  </label>
7
7
  <UiInput
@@ -10,26 +10,30 @@
10
10
  type="text"
11
11
  accent="brand"
12
12
  :aria-label="uiStore.isMobile ? t('core.query-search-bar.label') : undefined"
13
- :icon="uiStore.isDesktop ? faMagnifyingGlass : undefined"
13
+ :icon="!uiStore.isMobile ? faMagnifyingGlass : undefined"
14
14
  :placeholder="t('core.query-search-bar.placeholder')"
15
15
  />
16
- <template v-if="uiStore.isDesktop">
17
- <UiButton size="medium" accent="brand" variant="primary" type="submit">{{ t('core.search') }}</UiButton>
16
+ <template v-if="!uiStore.isMobile">
17
+ <UiButton size="medium" accent="brand" variant="primary" type="submit" class="action-button">
18
+ {{ t('core.search') }}
19
+ </UiButton>
18
20
  <VtsDivider type="stretch" />
19
21
  <UiButton
20
22
  v-tooltip="t('coming-soon')"
21
23
  size="medium"
22
24
  accent="brand"
23
25
  variant="secondary"
24
- :left-icon="faFilter"
25
26
  disabled
27
+ class="action-button"
26
28
  >
27
29
  {{ t('core.query-search-bar.use-query-builder') }}
28
30
  </UiButton>
29
31
  </template>
32
+
33
+ <!-- Mobile icons: search + filter -->
30
34
  <template v-else>
31
- <UiButtonIcon accent="brand" size="medium" type="submit" :icon="faMagnifyingGlass" />
32
- <UiButtonIcon accent="brand" size="medium" disabled :icon="faFilter" />
35
+ <UiButtonIcon accent="brand" size="medium" type="submit" :icon="faMagnifyingGlass" class="action-button" />
36
+ <UiButtonIcon accent="brand" size="medium" disabled :icon="faFilter" class="action-button" />
33
37
  </template>
34
38
  </form>
35
39
  </template>
@@ -62,11 +66,21 @@ const value = ref<string>('')
62
66
  <style lang="postcss" scoped>
63
67
  .ui-query-search-bar {
64
68
  display: flex;
65
- gap: 1.6rem;
69
+ flex-wrap: nowrap;
66
70
  align-items: center;
71
+ gap: 1.6rem;
72
+ overflow-x: auto;
73
+ width: 100%;
74
+ }
75
+
76
+ .label {
77
+ color: var(--color-neutral-txt-secondary);
78
+ flex: 0 0 auto;
79
+ white-space: nowrap;
80
+ }
67
81
 
68
- .label {
69
- color: var(--color-neutral-txt-secondary);
70
- }
82
+ .action-button {
83
+ flex: 0 0 auto;
84
+ white-space: nowrap;
71
85
  }
72
86
  </style>
@@ -42,5 +42,12 @@ const slots = defineSlots<{
42
42
  align-items: center;
43
43
  gap: 2.4rem;
44
44
  }
45
+
46
+ /* TODO remove this when we have a better solution for the mobile view (waiting for the dropdown component) */
47
+ @media (max-width: 1440px) {
48
+ .actions {
49
+ display: block;
50
+ }
51
+ }
45
52
  }
46
53
  </style>
@@ -27,6 +27,7 @@
27
27
  "all-done": "Vše dokončeno!",
28
28
  "all-good": "Všechno v pořádku!",
29
29
  "allow-self-signed-ssl": "Je možné že bude třeba ve vámi využívaném prohlížeči povolit samy sebou podepsané SSL certifikáty",
30
+ "api-error-details": "Podrobnosti o chybě API rozhraní",
30
31
  "appearance": "Vzhled",
31
32
  "ascending": "vzestupně",
32
33
  "auto-generated": "Automaticky vytvořeno",
@@ -59,7 +60,7 @@
59
60
  "bond-status": "Stav svazku",
60
61
  "boot-firmware": "Firmware zavádění",
61
62
  "boot-firmware-bios": "Šablona už obsahuje řetězec BIOS",
62
- "boot-firmware-uefi": "Firmware zavádění je UEFI",
63
+ "boot-firmware-uefi": "Firmware, kterým zaváděno, je UEFI",
63
64
  "boot-vm": "Nastartovat virt. stroj",
64
65
  "build-number": "Číslo sestavení",
65
66
  "bytes.gi": "GiB",
@@ -79,9 +80,12 @@
79
80
  "confirm-cancel": "Opravdu chcete zrušit?",
80
81
  "confirm-delete": "Chystáte se smazat {0}",
81
82
  "connected": "Připojeno",
83
+ "connecting": "Připojuje se",
84
+ "connection": "Připojení",
85
+ "connection-failed": "Připojení se nezdařilo",
82
86
  "connections": "Spojení",
83
87
  "console": "Konzole",
84
- "console-actions": "Akce konzole",
88
+ "console-actions": "Akce ohledně konzole",
85
89
  "console-clipboard": "Schránka konzole",
86
90
  "console-unavailable": "Konzole není k dispozici",
87
91
  "control-domain-memory": "Operační paměť řídící (virt) domény",
@@ -120,7 +124,7 @@
120
124
  "core.sort.descending": "Seřadit sestupně",
121
125
  "core.textarea.exceeds-max-characters": "Je třeba, aby zadání v kolonce bylo dlouhé nejvýše {max} znaků.",
122
126
  "cores-with-sockets": "Jader (patic)",
123
- "cpu-cap": "zastropování procesoru",
127
+ "cpu-cap": "Zastropování procesoru",
124
128
  "cpu-mask": "Maska procesoru",
125
129
  "cpu-model": "Model procesoru",
126
130
  "cpu-provisioning": "Poskytování procesorů",
@@ -161,7 +165,7 @@
161
165
  "disk-name": "Název disku",
162
166
  "display": "Zobrazení",
163
167
  "dns": "DNS",
164
- "do-you-have-needs": "Něco zde chybí a/nebo očekáváte? Dejte nám vědět",
168
+ "do-you-have-needs": "Něco potřebujete a/nebo očekáváte? Dejte nám vědět",
165
169
  "documentation": "Dokumentace",
166
170
  "documentation-name": "Dokumentace k {name}",
167
171
  "edit": "Upravit",
@@ -169,6 +173,7 @@
169
173
  "enabled": "Povoleno",
170
174
  "end-of-life": "Podpora pro tuto verzi skončila",
171
175
  "eol": "EOL",
176
+ "error": "Chyba",
172
177
  "error-no-data": "Chyba – nebylo možné shromáždit údaje.",
173
178
  "error-occurred": "Došlo k chybě",
174
179
  "exit-fullscreen": "Opustit režim na celou obrazovku",
@@ -202,6 +207,7 @@
202
207
  "for-replication": "Pro replikaci",
203
208
  "force-reboot": "Vynutit restart",
204
209
  "force-shutdown": "Vynutit vypnutí",
210
+ "forget": "Zapomenout",
205
211
  "fullscreen": "Na celou obrazovku",
206
212
  "fullscreen-leave": "Ukončit celoobrazovkový režim",
207
213
  "gateway": "Brána",
@@ -366,6 +372,7 @@
366
372
  "no-results": "Nic nenalezeno",
367
373
  "no-selected-vm-can-be-exported": "Žádný z označených virt. strojů není možné exportovat",
368
374
  "no-selected-vm-can-be-migrated": "Žádný z označených virt. strojů není možné přestěhovat",
375
+ "no-server-detected": "Nezjištěn žádný server",
369
376
  "no-tasks": "Žádné úlohy",
370
377
  "no-vif-detected": "Nebylo zjištěno žádné virt. rozhraní",
371
378
  "none": "Žádný",
@@ -428,7 +435,7 @@
428
435
  "reboot": "Restartovat",
429
436
  "receive": "Přijmout",
430
437
  "reformat": "Přeformátovat",
431
- "relative-time.day": "1 den | {n} dny",
438
+ "relative-time.day": "1 den | {n} dnů",
432
439
  "relative-time.future": "V {str}",
433
440
  "relative-time.hour": "1 hodina | {n} hodin",
434
441
  "relative-time.minute": "1 minuta | {n} minut",
@@ -490,7 +497,7 @@
490
497
  "storage": "Úložiště",
491
498
  "storage-configuration": "Nastavení úložiště",
492
499
  "storage-repositories": "Repozitáře úložiště",
493
- "storage-repository": "Repozitář na úložišti",
500
+ "storage-repository": "Repozitář úložiště",
494
501
  "storage-usage": "Využití úložiště",
495
502
  "success": "Úspěch",
496
503
  "summary": "Souhrn",
@@ -531,6 +538,7 @@
531
538
  "total-storage-repository": "Celkové úložiště repozitáře",
532
539
  "total-used": "Celkem využito",
533
540
  "total-used:": "Celkem využito:",
541
+ "unable-to-connect-to-the-pool": "Nebylo možné se připojit k fondu",
534
542
  "unknown": "Neznámé",
535
543
  "unlocked": "Odemčeno",
536
544
  "unreachable-hosts": "Nedosažitelní hostitelé",
@@ -544,7 +552,7 @@
544
552
  "vcpus-assigned": "Přiděleno virt. procesorů",
545
553
  "vcpus-used": "využito virt. procesorů",
546
554
  "vdi-throughput": "propustnost VDI",
547
- "vdis": "Virt. disk | Virt. disk | Virt. disky",
555
+ "vdis": "Virt. disk | Virt. disky | Virt. disků",
548
556
  "version": "Verze",
549
557
  "vga": "VGA",
550
558
  "video-ram": "Videopaměť",
@@ -569,7 +577,7 @@
569
577
  "vm-status.suspended": "Uspané",
570
578
  "vm.active": "Aktivní",
571
579
  "vm.inactive": "Neaktivní",
572
- "vms": "Virt. stroj | Virt. stroje",
580
+ "vms": "Virt. stroj | Virt. stroj | Virt. stroje",
573
581
  "vms-status": "Stav virt. strojů",
574
582
  "vms-status.halted": "Zastaveno",
575
583
  "vms-status.inactive": "Neaktivní",
@@ -10,15 +10,15 @@
10
10
  "admin-login": "Administrator Login",
11
11
  "admin-password": "Administrator Passwort",
12
12
  "admin-password-confirm": "Administrator Passwortbestätigung",
13
- "alarm-type.cpu_usage": "CPU-Nutzung überschreitet {n}%",
14
- "alarm-type.disk_usage": "Speicherbelegung überschreitet {n}%",
15
- "alarm-type.fs_usage": "Dateisystem überschreitet {n}%",
16
- "alarm-type.log_fs_usage": "Loggingdateisystem überschreitet {n}%",
17
- "alarm-type.mem_usage": "Arbeitsspeicherauslastung überschreitet {n}%",
18
- "alarm-type.memory_free_kib": "Freier Speicher fällt unter {n}%",
19
- "alarm-type.network_usage": "Netzwerkauslastung überschreitet {n}%",
20
- "alarm-type.physical_utilisation": "Physische Nutzung übersteigt {n}%",
21
- "alarm-type.sr_io_throughput_total_per_host": "Gesamtdurchsatz der IO SR pro Host beträgt mehr als {n}%",
13
+ "alarm-type.cpu_usage": "CPU-Nutzung überschreitet @:n-percent",
14
+ "alarm-type.disk_usage": "Speicherbelegung überschreitet @:n-percent",
15
+ "alarm-type.fs_usage": "Dateisystem überschreitet @:n-percent",
16
+ "alarm-type.log_fs_usage": "Loggingdateisystem überschreitet @:n-percent",
17
+ "alarm-type.mem_usage": "Arbeitsspeicherauslastung überschreitet @:n-percent",
18
+ "alarm-type.memory_free_kib": "Freier Speicher fällt unter @:n-percent",
19
+ "alarm-type.network_usage": "Netzwerkauslastung überschreitet @:n-percent",
20
+ "alarm-type.physical_utilisation": "Physische Nutzung übersteigt @:n-percent",
21
+ "alarm-type.sr_io_throughput_total_per_host": "Gesamtdurchsatz der IO SR pro Host beträgt mehr als @:n-percent",
22
22
  "alarm-type.unknown": "unbekannter Alarmtyp",
23
23
  "alarms": "Alarme",
24
24
  "all-done": "Alles erledigt!",
@@ -113,6 +113,7 @@
113
113
  "cpu-usage": "Prozessorauslastung",
114
114
  "cpus": "Prozessoren",
115
115
  "create": "Erstellen",
116
+ "created-on": "Erstellt am",
116
117
  "custom-config": "Benutzerdefinierte Konfiguration",
117
118
  "dark-mode.auto": "Automatischer Dunkelmodus",
118
119
  "dark-mode.disable": "Deaktiviere den Dunkelmodus",
@@ -293,6 +294,7 @@
293
294
  "n-hosts": "1 Host | {n} Hosts",
294
295
  "n-hosts-awaiting-patch": "Der Host {n} benötigt dieses Patch | Die Hosts {n} benötigen dieses Patch",
295
296
  "n-missing": "{n} fehlen",
297
+ "n-percent": "{n}%",
296
298
  "n-vms": "1 VM | {n} VMs",
297
299
  "name": "Name",
298
300
  "netmask": "Netzmaske",
@@ -494,7 +496,7 @@
494
496
  "vm-management": "VM Verwaltung",
495
497
  "vm.active": "Aktiv",
496
498
  "vm.inactive": "Inaktiv",
497
- "vms": "VMs",
499
+ "vms": "VM | VM | VMs",
498
500
  "vms-status": "VMs-Status",
499
501
  "vms-status.halted": "Angehalten",
500
502
  "vms-status.inactive": "Inaktiv",
@@ -13,15 +13,15 @@
13
13
  "admin-password": "Admin password",
14
14
  "admin-password-confirm": "Confirm admin password",
15
15
  "affinity-host": "Affinity host",
16
- "alarm-type.cpu_usage": "CPU usage exceeds {n}%",
17
- "alarm-type.disk_usage": "Disk usage exceeds {n}%",
18
- "alarm-type.fs_usage": "FS usage exceeds {n}%",
19
- "alarm-type.log_fs_usage": "Log FS usage exceeds {n}%",
20
- "alarm-type.mem_usage": "Memory usage exceeds {n}%",
21
- "alarm-type.memory_free_kib": "Free memory falls below {n}%",
22
- "alarm-type.network_usage": "Network usage exceeds {n}%",
23
- "alarm-type.physical_utilisation": "Physical utilisation exceeds {n}%",
24
- "alarm-type.sr_io_throughput_total_per_host": "SR IO throughput total per host exceeds {n}%",
16
+ "alarm-type.cpu_usage": "CPU usage exceeds @:n-percent",
17
+ "alarm-type.disk_usage": "Disk usage exceeds @:n-percent",
18
+ "alarm-type.fs_usage": "FS usage exceeds @:n-percent",
19
+ "alarm-type.log_fs_usage": "Log FS usage exceeds @:n-percent",
20
+ "alarm-type.mem_usage": "Memory usage exceeds @:n-percent",
21
+ "alarm-type.memory_free_kib": "Free memory falls below @:n-percent",
22
+ "alarm-type.network_usage": "Network usage exceeds @:n-percent",
23
+ "alarm-type.physical_utilisation": "Physical utilisation exceeds @:n-percent",
24
+ "alarm-type.sr_io_throughput_total_per_host": "SR IO throughput total per host exceeds @:n-percent",
25
25
  "alarm-type.unknown": "Unknown alarm type",
26
26
  "alarms": "Alarms",
27
27
  "all-done": "All done!",
@@ -340,6 +340,7 @@
340
340
  "n-hosts": "1 host | {n} hosts",
341
341
  "n-hosts-awaiting-patch": "{n} host is awaiting this patch | {n} hosts are awaiting this patch",
342
342
  "n-missing": "{n} missing",
343
+ "n-percent": "{n}%",
343
344
  "n-vms": "1 VM | {n} VMs",
344
345
  "name": "Name",
345
346
  "nested-virtualization": "Nested virtualization",
@@ -577,7 +578,7 @@
577
578
  "vm-status.suspended": "Suspended",
578
579
  "vm.active": "Active",
579
580
  "vm.inactive": "Inactive",
580
- "vms": "VM | VMs",
581
+ "vms": "VM | VM | VMs",
581
582
  "vms-status": "VMs status",
582
583
  "vms-status.halted": "Halted",
583
584
  "vms-status.inactive": "Inactive",