@xen-orchestra/web-core 0.28.0 → 0.29.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.
- package/lib/components/progress-bar/VtsProgressBar.vue +54 -0
- package/lib/components/progress-bar-group/VtsProgressBarGroup.vue +76 -0
- package/lib/components/ui/data-ruler/UiDataRuler.vue +54 -6
- package/lib/components/ui/progress-bar/UiProgressBar.vue +14 -70
- package/lib/locales/en.json +1 -0
- package/lib/locales/fr.json +1 -0
- package/lib/packages/progress/use-progress-group.ts +1 -1
- package/lib/utils/progress.util.ts +72 -0
- package/lib/utils/size.util.ts +6 -0
- package/package.json +1 -1
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="vts-progress-bar">
|
|
3
|
+
<UiDataRuler :max="percentageCap" :warning="threshold.payload" />
|
|
4
|
+
<UiProgressBar :accent="threshold.payload.accent ?? 'info'" :fill-width :legend />
|
|
5
|
+
</div>
|
|
6
|
+
</template>
|
|
7
|
+
|
|
8
|
+
<script generic="TSource" lang="ts" setup>
|
|
9
|
+
import UiDataRuler from '@core/components/ui/data-ruler/UiDataRuler.vue'
|
|
10
|
+
import UiProgressBar, { type ProgressBarAccent } from '@core/components/ui/progress-bar/UiProgressBar.vue'
|
|
11
|
+
import { useProgress } from '@core/packages/progress/use-progress.ts'
|
|
12
|
+
import type { ThresholdConfig } from '@core/packages/threshold/type.ts'
|
|
13
|
+
import { useThreshold } from '@core/packages/threshold/use-threshold.ts'
|
|
14
|
+
import { defaultProgressThresholds, useProgressToLegend } from '@core/utils/progress.util.ts'
|
|
15
|
+
|
|
16
|
+
export type ProgressBarLegendType = 'percent' | 'bytes' | 'bytes-with-total' | 'value' | 'value-with-total'
|
|
17
|
+
|
|
18
|
+
export type ProgressBarThresholdPayload = { accent?: ProgressBarAccent; tooltip?: string }
|
|
19
|
+
|
|
20
|
+
export type ProgressBarThresholdConfig = ThresholdConfig<ProgressBarThresholdPayload>
|
|
21
|
+
|
|
22
|
+
const {
|
|
23
|
+
current,
|
|
24
|
+
total,
|
|
25
|
+
label,
|
|
26
|
+
thresholds = defaultProgressThresholds(),
|
|
27
|
+
legendType,
|
|
28
|
+
} = defineProps<{
|
|
29
|
+
current: number
|
|
30
|
+
total: number
|
|
31
|
+
label: string
|
|
32
|
+
legendType?: ProgressBarLegendType
|
|
33
|
+
thresholds?: ProgressBarThresholdConfig
|
|
34
|
+
}>()
|
|
35
|
+
|
|
36
|
+
const progress = useProgress(
|
|
37
|
+
() => current,
|
|
38
|
+
() => total
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
const { percentageCap, percentage, fillWidth } = progress
|
|
42
|
+
|
|
43
|
+
const legend = useProgressToLegend(() => legendType, label, progress)
|
|
44
|
+
|
|
45
|
+
const threshold = useThreshold(percentage, () => thresholds)
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<style lang="postcss" scoped>
|
|
49
|
+
.vts-progress-bar {
|
|
50
|
+
display: flex;
|
|
51
|
+
flex-direction: column;
|
|
52
|
+
gap: 1.6rem;
|
|
53
|
+
}
|
|
54
|
+
</style>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="vts-progress-bar-group">
|
|
3
|
+
<UiDataRuler :max="highestPercentageCap" :warning="threshold.payload" />
|
|
4
|
+
<UiProgressBar
|
|
5
|
+
v-for="item of progressItems"
|
|
6
|
+
:key="item.source.id"
|
|
7
|
+
:accent="item.threshold.payload?.accent ?? 'info'"
|
|
8
|
+
:fill-width="item.fillWidth"
|
|
9
|
+
:legend="toLegend(item.source.label, item)"
|
|
10
|
+
/>
|
|
11
|
+
</div>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script generic="TSource" lang="ts" setup>
|
|
15
|
+
import type {
|
|
16
|
+
ProgressBarLegendType,
|
|
17
|
+
ProgressBarThresholdConfig,
|
|
18
|
+
} from '@core/components/progress-bar/VtsProgressBar.vue'
|
|
19
|
+
import UiDataRuler from '@core/components/ui/data-ruler/UiDataRuler.vue'
|
|
20
|
+
import UiProgressBar from '@core/components/ui/progress-bar/UiProgressBar.vue'
|
|
21
|
+
import { useProgressGroup } from '@core/packages/progress/use-progress-group.ts'
|
|
22
|
+
import { useThreshold } from '@core/packages/threshold/use-threshold.ts'
|
|
23
|
+
import { defaultProgressThresholds, useProgressToLegend } from '@core/utils/progress.util.ts'
|
|
24
|
+
import { computed } from 'vue'
|
|
25
|
+
|
|
26
|
+
export type ProgressBarGroupItem = { id: string; current: number; total: number; label: string }
|
|
27
|
+
|
|
28
|
+
const {
|
|
29
|
+
items,
|
|
30
|
+
thresholds = defaultProgressThresholds(),
|
|
31
|
+
sort = 'desc',
|
|
32
|
+
legendType,
|
|
33
|
+
nItems,
|
|
34
|
+
} = defineProps<{
|
|
35
|
+
items: ProgressBarGroupItem[]
|
|
36
|
+
legendType?: ProgressBarLegendType
|
|
37
|
+
thresholds?: ProgressBarThresholdConfig
|
|
38
|
+
sort?: 'asc' | 'desc' | 'none'
|
|
39
|
+
nItems?: number
|
|
40
|
+
}>()
|
|
41
|
+
|
|
42
|
+
const {
|
|
43
|
+
highestPercentageCap,
|
|
44
|
+
progressItems: rawProgressItems,
|
|
45
|
+
highestPercentage,
|
|
46
|
+
} = useProgressGroup(() => items, {
|
|
47
|
+
sort: () => (sort === 'none' ? undefined : sort),
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const progressItems = computed(() => {
|
|
51
|
+
const items = rawProgressItems.value.map(item => {
|
|
52
|
+
return {
|
|
53
|
+
...item,
|
|
54
|
+
threshold: useThreshold(item.percentage, thresholds).value,
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
if (nItems) {
|
|
59
|
+
return items.slice(0, nItems)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return items
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const toLegend = useProgressToLegend(() => legendType)
|
|
66
|
+
|
|
67
|
+
const threshold = useThreshold(highestPercentage, () => thresholds)
|
|
68
|
+
</script>
|
|
69
|
+
|
|
70
|
+
<style lang="postcss" scoped>
|
|
71
|
+
.vts-progress-bar-group {
|
|
72
|
+
display: flex;
|
|
73
|
+
flex-direction: column;
|
|
74
|
+
gap: 1.6rem;
|
|
75
|
+
}
|
|
76
|
+
</style>
|
|
@@ -1,24 +1,72 @@
|
|
|
1
1
|
<!-- v1 -->
|
|
2
2
|
<template>
|
|
3
3
|
<div class="ui-data-ruler typo-body-regular-small">
|
|
4
|
-
<span>{{
|
|
5
|
-
<span>{{
|
|
6
|
-
<span
|
|
4
|
+
<span>{{ n(0, 'percent') }}</span>
|
|
5
|
+
<span>{{ n(max / 200, 'percent') }}</span>
|
|
6
|
+
<span class="max">
|
|
7
|
+
<VtsIcon
|
|
8
|
+
v-if="warning?.accent || warning?.tooltip"
|
|
9
|
+
v-tooltip="warning.tooltip ?? false"
|
|
10
|
+
:class="warning.accent ?? 'warning'"
|
|
11
|
+
class="icon"
|
|
12
|
+
name="fa:exclamation-circle"
|
|
13
|
+
size="small"
|
|
14
|
+
/>
|
|
15
|
+
{{ n(max / 100, 'percent') }}</span
|
|
16
|
+
>
|
|
7
17
|
</div>
|
|
8
18
|
</template>
|
|
9
19
|
|
|
10
20
|
<script lang="ts" setup>
|
|
21
|
+
import VtsIcon from '@core/components/icon/VtsIcon.vue'
|
|
22
|
+
import { vTooltip } from '@core/directives/tooltip.directive.ts'
|
|
11
23
|
import { useI18n } from 'vue-i18n'
|
|
12
24
|
|
|
13
|
-
const {
|
|
25
|
+
const { max = 100 } = defineProps<{
|
|
26
|
+
max?: number
|
|
27
|
+
warning?: {
|
|
28
|
+
accent?: 'info' | 'warning' | 'danger'
|
|
29
|
+
tooltip?: string
|
|
30
|
+
}
|
|
31
|
+
}>()
|
|
32
|
+
|
|
33
|
+
const { n } = useI18n()
|
|
14
34
|
</script>
|
|
15
35
|
|
|
16
36
|
<style lang="postcss" scoped>
|
|
17
37
|
.ui-data-ruler {
|
|
18
|
-
display:
|
|
38
|
+
display: grid;
|
|
39
|
+
grid-template-columns: 1fr auto 1fr;
|
|
19
40
|
align-items: center;
|
|
20
|
-
justify-content: space-between;
|
|
21
41
|
gap: 1rem;
|
|
22
42
|
color: var(--color-neutral-txt-secondary);
|
|
43
|
+
|
|
44
|
+
.icon {
|
|
45
|
+
&.warning {
|
|
46
|
+
color: var(--color-warning-item-base);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
&.danger {
|
|
50
|
+
color: var(--color-danger-item-base);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.max {
|
|
55
|
+
display: flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
gap: 0.8rem;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
& > span:first-child {
|
|
61
|
+
justify-self: start;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
& > span:nth-child(2) {
|
|
65
|
+
justify-self: center;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
& > span:last-child {
|
|
69
|
+
justify-self: end;
|
|
70
|
+
}
|
|
23
71
|
}
|
|
24
72
|
</style>
|
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
<!-- v2 -->
|
|
2
2
|
<template>
|
|
3
|
-
<div class="ui-progress-bar"
|
|
3
|
+
<div :class="className" class="ui-progress-bar">
|
|
4
4
|
<div class="progress-bar">
|
|
5
|
-
<div
|
|
5
|
+
<div :style="{ width: fillWidth }" class="fill" />
|
|
6
6
|
</div>
|
|
7
|
-
<
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
</div>
|
|
11
|
-
<VtsLegendList class="legend">
|
|
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 }}
|
|
7
|
+
<VtsLegendList v-if="legend" class="legend">
|
|
8
|
+
<UiLegend :accent :value="legend.value">
|
|
9
|
+
{{ legend.label }}
|
|
17
10
|
</UiLegend>
|
|
18
11
|
</VtsLegendList>
|
|
19
12
|
</div>
|
|
@@ -22,61 +15,20 @@
|
|
|
22
15
|
<script lang="ts" setup>
|
|
23
16
|
import VtsLegendList from '@core/components/legend-list/VtsLegendList.vue'
|
|
24
17
|
import UiLegend from '@core/components/ui/legend/UiLegend.vue'
|
|
25
|
-
import { formatSizeRaw } from '@core/utils/size.util.ts'
|
|
26
18
|
import { toVariants } from '@core/utils/to-variants.util'
|
|
27
|
-
import { useClamp, useMax } from '@vueuse/math'
|
|
28
19
|
import { computed } from 'vue'
|
|
29
|
-
import { useI18n } from 'vue-i18n'
|
|
30
|
-
|
|
31
|
-
interface Props {
|
|
32
|
-
legend: string
|
|
33
|
-
value: number
|
|
34
|
-
showSteps?: boolean
|
|
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)>()
|
|
46
|
-
|
|
47
|
-
const { n } = useI18n()
|
|
48
20
|
|
|
49
|
-
|
|
21
|
+
export type ProgressBarAccent = 'info' | 'warning' | 'danger'
|
|
50
22
|
|
|
51
|
-
|
|
23
|
+
export type ProgressBarLegend = { label: string; value?: string | number }
|
|
52
24
|
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const accent = computed(() => {
|
|
60
|
-
if (percentage.value >= 90) {
|
|
61
|
-
return 'danger'
|
|
62
|
-
}
|
|
25
|
+
const { accent, legend } = defineProps<{
|
|
26
|
+
accent: ProgressBarAccent
|
|
27
|
+
fillWidth: string
|
|
28
|
+
legend?: ProgressBarLegend
|
|
29
|
+
}>()
|
|
63
30
|
|
|
64
|
-
|
|
65
|
-
return 'warning'
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return 'info'
|
|
69
|
-
})
|
|
70
|
-
|
|
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
|
-
})
|
|
31
|
+
const className = computed(() => toVariants({ accent }))
|
|
80
32
|
</script>
|
|
81
33
|
|
|
82
34
|
<style lang="postcss" scoped>
|
|
@@ -87,30 +39,22 @@ const legendValue = computed(() => {
|
|
|
87
39
|
|
|
88
40
|
.progress-bar {
|
|
89
41
|
width: 100%;
|
|
90
|
-
height: 1.2rem;
|
|
91
42
|
border-radius: 0.4rem;
|
|
92
43
|
overflow: hidden;
|
|
93
44
|
background-color: var(--color-neutral-background-disabled);
|
|
94
45
|
|
|
95
46
|
.fill {
|
|
96
47
|
width: 0;
|
|
97
|
-
height:
|
|
48
|
+
height: 1.2rem;
|
|
98
49
|
transition: width 0.25s ease-in-out;
|
|
99
50
|
}
|
|
100
51
|
}
|
|
101
52
|
|
|
102
|
-
.steps {
|
|
103
|
-
color: var(--color-neutral-txt-secondary);
|
|
104
|
-
display: flex;
|
|
105
|
-
justify-content: space-between;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
53
|
.legend {
|
|
109
54
|
margin-inline-start: auto;
|
|
110
55
|
}
|
|
111
56
|
|
|
112
57
|
/* ACCENT */
|
|
113
|
-
|
|
114
58
|
&.accent--info {
|
|
115
59
|
.fill {
|
|
116
60
|
background-color: var(--color-info-item-base);
|
package/lib/locales/en.json
CHANGED
|
@@ -260,6 +260,7 @@
|
|
|
260
260
|
"hyper-threading": "Hyper threading (SMT)",
|
|
261
261
|
"id": "ID",
|
|
262
262
|
"in-last-three-jobs": "In their last three jobs",
|
|
263
|
+
"in-progress": "In progress",
|
|
263
264
|
"install-settings": "Install settings",
|
|
264
265
|
"interfaces": "Interface | Interface | Interfaces",
|
|
265
266
|
"interrupted": "Interrupted",
|
package/lib/locales/fr.json
CHANGED
|
@@ -260,6 +260,7 @@
|
|
|
260
260
|
"hyper-threading": "Hyper-threading (SMT)",
|
|
261
261
|
"id": "ID",
|
|
262
262
|
"in-last-three-jobs": "Dans leurs trois derniers jobs",
|
|
263
|
+
"in-progress": "En cours",
|
|
263
264
|
"install-settings": "Paramètres d'installation",
|
|
264
265
|
"interfaces": "Interface | Interface | Interfaces",
|
|
265
266
|
"interrupted": "Interrompu",
|
|
@@ -37,7 +37,7 @@ export function useProgressGroup<TSource>(
|
|
|
37
37
|
)
|
|
38
38
|
|
|
39
39
|
const highestPercentageCap = computed(() =>
|
|
40
|
-
progressItems.value.reduce((highestCap, item) => Math.max(highestCap, item.percentageCap.value),
|
|
40
|
+
progressItems.value.reduce((highestCap, item) => Math.max(highestCap, item.percentageCap.value), 100)
|
|
41
41
|
)
|
|
42
42
|
|
|
43
43
|
const normalizedProgressItems = useArrayMap(progressItems, item => {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ProgressBarLegendType,
|
|
3
|
+
ProgressBarThresholdPayload,
|
|
4
|
+
} from '@core/components/progress-bar/VtsProgressBar.vue'
|
|
5
|
+
import type { ProgressBarLegend } from '@core/components/ui/progress-bar/UiProgressBar.vue'
|
|
6
|
+
import type { Progress } from '@core/packages/progress/types.ts'
|
|
7
|
+
import type { ThresholdConfig } from '@core/packages/threshold/type.ts'
|
|
8
|
+
import { formatSize } from '@core/utils/size.util.ts'
|
|
9
|
+
import { computed, type ComputedRef, type MaybeRefOrGetter, type Reactive, toValue } from 'vue'
|
|
10
|
+
import { useI18n } from 'vue-i18n'
|
|
11
|
+
|
|
12
|
+
export function defaultProgressThresholds(tooltip?: string): ThresholdConfig<ProgressBarThresholdPayload> {
|
|
13
|
+
return {
|
|
14
|
+
80: { accent: 'warning', tooltip },
|
|
15
|
+
90: { accent: 'danger', tooltip },
|
|
16
|
+
default: {},
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function cpuProgressThresholds(tooltip?: string): ThresholdConfig<ProgressBarThresholdPayload> {
|
|
21
|
+
return {
|
|
22
|
+
100: { accent: 'warning', tooltip },
|
|
23
|
+
300: { accent: 'danger', tooltip },
|
|
24
|
+
default: {},
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function useProgressToLegend(
|
|
29
|
+
rawType: MaybeRefOrGetter<ProgressBarLegendType | undefined>
|
|
30
|
+
): (label: string, progress: Progress | Reactive<Progress>) => ProgressBarLegend | undefined
|
|
31
|
+
|
|
32
|
+
export function useProgressToLegend(
|
|
33
|
+
rawType: MaybeRefOrGetter<ProgressBarLegendType | undefined>,
|
|
34
|
+
label: string,
|
|
35
|
+
progress: Progress | Reactive<Progress>
|
|
36
|
+
): ComputedRef<ProgressBarLegend | undefined>
|
|
37
|
+
|
|
38
|
+
export function useProgressToLegend(
|
|
39
|
+
rawType: MaybeRefOrGetter<ProgressBarLegendType | undefined>,
|
|
40
|
+
label?: string,
|
|
41
|
+
progress?: Progress | Reactive<Progress>
|
|
42
|
+
) {
|
|
43
|
+
const { n } = useI18n()
|
|
44
|
+
|
|
45
|
+
const type = computed(() => toValue(rawType))
|
|
46
|
+
|
|
47
|
+
function toLegend(label: string, progress: Progress | Reactive<Progress>): ProgressBarLegend | undefined {
|
|
48
|
+
switch (type.value) {
|
|
49
|
+
case 'percent':
|
|
50
|
+
return { label, value: n(toValue(progress.percentage) / 100, { maximumFractionDigits: 1, style: 'percent' }) }
|
|
51
|
+
case 'bytes':
|
|
52
|
+
return { label, value: formatSize(toValue(progress.current), 0) }
|
|
53
|
+
case 'bytes-with-total':
|
|
54
|
+
return {
|
|
55
|
+
label,
|
|
56
|
+
value: `${formatSize(toValue(progress.current), 0)} / ${formatSize(toValue(progress.total), 1)}`,
|
|
57
|
+
}
|
|
58
|
+
case 'value':
|
|
59
|
+
return { label, value: n(toValue(progress.current)) }
|
|
60
|
+
case 'value-with-total':
|
|
61
|
+
return { label, value: `${n(toValue(progress.current))} / ${n(toValue(progress.total))}` }
|
|
62
|
+
default:
|
|
63
|
+
return undefined
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (label && progress) {
|
|
68
|
+
return computed(() => toLegend(label, progress))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return toLegend
|
|
72
|
+
}
|
package/lib/utils/size.util.ts
CHANGED
|
@@ -39,3 +39,9 @@ export const formatSizeParse = (size: number | undefined, unit?: string) => {
|
|
|
39
39
|
|
|
40
40
|
return result
|
|
41
41
|
}
|
|
42
|
+
|
|
43
|
+
export function formatSize(bytes: number, decimals: number): string {
|
|
44
|
+
const { value, prefix } = formatSizeRaw(bytes, decimals)
|
|
45
|
+
|
|
46
|
+
return `${value} ${prefix}`
|
|
47
|
+
}
|