@xen-orchestra/web-core 0.49.0 → 0.51.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.
Files changed (45) hide show
  1. package/lib/components/delete-button/VtsDeleteButton.vue +27 -0
  2. package/lib/components/menu/MenuTrigger.vue +1 -0
  3. package/lib/components/menu/VtsActionsMenu.vue +53 -0
  4. package/lib/components/modal/VtsErrorModal.vue +36 -0
  5. package/lib/components/modal/VtsModalList.vue +1 -1
  6. package/lib/components/status/VtsStatus.vue +4 -0
  7. package/lib/components/table/cells/VtsActionCell.vue +44 -0
  8. package/lib/components/table/cells/VtsDoubleLinkCell.vue +53 -0
  9. package/lib/composables/default-tab.composable.md +17 -11
  10. package/lib/composables/default-tab.composable.ts +10 -4
  11. package/lib/icons/fa-icons.ts +2 -0
  12. package/lib/icons/object-icons.ts +2 -0
  13. package/lib/locales/cs.json +75 -35
  14. package/lib/locales/da.json +3 -3
  15. package/lib/locales/de.json +4 -4
  16. package/lib/locales/en.json +61 -3
  17. package/lib/locales/es.json +279 -196
  18. package/lib/locales/fa.json +4 -4
  19. package/lib/locales/fi.json +1 -1
  20. package/lib/locales/fr.json +60 -2
  21. package/lib/locales/it.json +5 -4
  22. package/lib/locales/ko.json +3 -1
  23. package/lib/locales/nb-NO.json +2 -2
  24. package/lib/locales/nl.json +22 -5
  25. package/lib/locales/pl.json +2 -2
  26. package/lib/locales/pt-BR.json +6 -6
  27. package/lib/locales/pt.json +4 -4
  28. package/lib/locales/ru.json +60 -13
  29. package/lib/locales/sk.json +509 -18
  30. package/lib/locales/sv.json +4 -4
  31. package/lib/locales/uk.json +1 -1
  32. package/lib/locales/zh-Hans.json +31 -4
  33. package/lib/packages/job/define-job.ts +1 -1
  34. package/lib/packages/table/define-columns.ts +4 -3
  35. package/lib/tables/column-definitions/action-column.ts +21 -0
  36. package/lib/tables/column-definitions/button-icon-column.ts +2 -2
  37. package/lib/tables/column-definitions/double-link-column.ts +14 -0
  38. package/lib/tables/column-definitions/text-column.ts +1 -1
  39. package/lib/tables/column-sets/host-columns.ts +1 -1
  40. package/lib/tables/column-sets/network-columns.ts +3 -1
  41. package/lib/tables/column-sets/snapshot-columns.ts +2 -2
  42. package/lib/tables/column-sets/traffic-rules-columns.ts +29 -0
  43. package/lib/tables/column-sets/vdi-columns.ts +2 -2
  44. package/lib/tables/column-sets/vif-columns.ts +2 -2
  45. package/package.json +3 -3
