@xen-orchestra/web-core 0.29.0 → 0.31.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 (43) hide show
  1. package/lib/assets/css/typography/_utils.pcss +11 -0
  2. package/lib/assets/no-result.svg +276 -80
  3. package/lib/components/backup-state/VtsBackupState.vue +2 -1
  4. package/lib/components/console/VtsRemoteConsole.vue +6 -2
  5. package/lib/components/data-table/VtsDataTable.vue +11 -6
  6. package/lib/components/quick-info-card/VtsQuickInfoCard.vue +2 -2
  7. package/lib/components/quick-info-row/VtsQuickInfoRow.vue +1 -1
  8. package/lib/components/select/VtsSelect.vue +1 -1
  9. package/lib/components/state-hero/VtsStateHero.vue +107 -47
  10. package/lib/components/task/VtsQuickTaskList.vue +1 -1
  11. package/lib/components/ui/card/UiCard.vue +6 -1
  12. package/lib/components/ui/collapsible-list/UiCollapsibleList.vue +5 -2
  13. package/lib/components/ui/panel/UiPanel.vue +1 -1
  14. package/lib/components/ui/table-pagination/UiTablePagination.vue +2 -2
  15. package/lib/composables/tree-filter.composable.ts +4 -2
  16. package/lib/icons/index.ts +1 -1
  17. package/lib/icons/object-icons.ts +33 -2
  18. package/lib/locales/cs.json +64 -18
  19. package/lib/locales/de.json +1 -1
  20. package/lib/locales/en.json +57 -3
  21. package/lib/locales/es.json +73 -9
  22. package/lib/locales/fa.json +1 -1
  23. package/lib/locales/fr.json +59 -5
  24. package/lib/locales/it.json +9 -4
  25. package/lib/locales/nl.json +47 -1
  26. package/lib/locales/pt_BR.json +32 -2
  27. package/lib/locales/ru.json +83 -1
  28. package/lib/locales/sv.json +1 -1
  29. package/lib/locales/uk.json +339 -1
  30. package/lib/types/value-matcher.d.ts +3 -0
  31. package/lib/utils/speed.util.ts +12 -0
  32. package/lib/utils/time.util.ts +57 -0
  33. package/package.json +1 -1
  34. package/lib/components/state-hero/VtsAllDoneHero.vue +0 -16
  35. package/lib/components/state-hero/VtsAllGoodHero.vue +0 -16
  36. package/lib/components/state-hero/VtsComingSoonHero.vue +0 -16
  37. package/lib/components/state-hero/VtsErrorNoDataHero.vue +0 -14
  38. package/lib/components/state-hero/VtsLoadingHero.vue +0 -57
  39. package/lib/components/state-hero/VtsNoDataHero.vue +0 -14
  40. package/lib/components/state-hero/VtsNoSelectionHero.vue +0 -16
  41. package/lib/components/state-hero/VtsObjectNotFoundHero.vue +0 -17
  42. package/lib/components/state-hero/VtsOfflineHero.vue +0 -16
  43. package/lib/components/state-hero/VtsPageNotFoundHero.vue +0 -33
@@ -1,7 +1,7 @@
1
1
  <template>
2
- <div :class="[type, { error }]" class="vts-state-hero">
2
+ <div :class="[className, { horizontal, error, success, 'no-background': noBackground }]" class="vts-state-hero">
3
3
  <UiLoader v-if="busy" class="loader" />
4
- <img v-else-if="imageSrc" :src="imageSrc" alt="" class="image" />
4
+ <img v-else-if="imageSrc" :src="imageSrc" :alt="type" class="image" />
5
5
  <div v-if="slots.default" :class="typoClass" class="content">
6
6
  <slot />
7
7
  </div>
@@ -10,23 +10,28 @@
10
10
 
11
11
  <script lang="ts" setup>
12
12
  import UiLoader from '@core/components/ui/loader/UiLoader.vue'
13
+ import { toVariants } from '@core/utils/to-variants.util.ts'
13
14
  import { computed } from 'vue'
14
15
 
