feat(lite): XOA quick deploy (#7245)
This commit is contained in:
parent
cc080ec681
commit
b0e000328d
@ -7,6 +7,7 @@
|
|||||||
- Added tooltip on CPU provisioning warning icon (PR [#7223](https://github.com/vatesfr/xen-orchestra/pull/7223))
|
- Added tooltip on CPU provisioning warning icon (PR [#7223](https://github.com/vatesfr/xen-orchestra/pull/7223))
|
||||||
- Add indeterminate state on FormToggle component (PR [#7230](https://github.com/vatesfr/xen-orchestra/pull/7230))
|
- Add indeterminate state on FormToggle component (PR [#7230](https://github.com/vatesfr/xen-orchestra/pull/7230))
|
||||||
- Add new UiStatusPanel component (PR [#7227](https://github.com/vatesfr/xen-orchestra/pull/7227))
|
- Add new UiStatusPanel component (PR [#7227](https://github.com/vatesfr/xen-orchestra/pull/7227))
|
||||||
|
- XOA quick deploy (PR [#7245](https://github.com/vatesfr/xen-orchestra/pull/7245))
|
||||||
- Fix infinite loader when no stats on pool dashboard (PR [#7236](https://github.com/vatesfr/xen-orchestra/pull/7236))
|
- Fix infinite loader when no stats on pool dashboard (PR [#7236](https://github.com/vatesfr/xen-orchestra/pull/7236))
|
||||||
|
|
||||||
## **0.1.6** (2023-11-30)
|
## **0.1.6** (2023-11-30)
|
||||||
|
@ -21,7 +21,8 @@ a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
code,
|
code,
|
||||||
code * {
|
code *,
|
||||||
|
pre {
|
||||||
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
||||||
"Courier New", monospace;
|
"Courier New", monospace;
|
||||||
}
|
}
|
||||||
|
1
@xen-orchestra/lite/src/assets/xo.svg
Normal file
1
@xen-orchestra/lite/src/assets/xo.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 43 KiB |
@ -13,6 +13,9 @@
|
|||||||
<slot />
|
<slot />
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<PoolOverrideWarning as-tooltip />
|
<PoolOverrideWarning as-tooltip />
|
||||||
|
<UiButton v-if="isDesktop" :icon="faDownload" @click="openXoaDeploy">
|
||||||
|
{{ $t("deploy-xoa") }}
|
||||||
|
</UiButton>
|
||||||
<AccountButton />
|
<AccountButton />
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@ -22,14 +25,20 @@
|
|||||||
import AccountButton from "@/components/AccountButton.vue";
|
import AccountButton from "@/components/AccountButton.vue";
|
||||||
import PoolOverrideWarning from "@/components/PoolOverrideWarning.vue";
|
import PoolOverrideWarning from "@/components/PoolOverrideWarning.vue";
|
||||||
import TextLogo from "@/components/TextLogo.vue";
|
import TextLogo from "@/components/TextLogo.vue";
|
||||||
|
import UiButton from "@/components/ui/UiButton.vue";
|
||||||
import UiIcon from "@/components/ui/icon/UiIcon.vue";
|
import UiIcon from "@/components/ui/icon/UiIcon.vue";
|
||||||
import { useNavigationStore } from "@/stores/navigation.store";
|
import { useNavigationStore } from "@/stores/navigation.store";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
import { useUiStore } from "@/stores/ui.store";
|
import { useUiStore } from "@/stores/ui.store";
|
||||||
import { faBars } from "@fortawesome/free-solid-svg-icons";
|
import { faBars, faDownload } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const openXoaDeploy = () => router.push({ name: "xoa.deploy" });
|
||||||
|
|
||||||
const uiStore = useUiStore();
|
const uiStore = useUiStore();
|
||||||
const { isMobile } = storeToRefs(uiStore);
|
const { isMobile, isDesktop } = storeToRefs(uiStore);
|
||||||
|
|
||||||
const navigationStore = useNavigationStore();
|
const navigationStore = useNavigationStore();
|
||||||
const { trigger: navigationTrigger } = storeToRefs(navigationStore);
|
const { trigger: navigationTrigger } = storeToRefs(navigationStore);
|
||||||
@ -62,5 +71,6 @@ const { trigger: navigationTrigger } = storeToRefs(navigationStore);
|
|||||||
.right {
|
.right {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 2rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
<template>
|
||||||
|
<UiModal color="error" @submit="modal.approve()">
|
||||||
|
<ConfirmModalLayout :icon="faExclamationCircle">
|
||||||
|
<template #title>{{ $t("invalid-field") }}</template>
|
||||||
|
|
||||||
|
<template #default>
|
||||||
|
{{ message }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #buttons>
|
||||||
|
<ModalApproveButton>
|
||||||
|
{{ $t("ok") }}
|
||||||
|
</ModalApproveButton>
|
||||||
|
</template>
|
||||||
|
</ConfirmModalLayout>
|
||||||
|
</UiModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import ConfirmModalLayout from "@/components/ui/modals/layouts/ConfirmModalLayout.vue";
|
||||||
|
import ModalApproveButton from "@/components/ui/modals/ModalApproveButton.vue";
|
||||||
|
import UiModal from "@/components/ui/modals/UiModal.vue";
|
||||||
|
import { IK_MODAL } from "@/types/injection-keys";
|
||||||
|
import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { inject } from "vue";
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
message: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const modal = inject(IK_MODAL)!;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="postcss" scoped></style>
|
18
@xen-orchestra/lite/src/components/ui/UiRaw.vue
Normal file
18
@xen-orchestra/lite/src/components/ui/UiRaw.vue
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<template>
|
||||||
|
<pre class="ui-raw"><slot /></pre>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup></script>
|
||||||
|
|
||||||
|
<style lang="postcss" scoped>
|
||||||
|
.ui-raw {
|
||||||
|
background-color: var(--color-blue-scale-400);
|
||||||
|
text-align: left;
|
||||||
|
overflow: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
width: 48em;
|
||||||
|
padding: 0.5em;
|
||||||
|
border-radius: 8px;
|
||||||
|
line-height: 150%;
|
||||||
|
}
|
||||||
|
</style>
|
@ -54,6 +54,7 @@ type ObjectTypeToRecordMapping = {
|
|||||||
host: XenApiHost;
|
host: XenApiHost;
|
||||||
host_metrics: XenApiHostMetrics;
|
host_metrics: XenApiHostMetrics;
|
||||||
message: XenApiMessage<any>;
|
message: XenApiMessage<any>;
|
||||||
|
network: XenApiNetwork;
|
||||||
pool: XenApiPool;
|
pool: XenApiPool;
|
||||||
sr: XenApiSr;
|
sr: XenApiSr;
|
||||||
vm: XenApiVm;
|
vm: XenApiVm;
|
||||||
@ -113,9 +114,11 @@ export interface XenApiHost extends XenApiRecord<"host"> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface XenApiSr extends XenApiRecord<"sr"> {
|
export interface XenApiSr extends XenApiRecord<"sr"> {
|
||||||
|
content_type: string;
|
||||||
name_label: string;
|
name_label: string;
|
||||||
physical_size: number;
|
physical_size: number;
|
||||||
physical_utilisation: number;
|
physical_utilisation: number;
|
||||||
|
shared: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface XenApiVm extends XenApiRecord<"vm"> {
|
export interface XenApiVm extends XenApiRecord<"vm"> {
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
{
|
{
|
||||||
"about": "About",
|
"about": "About",
|
||||||
|
"access-xoa": "Access XOA",
|
||||||
"add": "Add",
|
"add": "Add",
|
||||||
"add-filter": "Add filter",
|
"add-filter": "Add filter",
|
||||||
"add-or": "+OR",
|
"add-or": "+OR",
|
||||||
"add-sort": "Add sort",
|
"add-sort": "Add sort",
|
||||||
|
"admin-login": "Admin login",
|
||||||
|
"admin-password": "Admin password",
|
||||||
|
"admin-password-confirm": "Confirm admin password",
|
||||||
"alarm-type": {
|
"alarm-type": {
|
||||||
"cpu_usage": "CPU usage exceeds {n}%",
|
"cpu_usage": "CPU usage exceeds {n}%",
|
||||||
"disk_usage": "Disk usage exceeds {n}%",
|
"disk_usage": "Disk usage exceeds {n}%",
|
||||||
@ -26,12 +30,14 @@
|
|||||||
"backup": "Backup",
|
"backup": "Backup",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"change-state": "Change state",
|
"change-state": "Change state",
|
||||||
|
"check-errors": "Check out the errors:",
|
||||||
"click-to-display-alarms": "Click to display alarms:",
|
"click-to-display-alarms": "Click to display alarms:",
|
||||||
"click-to-return-default-pool": "Click here to return to the default pool",
|
"click-to-return-default-pool": "Click here to return to the default pool",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
"coming-soon": "Coming soon!",
|
"coming-soon": "Coming soon!",
|
||||||
"community": "Community",
|
"community": "Community",
|
||||||
"community-name": "{name} community",
|
"community-name": "{name} community",
|
||||||
|
"configuration": "Configuration",
|
||||||
"confirm-cancel": "Are you sure you want to cancel?",
|
"confirm-cancel": "Are you sure you want to cancel?",
|
||||||
"confirm-delete": "You're about to delete {0}",
|
"confirm-delete": "You're about to delete {0}",
|
||||||
"console": "Console",
|
"console": "Console",
|
||||||
@ -43,14 +49,28 @@
|
|||||||
"dashboard": "Dashboard",
|
"dashboard": "Dashboard",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"delete-vms": "Delete 1 VM | Delete {n} VMs",
|
"delete-vms": "Delete 1 VM | Delete {n} VMs",
|
||||||
|
"deploy": "Deploy",
|
||||||
|
"deploy-xoa": "Deploy XOA",
|
||||||
|
"deploy-xoa-available-on-desktop": "XOA deployment is available on your desktop interface",
|
||||||
|
"deploy-xoa-status": {
|
||||||
|
"configuring": "Configuring XOA…",
|
||||||
|
"importing": "Importing XOA…",
|
||||||
|
"not-responding": "XOA is not responding",
|
||||||
|
"ready": "XOA is ready!",
|
||||||
|
"starting": "Starting XOA…",
|
||||||
|
"waiting": "Waiting for XOA to respond…"
|
||||||
|
},
|
||||||
"descending": "descending",
|
"descending": "descending",
|
||||||
"description": "Description",
|
"description": "Description",
|
||||||
|
"dhcp": "DHCP",
|
||||||
"disabled": "Disabled",
|
"disabled": "Disabled",
|
||||||
"display": "Display",
|
"display": "Display",
|
||||||
|
"dns": "DNS",
|
||||||
"do-you-have-needs": "You have needs and/or expectations? Let us know",
|
"do-you-have-needs": "You have needs and/or expectations? Let us know",
|
||||||
"documentation": "Documentation",
|
"documentation": "Documentation",
|
||||||
"documentation-name": "{name} documentation",
|
"documentation-name": "{name} documentation",
|
||||||
"edit-config": "Edit config",
|
"edit-config": "Edit config",
|
||||||
|
"enabled": "Enabled",
|
||||||
"error-no-data": "Error, can't collect data.",
|
"error-no-data": "Error, can't collect data.",
|
||||||
"error-occurred": "An error has occurred",
|
"error-occurred": "An error has occurred",
|
||||||
"export": "Export",
|
"export": "Export",
|
||||||
@ -84,11 +104,16 @@
|
|||||||
"force-shutdown": "Force shutdown",
|
"force-shutdown": "Force shutdown",
|
||||||
"fullscreen": "Fullscreen",
|
"fullscreen": "Fullscreen",
|
||||||
"fullscreen-leave": "Leave fullscreen",
|
"fullscreen-leave": "Leave fullscreen",
|
||||||
|
"gateway": "Gateway",
|
||||||
|
"n-gb-left": "{n} GB left",
|
||||||
|
"n-gb-required": "{n} GB required",
|
||||||
"go-back": "Go back",
|
"go-back": "Go back",
|
||||||
"gzip": "gzip",
|
"gzip": "gzip",
|
||||||
"here": "Here",
|
"here": "Here",
|
||||||
"hosts": "Hosts",
|
"hosts": "Hosts",
|
||||||
|
"invalid-field": "Invalid field",
|
||||||
"keep-me-logged": "Keep me logged in",
|
"keep-me-logged": "Keep me logged in",
|
||||||
|
"keep-page-open": "Do not refresh or quit tab before end of deployment.",
|
||||||
"language": "Language",
|
"language": "Language",
|
||||||
"last-week": "Last week",
|
"last-week": "Last week",
|
||||||
"learn-more": "Learn more",
|
"learn-more": "Learn more",
|
||||||
@ -104,6 +129,7 @@
|
|||||||
"n-missing": "{n} missing",
|
"n-missing": "{n} missing",
|
||||||
"n-vms": "1 VM | {n} VMs",
|
"n-vms": "1 VM | {n} VMs",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
|
"netmask": "Netmask",
|
||||||
"network": "Network",
|
"network": "Network",
|
||||||
"network-download": "Download",
|
"network-download": "Download",
|
||||||
"network-throughput": "Network throughput",
|
"network-throughput": "Network throughput",
|
||||||
@ -119,6 +145,7 @@
|
|||||||
"not-found": "Not found",
|
"not-found": "Not found",
|
||||||
"object": "Object",
|
"object": "Object",
|
||||||
"object-not-found": "Object {id} can't be found…",
|
"object-not-found": "Object {id} can't be found…",
|
||||||
|
"ok": "OK",
|
||||||
"on-object": "on {object}",
|
"on-object": "on {object}",
|
||||||
"open-console-in-new-tab": "Open console in new tab",
|
"open-console-in-new-tab": "Open console in new tab",
|
||||||
"or": "Or",
|
"or": "Or",
|
||||||
@ -154,14 +181,23 @@
|
|||||||
"selected-vms-in-execution": "Some selected VMs are running",
|
"selected-vms-in-execution": "Some selected VMs are running",
|
||||||
"send-ctrl-alt-del": "Send Ctrl+Alt+Del",
|
"send-ctrl-alt-del": "Send Ctrl+Alt+Del",
|
||||||
"send-us-feedback": "Send us feedback",
|
"send-us-feedback": "Send us feedback",
|
||||||
|
"select": {
|
||||||
|
"network": "Select a network",
|
||||||
|
"storage": "Select a storage"
|
||||||
|
},
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"shutdown": "Shutdown",
|
"shutdown": "Shutdown",
|
||||||
"snapshot": "Snapshot",
|
"snapshot": "Snapshot",
|
||||||
"sort-by": "Sort by",
|
"sort-by": "Sort by",
|
||||||
|
"ssh-account": "SSH account",
|
||||||
|
"ssh-login": "SSH login",
|
||||||
|
"ssh-password": "SSH password",
|
||||||
|
"ssh-password-confirm": "Confirm SSH password",
|
||||||
"stacked-cpu-usage": "Stacked CPU usage",
|
"stacked-cpu-usage": "Stacked CPU usage",
|
||||||
"stacked-ram-usage": "Stacked RAM usage",
|
"stacked-ram-usage": "Stacked RAM usage",
|
||||||
"start": "Start",
|
"start": "Start",
|
||||||
"start-on-host": "Start on specific host",
|
"start-on-host": "Start on specific host",
|
||||||
|
"static-ip": "Static IP",
|
||||||
"stats": "Stats",
|
"stats": "Stats",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"storage": "Storage",
|
"storage": "Storage",
|
||||||
@ -193,6 +229,15 @@
|
|||||||
"vm-is-running": "The VM is running",
|
"vm-is-running": "The VM is running",
|
||||||
"vms": "VMs",
|
"vms": "VMs",
|
||||||
"xo-lite-under-construction": "XOLite is under construction",
|
"xo-lite-under-construction": "XOLite is under construction",
|
||||||
|
"xoa-admin-account": "XOA admin account",
|
||||||
|
"xoa-deploy": "XOA deployment",
|
||||||
|
"xoa-deploy-failed": "Sorry, deployment failed!",
|
||||||
|
"xoa-deploy-retry": "Try again to deploy XOA",
|
||||||
|
"xoa-deploy-successful": "XOA deployment successful!",
|
||||||
|
"xoa-ip": "XOA IP address",
|
||||||
|
"xoa-password-confirm-different": "XOA password confirmation is different",
|
||||||
|
"xoa-ssh-account": "XOA SSH account",
|
||||||
|
"xoa-ssh-password-confirm-different": "SSH password confirmation is different",
|
||||||
"you-are-currently-on": "You are currently on: {0}",
|
"you-are-currently-on": "You are currently on: {0}",
|
||||||
"zstd": "zstd"
|
"zstd": "zstd"
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
{
|
{
|
||||||
"about": "À propos",
|
"about": "À propos",
|
||||||
|
"access-xoa": "Accéder à la XOA",
|
||||||
"add": "Ajouter",
|
"add": "Ajouter",
|
||||||
"add-filter": "Ajouter un filtre",
|
"add-filter": "Ajouter un filtre",
|
||||||
"add-or": "+OU",
|
"add-or": "+OU",
|
||||||
"add-sort": "Ajouter un tri",
|
"add-sort": "Ajouter un tri",
|
||||||
|
"admin-login": "Nom d'utilisateur administrateur",
|
||||||
|
"admin-password": "Mot de passe administrateur",
|
||||||
|
"admin-password-confirm": "Confirmer le mot de passe administrateur",
|
||||||
"alarm-type": {
|
"alarm-type": {
|
||||||
"cpu_usage": "L'utilisation du CPU dépasse {n}%",
|
"cpu_usage": "L'utilisation du CPU dépasse {n}%",
|
||||||
"disk_usage": "L'utilisation du disque dépasse {n}%",
|
"disk_usage": "L'utilisation du disque dépasse {n}%",
|
||||||
@ -26,12 +30,14 @@
|
|||||||
"backup": "Sauvegarde",
|
"backup": "Sauvegarde",
|
||||||
"cancel": "Annuler",
|
"cancel": "Annuler",
|
||||||
"change-state": "Changer l'état",
|
"change-state": "Changer l'état",
|
||||||
|
"check-errors": "Consultez les erreurs :",
|
||||||
"click-to-display-alarms": "Cliquer pour afficher les alarmes :",
|
"click-to-display-alarms": "Cliquer pour afficher les alarmes :",
|
||||||
"click-to-return-default-pool": "Cliquer ici pour revenir au pool par défaut",
|
"click-to-return-default-pool": "Cliquer ici pour revenir au pool par défaut",
|
||||||
"close": "Fermer",
|
"close": "Fermer",
|
||||||
"coming-soon": "Bientôt disponible !",
|
"coming-soon": "Bientôt disponible !",
|
||||||
"community": "Communauté",
|
"community": "Communauté",
|
||||||
"community-name": "Communauté {name}",
|
"community-name": "Communauté {name}",
|
||||||
|
"configuration": "Configuration",
|
||||||
"confirm-cancel": "Êtes-vous sûr de vouloir annuler ?",
|
"confirm-cancel": "Êtes-vous sûr de vouloir annuler ?",
|
||||||
"confirm-delete": "Vous êtes sur le point de supprimer {0}",
|
"confirm-delete": "Vous êtes sur le point de supprimer {0}",
|
||||||
"console": "Console",
|
"console": "Console",
|
||||||
@ -43,14 +49,28 @@
|
|||||||
"dashboard": "Tableau de bord",
|
"dashboard": "Tableau de bord",
|
||||||
"delete": "Supprimer",
|
"delete": "Supprimer",
|
||||||
"delete-vms": "Supprimer 1 VM | Supprimer {n} VMs",
|
"delete-vms": "Supprimer 1 VM | Supprimer {n} VMs",
|
||||||
|
"deploy": "Déployer",
|
||||||
|
"deploy-xoa": "Déployer XOA",
|
||||||
|
"deploy-xoa-available-on-desktop": "Le déploiement de la XOA est disponible sur ordinateur",
|
||||||
|
"deploy-xoa-status": {
|
||||||
|
"configuring": "Configuration de la XOA…",
|
||||||
|
"importing": "Importation de la XOA…",
|
||||||
|
"not-responding": "La XOA ne répond pas",
|
||||||
|
"ready": "La XOA est prête !",
|
||||||
|
"starting": "Démarrage de la XOA…",
|
||||||
|
"waiting": "En attente de réponse de la XOA…"
|
||||||
|
},
|
||||||
"descending": "descendant",
|
"descending": "descendant",
|
||||||
"description": "Description",
|
"description": "Description",
|
||||||
|
"dhcp": "DHCP",
|
||||||
|
"dns": "DNS",
|
||||||
"disabled": "Désactivé",
|
"disabled": "Désactivé",
|
||||||
"display": "Affichage",
|
"display": "Affichage",
|
||||||
"do-you-have-needs": "Vous avez des besoins et/ou des attentes ? Faites le nous savoir",
|
"do-you-have-needs": "Vous avez des besoins et/ou des attentes ? Faites le nous savoir",
|
||||||
"documentation": "Documentation",
|
"documentation": "Documentation",
|
||||||
"documentation-name": "Documentation {name}",
|
"documentation-name": "Documentation {name}",
|
||||||
"edit-config": "Modifier config",
|
"edit-config": "Modifier config",
|
||||||
|
"enabled": "Activé",
|
||||||
"error-no-data": "Erreur, impossible de collecter les données.",
|
"error-no-data": "Erreur, impossible de collecter les données.",
|
||||||
"error-occurred": "Une erreur est survenue",
|
"error-occurred": "Une erreur est survenue",
|
||||||
"export": "Exporter",
|
"export": "Exporter",
|
||||||
@ -84,11 +104,16 @@
|
|||||||
"force-shutdown": "Forcer l'arrêt",
|
"force-shutdown": "Forcer l'arrêt",
|
||||||
"fullscreen": "Plein écran",
|
"fullscreen": "Plein écran",
|
||||||
"fullscreen-leave": "Quitter plein écran",
|
"fullscreen-leave": "Quitter plein écran",
|
||||||
|
"gateway": "Passerelle",
|
||||||
|
"n-gb-left": "{n} Go libres",
|
||||||
|
"n-gb-required": "{n} Go requis",
|
||||||
"go-back": "Revenir en arrière",
|
"go-back": "Revenir en arrière",
|
||||||
"gzip": "gzip",
|
"gzip": "gzip",
|
||||||
"here": "Ici",
|
"here": "Ici",
|
||||||
"hosts": "Hôtes",
|
"hosts": "Hôtes",
|
||||||
|
"invalid-field": "Champ invalide",
|
||||||
"keep-me-logged": "Rester connecté",
|
"keep-me-logged": "Rester connecté",
|
||||||
|
"keep-page-open": "Ne pas rafraichir ou quitter cette page avant la fin du déploiement.",
|
||||||
"language": "Langue",
|
"language": "Langue",
|
||||||
"last-week": "Semaine dernière",
|
"last-week": "Semaine dernière",
|
||||||
"learn-more": "En savoir plus",
|
"learn-more": "En savoir plus",
|
||||||
@ -104,6 +129,7 @@
|
|||||||
"n-missing": "{n} manquant | {n} manquants",
|
"n-missing": "{n} manquant | {n} manquants",
|
||||||
"n-vms": "1 VM | {n} VMs",
|
"n-vms": "1 VM | {n} VMs",
|
||||||
"name": "Nom",
|
"name": "Nom",
|
||||||
|
"netmask": "Masque réseau",
|
||||||
"network": "Réseau",
|
"network": "Réseau",
|
||||||
"network-download": "Descendant",
|
"network-download": "Descendant",
|
||||||
"network-throughput": "Débit du réseau",
|
"network-throughput": "Débit du réseau",
|
||||||
@ -119,6 +145,7 @@
|
|||||||
"not-found": "Non trouvé",
|
"not-found": "Non trouvé",
|
||||||
"object": "Objet",
|
"object": "Objet",
|
||||||
"object-not-found": "L'objet {id} est introuvable…",
|
"object-not-found": "L'objet {id} est introuvable…",
|
||||||
|
"ok": "OK",
|
||||||
"on-object": "sur {object}",
|
"on-object": "sur {object}",
|
||||||
"open-console-in-new-tab": "Ouvrir la console dans un nouvel onglet",
|
"open-console-in-new-tab": "Ouvrir la console dans un nouvel onglet",
|
||||||
"or": "Ou",
|
"or": "Ou",
|
||||||
@ -154,14 +181,23 @@
|
|||||||
"selected-vms-in-execution": "Certaines VMs sélectionnées sont en cours d'exécution",
|
"selected-vms-in-execution": "Certaines VMs sélectionnées sont en cours d'exécution",
|
||||||
"send-ctrl-alt-del": "Envoyer Ctrl+Alt+Suppr",
|
"send-ctrl-alt-del": "Envoyer Ctrl+Alt+Suppr",
|
||||||
"send-us-feedback": "Envoyez-nous vos commentaires",
|
"send-us-feedback": "Envoyez-nous vos commentaires",
|
||||||
|
"select": {
|
||||||
|
"network": "Sélectionner un réseau",
|
||||||
|
"storage": "Sélectionner un SR"
|
||||||
|
},
|
||||||
"settings": "Paramètres",
|
"settings": "Paramètres",
|
||||||
"shutdown": "Arrêter",
|
"shutdown": "Arrêter",
|
||||||
"snapshot": "Instantané",
|
"snapshot": "Instantané",
|
||||||
"sort-by": "Trier par",
|
"sort-by": "Trier par",
|
||||||
|
"ssh-account": "Compte SSH",
|
||||||
|
"ssh-login": "Nom d'utilisateur SSH",
|
||||||
|
"ssh-password": "Mot de passe SSH",
|
||||||
|
"ssh-password-confirm": "Confirmer le mot de passe SSH",
|
||||||
"stacked-cpu-usage": "Utilisation CPU empilée",
|
"stacked-cpu-usage": "Utilisation CPU empilée",
|
||||||
"stacked-ram-usage": "Utilisation RAM empilée",
|
"stacked-ram-usage": "Utilisation RAM empilée",
|
||||||
"start": "Démarrer",
|
"start": "Démarrer",
|
||||||
"start-on-host": "Démarrer sur un hôte spécifique",
|
"start-on-host": "Démarrer sur un hôte spécifique",
|
||||||
|
"static-ip": "IP statique",
|
||||||
"stats": "Stats",
|
"stats": "Stats",
|
||||||
"status": "Statut",
|
"status": "Statut",
|
||||||
"storage": "Stockage",
|
"storage": "Stockage",
|
||||||
@ -193,6 +229,15 @@
|
|||||||
"vm-is-running": "La VM est en cours d'exécution",
|
"vm-is-running": "La VM est en cours d'exécution",
|
||||||
"vms": "VMs",
|
"vms": "VMs",
|
||||||
"xo-lite-under-construction": "XOLite est en construction",
|
"xo-lite-under-construction": "XOLite est en construction",
|
||||||
|
"xoa-admin-account": "Compte administrateur de la XOA",
|
||||||
|
"xoa-deploy": "Déploiement de la XOA",
|
||||||
|
"xoa-deploy-failed": "Erreur lors du déploiement de la XOA !",
|
||||||
|
"xoa-deploy-retry": "Ré-essayer de déployer une XOA",
|
||||||
|
"xoa-deploy-successful": "XOA deployée avec succès !",
|
||||||
|
"xoa-ip": "XOA IP address",
|
||||||
|
"xoa-password-confirm-different": "La confirmation du mot de passe XOA est différente",
|
||||||
|
"xoa-ssh-account": "Compte SSH de la XOA",
|
||||||
|
"xoa-ssh-password-confirm-different": "La confirmation du mot de passe SSH est différente",
|
||||||
"you-are-currently-on": "Vous êtes actuellement sur : {0}",
|
"you-are-currently-on": "Vous êtes actuellement sur : {0}",
|
||||||
"zstd": "zstd"
|
"zstd": "zstd"
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,11 @@ const router = createRouter({
|
|||||||
name: "home",
|
name: "home",
|
||||||
component: HomeView,
|
component: HomeView,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/xoa-deploy",
|
||||||
|
name: "xoa.deploy",
|
||||||
|
component: () => import("@/views/xoa-deploy/XoaDeployView.vue"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/settings",
|
path: "/settings",
|
||||||
name: "settings",
|
name: "settings",
|
||||||
|
9
@xen-orchestra/lite/src/stores/xen-api/network.store.ts
Normal file
9
@xen-orchestra/lite/src/stores/xen-api/network.store.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { useXenApiStoreSubscribableContext } from "@/composables/xen-api-store-subscribable-context.composable";
|
||||||
|
import { createUseCollection } from "@/stores/xen-api/create-use-collection";
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
|
export const useNetworkStore = defineStore("xen-api-network", () => {
|
||||||
|
return useXenApiStoreSubscribableContext("network");
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useNetworkCollection = createUseCollection(useNetworkStore);
|
674
@xen-orchestra/lite/src/views/xoa-deploy/XoaDeployView.vue
Normal file
674
@xen-orchestra/lite/src/views/xoa-deploy/XoaDeployView.vue
Normal file
@ -0,0 +1,674 @@
|
|||||||
|
<template>
|
||||||
|
<TitleBar :icon="faDownload">{{ $t("deploy-xoa") }}</TitleBar>
|
||||||
|
<div v-if="deploying" class="status">
|
||||||
|
<img src="@/assets/xo.svg" width="300" alt="Xen Orchestra" />
|
||||||
|
|
||||||
|
<!-- Error -->
|
||||||
|
<template v-if="error !== undefined">
|
||||||
|
<div>
|
||||||
|
<h2>{{ $t("xoa-deploy-failed") }}</h2>
|
||||||
|
<UiIcon :icon="faExclamationCircle" class="danger" />
|
||||||
|
</div>
|
||||||
|
<div class="error">
|
||||||
|
<strong>{{ $t("check-errors") }}</strong>
|
||||||
|
<UiRaw>{{ error }}</UiRaw>
|
||||||
|
</div>
|
||||||
|
<UiButton :icon="faDownload" @click="resetValues()">
|
||||||
|
{{ $t("xoa-deploy-retry") }}
|
||||||
|
</UiButton>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Success -->
|
||||||
|
<template v-else-if="url !== undefined">
|
||||||
|
<div>
|
||||||
|
<h2>{{ $t("xoa-deploy-successful") }}</h2>
|
||||||
|
<UiIcon :icon="faCircleCheck" class="success" />
|
||||||
|
</div>
|
||||||
|
<UiButton :icon="faArrowUpRightFromSquare" @click="openXoa">
|
||||||
|
{{ $t("access-xoa") }}
|
||||||
|
</UiButton>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Deploying -->
|
||||||
|
<template v-else>
|
||||||
|
<div>
|
||||||
|
<h2>{{ $t("xoa-deploy") }}</h2>
|
||||||
|
<!-- TODO: add progress bar -->
|
||||||
|
<p>{{ status }}</p>
|
||||||
|
</div>
|
||||||
|
<p class="warning">
|
||||||
|
<UiIcon :icon="faExclamationCircle" />
|
||||||
|
{{ $t("keep-page-open") }}
|
||||||
|
</p>
|
||||||
|
<UiButton
|
||||||
|
:disabled="vmRef === undefined"
|
||||||
|
color="error"
|
||||||
|
outlined
|
||||||
|
@click="cancel()"
|
||||||
|
>
|
||||||
|
{{ $t("cancel") }}
|
||||||
|
</UiButton>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="isMobile" class="not-available">
|
||||||
|
<p>{{ $t("deploy-xoa-available-on-desktop") }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="card-view">
|
||||||
|
<UiCard>
|
||||||
|
<form @submit.prevent="deploy">
|
||||||
|
<FormSection :label="$t('configuration')">
|
||||||
|
<div class="row">
|
||||||
|
<FormInputWrapper
|
||||||
|
:label="$t('storage')"
|
||||||
|
:help="$t('n-gb-required', { n: REQUIRED_GB })"
|
||||||
|
>
|
||||||
|
<FormSelect v-model="selectedSr" required>
|
||||||
|
<option disabled :value="undefined">
|
||||||
|
{{ $t("select.storage") }}
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
v-for="sr in filteredSrs"
|
||||||
|
:value="sr"
|
||||||
|
:key="sr.uuid"
|
||||||
|
:class="
|
||||||
|
sr.physical_size - sr.physical_utilisation <
|
||||||
|
REQUIRED_GB * 1024 ** 3
|
||||||
|
? 'warning'
|
||||||
|
: 'success'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ sr.name_label }} -
|
||||||
|
{{
|
||||||
|
$t("n-gb-left", {
|
||||||
|
n: Math.round(
|
||||||
|
(sr.physical_size - sr.physical_utilisation) / 1024 ** 3
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
<span
|
||||||
|
v-if="
|
||||||
|
sr.physical_size - sr.physical_utilisation <
|
||||||
|
REQUIRED_GB * 1024 ** 3
|
||||||
|
"
|
||||||
|
>⚠️</span
|
||||||
|
>
|
||||||
|
</option>
|
||||||
|
</FormSelect>
|
||||||
|
</FormInputWrapper>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<FormInputWrapper :label="$t('network')" required>
|
||||||
|
<FormSelect v-model="selectedNetwork" required>
|
||||||
|
<option disabled :value="undefined">
|
||||||
|
{{ $t("select.network") }}
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
v-for="network in filteredNetworks"
|
||||||
|
:value="network"
|
||||||
|
:key="network.uuid"
|
||||||
|
>
|
||||||
|
{{ network.name_label }}
|
||||||
|
</option>
|
||||||
|
</FormSelect>
|
||||||
|
</FormInputWrapper>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<FormInputWrapper>
|
||||||
|
<div class="radio-group">
|
||||||
|
<label
|
||||||
|
><FormRadio value="static" v-model="ipStrategy" />{{
|
||||||
|
$t("static-ip")
|
||||||
|
}}</label
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
><FormRadio value="dhcp" v-model="ipStrategy" />{{
|
||||||
|
$t("dhcp")
|
||||||
|
}}</label
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</FormInputWrapper>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<FormInputWrapper
|
||||||
|
:label="$t('xoa-ip')"
|
||||||
|
learnMoreUrl="https://xen-orchestra.com/docs/xoa.html#network-configuration"
|
||||||
|
>
|
||||||
|
<FormInput
|
||||||
|
v-model="ip"
|
||||||
|
:disabled="!requireIpConf"
|
||||||
|
placeholder="xxx.xxx.xxx.xxx"
|
||||||
|
/>
|
||||||
|
</FormInputWrapper>
|
||||||
|
<FormInputWrapper
|
||||||
|
:label="$t('netmask')"
|
||||||
|
learnMoreUrl="https://xen-orchestra.com/docs/xoa.html#network-configuration"
|
||||||
|
>
|
||||||
|
<FormInput
|
||||||
|
v-model="netmask"
|
||||||
|
:disabled="!requireIpConf"
|
||||||
|
placeholder="255.255.255.0"
|
||||||
|
/>
|
||||||
|
</FormInputWrapper>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<FormInputWrapper
|
||||||
|
:label="$t('dns')"
|
||||||
|
learnMoreUrl="https://xen-orchestra.com/docs/xoa.html#network-configuration"
|
||||||
|
>
|
||||||
|
<FormInput
|
||||||
|
v-model="dns"
|
||||||
|
:disabled="!requireIpConf"
|
||||||
|
placeholder="8.8.8.8"
|
||||||
|
/>
|
||||||
|
</FormInputWrapper>
|
||||||
|
<FormInputWrapper
|
||||||
|
:label="$t('gateway')"
|
||||||
|
learnMoreUrl="https://xen-orchestra.com/docs/xoa.html#network-configuration"
|
||||||
|
>
|
||||||
|
<FormInput
|
||||||
|
v-model="gateway"
|
||||||
|
:disabled="!requireIpConf"
|
||||||
|
placeholder="xxx.xxx.xxx.xxx"
|
||||||
|
/>
|
||||||
|
</FormInputWrapper>
|
||||||
|
</div>
|
||||||
|
</FormSection>
|
||||||
|
|
||||||
|
<FormSection :label="$t('xoa-admin-account')">
|
||||||
|
<div class="row">
|
||||||
|
<FormInputWrapper
|
||||||
|
:label="$t('admin-login')"
|
||||||
|
learnMoreUrl="https://xen-orchestra.com/docs/xoa.html#default-xo-account"
|
||||||
|
>
|
||||||
|
<FormInput
|
||||||
|
v-model="xoaUser"
|
||||||
|
required
|
||||||
|
placeholder="email@example.com"
|
||||||
|
/>
|
||||||
|
</FormInputWrapper>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<FormInputWrapper
|
||||||
|
:label="$t('admin-password')"
|
||||||
|
learnMoreUrl="https://xen-orchestra.com/docs/xoa.html#default-xo-account"
|
||||||
|
>
|
||||||
|
<FormInput
|
||||||
|
type="password"
|
||||||
|
v-model="xoaPwd"
|
||||||
|
required
|
||||||
|
:placeholder="$t('password')"
|
||||||
|
/>
|
||||||
|
</FormInputWrapper>
|
||||||
|
<FormInputWrapper
|
||||||
|
:label="$t('admin-password-confirm')"
|
||||||
|
learnMoreUrl="https://xen-orchestra.com/docs/xoa.html#default-xo-account"
|
||||||
|
>
|
||||||
|
<FormInput
|
||||||
|
type="password"
|
||||||
|
v-model="xoaPwdConfirm"
|
||||||
|
required
|
||||||
|
:placeholder="$t('password')"
|
||||||
|
/>
|
||||||
|
</FormInputWrapper>
|
||||||
|
</div>
|
||||||
|
</FormSection>
|
||||||
|
|
||||||
|
<FormSection :label="$t('xoa-ssh-account')">
|
||||||
|
<div class="row">
|
||||||
|
<FormInputWrapper :label="$t('ssh-account')">
|
||||||
|
<label
|
||||||
|
><span>{{ $t("disabled") }}</span
|
||||||
|
><FormToggle v-model="enableSshAccount" /><span>{{
|
||||||
|
$t("enabled")
|
||||||
|
}}</span></label
|
||||||
|
>
|
||||||
|
</FormInputWrapper>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<FormInputWrapper :label="$t('ssh-login')">
|
||||||
|
<FormInput value="xoa" placeholder="xoa" disabled />
|
||||||
|
</FormInputWrapper>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<FormInputWrapper :label="$t('ssh-password')">
|
||||||
|
<FormInput
|
||||||
|
type="password"
|
||||||
|
v-model="sshPwd"
|
||||||
|
:placeholder="$t('password')"
|
||||||
|
:disabled="!enableSshAccount"
|
||||||
|
:required="enableSshAccount"
|
||||||
|
/>
|
||||||
|
</FormInputWrapper>
|
||||||
|
<FormInputWrapper :label="$t('ssh-password-confirm')">
|
||||||
|
<FormInput
|
||||||
|
type="password"
|
||||||
|
v-model="sshPwdConfirm"
|
||||||
|
:placeholder="$t('password')"
|
||||||
|
:disabled="!enableSshAccount"
|
||||||
|
:required="enableSshAccount"
|
||||||
|
/>
|
||||||
|
</FormInputWrapper>
|
||||||
|
</div>
|
||||||
|
</FormSection>
|
||||||
|
|
||||||
|
<UiButtonGroup>
|
||||||
|
<UiButton outlined @click="router.back()">
|
||||||
|
{{ $t("cancel") }}
|
||||||
|
</UiButton>
|
||||||
|
<UiButton type="submit">
|
||||||
|
{{ $t("deploy") }}
|
||||||
|
</UiButton>
|
||||||
|
</UiButtonGroup>
|
||||||
|
</form>
|
||||||
|
</UiCard>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, ref } from "vue";
|
||||||
|
import {
|
||||||
|
faArrowUpRightFromSquare,
|
||||||
|
faCircleCheck,
|
||||||
|
faDownload,
|
||||||
|
faExclamationCircle,
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { storeToRefs } from "pinia";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useModal } from "@/composables/modal.composable";
|
||||||
|
import { useNetworkCollection } from "@/stores/xen-api/network.store";
|
||||||
|
import { usePageTitleStore } from "@/stores/page-title.store";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import { useSrCollection } from "@/stores/xen-api/sr.store";
|
||||||
|
import { useUiStore } from "@/stores/ui.store";
|
||||||
|
import { useXenApiStore } from "@/stores/xen-api.store";
|
||||||
|
import type { XenApiNetwork, XenApiSr } from "@/libs/xen-api/xen-api.types";
|
||||||
|
import FormInput from "@/components/form/FormInput.vue";
|
||||||
|
import FormInputWrapper from "@/components/form/FormInputWrapper.vue";
|
||||||
|
import FormRadio from "@/components/form/FormRadio.vue";
|
||||||
|
import FormSection from "@/components/form/FormSection.vue";
|
||||||
|
import FormSelect from "@/components/form/FormSelect.vue";
|
||||||
|
import FormToggle from "@/components/form/FormToggle.vue";
|
||||||
|
import TitleBar from "@/components/TitleBar.vue";
|
||||||
|
import UiButton from "@/components/ui/UiButton.vue";
|
||||||
|
import UiButtonGroup from "@/components/ui/UiButtonGroup.vue";
|
||||||
|
import UiCard from "@/components/ui/UiCard.vue";
|
||||||
|
import UiIcon from "@/components/ui/icon/UiIcon.vue";
|
||||||
|
import UiRaw from "@/components/ui/UiRaw.vue";
|
||||||
|
|
||||||
|
const REQUIRED_GB = 20;
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
usePageTitleStore().setTitle(() => t("deploy-xoa"));
|
||||||
|
|
||||||
|
const invalidField = (message: string) =>
|
||||||
|
useModal(() => import("@/components/modals/InvalidFieldModal.vue"), {
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
|
||||||
|
const uiStore = useUiStore();
|
||||||
|
const { isMobile } = storeToRefs(uiStore);
|
||||||
|
|
||||||
|
const xapi = useXenApiStore().getXapi();
|
||||||
|
|
||||||
|
const { records: srs } = useSrCollection();
|
||||||
|
const filteredSrs = computed(() =>
|
||||||
|
srs.value
|
||||||
|
.filter((sr) => sr.content_type !== "iso" && sr.physical_size > 0)
|
||||||
|
// Sort: shared first then largest free space first
|
||||||
|
.sort((sr1, sr2) => {
|
||||||
|
if (sr1.shared === sr2.shared) {
|
||||||
|
return (
|
||||||
|
sr2.physical_size -
|
||||||
|
sr2.physical_utilisation -
|
||||||
|
(sr1.physical_size - sr1.physical_utilisation)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return sr1.shared ? -1 : 1;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const { records: networks } = useNetworkCollection();
|
||||||
|
const filteredNetworks = computed(() =>
|
||||||
|
[...networks.value].sort((network1, network2) =>
|
||||||
|
network1.name_label < network2.name_label ? -1 : 1
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const deploying = ref(false);
|
||||||
|
const status = ref<string | undefined>();
|
||||||
|
const error = ref<string | undefined>();
|
||||||
|
const url = ref<string | undefined>();
|
||||||
|
const vmRef = ref<string | undefined>();
|
||||||
|
|
||||||
|
const resetValues = () => {
|
||||||
|
deploying.value = false;
|
||||||
|
status.value = undefined;
|
||||||
|
error.value = undefined;
|
||||||
|
url.value = undefined;
|
||||||
|
vmRef.value = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const openXoa = () => {
|
||||||
|
window.open(url.value, "_blank", "noopener");
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectedSr = ref<XenApiSr>();
|
||||||
|
const selectedNetwork = ref<XenApiNetwork>();
|
||||||
|
const ipStrategy = ref<"static" | "dhcp">("dhcp");
|
||||||
|
const requireIpConf = computed(() => ipStrategy.value === "static");
|
||||||
|
|
||||||
|
const ip = ref("");
|
||||||
|
const netmask = ref("");
|
||||||
|
const dns = ref("");
|
||||||
|
const gateway = ref("");
|
||||||
|
const xoaUser = ref("");
|
||||||
|
const xoaPwd = ref("");
|
||||||
|
const xoaPwdConfirm = ref("");
|
||||||
|
const enableSshAccount = ref(true);
|
||||||
|
const sshPwd = ref("");
|
||||||
|
const sshPwdConfirm = ref("");
|
||||||
|
|
||||||
|
async function deploy() {
|
||||||
|
if (selectedSr.value === undefined || selectedNetwork.value === undefined) {
|
||||||
|
// Should not happen
|
||||||
|
console.error("SR or network is undefined");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
ipStrategy.value === "static" &&
|
||||||
|
(ip.value === "" ||
|
||||||
|
netmask.value === "" ||
|
||||||
|
dns.value === "" ||
|
||||||
|
gateway.value === "")
|
||||||
|
) {
|
||||||
|
// Should not happen
|
||||||
|
console.error("Missing IP config");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xoaUser.value === "" || xoaPwd.value === "") {
|
||||||
|
// Should not happen
|
||||||
|
console.error("Missing XOA credentials");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xoaPwd.value !== xoaPwdConfirm.value) {
|
||||||
|
// TODO: use formal validation system
|
||||||
|
invalidField(t("xoa-password-confirm-different"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enableSshAccount.value && sshPwd.value === "") {
|
||||||
|
// Should not happen
|
||||||
|
console.error("Missing XOA credentials");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enableSshAccount.value && sshPwd.value !== sshPwdConfirm.value) {
|
||||||
|
// TODO: use form validation system
|
||||||
|
invalidField(t("xoa-ssh-password-confirm-different"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
deploying.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
status.value = t("deploy-xoa-status.importing");
|
||||||
|
|
||||||
|
vmRef.value = (
|
||||||
|
(await xapi.call("VM.import", [
|
||||||
|
"http://xoa.io:8888/",
|
||||||
|
selectedSr.value.$ref,
|
||||||
|
false, // full_restore
|
||||||
|
false, // force
|
||||||
|
])) as string[]
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
status.value = t("deploy-xoa-status.configuring");
|
||||||
|
|
||||||
|
const [vifRef] = (await xapi.call("VM.get_VIFs", [
|
||||||
|
vmRef.value,
|
||||||
|
])) as string[];
|
||||||
|
await xapi.call("VIF.destroy", [vifRef]);
|
||||||
|
|
||||||
|
if (!deploying.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [device] = (await xapi.call("VM.get_allowed_VIF_devices", [
|
||||||
|
vmRef.value,
|
||||||
|
])) as string[];
|
||||||
|
await xapi.call("VIF.create", [
|
||||||
|
{
|
||||||
|
device,
|
||||||
|
MAC: "",
|
||||||
|
MTU: selectedNetwork.value.MTU,
|
||||||
|
network: selectedNetwork.value.$ref,
|
||||||
|
other_config: {},
|
||||||
|
qos_algorithm_params: {},
|
||||||
|
qos_algorithm_type: "",
|
||||||
|
VM: vmRef.value,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!deploying.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const promises = [
|
||||||
|
xapi.call("VM.add_to_xenstore_data", [
|
||||||
|
vmRef.value,
|
||||||
|
"vm-data/admin-account",
|
||||||
|
JSON.stringify({ email: xoaUser.value, password: xoaPwd.value }),
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
|
||||||
|
// TODO: add host to servers with session token?
|
||||||
|
|
||||||
|
if (ipStrategy.value === "static") {
|
||||||
|
promises.push(
|
||||||
|
xapi.call("VM.add_to_xenstore_data", [
|
||||||
|
vmRef.value,
|
||||||
|
"vm-data/ip",
|
||||||
|
ip.value,
|
||||||
|
]),
|
||||||
|
xapi.call("VM.add_to_xenstore_data", [
|
||||||
|
vmRef.value,
|
||||||
|
"vm-data/netmask",
|
||||||
|
netmask.value,
|
||||||
|
]),
|
||||||
|
xapi.call("VM.add_to_xenstore_data", [
|
||||||
|
vmRef.value,
|
||||||
|
"vm-data/gateway",
|
||||||
|
gateway.value,
|
||||||
|
]),
|
||||||
|
xapi.call("VM.add_to_xenstore_data", [
|
||||||
|
vmRef.value,
|
||||||
|
"vm-data/dns",
|
||||||
|
dns.value,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enableSshAccount.value) {
|
||||||
|
promises.push(
|
||||||
|
xapi.call("VM.add_to_xenstore_data", [
|
||||||
|
vmRef.value,
|
||||||
|
"vm-data/system-account-xoa-password",
|
||||||
|
sshPwd.value,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
if (!deploying.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
status.value = t("deploy-xoa-status.starting");
|
||||||
|
|
||||||
|
await xapi.call("VM.start", [
|
||||||
|
vmRef.value,
|
||||||
|
false, // start_paused
|
||||||
|
false, // force
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!deploying.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
status.value = t("deploy-xoa-status.waiting");
|
||||||
|
|
||||||
|
const metricsRef = await xapi.call("VM.get_guest_metrics", [vmRef.value]);
|
||||||
|
let attempts = 120;
|
||||||
|
let networks: { "0/ip": string } | undefined;
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 10e3)); // Sleep 10s
|
||||||
|
do {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1e3)); // Sleep 1s
|
||||||
|
networks = await xapi.call("VM_guest_metrics.get_networks", [metricsRef]);
|
||||||
|
if (!deploying.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} while (--attempts > 0 && networks?.["0/ip"] === undefined);
|
||||||
|
|
||||||
|
if (attempts === 0 || networks === undefined) {
|
||||||
|
status.value = t("deploy-xoa-status.not-responding");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
[
|
||||||
|
"admin-account",
|
||||||
|
"dns",
|
||||||
|
"gateway",
|
||||||
|
"ip",
|
||||||
|
"netmask",
|
||||||
|
"xoa-updater-credentials",
|
||||||
|
].map((key) =>
|
||||||
|
xapi.call("VM.remove_from_xenstore_data", [
|
||||||
|
vmRef.value,
|
||||||
|
`vm-data/${key}`,
|
||||||
|
])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
status.value = t("deploy-xoa-status.ready");
|
||||||
|
|
||||||
|
// TODO: handle IPv6
|
||||||
|
url.value = `https://${networks["0/ip"]}`;
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error(err);
|
||||||
|
error.value = err?.message ?? err?.code ?? "Unknown error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cancel() {
|
||||||
|
const _vmRef = vmRef.value;
|
||||||
|
console.log("_vmRef:", _vmRef);
|
||||||
|
resetValues();
|
||||||
|
if (_vmRef !== undefined) {
|
||||||
|
try {
|
||||||
|
await xapi.call("VM.destroy", [_vmRef]);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="postcss" scoped>
|
||||||
|
.card-view {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
column-gap: 10rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-toggle {
|
||||||
|
margin: 0 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input-wrapper {
|
||||||
|
flex-grow: 1;
|
||||||
|
min-width: 60rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-container * {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin: 1.67rem 0;
|
||||||
|
& > * {
|
||||||
|
min-width: 20rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-radio {
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.not-available,
|
||||||
|
.status {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 42px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 76.5vh;
|
||||||
|
color: var(--color-extra-blue-base);
|
||||||
|
text-align: center;
|
||||||
|
padding: 5rem;
|
||||||
|
margin: auto;
|
||||||
|
h2 {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
* {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.not-available {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
.status {
|
||||||
|
color: var(--color-blue-scale-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.success {
|
||||||
|
color: var(--color-green-infra-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger {
|
||||||
|
color: var(--color-red-vates-base);
|
||||||
|
}
|
||||||
|
.success,
|
||||||
|
.danger {
|
||||||
|
&.ui-icon {
|
||||||
|
font-size: 3rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: left;
|
||||||
|
gap: 0.5em;
|
||||||
|
}
|
||||||
|
.warning {
|
||||||
|
color: var(--color-orange-world-base);
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in New Issue
Block a user