@@ -0,0 +1,27 @@
1
+ <template>
2
+ <UiButton
3
+ v-tooltip="tooltip"
4
+ size="medium"
5
+ variant="tertiary"
6
+ accent="danger"
7
+ left-icon="action:delete"
8
+ :disabled
9
+ :busy
10
+ >
11
+ {{ t('action:delete') }}
12
+ </UiButton>
13
+ </template>
14
+
15
+ <script setup lang="ts">
16
+ import UiButton from '@core/components/ui/button/UiButton.vue'
17
+ import { vTooltip } from '@core/directives/tooltip.directive'
18
+ import { useI18n } from 'vue-i18n'
19
+
20
+ defineProps<{
21
+ busy?: boolean
22
+ disabled?: boolean
23
+ tooltip?: string | false
24
+ }>()
25
+
26
+ const { t } = useI18n()
27
+ </script>
@@ -28,6 +28,7 @@ defineProps<{
28
28
 
29
29
  &.disabled {
30
30
  color: var(--color-neutral-txt-secondary);
31
+ background-color: var(--color-neutral-background-disabled);
31
32
  }
32
33
 
33
34
  &:not(.disabled) {
@@ -0,0 +1,53 @@
1
+ <template>
2
+ <MenuList placement="bottom-end">
3
+ <template #trigger="{ open }">
4
+ <UiButtonIcon
5
+ v-tooltip="{
6
+ placement: 'top',
7
+ content: t('quick-actions'),
8
+ }"
9
+ icon="action:more-actions"
10
+ accent="brand"
11
+ :size
12
+ @click="open($event)"
13
+ />
14
+ </template>
15
+ <MenuItem
16
+ v-for="(action, index) of actions"
17
+ :key="index"
18
+ :icon="action.icon"
19
+ :disabled="action.disabled"
20
+ :busy="action.busy"
21
+ :on-click="action.onClick"
22
+ >
23
+ {{ action.label }}
24
+ <i v-if="action.hint">{{ action.hint }}</i>
25
+ </MenuItem>
26
+ </MenuList>
27
+ </template>
28
+
29
+ <script setup lang="ts">
30
+ import MenuItem from '@core/components/menu/MenuItem.vue'
31
+ import MenuList from '@core/components/menu/MenuList.vue'
32
+ import type { ButtonIconSize } from '@core/components/ui/button-icon/UiButtonIcon.vue'
33
+ import UiButtonIcon from '@core/components/ui/button-icon/UiButtonIcon.vue'
34
+ import { vTooltip } from '@core/directives/tooltip.directive.ts'
35
+ import type { IconName } from '@core/icons'
36
+ import { useI18n } from 'vue-i18n'
37
+
38
+ const { size = 'small', actions = [] } = defineProps<{
39
+ size?: ButtonIconSize
40
+ actions?: ActionItem[]
41
+ }>()
42
+
43
+ const { t } = useI18n()
44
+
45
+ export type ActionItem = {
46
+ label: string
47
+ hint?: string
48
+ icon?: IconName
49
+ onClick: () => any
50
+ disabled?: boolean
51
+ busy?: boolean
52
+ }
53
+ </script>
@@ -0,0 +1,36 @@
1
+ <template>
2
+ <VtsModal accent="danger" icon="status:danger-picto" dismissible>
3
+ <template #title>
4
+ {{ title }}
5
+ </template>
6
+
7
+ <template #content>
8
+ <slot name="content">
9
+ {{ error }}
10
+ </slot>
11
+ </template>
12
+
13
+ <template #buttons>
14
+ <VtsModalConfirmButton>
15
+ {{ t('action:close') }}
16
+ </VtsModalConfirmButton>
17
+ </template>
18
+ </VtsModal>
19
+ </template>
20
+
21
+ <script lang="ts" setup>
22
+ import VtsModal from '@core/components/modal/VtsModal.vue'
23
+ import VtsModalConfirmButton from '@core/components/modal/VtsModalConfirmButton.vue'
24
+ import { useI18n } from 'vue-i18n'
25
+
26
+ defineProps<{
27
+ title: string
28
+ error?: string
29
+ }>()
30
+
31
+ defineSlots<{
32
+ content?(): any
33
+ }>()
34
+
35
+ const { t } = useI18n()
36
+ </script>
@@ -25,7 +25,7 @@ const modalStore = useModalStore()
25
25
  position: fixed;
26
26
  inset: 0;
27
27
  background-color: var(--color-opacity-primary);
28
- z-index: 1011;
28
+ z-index: 1012;
29
29
 
30
30
  .modal-component:not(:last-child) {
31
31
  filter: brightness(0.8);
@@ -25,6 +25,8 @@ export type Status =
25
25
  | 'pending'
26
26
  | 'enabled'
27
27
  | 'disabled'
28
+ | 'allow'
29
+ | 'drop'
28
30
  | true
29
31
  | false
30
32
 
@@ -52,6 +54,8 @@ const currentStatus = useMapper<Status, { text: string; accent: InfoAccent }>(
52
54
  ['pending', { text: t('in-progress'), accent: 'info' }],
53
55
  ['enabled', { text: t('enabled'), accent: 'success' }],
54
56
  ['disabled', { text: t('disabled'), accent: 'muted' }],
57
+ ['allow', { text: t('allow'), accent: 'success' }],
58
+ ['drop', { text: t('drop'), accent: 'danger' }],
55
59
  [true, { text: t('enabled'), accent: 'success' }],
56
60
  [false, { text: t('disabled'), accent: 'muted' }],
57
61
  ],
@@ -0,0 +1,44 @@
1
+ <template>
2
+ <UiTableCell align="center">
3
+ <div class="vts-action-cell">
4
+ <UiButtonIcon
5
+ :icon="buttonIcon"
6
+ :accent="buttonAccent"
7
+ :size="buttonSize"
8
+ :target-scale="1.5"
9
+ @click="emit('click')"
10
+ />
11
+ <VtsActionsMenu v-if="actions.length" :actions />
12
+ </div>
13
+ </UiTableCell>
14
+ </template>
15
+
16
+ <script setup lang="ts">
17
+ import VtsActionsMenu, { type ActionItem } from '@core/components/menu/VtsActionsMenu.vue'
18
+ import type { ButtonIconAccent, ButtonIconSize } from '@core/components/ui/button-icon/UiButtonIcon.vue'
19
+ import UiButtonIcon from '@core/components/ui/button-icon/UiButtonIcon.vue'
20
+ import UiTableCell from '@core/components/ui/table-cell/UiTableCell.vue'
21
+ import type { IconName } from '@core/icons'
22
+
23
+ const {
24
+ buttonAccent = 'brand',
25
+ buttonSize = 'small',
26
+ actions = [],
27
+ } = defineProps<{
28
+ buttonIcon: IconName
29
+ buttonAccent?: ButtonIconAccent
30
+ buttonSize?: ButtonIconSize
31
+ actions?: ActionItem[]
32
+ }>()
33
+
34
+ const emit = defineEmits<{
35
+ click: []
36
+ }>()
37
+ </script>
38
+
39
+ <style scoped lang="postcss">
40
+ .vts-action-cell {
41
+ display: flex;
42
+ gap: 0.8rem;
43
+ }
44
+ </style>
@@ -0,0 +1,53 @@
1
+ <template>
2
+ <UiTableCell>
3
+ <div class="vts-double-link-cell">
4
+ <UiLink size="medium" :icon :to :href :target class="link">
5
+ <slot />
6
+ </UiLink>
7
+ <UiLink
8
+ v-if="suffix"
9
+ size="medium"
10
+ :icon="suffix.icon"
11
+ :to="suffix.to"
12
+ :href="suffix.href"
13
+ :target="suffix.target"
14
+ class="link"
15
+ >
16
+ {{ suffix.label }}
17
+ </UiLink>
18
+ </div>
19
+ </UiTableCell>
20
+ </template>
21
+
22
+ <script setup lang="ts">
23
+ import UiLink from '@core/components/ui/link/UiLink.vue'
24
+ import UiTableCell from '@core/components/ui/table-cell/UiTableCell.vue'
25
+ import type { LinkOptions } from '@core/composables/link-component.composable.ts'
26
+ import type { IconName } from '@core/icons'
27
+
28
+ export type VtsDoubleLinkCellProps = LinkOptions & {
29
+ icon?: IconName
30
+ suffix?: LinkOptions & {
31
+ label: string
32
+ icon?: IconName
33
+ }
34
+ }
35
+
36
+ defineProps<VtsDoubleLinkCellProps>()
37
+
38
+ defineSlots<{
39
+ default(): any
40
+ }>()
41
+ </script>
42
+
43
+ <style scoped lang="postcss">
44
+ .vts-double-link-cell {
45
+ display: flex;
46
+ align-items: center;
47
+ gap: 0.8rem;
48
+ }
49
+
50
+ .link {
51
+ line-height: 1.5;
52
+ }
53
+ </style>
@@ -7,7 +7,8 @@ This composable manages automatic navigation to default tabs and remembers the l
7
7
  The composable watches route changes and:
8
8
 
9
9
  - Automatically redirects to the remembered or default tab when navigating to the base dispatcher route
10
- - Stores the current tab in localStorage for future visits
10
+ - Stores the last visited tab when switching between objects of the same type (e.g., VM → VM)
11
+ - Resets to default tab when navigating to a different object type or any other page
11
12
  - Uses the naming convention `{dispatcherRouteName}/{tabName}` for tab route names
12
13
 
13
14
  ## Parameters
@@ -21,22 +22,27 @@ The composable watches route changes and:
21
22
 
22
23
  ```typescript
23
24
  // In the dispatcher page component (e.g., `pages/pool/[id].vue`)
24
- useDefaultTab('pool/[id]', 'dashboard')
25
+ useDefaultTab('/pool/[id]', 'dashboard')
25
26
 
26
- // User first navigates to /pool/123
27
+ // User navigates to /pool/123
27
28
  // → Automatic redirect to /pool/123/dashboard (default tab)
28
29
 
29
- // User then navigates to /pool/123/stats
30
- // → 'stats' is stored as the last visited tab for this route
30
+ // User navigates to /pool/123/system
31
+ // → 'system' is stored as the last visited tab
31
32
 
32
- // Finally, user navigates to /pool/456
33
- // → Automatic redirect to /pool/456/stats
33
+ // User navigates to /pool/456 (same object type)
34
+ // → Automatic redirect to /pool/456/system (tab remembered)
35
+
36
+ // User navigates to /vm/789 (different object type)
37
+ // → Automatic redirect to /vm/789/dashboard (Tab memory is reset)
38
+
39
+ // User navigates back to /pool/456
40
+ // → Automatic redirect to /pool/456/dashboard (default tab)
34
41
  ```
35
42
 
36
43
  ## Storage
37
44
 
38
- Tab preferences are stored in localStorage under the key `default-tabs` with the structure:
45
+ Tab memory is stored in localStorage using two different keys:
39
46
 
40
- ```typescript
41
- Map<string, string> // dispatcherRouteName -> lastVisitedTab
42
- ```
47
+ - `tab-memory.dispatcher` — the dispatcher route name of the last visited object type
48
+ - `tab-memory.last` the last visited tab name
@@ -1,17 +1,22 @@
1
- import { useLocalStorage } from '@vueuse/core'
2
1
  import { watch } from 'vue'
3
2
  import { type RouteLocationRaw, type RouteRecordName, useRoute, useRouter } from 'vue-router'
4
3
 
4
+ const TAB_MEMORY_DISPATCHER = 'tab-memory.dispatcher'
5
+ const TAB_MEMORY_LAST = 'tab-memory.last'
6
+
5
7
  export function useDefaultTab(dispatcherRouteName: RouteRecordName & string, defaultTab: string) {
6
- const storage = useLocalStorage('default-tabs', new Map<string, string>())
7
8
  const router = useRouter()
8
9
  const route = useRoute()
9
10
 
11
+ // TODO: Delete after 2 to 3 months (once all users have cleared their local storage)
12
+ localStorage.removeItem('default-tabs')
13
+
10
14
  watch(
11
15
  () => route.name as string,
12
16
  name => {
13
17
  if (name === dispatcherRouteName) {
14
- const tabName = storage.value.get(dispatcherRouteName) ?? defaultTab
18
+ const isSameDispatcher = localStorage.getItem(TAB_MEMORY_DISPATCHER) === dispatcherRouteName
19
+ const tabName = (isSameDispatcher ? localStorage.getItem(TAB_MEMORY_LAST) : null) ?? defaultTab
15
20
  void router.replace({ name: `${dispatcherRouteName}/${tabName}` } as RouteLocationRaw)
16
21
  } else if (!name.startsWith(dispatcherRouteName)) {
17
22
  return
@@ -19,7 +24,8 @@ export function useDefaultTab(dispatcherRouteName: RouteRecordName & string, def
19
24
 
20
25
  const tab = name.slice(dispatcherRouteName.length).split('/')[1] ?? defaultTab
21
26
 
22
- storage.value.set(dispatcherRouteName, tab)
27
+ localStorage.setItem(TAB_MEMORY_DISPATCHER, dispatcherRouteName)
28
+ localStorage.setItem(TAB_MEMORY_LAST, tab)
23
29
  },
24
30
  { immediate: true }
25
31
  )
@@ -99,6 +99,7 @@ import {
99
99
  faRoute,
100
100
  faSatellite,
101
101
  faServer,
102
+ faShieldHalved,
102
103
  faSliders,
103
104
  faSpinner,
104
105
  faSquare,
@@ -226,6 +227,7 @@ export const faIcons = defineIconPack({
226
227
  time: { icon: faClock },
227
228
  'thumb-tack': { icon: faThumbTack },
228
229
  'thumb-tack-slash': { icon: faThumbTackSlash },
230
+ 'traffic-rule': { icon: faShieldHalved },
229
231
  trash: { icon: faTrash },
230
232
  'triangle-exclamation': { icon: faTriangleExclamation },
231
233
  'up-right-and-down-left-from-center': { icon: faUpRightAndDownLeftFromCenter },
@@ -12,6 +12,7 @@ import {
12
12
  faClock,
13
13
  faDatabase,
14
14
  faDesktop,
15
+ faEthernet,
15
16
  faHdd,
16
17
  faNetworkWired,
17
18
  faPlay,
@@ -209,6 +210,7 @@ export const objectIcons = defineIconPack({
209
210
  'vdi:disabled': [constructIcon(faHdd), ...constructCircleStatus('disabled')],
210
211
  'vdi:warning': [constructIcon(faHdd), ...constructCircleStatus('warning-circle')],
211
212
  'vdi:detached': [constructIcon(faHdd), ...constructCircleStatus('danger-circle')],
213
+ vif: constructIcon(faEthernet),
212
214
  network: constructIcon(faNetworkWired),
213
215
  'network:unknown': [
214
216
  {