15
- export type StateHeroType = 'page' | 'card' | 'panel' | 'table'
16
-
17
- const { type, busy, image, noBackground } = defineProps<{
18
- type: StateHeroType
16
+ export type StateHeroFormat = 'page' | 'card' | 'panel' | 'table'
17
+
18
+ type StateHeroType =
19
+ | 'no-result'
20
+ | 'under-construction'
21
+ | 'no-data'
22
+ | 'no-selection'
23
+ | 'error'
24
+ | 'not-found'
25
+ | 'offline'
26
+ | 'all-good'
27
+ | 'all-done'
28
+
29
+ const { format, type, size, busy } = defineProps<{
30
+ format: StateHeroFormat
31
+ type?: StateHeroType
32
+ size: 'extra-small' | 'small' | 'medium' | 'large'
33
+ horizontal?: boolean
19
34
  busy?: boolean
20
- image?:
21
- | 'no-result'
22
- | 'under-construction'
23
- | 'no-data'
24
- | 'no-selection'
25
- | 'error'
26
- | 'not-found'
27
- | 'offline'
28
- | 'all-good'
29
- | 'all-done'
30
35
  noBackground?: boolean
31
36
  }>()
32
37
 
@@ -34,15 +39,20 @@ const slots = defineSlots<{
34
39
  default?(): any
35
40
  }>()
36
41
 
37
- const typoClass = computed(() => (type === 'page' ? 'typo-h2' : 'typo-h4'))
38
- const error = computed(() => !noBackground && !busy && image === 'error')
42
+ const typoClass = computed(() => (format === 'page' ? 'typo-h2' : 'typo-h4'))
43
+
44
+ const className = computed(() => toVariants({ size, format }))
45
+
46
+ const error = computed(() => !busy && type === 'error')
47
+
48
+ const success = computed(() => !busy && (type === 'all-good' || type === 'all-done'))
39
49
 
40
50
  const imageSrc = computed(() => {
41
- if (!image) {
51
+ if (!type) {
42
52
  return undefined
43
53
  }
44
54
 
45
- return new URL(`../../assets/${image}.svg`, import.meta.url).href
55
+ return new URL(`../../assets/${type}.svg`, import.meta.url).href
46
56
  })
47
57
  </script>
48
58
 
@@ -50,33 +60,54 @@ const imageSrc = computed(() => {
50
60
  .vts-state-hero {
51
61
  flex: 1;
52
62
  display: flex;
53
- flex-direction: column;
54
63
  align-items: center;
55
64
  justify-content: center;
65
+ gap: 2.4rem;
56
66
 
57
- &.error {
58
- background-color: var(--color-danger-background-selected);
67
+ &:not(.horizontal) {
68
+ flex-direction: column;
59
69
 
60
70
  .content {
61
- color: var(--color-danger-txt-base);
71
+ align-items: center;
62
72
  }
63
73
  }
64
74
 
75
+ .image {
76
+ order: 2;
77
+ }
78
+
79
+ .content {
80
+ display: flex;
81
+ flex-direction: column;
82
+ gap: 1.6rem;
83
+ }
84
+
65
85
  .loader,
66
86
  .content {
87
+ order: 3;
67
88
  color: var(--color-brand-txt-base);
68
89
  }
69
90
 
70
- .image {
71
- order: 2;
91
+ &.success {
92
+ .content {
93
+ color: var(--color-success-txt-base);
94
+ }
72
95
  }
73
96
 
74
- .content {
75
- order: 3;
97
+ &.error {
98
+ background-color: var(--color-danger-background-selected);
99
+
100
+ &.no-background {
101
+ background-color: transparent;
102
+ }
103
+
104
+ .content {
105
+ color: var(--color-danger-txt-base);
106
+ }
76
107
  }
77
108
 
78
- &.page {
79
- gap: 2.4rem;
109
+ &.format--card {
110
+ gap: 2rem;
80
111
 
81
112
  .content {
82
113
  order: 3;
@@ -84,36 +115,31 @@ const imageSrc = computed(() => {
84
115
 
85
116
  .loader {
86
117
  order: 1;
87
- font-size: 10rem;
88
118
  }
89
119
 
90
120
  .image {
91
121
  order: 2;
92
- width: 90%;
93
- max-height: none;
94
122
  }
95
123
  }
96
124
 
97
- &.card {
98
- gap: 2rem;
125
+ &.format--table {
126
+ padding: 4rem;
127
+ gap: 2.4rem;
99
128
 
100
129
  .content {
101
130
  order: 3;
102
131
  }
103
132
 
104
133
  .loader {
105
- font-size: 6rem;
106
134
  order: 1;
107
135
  }
108
136
 
109
137
  .image {
110
138
  order: 2;
111
- width: 70%;
112
- max-height: 20rem;
113
139
  }
114
140
  }
115
141
 
116
- &.panel {
142
+ &.format--panel {
117
143
  gap: 4rem;
118
144
  justify-content: unset;
119
145
  padding-top: 8rem;
@@ -124,31 +150,65 @@ const imageSrc = computed(() => {
124
150
 
125
151
  .loader {
126
152
  order: 3;
127
- font-size: 6.4rem;
128
153
  }
129
154
 
130
155
  .image {
131
156
  order: 2;
132
- width: 80%;
133
157
  }
134
158
  }
135
159
 
136
- &.table {
137
- padding: 4rem;
138
- gap: 2.4rem;
160
+ &.format--page {
161
+ gap: 10rem;
139
162
 
140
163
  .content {
141
164
  order: 3;
142
165
  }
143
166
 
167
+ .loader {
168
+ order: 1;
169
+ }
170
+
144
171
  .image {
145
172
  order: 2;
146
- max-height: 20rem;
147
173
  }
174
+ }
148
175
 
176
+ &.size--extra-small {
149
177
  .loader {
150
- order: 1;
151
- font-size: 10rem;
178
+ font-size: 1.6rem;
179
+ }
180
+
181
+ .image {
182
+ max-height: 14rem;
183
+ }
184
+ }
185
+
186
+ &.size--small {
187
+ .loader {
188
+ font-size: 2.4rem;
189
+ }
190
+
191
+ .image {
192
+ max-height: 18rem;
193
+ }
194
+ }
195
+
196
+ &.size--medium {
197
+ .loader {
198
+ font-size: 6.4rem;
199
+ }
200
+
201
+ .image {
202
+ max-height: 30rem;
203
+ }
204
+ }
205
+
206
+ &.size--large {
207
+ .loader {
208
+ font-size: 9.6rem;
209
+ }
210
+ .image {
211
+ max-height: 50rem;
152
212
  }
153
213
  }
154
214
  }
@@ -3,7 +3,7 @@
3
3
  <li v-if="loading">
4
4
  <div class="loading">
5
5
  <UiLoader />
6
- <div>{{ t('loading-in-progress') }}</div>
6
+ <div>{{ t('loading') }}</div>
7
7
  </div>
8
8
  </li>
9
9
  <template v-else>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="ui-card" :class="{ horizontal }">
2
+ <div class="ui-card" :class="{ horizontal, 'has-error': hasError }">
3
3
  <slot />
4
4
  </div>
5
5
  </template>
@@ -7,6 +7,7 @@
7
7
  <script lang="ts" setup>
8
8
  defineProps<{
9
9
  horizontal?: boolean
10
+ hasError?: boolean
10
11
  }>()
11
12
 
12
13
  defineSlots<{
@@ -39,5 +40,9 @@ defineSlots<{
39
40
  &.horizontal {
40
41
  flex-direction: row;
41
42
  }
43
+
44
+ &.has-error {
45
+ background-color: var(--color-danger-background-selected);
46
+ }
42
47
  }
43
48
  </style>
@@ -5,9 +5,8 @@
5
5
  <slot />
6
6
  </component>
7
7
  <div v-if="hasMoreItems" class="footer">
8
- <span v-if="!isExpanded" class="typo-body-regular-small">{{ t('n-more', { n: remainingItems }) }}</span>
9
8
  <UiButton size="small" accent="brand" variant="tertiary" @click="isExpanded = !isExpanded">
10
- {{ isExpanded ? t('see-less') : t('see-all') }}
9
+ {{ isExpanded ? t('see-less') : t('see-n-more', { n: remainingItems }) }}
11
10
  </UiButton>
12
11
  </div>
13
12
  </div>
@@ -61,6 +60,10 @@ useStyleTag(style)
61
60
  flex-direction: column;
62
61
  gap: 0.8rem;
63
62
  width: 100%;
63
+
64
+ :deep(li) {
65
+ line-height: 1;
66
+ }
64
67
  }
65
68
 
66
69
  .footer {
@@ -35,7 +35,7 @@ const uiStore = useUiStore()
35
35
  background-color: var(--color-neutral-background-secondary);
36
36
 
37
37
  &:not(.mobile-drawer) {
38
- height: calc(100dvh - 16rem);
38
+ min-height: calc(100dvh - 16rem);
39
39
  }
40
40
 
41
41
  .header {
@@ -91,13 +91,13 @@ const { id: showBySelectId } = useFormSelect([12, 24, 48, -1], {
91
91
 
92
92
  /* Workaround: we don't have "small" select yet */
93
93
  .show-by-select {
94
- width: 6rem;
94
+ width: 7rem;
95
95
 
96
96
  &:deep(.ui-input) {
97
97
  height: 3rem;
98
98
  padding-inline: 0.8rem;
99
99
  gap: 0.8rem;
100
- min-width: 6rem;
100
+ min-width: 7rem;
101
101
  }
102
102
 
103
103
  &:deep(.input) {
@@ -1,12 +1,14 @@
1
1
  import type { TreeNodeBase } from '@core/composables/tree/tree-node-base'
2
+ import { refDebounced } from '@vueuse/shared'
2
3
  import { computed, ref } from 'vue'
3
4
 
4
5
  export function useTreeFilter() {
5
6
  const filter = ref('')
6
- const hasFilter = computed(() => filter.value.trim().length > 0)
7
+ const debouncedFilter = refDebounced(filter, 500)
8
+ const hasFilter = computed(() => debouncedFilter.value.trim().length > 0)
7
9
 
8
10
  const predicate = (node: TreeNodeBase) =>
9
- hasFilter.value ? node.label.toLocaleLowerCase().includes(filter.value.toLocaleLowerCase()) : undefined
11
+ hasFilter.value ? node.label.toLocaleLowerCase().includes(debouncedFilter.value.toLocaleLowerCase()) : undefined
10
12
 
11
13
  return { filter, predicate }
12
14
  }
@@ -12,7 +12,7 @@ export const icons = defineIconPack({
12
12
 
13
13
  export type IconName = Exclude<keyof typeof icons, typeof ICON_SYMBOL>
14
14
 
15
- export type ObjectIconName = Extract<IconName, `object:${string}`>
15
+ export type ObjectIconName = Extract<IconName, `object:${string}:${string}`>
16
16
 
17
17
  export function icon<TName extends IconName>(name: TName): TName {
18
18
  return name
@@ -3,9 +3,12 @@ import { defineIcon } from '@core/packages/icon/define-icon.ts'
3
3
  import type { IconTransforms } from '@core/packages/icon/types.ts'
4
4
  import { createMapper } from '@core/packages/mapper/create-mapper.ts'
5
5
  import {
6
+ faArrowLeft,
6
7
  faBan,
8
+ faBoxArchive,
7
9
  faCheck,
8
10
  faCircle,
11
+ faClock,
9
12
  faDatabase,
10
13
  faDesktop,
11
14
  faMinus,
@@ -17,7 +20,6 @@ import {
17
20
  faServer,
18
21
  faStop,
19
22
  faTriangleExclamation,
20
- faWarehouse,
21
23
  faXmark,
22
24
  } from '@fortawesome/free-solid-svg-icons'
23
25
 
@@ -148,6 +150,32 @@ function getMainColor(state: string) {
148
150
  : 'var(--color-neutral-txt-primary)'
149
151
  }
150
152
 
153
+ const backupIcon = defineIcon({ icon: faBoxArchive })
154
+
155
+ const backupRepository = defineIcon([
156
+ { icon: backupIcon, translate: [0, -4], borderColor: defaultTransforms.borderColor, size: 10 },
157
+ { icon: backupIcon, translate: [-4, 4], borderColor: defaultTransforms.borderColor, size: 10 },
158
+ { icon: backupIcon, translate: [4, 4], borderColor: defaultTransforms.borderColor, size: 10 },
159
+ ])
160
+
161
+ const backupJob = defineIcon([
162
+ { icon: backupIcon, borderColor: defaultTransforms.borderColor },
163
+ { icon: faCircle, translate: [6, 6], borderColor: defaultTransforms.borderColor, size: 12 },
164
+ { icon: faArrowLeft, translate: [6, 6], color: 'var(--color-neutral-background-primary)', size: 8 },
165
+ ])
166
+
167
+ const backupSchedule = defineIcon([
168
+ { icon: backupIcon, borderColor: defaultTransforms.borderColor },
169
+ { icon: faCircle, translate: [6, 6], size: 12, color: 'var(--color-neutral-background-primary)' },
170
+ { icon: faClock, translate: [6, 6], size: 11 },
171
+ ])
172
+
173
+ const backupLog = defineIcon([
174
+ { icon: backupIcon, borderColor: defaultTransforms.borderColor },
175
+ { icon: faCircle, translate: [6, 6], borderColor: defaultTransforms.borderColor, size: 12 },
176
+ { icon: faPlay, translate: [6.5, 6], color: 'var(--color-neutral-background-primary)', size: 6 },
177
+ ])
178
+
151
179
  export const objectIcons = defineIconPack({
152
180
  vm: defineIcon([['running', 'halted', 'suspended', 'paused', 'muted']], state => [
153
181
  {
@@ -179,9 +207,12 @@ export const objectIcons = defineIconPack({
179
207
  ]),
180
208
  'backup-repository': defineIcon([['connected', 'disconnected']], state => [
181
209
  {
182
- icon: faWarehouse,
210
+ icon: backupRepository,
183
211
  color: getMainColor(state),
184
212
  },
185
213
  { icon: getStatusIcon(state) },
186
214
  ]),
215
+ 'backup-job': backupJob,
216
+ 'backup-schedule': backupSchedule,
217
+ 'backup-log': backupLog,
187
218
  })