feat(lite): settings page (#6418)

This commit is contained in:
Pierre Donias 2022-10-27 17:13:13 +02:00 committed by Julien Fontanet
parent b2cebbfaf4
commit c7227d2f50
15 changed files with 248 additions and 54 deletions

View File

@ -3,6 +3,7 @@
## **0.2.0** ## **0.2.0**
- Invalidate sessionId token after logout (PR [#6480](https://github.com/vatesfr/xen-orchestra/pull/6480)) - Invalidate sessionId token after logout (PR [#6480](https://github.com/vatesfr/xen-orchestra/pull/6480))
- Settings page (PR [#6418](https://github.com/vatesfr/xen-orchestra/pull/6418))
## **0.1.0** ## **0.1.0**

View File

@ -1,11 +1,11 @@
{ {
"name": "@xen-orchestra/lite", "name": "@xen-orchestra/lite",
"version": "0.0.0", "version": "0.1.0",
"scripts": { "scripts": {
"dev": "vite", "dev": "GIT_HEAD=$(git rev-parse HEAD) vite",
"build": "run-p type-check build-only", "build": "run-p type-check build-only",
"preview": "vite preview --port 4173", "preview": "vite preview --port 4173",
"build-only": "vite build", "build-only": "GIT_HEAD=$(git rev-parse HEAD) vite build",
"deploy": "./scripts/deploy.sh", "deploy": "./scripts/deploy.sh",
"type-check": "vue-tsc --noEmit", "type-check": "vue-tsc --noEmit",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"

View File

@ -11,3 +11,17 @@ body {
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
color: var(--color-blue-scale-100); color: var(--color-blue-scale-100);
} }
a {
color: var(--color-extra-blue-base);
}
code {
font-family: monospace;
}
.card-view {
padding: 1.2rem;
display: flex;
gap: 2rem;
}

View File

@ -6,7 +6,9 @@
<UiIcon :icon="faAngleDown" class="dropdown-icon" /> <UiIcon :icon="faAngleDown" class="dropdown-icon" />
</button> </button>
</template> </template>
<MenuItem :icon="faGear">{{ $t("settings") }}</MenuItem> <MenuItem :icon="faGear" @click="openSettings">{{
$t("settings")
}}</MenuItem>
<MenuItem :icon="faMessage" @click="openFeedbackUrl"> <MenuItem :icon="faMessage" @click="openFeedbackUrl">
{{ $t("send-us-feedback") }} {{ $t("send-us-feedback") }}
</MenuItem> </MenuItem>
@ -50,6 +52,8 @@ const openFeedbackUrl = () => {
"noopener" "noopener"
); );
}; };
const openSettings = () => router.push({ name: "settings" });
</script> </script>
<style scoped> <style scoped>

View File

@ -5,50 +5,13 @@
</RouterLink> </RouterLink>
<slot /> <slot />
<div class="right"> <div class="right">
<FontAwesomeIcon
:icon="colorModeIcon"
style="font-size: 1.5em; cursor: pointer"
@click="toggleTheme"
/>
<FormWidget :before="faEarthAmericas">
<select v-model="$i18n.locale">
<option v-for="locale in $i18n.availableLocales" :key="locale">
{{ locale }}
</option>
</select>
</FormWidget>
<AccountButton /> <AccountButton />
</div> </div>
</header> </header>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, watch } from "vue"; import AccountButton from '@/components/AccountButton.vue'
import { useI18n } from "vue-i18n";
import { useRouter } from "vue-router";
import {
faEarthAmericas,
faMoon,
faSun,
} from "@fortawesome/free-solid-svg-icons";
import { useLocalStorage } from "@vueuse/core";
import AccountButton from "@/components/AccountButton.vue";
import FormWidget from "@/components/FormWidget.vue";
const router = useRouter();
const { locale } = useI18n();
watch(locale, (newLocale) => localStorage.setItem("lang", newLocale));
const colorMode = useLocalStorage<string>("colorMode", "dark");
const toggleTheme = () => {
colorMode.value = document.documentElement.classList.toggle("dark")
? "dark"
: "light";
};
const colorModeIcon = computed(() =>
colorMode.value === "light" ? faMoon : faSun
);
</script> </script>
<style lang="postcss" scoped> <style lang="postcss" scoped>

View File

@ -0,0 +1,14 @@
<template>
<div class="ui-key-value-list"><slot /></div>
</template>
<script lang="ts" setup></script>
<style lang="postcss" scoped>
.ui-key-value-list {
margin-top: 2rem;
font-size: 1.4rem;
/* UiKeyValueRow: 15em (key) + 15em (value) + 1rem (gap) */
min-width: calc(30em + 1rem);
}
</style>

View File

@ -0,0 +1,37 @@
<template>
<div class="ui-key-value-row">
<span class="key" v-if="$slots.key">
<slot name="key" />
</span>
<span class="value">
<slot name="value" />
</span>
</div>
</template>
<script lang="ts" setup></script>
<style lang="postcss" scoped>
.ui-key-value-row {
display: flex;
gap: 1rem;
align-items: baseline;
margin: 0.5em 0;
}
.key {
color: var(--color-blue-scale-300);
width: 15%;
max-width: 15em;
max-width: 30em;
}
.value {
flex-grow: 1;
}
.key,
.value {
text-overflow: ellipsis;
width: 100%;
min-width: 15em;
max-width: 30em;
}
</style>

View File

@ -2,6 +2,24 @@ import { createI18n } from "vue-i18n";
import en from "@/locales/en.json"; import en from "@/locales/en.json";
import fr from "@/locales/fr.json"; import fr from "@/locales/fr.json";
interface Locales {
[key: string]: {
code: string;
name: string;
};
}
export const locales: Locales = {
en: {
code: "en",
name: "English",
},
fr: {
code: "fr",
name: "Français",
},
};
export default createI18n<[typeof en], "en" | "fr">({ export default createI18n<[typeof en], "en" | "fr">({
locale: localStorage.getItem("lang") ?? "en", locale: localStorage.getItem("lang") ?? "en",
fallbackLocale: "en", fallbackLocale: "en",

View File

@ -4,26 +4,34 @@
"add-or": "+OR", "add-or": "+OR",
"add-sort": "Add sort", "add-sort": "Add sort",
"alarms": "Alarms", "alarms": "Alarms",
"appearance": "Appearance",
"ascending": "ascending", "ascending": "ascending",
"available-properties-for-advanced-filter": "Available properties for advanced filter:", "available-properties-for-advanced-filter": "Available properties for advanced filter:",
"backup": "Backup", "backup": "Backup",
"cancel": "Cancel", "cancel": "Cancel",
"change-power-state": "Change power state", "change-power-state": "Change power state",
"community": "Community",
"community-name": "{name} community",
"copy": "Copy", "copy": "Copy",
"cpu-usage":"CPU usage", "cpu-usage":"CPU usage",
"dark-mode": "Dark mode",
"dashboard": "Dashboard", "dashboard": "Dashboard",
"delete": "Delete", "delete": "Delete",
"descending": "descending", "descending": "descending",
"display": "Display",
"edit-config": "Edit config", "edit-config": "Edit config",
"export": "Export", "export": "Export",
"export-table-to": "Export table to {type}", "export-table-to": "Export table to {type}",
"export-vms": "Export VMs", "export-vms": "Export VMs",
"hosts": "Hosts", "hosts": "Hosts",
"language": "Language",
"loading-hosts": "Loading hosts…", "loading-hosts": "Loading hosts…",
"log-out": "Log out", "log-out": "Log out",
"login": "Login", "login": "Login",
"migrate": "Migrate", "migrate": "Migrate",
"network": "Network", "network": "Network",
"news": "News",
"news-name": "{name} news",
"or": "Or", "or": "Or",
"password": "Password", "password": "Password",
"property": "Property", "property": "Property",
@ -41,5 +49,6 @@
"top-#": "Top {n}", "top-#": "Top {n}",
"total-free": "Total free", "total-free": "Total free",
"total-used": "Total used", "total-used": "Total used",
"version": "Version",
"vms": "VMs" "vms": "VMs"
} }

View File

@ -4,26 +4,34 @@
"add-or": "+OU", "add-or": "+OU",
"add-sort": "Ajouter un tri", "add-sort": "Ajouter un tri",
"alarms": "Alarmes", "alarms": "Alarmes",
"appearance": "Apparence",
"ascending": "ascendant", "ascending": "ascendant",
"available-properties-for-advanced-filter": "Propriétés disponibles pour le filtrage avancé :", "available-properties-for-advanced-filter": "Propriétés disponibles pour le filtrage avancé :",
"backup": "Sauvegarde", "backup": "Sauvegarde",
"cancel": "Annuler", "cancel": "Annuler",
"change-power-state": "Changer l'état d'alimentation", "change-power-state": "Changer l'état d'alimentation",
"community": "Communauté",
"community-name": "Communauté {name}",
"copy": "Copier", "copy": "Copier",
"cpu-usage":"Utilisation CPU", "cpu-usage":"Utilisation CPU",
"dark-mode": "Mode sombre",
"dashboard": "Tableau de bord", "dashboard": "Tableau de bord",
"delete": "Supprimer", "delete": "Supprimer",
"descending": "descendant", "descending": "descendant",
"display": "Affichage",
"edit-config": "Modifier config", "edit-config": "Modifier config",
"export": "Exporter", "export": "Exporter",
"export-table-to": "Exporter le tableau en {type}", "export-table-to": "Exporter le tableau en {type}",
"export-vms": "Exporter les VMs", "export-vms": "Exporter les VMs",
"hosts": "Hôtes", "hosts": "Hôtes",
"language": "Langue",
"loading-hosts": "Chargement des hôtes…", "loading-hosts": "Chargement des hôtes…",
"log-out": "Se déconnecter", "log-out": "Se déconnecter",
"login": "Connexion", "login": "Connexion",
"migrate": "Migrer", "migrate": "Migrer",
"network": "Réseau", "network": "Réseau",
"news": "Actualités",
"news-name": "Actualités {name}",
"or": "Ou", "or": "Ou",
"password": "Mot de passe", "password": "Mot de passe",
"property": "Propriété", "property": "Propriété",
@ -41,5 +49,6 @@
"top-#": "Top {n}", "top-#": "Top {n}",
"total-free": "Total libre", "total-free": "Total libre",
"total-used": "Total utilisé", "total-used": "Total utilisé",
"version": "Version",
"vms": "VMs" "vms": "VMs"
} }

View File

@ -3,6 +3,7 @@ import pool from "@/router/pool";
import HomeView from "@/views/HomeView.vue"; import HomeView from "@/views/HomeView.vue";
import HostDashboardView from "@/views/host/HostDashboardView.vue"; import HostDashboardView from "@/views/host/HostDashboardView.vue";
import HostRootView from "@/views/host/HostRootView.vue"; import HostRootView from "@/views/host/HostRootView.vue";
import SettingsView from "@/views/settings/SettingsView.vue";
import VmConsoleView from "@/views/vm/VmConsoleView.vue"; import VmConsoleView from "@/views/vm/VmConsoleView.vue";
import VmRootView from "@/views/vm/VmRootView.vue"; import VmRootView from "@/views/vm/VmRootView.vue";
@ -14,6 +15,11 @@ const router = createRouter({
name: "home", name: "home",
component: HomeView, component: HomeView,
}, },
{
path: "/settings",
name: "settings",
component: SettingsView,
},
pool, pool,
{ {
path: "/host/:uuid", path: "/host/:uuid",

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="pool-dashboard-view"> <div class="pool-dashboard-view card-view">
<PoolDashboardStatus class="item" /> <PoolDashboardStatus class="item" />
<PoolDashboardStorageUsage class="item" /> <PoolDashboardStorageUsage class="item" />
<PoolDashboardCpuUsage class="item" /> <PoolDashboardCpuUsage class="item" />
@ -64,11 +64,6 @@ onMounted(() => {
</script> </script>
<style lang="postcss" scoped> <style lang="postcss" scoped>
.pool-dashboard-view {
display: flex;
gap: 2rem;
}
.item { .item {
min-width: 37rem; min-width: 37rem;
} }

View File

@ -2,7 +2,7 @@
<div class="pool-root-view"> <div class="pool-root-view">
<PoolHeader /> <PoolHeader />
<PoolTabBar /> <PoolTabBar />
<div class="view"> <div class="card-view">
<RouterView /> <RouterView />
</div> </div>
</div> </div>
@ -13,8 +13,4 @@ import PoolHeader from "@/components/pool/PoolHeader.vue";
import PoolTabBar from "@/components/pool/PoolTabBar.vue"; import PoolTabBar from "@/components/pool/PoolTabBar.vue";
</script> </script>
<style lang="postcss" scoped> <style lang="postcss" scoped></style>
.view {
padding: 2rem;
}
</style>

View File

@ -0,0 +1,124 @@
<template>
<TitleBar :icon="faGear">{{ $t("settings") }}</TitleBar>
<div class="card-view">
<UiCard class="group">
<UiTitle type="h4">Xen Orchestra Lite</UiTitle>
<UiKeyValueList>
<UiKeyValueRow>
<template #key>{{ $t("version") }}</template>
<template #value
>v{{ version
}}<code v-if="gitHead"> ({{ gitHead.slice(0, 5) }})</code></template
>
</UiKeyValueRow>
<UiKeyValueRow>
<template #key>{{ $t("news") }}</template>
<template #value
><a
target="_blank"
rel="noopener noreferrer"
href="https://xcp-ng.org/blog/"
>{{ $t("news-name", { name: "XCP-ng" }) }}</a
> - <a
target="_blank"
rel="noopener noreferrer"
href="https://xen-orchestra.com/blog/"
>{{ $t("news-name", { name: "Xen Orchestra" }) }}</a
></template
>
</UiKeyValueRow>
<UiKeyValueRow>
<template #key>{{ $t("community") }}</template>
<template #value
><a
target="_blank"
rel="noopener noreferrer"
href="https://xcp-ng.org/forum"
>{{ $t("community-name", { name: "XCP-ng" }) }}</a
> - <a
target="_blank"
rel="noopener noreferrer"
href="https://xcp-ng.org/forum/category/12/xen-orchestra"
>{{ $t("community-name", { name: "Xen Orchestra" }) }}</a
></template
>
</UiKeyValueRow>
</UiKeyValueList>
</UiCard>
<UiCard class="group">
<UiTitle type="h4">{{ $t("display") }}</UiTitle>
<UiKeyValueList>
<UiKeyValueRow>
<template #key>{{ $t("appearance") }}</template>
<template #value
><FormLabel>
<FormToggle
:modelValue="darkMode"
@update:modelValue="setDarkMode"
/>{{ $t("dark-mode") }}</FormLabel
></template
>
</UiKeyValueRow>
</UiKeyValueList>
</UiCard>
<UiCard class="group">
<UiTitle type="h4">{{ $t("language") }}</UiTitle>
<UiKeyValueList>
<UiKeyValueRow>
<template #value>
<FormWidget class="full-length" :before="faEarthAmericas">
<select v-model="$i18n.locale">
<option
:value="locale"
v-for="locale in $i18n.availableLocales"
:key="locale"
>
{{ locales[locale].name ?? locale }}
</option>
</select>
</FormWidget></template
>
</UiKeyValueRow>
</UiKeyValueList>
</UiCard>
</div>
</template>
<script lang="ts" setup>
import { computed, watch } from "vue";
import { useI18n } from "vue-i18n";
import { locales } from "@/i18n";
import { faEarthAmericas, faGear } from "@fortawesome/free-solid-svg-icons";
import { useLocalStorage } from "@vueuse/core";
import FormWidget from "@/components/FormWidget.vue";
import TitleBar from "@/components/TitleBar.vue";
import FormLabel from "@/components/form/FormLabel.vue";
import FormToggle from "@/components/form/FormToggle.vue";
import UiCard from "@/components/ui/UiCard.vue";
import UiKeyValueList from "@/components/ui/UiKeyValueList.vue";
import UiKeyValueRow from "@/components/ui/UiKeyValueRow.vue";
import UiTitle from "@/components/ui/UiTitle.vue";
const version = XO_LITE_VERSION;
const gitHead = XO_LITE_GIT_HEAD;
const { locale } = useI18n();
watch(locale, (newLocale) => localStorage.setItem("lang", newLocale));
const colorMode = useLocalStorage<string>("colorMode", "dark");
const darkMode = computed(() => colorMode.value !== "light");
const setDarkMode = (enabled: boolean) => {
colorMode.value = enabled ? "dark" : "light";
document.documentElement.classList[enabled ? "add" : "remove"]("dark");
};
</script>
<style lang="postcss" scoped>
.card-view {
flex-direction: column;
}
.full-length {
width: 100%;
}
</style>

View File

@ -6,6 +6,10 @@ import vue from "@vitejs/plugin-vue";
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [vue(), vueI18n()], plugins: [vue(), vueI18n()],
define: {
XO_LITE_VERSION: JSON.stringify(process.env.npm_package_version),
XO_LITE_GIT_HEAD: JSON.stringify(process.env.GIT_HEAD),
},
resolve: { resolve: {
alias: { alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)), "@": fileURLToPath(new URL("./src", import.meta.url)),