feat(lite/component): Menu component (#6354)
* feat(lite/component): Menu component * feat(lite/component): Add disabled prop to AppMenu * feat(lite/component): Add custom placement to AppMenu + Fix trigger color * feat(lite/component): Update VmsActionBar to use new AppMenu (#6357) * fix(lite/menu): Doesn't teleport the root menu if no trigger * Don't disable a menu item having a submenu * i18n
This commit is contained in:
committed by
Julien Fontanet
parent
94b2b8ec70
commit
5218d6df1a
@@ -1,24 +1,67 @@
|
|||||||
<template>
|
<template>
|
||||||
<button class="account-button">
|
<AppMenu placement="bottom-end" shadow>
|
||||||
<FontAwesomeIcon class="user-icon" :icon="faCircleUser" />
|
<template #trigger="{ open, isOpen }">
|
||||||
<FontAwesomeIcon class="dropdown-icon" :icon="faAngleDown" />
|
<button :class="{ active: isOpen }" class="account-button" @click="open">
|
||||||
</button>
|
<UiIcon :icon="faCircleUser" class="user-icon" />
|
||||||
|
<UiIcon :icon="faAngleDown" class="dropdown-icon" />
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<MenuItem :icon="faGear">{{ $t("settings") }}</MenuItem>
|
||||||
|
<MenuItem :icon="faMessage" @click="openFeedbackUrl">
|
||||||
|
{{ $t("send-us-feedback") }}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
:icon="faArrowRightFromBracket"
|
||||||
|
class="menu-item-logout"
|
||||||
|
@click="logout"
|
||||||
|
>
|
||||||
|
{{ $t("log-out") }}
|
||||||
|
</MenuItem>
|
||||||
|
</AppMenu>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { faAngleDown, faCircleUser } from "@fortawesome/free-solid-svg-icons";
|
import { nextTick } from "vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import {
|
||||||
|
faAngleDown,
|
||||||
|
faArrowRightFromBracket,
|
||||||
|
faCircleUser,
|
||||||
|
faGear,
|
||||||
|
faMessage,
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import AppMenu from "@/components/menu/AppMenu.vue";
|
||||||
|
import MenuItem from "@/components/menu/MenuItem.vue";
|
||||||
|
import UiIcon from "@/components/ui/UiIcon.vue";
|
||||||
|
import { useXenApiStore } from "@/stores/xen-api.store";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const logout = () => {
|
||||||
|
const xenApiStore = useXenApiStore();
|
||||||
|
xenApiStore.disconnect();
|
||||||
|
nextTick(() => router.push({ name: "home" }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const openFeedbackUrl = () => {
|
||||||
|
window.open(
|
||||||
|
"https://xcp-ng.org/forum/topic/4731/xen-orchestra-lite",
|
||||||
|
"_blank",
|
||||||
|
"noopener"
|
||||||
|
);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.account-button {
|
.account-button {
|
||||||
padding: 1rem;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.8rem;
|
padding: 1rem;
|
||||||
border-radius: 0.8rem;
|
|
||||||
border: none;
|
|
||||||
color: var(--color-blue-scale-100);
|
color: var(--color-blue-scale-100);
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.8rem;
|
||||||
background-color: var(--background-color-secondary);
|
background-color: var(--background-color-secondary);
|
||||||
|
gap: 0.8rem;
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
color: var(--color-blue-scale-400);
|
color: var(--color-blue-scale-400);
|
||||||
@@ -26,12 +69,15 @@ import { faAngleDown, faCircleUser } from "@fortawesome/free-solid-svg-icons";
|
|||||||
|
|
||||||
&:not(:disabled) {
|
&:not(:disabled) {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:active {
|
&:active,
|
||||||
|
&.active {
|
||||||
background-color: var(--background-color-primary);
|
background-color: var(--background-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active,
|
||||||
|
&.active {
|
||||||
color: var(--color-extra-blue-base);
|
color: var(--color-extra-blue-base);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -44,4 +90,8 @@ import { faAngleDown, faCircleUser } from "@fortawesome/free-solid-svg-icons";
|
|||||||
.dropdown-icon {
|
.dropdown-icon {
|
||||||
font-size: 1.6rem;
|
font-size: 1.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.menu-item-logout {
|
||||||
|
color: var(--color-red-vates-base);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -4,13 +4,12 @@
|
|||||||
<img alt="XO Lite" src="../assets/logo.svg" />
|
<img alt="XO Lite" src="../assets/logo.svg" />
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<slot />
|
<slot />
|
||||||
<div style="display: flex; align-items: center; gap: 1rem">
|
<div class="right">
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
:icon="colorModeIcon"
|
:icon="colorModeIcon"
|
||||||
style="font-size: 1.5em"
|
style="font-size: 1.5em; cursor: pointer"
|
||||||
@click="toggleTheme"
|
@click="toggleTheme"
|
||||||
/>
|
/>
|
||||||
<span @click="logout">Logout</span>
|
|
||||||
<FormWidget :before="faEarthAmericas">
|
<FormWidget :before="faEarthAmericas">
|
||||||
<select v-model="$i18n.locale">
|
<select v-model="$i18n.locale">
|
||||||
<option v-for="locale in $i18n.availableLocales" :key="locale">
|
<option v-for="locale in $i18n.availableLocales" :key="locale">
|
||||||
@@ -18,12 +17,13 @@
|
|||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</FormWidget>
|
</FormWidget>
|
||||||
|
<AccountButton />
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, nextTick } from "vue";
|
import { computed } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import {
|
import {
|
||||||
faEarthAmericas,
|
faEarthAmericas,
|
||||||
@@ -31,8 +31,8 @@ import {
|
|||||||
faSun,
|
faSun,
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { useLocalStorage } from "@vueuse/core";
|
import { useLocalStorage } from "@vueuse/core";
|
||||||
|
import AccountButton from "@/components/AccountButton.vue";
|
||||||
import FormWidget from "@/components/FormWidget.vue";
|
import FormWidget from "@/components/FormWidget.vue";
|
||||||
import { useXenApiStore } from "@/stores/xen-api.store";
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -45,12 +45,6 @@ const toggleTheme = () => {
|
|||||||
const colorModeIcon = computed(() =>
|
const colorModeIcon = computed(() =>
|
||||||
colorMode.value === "light" ? faMoon : faSun
|
colorMode.value === "light" ? faMoon : faSun
|
||||||
);
|
);
|
||||||
|
|
||||||
const logout = () => {
|
|
||||||
const xenApiStore = useXenApiStore();
|
|
||||||
xenApiStore.disconnect();
|
|
||||||
nextTick(() => router.push({ name: "home" }));
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="postcss" scoped>
|
<style lang="postcss" scoped>
|
||||||
@@ -66,9 +60,10 @@ const logout = () => {
|
|||||||
img {
|
img {
|
||||||
width: 4rem;
|
width: 4rem;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
span {
|
.right {
|
||||||
cursor: pointer;
|
display: flex;
|
||||||
}
|
align-items: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
91
@xen-orchestra/lite/src/components/menu/AppMenu.vue
Normal file
91
@xen-orchestra/lite/src/components/menu/AppMenu.vue
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
<template>
|
||||||
|
<slot :is-open="isOpen" :open="open" name="trigger" />
|
||||||
|
<Teleport to="body" :disabled="!isRoot || !slots.trigger">
|
||||||
|
<ul
|
||||||
|
v-if="!$slots.trigger || isOpen"
|
||||||
|
ref="menu"
|
||||||
|
:class="{ horizontal, shadow }"
|
||||||
|
class="app-menu"
|
||||||
|
v-bind="$attrs"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</ul>
|
||||||
|
</Teleport>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import placement, { type Options } from "placement.js";
|
||||||
|
import { inject, nextTick, provide, ref, toRef, unref, useSlots } from "vue";
|
||||||
|
import { onClickOutside, unrefElement, whenever } from "@vueuse/core";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
horizontal?: boolean;
|
||||||
|
shadow?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
placement?: Options["placement"];
|
||||||
|
}>();
|
||||||
|
const isRoot = inject("isMenuRoot", true);
|
||||||
|
provide("isMenuRoot", false);
|
||||||
|
const slots = useSlots();
|
||||||
|
const isOpen = ref(false);
|
||||||
|
const menu = ref();
|
||||||
|
const isParentHorizontal = inject("isMenuHorizontal", undefined);
|
||||||
|
provide("isMenuHorizontal", toRef(props, "horizontal"));
|
||||||
|
provide("isMenuDisabled", toRef(props, "disabled"));
|
||||||
|
let clearClickOutsideEvent: (() => void) | undefined;
|
||||||
|
|
||||||
|
whenever(
|
||||||
|
() => !isOpen.value,
|
||||||
|
() => clearClickOutsideEvent?.()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (slots.trigger && !inject("closeMenu", false)) {
|
||||||
|
provide("closeMenu", () => (isOpen.value = false));
|
||||||
|
}
|
||||||
|
|
||||||
|
const open = (event: MouseEvent) => {
|
||||||
|
if (isOpen.value) {
|
||||||
|
return (isOpen.value = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
isOpen.value = true;
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
clearClickOutsideEvent = onClickOutside(
|
||||||
|
menu,
|
||||||
|
() => (isOpen.value = false),
|
||||||
|
{
|
||||||
|
ignore: [event.currentTarget as HTMLElement],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
placement(event.currentTarget as HTMLElement, unrefElement(menu), {
|
||||||
|
placement:
|
||||||
|
props.placement ??
|
||||||
|
(unref(isParentHorizontal) !== false ? "bottom-start" : "right-start"),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="postcss" scoped>
|
||||||
|
.app-menu {
|
||||||
|
z-index: 1;
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0.5rem;
|
||||||
|
cursor: default;
|
||||||
|
color: var(--color-blue-scale-200);
|
||||||
|
border-radius: 0.8rem;
|
||||||
|
background-color: var(--color-blue-scale-500);
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
&.horizontal {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.shadow {
|
||||||
|
box-shadow: var(--shadow-300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
80
@xen-orchestra/lite/src/components/menu/MenuItem.vue
Normal file
80
@xen-orchestra/lite/src/components/menu/MenuItem.vue
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<template>
|
||||||
|
<li class="menu-item">
|
||||||
|
<MenuTrigger
|
||||||
|
v-if="!$slots.submenu"
|
||||||
|
:active="isBusy"
|
||||||
|
:busy="isBusy"
|
||||||
|
:disabled="isDisabled"
|
||||||
|
:icon="icon"
|
||||||
|
@click="handleClick"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</MenuTrigger>
|
||||||
|
<AppMenu v-else shadow :disabled="isDisabled">
|
||||||
|
<template #trigger="{ open, isOpen }">
|
||||||
|
<MenuTrigger :active="isOpen" :icon="icon" @click="open">
|
||||||
|
<slot />
|
||||||
|
<UiIcon
|
||||||
|
:fixed-width="false"
|
||||||
|
:icon="submenuIcon"
|
||||||
|
class="submenu-icon"
|
||||||
|
/>
|
||||||
|
</MenuTrigger>
|
||||||
|
</template>
|
||||||
|
<slot name="submenu" />
|
||||||
|
</AppMenu>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, inject, ref, unref } from "vue";
|
||||||
|
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
|
||||||
|
import { faAngleDown, faAngleRight } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { noop } from "@vueuse/core";
|
||||||
|
import AppMenu from "@/components/menu/AppMenu.vue";
|
||||||
|
import MenuTrigger from "@/components/menu/MenuTrigger.vue";
|
||||||
|
import UiIcon from "@/components/ui/UiIcon.vue";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
icon?: IconDefinition;
|
||||||
|
onClick?: () => any;
|
||||||
|
disabled?: boolean;
|
||||||
|
busy?: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const isParentHorizontal = inject("isMenuHorizontal", false);
|
||||||
|
const isMenuDisabled = inject("isMenuDisabled", false);
|
||||||
|
const isDisabled = computed(() => props.disabled || unref(isMenuDisabled));
|
||||||
|
|
||||||
|
const submenuIcon = computed(() =>
|
||||||
|
unref(isParentHorizontal) ? faAngleDown : faAngleRight
|
||||||
|
);
|
||||||
|
|
||||||
|
const isHandlingClick = ref(false);
|
||||||
|
const isBusy = computed(() => isHandlingClick.value || props.busy);
|
||||||
|
const closeMenu = inject("closeMenu", noop);
|
||||||
|
|
||||||
|
const handleClick = async () => {
|
||||||
|
if (isDisabled.value || isBusy.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isHandlingClick.value = true;
|
||||||
|
try {
|
||||||
|
await props.onClick?.();
|
||||||
|
closeMenu();
|
||||||
|
} finally {
|
||||||
|
isHandlingClick.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="postcss" scoped>
|
||||||
|
.menu-item {
|
||||||
|
color: var(--color-blue-scale-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-icon {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
22
@xen-orchestra/lite/src/components/menu/MenuSeparator.vue
Normal file
22
@xen-orchestra/lite/src/components/menu/MenuSeparator.vue
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<template>
|
||||||
|
<li :class="{ horizontal }" class="ui-menu-separator" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { inject } from "vue";
|
||||||
|
|
||||||
|
const horizontal = inject("isParentMenuHorizontal", false);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="postcss" scoped>
|
||||||
|
.ui-menu-separator {
|
||||||
|
&.horizontal {
|
||||||
|
margin: 0 0.5rem;
|
||||||
|
border-right: 1px solid var(--color-blue-scale-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.horizontal) {
|
||||||
|
border-bottom: 1px solid var(--color-blue-scale-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
51
@xen-orchestra/lite/src/components/menu/MenuTrigger.vue
Normal file
51
@xen-orchestra/lite/src/components/menu/MenuTrigger.vue
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<template>
|
||||||
|
<div :class="{ active, disabled }" class="menu-trigger">
|
||||||
|
<UiIcon :busy="busy" :icon="icon" />
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
|
||||||
|
import UiIcon from "@/components/ui/UiIcon.vue";
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
active?: boolean;
|
||||||
|
busy?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
icon?: IconDefinition;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="postcss" scoped>
|
||||||
|
.menu-trigger {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
font-weight: 400;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 4.4rem;
|
||||||
|
padding-right: 1.5rem;
|
||||||
|
padding-left: 1rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
border-radius: 0.8rem;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
color: var(--color-blue-scale-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.disabled) {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--background-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active,
|
||||||
|
&.active {
|
||||||
|
color: var(--color-extra-blue-base);
|
||||||
|
background-color: var(--background-color-extra-blue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,35 +1,47 @@
|
|||||||
<template>
|
<template>
|
||||||
<UiButtonGroup
|
<AppMenu
|
||||||
:disabled="selectedRefs.length === 0"
|
:disabled="selectedRefs.length === 0"
|
||||||
class="vms-actions-bar"
|
class="vms-actions-bar"
|
||||||
color="secondary"
|
horizontal
|
||||||
>
|
>
|
||||||
<UiButton :icon="faPowerOff">{{ $t("change-power-state") }}</UiButton>
|
<MenuItem :icon="faPowerOff">{{ $t("change-power-state") }}</MenuItem>
|
||||||
<UiButton :icon="faRoute">{{ $t("migrate") }}</UiButton>
|
<MenuItem :icon="faRoute">{{ $t("migrate") }}</MenuItem>
|
||||||
<UiButton :icon="faCopy">{{ $t("copy") }}</UiButton>
|
<MenuItem :icon="faCopy">{{ $t("copy") }}</MenuItem>
|
||||||
<UiButton :icon="faEdit">{{ $t("edit-config") }}</UiButton>
|
<MenuItem :icon="faEdit">{{ $t("edit-config") }}</MenuItem>
|
||||||
<UiButton :icon="faCamera">{{ $t("snapshot") }}</UiButton>
|
<MenuItem :icon="faCamera">{{ $t("snapshot") }}</MenuItem>
|
||||||
<UiButton :icon="faBox">{{ $t("backup") }}</UiButton>
|
<MenuItem :icon="faBox">{{ $t("backup") }}</MenuItem>
|
||||||
<UiButton :icon="faTrashCan">{{ $t("delete") }}</UiButton>
|
<MenuItem :icon="faTrashCan">{{ $t("delete") }}</MenuItem>
|
||||||
<UiButton :icon="faFileExport">{{ $t("export") }}</UiButton>
|
<MenuItem :icon="faFileExport">
|
||||||
</UiButtonGroup>
|
{{ $t("export") }}
|
||||||
|
<template #submenu>
|
||||||
|
<MenuItem :icon="faDisplay">{{ $t("export-vms") }}</MenuItem>
|
||||||
|
<MenuItem :icon="faCode">
|
||||||
|
{{ $t("export-table-to", { type: ".json" }) }}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem :icon="faFileCsv">
|
||||||
|
{{ $t("export-table-to", { type: ".csv" }) }}
|
||||||
|
</MenuItem>
|
||||||
|
</template>
|
||||||
|
</MenuItem>
|
||||||
|
</AppMenu>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {
|
|
||||||
faCopy,
|
|
||||||
faEdit,
|
|
||||||
faTrashCan,
|
|
||||||
} from "@fortawesome/free-regular-svg-icons";
|
|
||||||
import {
|
import {
|
||||||
faBox,
|
faBox,
|
||||||
faCamera,
|
faCamera,
|
||||||
|
faCode,
|
||||||
|
faCopy,
|
||||||
|
faDisplay,
|
||||||
|
faEdit,
|
||||||
|
faFileCsv,
|
||||||
faFileExport,
|
faFileExport,
|
||||||
faPowerOff,
|
faPowerOff,
|
||||||
faRoute,
|
faRoute,
|
||||||
|
faTrashCan,
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import UiButton from "@/components/ui/UiButton.vue";
|
import AppMenu from "@/components/menu/AppMenu.vue";
|
||||||
import UiButtonGroup from "@/components/ui/UiButtonGroup.vue";
|
import MenuItem from "@/components/menu/MenuItem.vue";
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
|||||||
@@ -15,14 +15,19 @@
|
|||||||
"descending": "descending",
|
"descending": "descending",
|
||||||
"edit-config": "Edit config",
|
"edit-config": "Edit config",
|
||||||
"export": "Export",
|
"export": "Export",
|
||||||
|
"export-table-to": "Export table to {type}",
|
||||||
|
"export-vms": "Export VMs",
|
||||||
"hosts": "Hosts",
|
"hosts": "Hosts",
|
||||||
"loading-hosts": "Loading hosts…",
|
"loading-hosts": "Loading hosts…",
|
||||||
|
"log-out": "Log out",
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
"migrate": "Migrate",
|
"migrate": "Migrate",
|
||||||
"network": "Network",
|
"network": "Network",
|
||||||
"or": "Or",
|
"or": "Or",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"property": "Property",
|
"property": "Property",
|
||||||
|
"send-us-feedback": "Send us feedback",
|
||||||
|
"settings": "Settings",
|
||||||
"snapshot": "Snapshot",
|
"snapshot": "Snapshot",
|
||||||
"sort-by": "Sort by",
|
"sort-by": "Sort by",
|
||||||
"stats": "Stats",
|
"stats": "Stats",
|
||||||
|
|||||||
@@ -15,14 +15,19 @@
|
|||||||
"descending": "descendant",
|
"descending": "descendant",
|
||||||
"edit-config": "Modifier config",
|
"edit-config": "Modifier config",
|
||||||
"export": "Exporter",
|
"export": "Exporter",
|
||||||
|
"export-table-to": "Exporter le tableau en {type}",
|
||||||
|
"export-vms": "Exporter les VMs",
|
||||||
"hosts": "Hôtes",
|
"hosts": "Hôtes",
|
||||||
"loading-hosts": "Chargement des hôtes…",
|
"loading-hosts": "Chargement des hôtes…",
|
||||||
|
"log-out": "Se déconnecter",
|
||||||
"login": "Connexion",
|
"login": "Connexion",
|
||||||
"migrate": "Migrer",
|
"migrate": "Migrer",
|
||||||
"network": "Réseau",
|
"network": "Réseau",
|
||||||
"or": "Ou",
|
"or": "Ou",
|
||||||
"password": "Mot de passe",
|
"password": "Mot de passe",
|
||||||
"property": "Propriété",
|
"property": "Propriété",
|
||||||
|
"send-us-feedback": "Envoyez-nous vos commentaires",
|
||||||
|
"settings": "Paramètres",
|
||||||
"snapshot": "Instantané",
|
"snapshot": "Instantané",
|
||||||
"sort-by": "Trier par",
|
"sort-by": "Trier par",
|
||||||
"stats": "Stats",
|
"stats": "Stats",
|
||||||
|
|||||||
Reference in New Issue
Block a user