@xen-orchestra/web-core 0.7.0 → 0.9.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 (38) hide show
  1. package/lib/assets/css/typography/_style.pcss +1 -0
  2. package/lib/components/layout/VtsLayoutSidebar.vue +1 -1
  3. package/lib/components/state-hero/VtsErrorNoDataHero.vue +11 -0
  4. package/lib/components/state-hero/VtsNoSelectionHero.vue +13 -0
  5. package/lib/components/state-hero/VtsObjectNotFoundHero.vue +3 -2
  6. package/lib/components/state-hero/VtsStateHero.vue +30 -2
  7. package/lib/components/ui/donut-chart/UiDonutChart.vue +2 -2
  8. package/lib/components/ui/dropdown-button/UiDropdownButton.vue +81 -0
  9. package/lib/components/ui/input/UiInput.vue +121 -92
  10. package/lib/components/ui/link/UiLink.vue +75 -0
  11. package/lib/components/ui/query-search-bar/UiQuerySearchBar.vue +2 -0
  12. package/lib/components/ui/tag/UiTagsList.vue +14 -0
  13. package/lib/composables/link-component.composable.ts +53 -0
  14. package/lib/locales/cs.json +1 -0
  15. package/lib/locales/de.json +2 -0
  16. package/lib/locales/en.json +4 -0
  17. package/lib/locales/fa.json +2 -0
  18. package/lib/locales/fr.json +4 -0
  19. package/lib/packages/job/README.md +130 -0
  20. package/lib/packages/job/define-job-arg.ts +12 -0
  21. package/lib/packages/job/define-job.ts +130 -0
  22. package/lib/packages/job/index.ts +4 -0
  23. package/lib/packages/job/job-error.ts +14 -0
  24. package/lib/packages/job/use-job-store.ts +44 -0
  25. package/lib/packages/menu/README.md +194 -0
  26. package/lib/packages/menu/action.ts +101 -0
  27. package/lib/packages/menu/base.ts +26 -0
  28. package/lib/packages/menu/context.ts +27 -0
  29. package/lib/packages/menu/index.ts +10 -0
  30. package/lib/packages/menu/job.ts +15 -0
  31. package/lib/packages/menu/link.ts +56 -0
  32. package/lib/packages/menu/menu.ts +50 -0
  33. package/lib/packages/menu/router-link.ts +51 -0
  34. package/lib/packages/menu/structure.ts +88 -0
  35. package/lib/packages/menu/toggle-target.ts +59 -0
  36. package/lib/packages/menu/toggle-trigger.ts +72 -0
  37. package/lib/packages/menu/toggle.ts +43 -0
  38. package/package.json +2 -1
@@ -6,6 +6,7 @@
6
6
  &.p3-regular-underline,
7
7
  &.p4-regular-underline {
8
8
  text-decoration: underline;
9
+ text-underline-offset: 0.2rem;
9
10
  }
10
11
 
11
12
  /* ITALIC */
@@ -74,7 +74,7 @@ const ui = useUiStore()
74
74
 
75
75
  .lock {
76
76
  text-align: right;
77
- padding: 0.8rem;
77
+ padding: 0.8rem 0.8rem 0;
78
78
  }
79
79
 
