Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(xo-web): add patches component to xo6 dashboard #7814

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ module.exports = {
'@intlify/vue-i18n/no-v-html': 'error',
'@intlify/vue-i18n/valid-message-syntax': 'error',
'@intlify/vue-i18n/no-duplicate-keys-in-locale': 'error',
'@intlify/vue-i18n/no-missing-keys-in-other-locales': ['error', { ignoreLocales: ['de'] }],
'@intlify/vue-i18n/no-missing-keys-in-other-locales': ['error', { ignoreLocales: ['de', 'fa'] }],
},
},
// Specific rules for XO dev-related files
Expand Down Expand Up @@ -274,7 +274,7 @@ module.exports = {
// this rule can prevent race condition bugs like parallel `a += await foo()`
//
// as it has a lots of false positive, it is only enabled as a warning for now
'require-atomic-updates': 'warn',
'require-atomic-updates': ['warn', { allowProperties: true }],

strict: 'error',
},
Expand Down
2 changes: 0 additions & 2 deletions @xen-orchestra/lite/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@
"host.active": "Active",
"host.inactive": "Inactive",

"hosts": "Hosts",
"invalid-field": "Invalid field",
"keep-me-logged": "Keep me logged in",
"keep-page-open": "Do not refresh or quit tab before end of deployment.",
Expand Down Expand Up @@ -146,7 +145,6 @@
"page-not-found": "This page is not to be found…",
"password": "Password",
"password-invalid": "Password invalid",
"patches": "Patches",
"pause": "Pause",
"please-confirm": "Please confirm",
"pool-cpu-usage": "Pool CPU Usage",
Expand Down
2 changes: 0 additions & 2 deletions @xen-orchestra/lite/src/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@
"host.active": "Actif | Actif | Actifs",
"host.inactive": "Inactif | Inactif | Inactifs",

"hosts": "Hôtes",
"invalid-field": "Champ invalide",
"keep-me-logged": "Rester connecté",
"keep-page-open": "Ne pas rafraichir ou quitter cette page avant la fin du déploiement.",
Expand Down Expand Up @@ -146,7 +145,6 @@
"page-not-found": "Cette page est introuvable…",
"password": "Mot de passe",
"password-invalid": "Mot de passe incorrect",
"patches": "Patches",
"pause": "Pause",
"please-confirm": "Veuillez confirmer",
"pool-cpu-usage": "Utilisation CPU du Pool",
Expand Down
9 changes: 9 additions & 0 deletions @xen-orchestra/web-core/docs/guidelines/i18n.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,12 @@ If you really need to **exceptionally** include a literal string, you can either

> [!TIP]
> In most cases, literal strings will not be necessary. Think twice before using them.

## Adding a new locale

> [!NOTE]
> Only `EN` and `FR` locales are fully supported by XO team.

For other locales added by contributors, some keys might be missing when we add new translations.

This can result in ESLint errors when some translation keys are missing. To avoid this, remember to add the locale to the list of excluded files in `.eslintrc.js` at the root of the project (rule is `'@intlify/vue-i18n/no-missing-keys-in-other-locales': ['error', { ignoreLocales: ['de', 'fa'] }]` on line 218).
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<template>
<div class="donut-with-legends">
<DonutChart :segments="donutSegments" :icon :max-value="maxValue" />
<div class="legends-and-title">
<slot />
<ul class="legends">
<UiLegend
v-for="(legendSegment, index) in legendSegments"
:key="index"
:color="legendSegment.color"
:value="legendSegment.value"
:unit="legendSegment.unit"
:tooltip="legendSegment.tooltip"
>
{{ legendSegment.label }}
</UiLegend>
</ul>
</div>
</div>
</template>

<script setup lang="ts">
import DonutChart from '@core/components/DonutChart.vue'
import UiLegend from '@core/components/UiLegend.vue'
import type { DonutSegmentColor } from '@core/types/donut-chart.type'
import type { LegendColor } from '@core/types/legend.type'
import type { IconDefinition } from '@fortawesome/fontawesome-common-types'
import { computed } from 'vue'
const props = defineProps<{
segments: {
label: string
value: number
unit?: string
color: DonutSegmentColor
tooltip?: string
}[]
icon?: IconDefinition
maxValue?: number
}>()
defineSlots<{
default: () => void
}>()
const donutSegments = computed(() =>
props.segments.map(segment => ({
value: segment.value,
color: segment.color,
}))
)
const legendSegments = computed(() =>
props.segments.map(segment => ({
label: segment.label,
value: segment.value,
unit: segment.unit,
color: segment.color === 'unknown' ? 'disabled' : (segment.color as LegendColor),
tooltip: segment.tooltip,
}))
)
</script>

<style lang="postcss" scoped>
.donut-with-legends {
display: flex;
align-items: center;
gap: 3.2rem;
}
.legends-and-title {
display: flex;
flex-direction: column;
gap: 0.8rem;
}
.legends {
display: flex;
flex-direction: column;
gap: 0.8rem;
}
</style>
1 change: 1 addition & 0 deletions @xen-orchestra/web-core/lib/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"master": "Primary host",
"network": "Network",
"object-not-found": "Object {id} can't be found…",
"patches": "Patches",
"power-on-for-console": "Power on your VM to access its console",
"stats": "Stats",
"storage": "Storage",
Expand Down
1 change: 1 addition & 0 deletions @xen-orchestra/web-core/lib/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"master": "Hôte primaire",
"network": "Réseau",
"object-not-found": "L'objet {id} est introuvable…",
"patches": "Patches",
"power-on-for-console": "Allumez votre VM pour accéder à sa console",
"stats": "Stats",
"storage": "Stockage",
Expand Down
5 changes: 5 additions & 0 deletions @xen-orchestra/web-core/lib/types/donut-chart.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type DonutSegmentColor = 'success' | 'warning' | 'error' | 'unknown'
export type DonutSegment = {
value: number
color: DonutSegmentColor
}
1 change: 1 addition & 0 deletions @xen-orchestra/web-core/lib/types/legend.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type LegendColor = 'default' | 'success' | 'warning' | 'error' | 'disabled' | 'dark-blue'
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<template>
<UiCard>
<CardTitle>{{ $t('patches') }}</CardTitle>
<DonutWithLegends :segments="serversSegments">
<LegendTitle>{{ $t('servers') }}</LegendTitle>
</DonutWithLegends>
<UiSeparator />
<DonutWithLegends :segments="hostsSegments">
<LegendTitle>{{ $t('hosts') }}</LegendTitle>
</DonutWithLegends>
</UiCard>
</template>

<script setup lang="ts">
// import { useDashboardStore } from '@/stores/xo-rest-api/dashboard.store'
import CardTitle from '@core/components/card/CardTitle.vue'
import LegendTitle from '@core/components/LegendTitle.vue'
import DonutWithLegends from '@core/components/pool/dashboard/DonutWithLegends.vue'
import UiCard from '@core/components/UiCard.vue'
import UiSeparator from '@core/components/UiSeparator.vue'
import { useFetch } from '@vueuse/core'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()

// const { records: dashboard, isReady } = useDashboardStore().subscribe()

const { data } = useFetch<string>('/rest/v0/dashboard').get().json<{
nHosts: number
nPools: number
missingPatches?: {
nHostsWithMissingPatches: number
nPoolsWithMissingPatches: number
}
}>()

const serversSegments = computed(() => [
{
label: t('up-to-date'),
value: data.value?.nPools ?? 0,
color: 'success',
},
{
label: t('missing-patches'),
value: data.value?.missingPatches?.nPoolsWithMissingPatches ?? 0,
color: 'warning',
},
])