80
80
  .content {
@@ -0,0 +1,11 @@
1
+ <template>
2
+ <VtsStateHero class="vts-error-hero" image="error" :type>{{ $t('error-no-data') }}</VtsStateHero>
3
+ </template>
4
+
5
+ <script lang="ts" setup>
6
+ import VtsStateHero, { type StateHeroType } from '@core/components/state-hero/VtsStateHero.vue'
7
+
8
+ defineProps<{
9
+ type: StateHeroType
10
+ }>()
11
+ </script>
@@ -0,0 +1,13 @@
1
+ <template>
2
+ <VtsStateHero :type class="vts-no-selection-hero" image="no-selection">
3
+ {{ $t('select-to-see-details') }}
4
+ </VtsStateHero>
5
+ </template>
6
+
7
+ <script lang="ts" setup>
8
+ import VtsStateHero, { type StateHeroType } from '@core/components/state-hero/VtsStateHero.vue'
9
+
10
+ defineProps<{
11
+ type: StateHeroType
12
+ }>()
13
+ </script>
@@ -1,13 +1,14 @@
1
1
  <template>
2
- <VtsStateHero class="vts-object-not-found-hero" image="no-result" type="page">
2
+ <VtsStateHero class="vts-object-not-found-hero" image="no-result" :type>
3
3
  {{ $t('object-not-found', { id }) }}
4
4
  </VtsStateHero>
5
5
  </template>
6
6
 
7
7
  <script lang="ts" setup>
8
- import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
8
+ import VtsStateHero, { type StateHeroType } from '@core/components/state-hero/VtsStateHero.vue'
9
9
 
10
10
  defineProps<{
11
11
  id: string
12
+ type: StateHeroType
12
13
  }>()
13
14
  </script>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div :class="type" class="vts-state-hero">
2
+ <div :class="[type, { error }]" class="vts-state-hero">
3
3
  <UiLoader v-if="busy" class="loader" />
4
4
  <img v-else-if="imageSrc" :src="imageSrc" alt="" class="image" />
5
5
  <p v-if="slots.default" :class="typoClass" class="text">
@@ -12,7 +12,7 @@
12
12
  import UiLoader from '@core/components/ui/loader/UiLoader.vue'
13
13
  import { computed } from 'vue'
14
14
 
15
- export type StateHeroType = 'page' | 'card' | 'panel'
15
+ export type StateHeroType = 'page' | 'card' | 'panel' | 'table'
16
16
 
17
17
  const props = defineProps<{
18
18
  type: StateHeroType
@@ -25,6 +25,7 @@ const slots = defineSlots<{
25
25
  }>()
26
26
 
27
27
  const typoClass = computed(() => (props.type === 'page' ? 'typo h2-black' : 'typo h4-medium'))
28
+ const error = computed(() => !props.busy && props.image === 'error')
28
29
 
29
30
  const imageSrc = computed(() => {
30
31
  if (!props.image) {
@@ -43,6 +44,14 @@ const imageSrc = computed(() => {
43
44
  align-items: center;
44
45
  justify-content: center;
45
46
 
47
+ &.error {
48
+ background-color: var(--color-danger-background-selected);
49
+
50
+ .text {
51
+ color: var(--color-danger-txt-base);
52
+ }
53
+ }
54
+
46
55
  .loader,
47
56
  .text {
48
57
  color: var(--color-info-txt-base);
@@ -113,5 +122,24 @@ const imageSrc = computed(() => {
113
122
  width: 80%;
114
123
  }
115
124
  }
125
+
126
+ &.table {
127
+ padding: 4rem;
128
+ gap: 2.4rem;
129
+
130
+ .text {
131
+ order: 3;
132
+ }
133
+
134
+ .image {
135
+ order: 2;
136
+ max-height: 20rem;
137
+ }
138
+
139
+ .loader {
140
+ order: 1;
141
+ font-size: 10rem;
142
+ }
143
+ }
116
144
  }
117
145
  </style>
@@ -1,7 +1,7 @@
1
1
  <!-- v3 -->
2
2
  <template>
3
3
  <svg class="ui-donut-chart" viewBox="0 0 100 100">
4
- <circle class="segment" cx="50" cy="50" r="40" />
4
+ <circle class="segment accent--muted" cx="50" cy="50" r="40" />
5
5
  <circle
6
6
  v-for="(segment, index) in computedSegments"
7
7
  :key="index"
@@ -44,8 +44,8 @@ const computedSegments = computed(() => {
44
44
  let nextOffset = circumference / 4
45
45
 
46
46
  return props.segments.map(segment => {
47
+ const percent = totalValue.value === 0 ? 0 : (segment.value / totalValue.value) * circumference
47
48
  const offset = nextOffset
48
- const percent = (segment.value / totalValue.value) * circumference
49
49
  nextOffset -= percent
50
50
 
51
51
  return {
@@ -0,0 +1,81 @@
1
+ <!-- v3 -->
2
+ <template>
3
+ <button type="button" class="ui-dropdown-item" :class="{ selected }" :disabled="isDisabled">
4
+ <VtsIcon :icon accent="current" class="left-icon" fixed-width />
5
+ <span class="typo p1-regular label">
6
+ <slot />
7
+ </span>
8
+ <VtsIcon :icon="faAngleDown" accent="current" class="right-icon" fixed-width />
9
+ </button>
10
+ </template>
11
+
12
+ <script lang="ts" setup>
13
+ import VtsIcon from '@core/components/icon/VtsIcon.vue'
14
+ import { useContext } from '@core/composables/context.composable'
15
+ import { DisabledContext } from '@core/context'
16
+ import type { IconDefinition } from '@fortawesome/fontawesome-common-types'
17
+ import { faAngleDown } from '@fortawesome/free-solid-svg-icons'
18
+
19
+ const props = withDefaults(
20
+ defineProps<{
21
+ disabled?: boolean
22
+ selected?: boolean
23
+ icon?: IconDefinition
24
+ }>(),
25
+ { disabled: undefined }
26
+ )
27
+
28
+ const isDisabled = useContext(DisabledContext, () => props.disabled)
29
+ </script>
30
+
31
+ <style scoped lang="postcss">
32
+ .ui-dropdown-item {
33
+ display: inline-flex;
34
+ align-items: center;
35
+ padding-block: 0.4rem;
36
+ padding-inline: 1.6rem;
37
+ gap: 0.8rem;
38
+ background: var(--color-neutral-background-primary);
39
+ border: 0.1rem solid var(--color-info-txt-base);
40
+ border-radius: 9rem;
41
+ cursor: pointer;
42
+ position: relative;
43
+ color: var(--color-info-txt-base);
44
+
45
+ &:hover {
46
+ border-color: var(--color-info-txt-hover);
47
+ background-color: var(--color-info-background-hover);
48
+ color: var(--color-info-txt-hover);
49
+ }
50
+
51
+ &:active {
52
+ border-color: var(--color-info-txt-active);
53
+ background-color: var(--color-info-background-active);
54
+ color: var(--color-info-txt-active);
55
+ }
56
+
57
+ &.selected:not(:disabled) {
58
+ background-color: var(--color-info-background-selected);
59
+ outline: 0.1rem solid var(--color-info-txt-base);
60
+ }
61
+
62
+ &:focus-visible {
63
+ outline: none;
64
+
65
+ &::before {
66
+ content: '';
67
+ position: absolute;
68
+ inset: -0.5rem;
69
+ border: 0.2rem solid var(--color-info-txt-base);
70
+ border-radius: 0.4rem;
71
+ }
72
+ }
73
+
74
+ &:disabled {
75
+ cursor: not-allowed;
76
+ border-color: var(--color-neutral-txt-secondary);
77
+ background-color: var(--color-neutral-background-disabled);
78
+ color: var(--color-neutral-txt-secondary);
79
+ }
80
+ }
81
+ </style>
@@ -1,141 +1,170 @@
1
- <!-- WIP -->
1
+ <!-- v5 -->
2
2
  <template>
3
- <div class="ui-input">
4
- <VtsIcon :icon accent="current" class="before" />
5
- <input :id v-model.trim="modelValue" class="typo p1-regular input" type="search" v-bind="attrs" />
6
- <VtsIcon
7
- v-if="!attrs.disabled && modelValue"
8
- :icon="faXmark"
9
- class="after"
10
- accent="info"
11
- @click="modelValue = ''"
12
- />
3
+ <div class="ui-input" :class="toVariants({ accent })">
4
+ <UiLabel v-if="slots.default || label" :accent="labelAccent" :required :icon="labelIcon" :href :for="id">
5
+ <slot>{{ label }}</slot>
6
+ </UiLabel>
7
+ <div>
8
+ <VtsIcon :icon accent="current" class="before" />
9
+ <input :id v-model.trim="modelValue" class="typo p1-regular input text-ellipsis" :type :disabled v-bind="attrs" />
10
+ <VtsIcon
11
+ v-if="!attrs.disabled && modelValue && clearable"
12
+ :icon="faXmark"
13
+ class="after"
14
+ accent="info"
15
+ @click="modelValue = ''"
16
+ />
17
+ </div>
18
+ <UiInfo v-if="slots.info || info" :accent>
19
+ <slot name="info">{{ info }}</slot>
20
+ </UiInfo>
13
21
  </div>
14
22
  </template>
15
23
 
16
24
  <script lang="ts" setup>
17
25
  import VtsIcon from '@core/components/icon/VtsIcon.vue'
18
- import { uniqueId } from '@core/utils/unique-id.util'
26
+ import UiInfo from '@core/components/ui/info/UiInfo.vue'
27
+ import UiLabel from '@core/components/ui/label/UiLabel.vue'
28
+ import { toVariants } from '@core/utils/to-variants.util'
19
29
  import type { IconDefinition } from '@fortawesome/fontawesome-common-types'
20
30
  import { faXmark } from '@fortawesome/free-solid-svg-icons'
21
- import { computed, useAttrs } from 'vue'
31
+ import { computed, useAttrs, useId } from 'vue'
32
+
33
+ type InputAccent = 'info' | 'warning' | 'danger'
34
+ type InputType = 'text' | 'number' | 'password' | 'search'
22
35
 
23
36
  defineOptions({
24
37
  inheritAttrs: false,
25
38
  })
26
39
 
27
- defineProps<{
40
+ const { accent, id = useId() } = defineProps<{
41
+ accent: InputAccent
42
+ label?: string
43
+ info?: string
44
+ id?: string
45
+ disabled?: boolean
46
+ clearable?: boolean
47
+ href?: string
48
+ required?: boolean
49
+ labelIcon?: IconDefinition
28
50
  icon?: IconDefinition
51
+ type?: InputType
29
52
  }>()
30
53
 
31
- const modelValue = defineModel<string>({ required: true })
54
+ const modelValue = defineModel<string | number>({ required: true })
55
+
56
+ const slots = defineSlots<{
57
+ default?(): any
58
+ info?(): any
59
+ }>()
32
60
 
33
61
  const attrs = useAttrs()
34
62
 
35
- const id = computed(() => uniqueId('input-'))
63
+ const labelAccent = computed(() => (accent === 'info' ? 'neutral' : accent))
36
64
  </script>
37
65
 
38
66
  <style lang="postcss" scoped>
39
- /* COLOR VARIANTS */
40
- .input {
41
- & {
42
- --border-color: var(--color-neutral-border);
43
- --background-color: var(--color-neutral-background-primary);
67
+ /* IMPLEMENTATION */
68
+ .ui-input {
69
+ position: relative;
70
+ display: flex;
71
+ flex-direction: column;
72
+ gap: 0.4rem;
73
+
74
+ .input {
75
+ border-radius: 0.4rem;
76
+ border-width: 0.1rem;
77
+ border-style: solid;
78
+ background-color: var(--color-neutral-background-primary);
79
+ color: var(--color-neutral-txt-primary);
80
+ height: 4rem;
81
+ outline: none;
82
+ width: 100%;
83
+ padding-block: 0.8rem;
84
+ padding-inline: 1.6rem;
85
+
86
+ &::placeholder {
87
+ color: var(--color-neutral-txt-secondary);
88
+ }
89
+
90
+ &:focus {
91
+ border-width: 0.2rem;
92
+ }
93
+
94
+ &:has(+ .after) {
95
+ padding-inline-end: 4.8rem;
96
+ }
44
97
  }
45
98
 
46
- &:is(:hover, :focus-visible) {
47
- --border-color: var(--color-info-item-hover);
48
- --background-color: var(--color-neutral-background-primary);
49
- }
99
+ /* VARIANT */
50
100
 
51
- &:focus {
52
- --border-color: var(--color-info-item-base);
53
- --background-color: var(--color-neutral-background-primary);
54
- }
101
+ &.accent--info {
102
+ .input {
103
+ border-color: var(--color-neutral-border);
55
104
 
56
- &:active {
57
- --border-color: var(--color-info-item-active);
58
- --background-color: var(--color-neutral-background-primary);
59
- }
105
+ &:hover {
106
+ border-color: var(--color-info-item-hover);
107
+ }
60
108
 
61
- &:disabled {
62
- --border-color: var(--color-neutral-border);
63
- --background-color: var(--color-neutral-background-disabled);
64
- }
65
- }
109
+ &:focus {
110
+ border-color: var(--color-info-item-base);
111
+ }
66
112
 
67
- /* BORDER VARIANTS */
68
- .input {
69
- --border-width: 0.1rem;
113
+ &:active {
114
+ border-color: var(--color-info-item-active);
115
+ }
70
116
 
71
- &:is(:hover, :focus-visible) {
72
- --border-width: 0.1rem;
117
+ &:disabled {
118
+ border-color: var(--color-neutral-border);
119
+ background-color: var(--color-neutral-background-disabled);
120
+ }
121
+ }
73
122
  }
74
123
 
75
- &:focus {
76
- --border-width: 0.2rem;
77
- }
124
+ &.accent--warning {
125
+ .input {
126
+ border-color: var(--color-warning-item-base);
78
127
 
79
- &:active {
80
- --border-width: 0.1rem;
128
+ &:disabled {
129
+ border-color: var(--color-neutral-border);
130
+ background-color: var(--color-neutral-background-disabled);
131
+ }
132
+ }
81
133
  }
82
134
 
83
- &:disabled {
84
- --border-width: 0.1rem;
135
+ &.accent--danger {
136
+ .input {
137
+ border-color: var(--color-danger-item-base);
138
+
139
+ &:disabled {
140
+ border-color: var(--color-neutral-border);
141
+ background-color: var(--color-neutral-background-disabled);
142
+ }
143
+ }
85
144
  }
86
- }
87
145
 
88
- /* IMPLEMENTATION */
89
- .ui-input {
90
- position: relative;
146
+ /* ICON POSITION */
91
147
 
92
148
  .before + .input {
93
149
  padding-inline-start: 4.8rem;
94
150
  }
95
- }
96
-
97
- .input {
98
- border: var(--border-width) solid var(--border-color);
99
- border-radius: 0.8rem;
100
- outline: none;
101
- width: 100%;
102
- height: 4rem;
103
- padding: 0.8rem 1.6rem;
104
- color: var(--color-neutral-txt-primary);
105
- background-color: var(--background-color);
106
-
107
- &:has(+ .after) {
108
- padding-inline-end: 4.8rem;
109
- }
110
151
 
111
- &:disabled {
112
- cursor: not-allowed;
152
+ .before,
153
+ .after {
154
+ position: absolute;
155
+ inset-block: 1.2rem;
113
156
  }
114
157
 
115
- &::placeholder {
158
+ .before {
116
159
  color: var(--color-neutral-txt-secondary);
160
+ inset-inline-start: 1.6rem;
161
+ pointer-events: none;
162
+ z-index: 1;
117
163
  }
118
164
 
119
- &[type='search']::-webkit-search-cancel-button {
120
- display: none;
165
+ .after {
166
+ inset-inline-end: 1.6rem;
167
+ cursor: pointer;
121
168
  }
122
169
  }
123
-
124
- .before,
125
- .after {
126
- position: absolute;
127
- inset-block: 1.2rem;
128
- }
129
-
130
- .before {
131
- color: var(--color-neutral-txt-secondary);
132
- inset-inline-start: 1.6rem;
133
- pointer-events: none;
134
- z-index: 1;
135
- }
136
-
137
- .after {
138
- inset-inline-end: 1.6rem;
139
- cursor: pointer;
140
- }
141
170
  </style>
@@ -0,0 +1,75 @@
1
+ <!-- v2 -->
2
+ <template>
3
+ <component :is="component" :class="classes" class="ui-link" v-bind="attributes">
4
+ <VtsIcon :icon accent="current" />
5
+ <slot />
6
+ <VtsIcon
7
+ v-if="attributes.target === '_blank'"
8
+ :icon="faArrowUpRightFromSquare"
9
+ accent="current"
10
+ class="external-icon"
11
+ />
12
+ </component>
13
+ </template>
14
+
15
+ <script lang="ts" setup>
16
+ import VtsIcon from '@core/components/icon/VtsIcon.vue'
17
+ import { type LinkOptions, useLinkComponent } from '@core/composables/link-component.composable'
18
+ import type { IconDefinition } from '@fortawesome/fontawesome-common-types'
19
+ import { faArrowUpRightFromSquare } from '@fortawesome/free-solid-svg-icons'
20
+ import { computed } from 'vue'
21
+
22
+ const props = defineProps<
23
+ LinkOptions & {
24
+ size: 'small' | 'medium'
25
+ icon?: IconDefinition
26
+ }
27
+ >()
28
+
29
+ const typoClasses = {
30
+ small: 'typo p3-regular-underline',
31
+ medium: 'typo p1-regular-underline',
32
+ }
33
+
34
+ const { component, attributes, isDisabled } = useLinkComponent('span', () => props)
35
+
36
+ const classes = computed(() => [typoClasses[props.size], { disabled: isDisabled.value }])
37
+ </script>
38
+
39
+ <style lang="postcss" scoped>
40
+ .ui-link {
41
+ display: inline-flex;
42
+ align-items: center;
43
+ gap: 0.8rem;
44
+ color: var(--color-info-txt-base);
45
+
46
+ &:hover {
47
+ color: var(--color-info-txt-hover);
48
+ }
49
+
50
+ &:active {
51
+ color: var(--color-info-txt-active);
52
+ }
53
+
54
+ &:focus-visible {
55
+ outline: none;
56
+
57
+ &::before {
58
+ content: '';
59
+ position: absolute;
60
+ inset: -0.6rem;
61
+ border: 0.2rem solid var(--color-info-txt-base);
62
+ border-radius: 0.4rem;
63
+ }
64
+ }
65
+
66
+ &.disabled {
67
+ color: var(--color-neutral-txt-secondary);
68
+ cursor: not-allowed;
69
+ }
70
+
71
+ .external-icon {
72
+ font-size: 0.75em;
73
+ }
74
+ }
75
+ </style>
@@ -7,6 +7,8 @@
7
7
  <UiInput
8
8
  :id
9
9
  v-model="value"
10
+ type="text"
11
+ accent="info"
10
12
  :aria-label="uiStore.isMobile ? $t('core.query-search-bar.label') : undefined"
11
13
  :icon="uiStore.isDesktop ? faMagnifyingGlass : undefined"
12
14
  :placeholder="$t('core.query-search-bar.placeholder')"
@@ -0,0 +1,14 @@
1
+ <!-- v1 -->
2
+ <template>
3
+ <div class="ui-tags-list">
4
+ <slot />
5
+ </div>
6
+ </template>
7
+
8
+ <style lang="postcss" scoped>
9
+ .ui-tags-list {
10
+ display: flex;
11
+ flex-wrap: wrap;
12
+ gap: 0.4rem;
13
+ }
14
+ </style>
@@ -0,0 +1,53 @@
1
+ import type { MaybeRefOrGetter } from 'vue'
2
+ import { computed, toValue } from 'vue'
3
+ import type { RouteLocationRaw } from 'vue-router'
4
+
5
+ export type LinkOptions = {
6
+ to?: RouteLocationRaw
7
+ href?: string
8
+ target?: '_blank' | '_self'
9
+ disabled?: boolean
10
+ }
11
+
12
+ export function useLinkComponent(defaultComponent: string, options: MaybeRefOrGetter<LinkOptions>) {
13
+ const config = computed(() => toValue(options))
14
+
15
+ const isDisabled = computed(() => config.value.disabled || (!config.value.to && !config.value.href))
16
+
17
+ const component = computed(() => {
18
+ if (isDisabled.value) {
19
+ return defaultComponent
20
+ }
21
+
22
+ if (config.value.href) {
23
+ return 'a'
24
+ }
25
+
26
+ return 'RouterLink'
27
+ })
28
+
29
+ const attributes = computed(() => {
30
+ if (isDisabled.value) {
31
+ return {}
32
+ }
33
+
34
+ if (config.value.href) {
35
+ return {
36
+ rel: 'noopener noreferrer',
37
+ target: config.value.target ?? '_blank',
38
+ href: config.value.href,
39
+ }
40
+ }
41
+
42
+ return {
43
+ target: config.value.target,
44
+ to: config.value.to,
45
+ }
46
+ })
47
+
48
+ return {
49
+ isDisabled,
50
+ component,
51
+ attributes,
52
+ }
53
+ }
@@ -41,6 +41,7 @@
41
41
 
42
42
  "dashboard": "Přehled",
43
43
  "documentation-name": "Dokumentace k {name}",
44
+ "error-no-data": "Chyba – není možné shromažďovat údaje.",
44
45
  "hosts": "Hostitelé",
45
46
  "learn-more": "Zjistit více",
46
47
  "loading-in-progress": "Probíhá načítání…",
@@ -19,7 +19,9 @@
19
19
 
20
20
  "dashboard": "Dashboard",
21
21
  "documentation-name": "{name} Dokumentation",
22
+ "error-no-data": "Fehler beim Datenabruf.",
22
23
  "fullscreen": "Vollbild",
24
+ "gateway": "Gateway",
23
25
  "hosts": "Hosts",
24
26
  "learn-more": "Mehr erfahren",
25
27
  "loading-in-progress": "Ladevorgang läuft…",
@@ -45,8 +45,10 @@
45
45
  "access-forum": "Access forum",
46
46
  "dashboard": "Dashboard",
47
47
  "documentation-name": "{name} documentation",
48
+ "error-no-data": "Error, can't collect data.",
48
49
  "exit-fullscreen": "Exit fullscreen",
49
50
  "fullscreen": "Fullscreen",
51
+ "gateway": "Gateway",
50
52
  "hosts": "Hosts",
51
53
  "learn-more": "Learn more",
52
54
  "loading-in-progress": "Loading in progress…",
@@ -66,8 +68,10 @@
66
68
  "receive": "Receive",
67
69
  "running-vm": "Running VM | Running VMs",
68
70
  "see-all": "See all",
71
+ "select-to-see-details": "Select an element to see details",
69
72
  "send": "Send",
70
73
  "send-ctrl-alt-del": "Send Ctrl+Alt+Del",
74
+ "speed": "Speed",
71
75
  "stats": "Stats",
72
76
  "storage": "Storage",
73
77
  "system": "System",