const hostsSegments = computed(() => [
{
label: t('up-to-date'),
value: data.value?.nHosts ?? 0,
color: 'success',
},
{
label: t('missing-patches'),
value: data.value?.missingPatches?.nHostsWithMissingPatches ?? 0,
color: 'warning',
},
])
</script>
13 changes: 12 additions & 1 deletion @xen-orchestra/web/src/locales/de.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
{
"account-organization-more": "Konto, Organisation & mehr…",
"tasks.quick-view": "Schnellansicht der Aufgaben (Bald verfügbar)"
"end-of-life": "Ende des Lebenszyklus",
"hosts": "Hosts",

Check failure on line 4 in @xen-orchestra/web/src/locales/de.json

View workflow job for this annotation

GitHub Actions / CI

duplicate key 'hosts' in 'de'. "./@xen-orchestra/lite/src/locales/de.json" has the same key
"missing-patches": "Fehlende Patches",
"no-results": "Keine Ergebnisse",
"servers": "Server",

"sidebar.search-tree-view": "Suche im Baumansicht",
"sidebar.vms-treeview": "VMs Baumansicht",

"tasks.quick-view": "Schnellansicht der Aufgaben (Bald verfügbar)",

"up-to-date": "Auf dem neuesten Stand"
}
8 changes: 7 additions & 1 deletion @xen-orchestra/web/src/locales/en.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
{
"account-organization-more": "Account, organization & more…",
"end-of-life": "End of life",
"hosts": "Hosts",
"missing-patches": "Missing patches",
"no-results": "No results",
"servers": "Servers",

"sidebar.search-tree-view": "Search in treeview",
"sidebar.vms-treeview": "VMs treeview",

"tasks.quick-view": "Tasks' quick view (coming soon)"
"tasks.quick-view": "Tasks' quick view (coming soon)",

"up-to-date": "Up-to-date"
}
8 changes: 7 additions & 1 deletion @xen-orchestra/web/src/locales/fr.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
{
"account-organization-more": "Compte, organisation et plus…",
"end-of-life": "Fin de vie",
"hosts": "Hosts",
"missing-patches": "Correctifs manquants",
"no-results": "Aucun résultat",
"servers": "Serveurs",

"sidebar.search-tree-view": "Rechercher dans l'arborescence",
"sidebar.vms-treeview": "Arborescence des VMs",

"tasks.quick-view": "Vue rapide des tâches (bientôt disponible)"
"tasks.quick-view": "Vue rapide des tâches (bientôt disponible)",

"up-to-date": "À jour"
}
8 changes: 5 additions & 3 deletions @xen-orchestra/web/src/pages/index.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<template>
<!-- eslint-disable @intlify/vue-i18n/no-raw-text -->
Welcome to XO 6
<!-- eslint-enable @intlify/vue-i18n/no-raw-text -->
<DashboardPatches />
</template>

<script setup lang="ts">
import DashboardPatches from '@/components/pool/dashboard/DashboardPatches.vue'
</script>
10 changes: 10 additions & 0 deletions @xen-orchestra/web/src/stores/xo-rest-api/dashboard.store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { createXoStoreConfig } from '@/utils/create-xo-store-config.util'
import { createSubscribableStoreContext } from '@core/utils/create-subscribable-store-context.util'
// import { sortByNameLabel } from '@core/utils/sort-by-name-label.util'
import { defineStore } from 'pinia'

export const useDashboardStore = defineStore('dashboard', () => {
const config = createXoStoreConfig('dashboard', {})

return createSubscribableStoreContext(config, {})
})
16 changes: 16 additions & 0 deletions @xen-orchestra/web/src/types/dashboard.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// import type { RecordId } from '@/types/xo-object.type'
interface MissingPatches {
nHostsWithMissingPatches: number
nPoolsWithMissingPatches: number
}

interface DashboardData {
nPools: number
nHosts: number
missingPatches: MissingPatches
}

export type Dashboard = {
type: 'dashboard'
DashboardData: DashboardData
}
3 changes: 2 additions & 1 deletion @xen-orchestra/web/src/types/xo-object.type.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Dashboard } from '@/types/dashboard.type'
import type { Host } from '@/types/host.type'
import type { Pool } from '@/types/pool.type'
import type { Vm } from '@/types/vm.type'
Expand All @@ -8,7 +9,7 @@ declare const __brand: unique symbol
// eslint-disable-next-line no-use-before-define
export type RecordId<Type extends XoObjectType> = string & { [__brand]: `${Type}Id` }

export type XoObject = Vm | Host | Pool
export type XoObject = Vm | Host | Pool | Dashboard

export type XoObjectType = XoObject['type']

Expand Down
4 changes: 4 additions & 0 deletions @xen-orchestra/web/src/utils/rest-api-config.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,8 @@ export const restApiConfig: Record<XoObjectType, { path: string; fields: string
path: 'pools',
fields: 'id,name_label,master',
},
dashboard: {
path: 'dashboard',
fields: '*',
},
}
Loading