Compare commits

..

10 Commits

Author SHA1 Message Date
ggunullu
124fd97d38 revert to vhd-util 2023-02-01 10:30:04 +01:00
ggunullu
7037b15cb3 update 2023-02-01 10:22:27 +01:00
ggunullu
9253718d5c try with debian 2023-02-01 10:21:18 +01:00
ggunullu
99f2b42243 update 2023-02-01 10:21:18 +01:00
ggunullu
43104d908c update 2023-02-01 10:21:18 +01:00
ggunullu
fde65bcd6a update dockerfile 2023-02-01 10:21:18 +01:00
ggunullu
26711456d5 try with bionic version 2023-02-01 10:21:18 +01:00
ggunullu
73a0356e4f remove blktap-utils installation 2023-02-01 10:21:18 +01:00
ggunullu
a4a828a71c first try with qemu check 2023-02-01 10:21:18 +01:00
ggunullu
3d9b9efc04 fix(dockerfile): change Node.js version 2023-02-01 10:21:18 +01:00
184 changed files with 2720 additions and 4075 deletions

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@vates/async-each):
```sh
npm install --save @vates/async-each
```
> npm install --save @vates/async-each
```
## Usage

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@vates/cached-dns.lookup):
```sh
npm install --save @vates/cached-dns.lookup
```
> npm install --save @vates/cached-dns.lookup
```
## Usage

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@vates/coalesce-calls):
```sh
npm install --save @vates/coalesce-calls
```
> npm install --save @vates/coalesce-calls
```
## Usage

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@vates/compose):
```sh
npm install --save @vates/compose
```
> npm install --save @vates/compose
```
## Usage

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@vates/decorate-with):
```sh
npm install --save @vates/decorate-with
```
> npm install --save @vates/decorate-with
```
## Usage

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@vates/disposable):
```sh
npm install --save @vates/disposable
```
> npm install --save @vates/disposable
```
## Usage

View File

@@ -8,8 +8,8 @@
Installation of the [npm package](https://npmjs.org/package/@vates/event-listeners-manager):
```sh
npm install --save @vates/event-listeners-manager
```
> npm install --save @vates/event-listeners-manager
```
## Usage

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@vates/multi-key-map):
```sh
npm install --save @vates/multi-key-map
```
> npm install --save @vates/multi-key-map
```
## Usage

View File

@@ -8,8 +8,8 @@
Installation of the [npm package](https://npmjs.org/package/@vates/nbd-client):
```sh
npm install --save @vates/nbd-client
```
> npm install --save @vates/nbd-client
```
## Usage

View File

@@ -16,7 +16,6 @@ const {
NBD_REPLY_MAGIC,
NBD_REQUEST_MAGIC,
OPTS_MAGIC,
NBD_CMD_DISC,
} = require('./constants.js')
const { fromCallback } = require('promise-toolbox')
const { readChunkStrict } = require('@vates/read-chunk')
@@ -90,11 +89,6 @@ module.exports = class NbdClient {
}
async disconnect() {
const buffer = Buffer.alloc(28)
buffer.writeInt32BE(NBD_REQUEST_MAGIC, 0) // it is a nbd request
buffer.writeInt16BE(0, 4) // no command flags for a disconnect
buffer.writeInt16BE(NBD_CMD_DISC, 6) // we want to disconnect from nbd server
await this.#write(buffer)
await this.#serverSocket.destroy()
}

View File

@@ -13,7 +13,7 @@
"url": "https://vates.fr"
},
"license": "ISC",
"version": "1.0.1",
"version": "1.0.0",
"engines": {
"node": ">=14.0"
},
@@ -22,7 +22,7 @@
"@vates/read-chunk": "^1.0.1",
"@xen-orchestra/async-map": "^0.1.2",
"promise-toolbox": "^0.21.0",
"xen-api": "^1.2.5"
"xen-api": "^1.2.2"
},
"devDependencies": {
"tap": "^16.3.0",

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@vates/otp):
```sh
npm install --save @vates/otp
```
> npm install --save @vates/otp
```
## Usage

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@vates/parse-duration):
```sh
npm install --save @vates/parse-duration
```
> npm install --save @vates/parse-duration
```
## Usage

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@vates/predicates):
```sh
npm install --save @vates/predicates
```
> npm install --save @vates/predicates
```
## Usage

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@vates/read-chunk):
```sh
npm install --save @vates/read-chunk
```
> npm install --save @vates/read-chunk
```
## Usage

View File

@@ -1,12 +1,5 @@
'use strict'
/**
* Read a chunk of data from a stream.
*
* @param {Readable} stream - A readable stream to read from.
* @param {number} size - The number of bytes to read.
* @returns {Promise<Buffer|null>} - A Promise that resolves to a Buffer of up to size bytes if available, or null if end of stream is reached. The Promise is rejected if there is an error while reading from the stream.
*/
const readChunk = (stream, size) =>
stream.closed || stream.readableEnded
? Promise.resolve(null)
@@ -40,13 +33,6 @@ const readChunk = (stream, size) =>
})
exports.readChunk = readChunk
/**
* Read a chunk of data from a stream.
*
* @param {Readable} stream - A readable stream to read from.
* @param {number} size - The number of bytes to read.
* @returns {Promise<Buffer>} - A Promise that resolves to a Buffer of size bytes. The Promise is rejected if there is an error while reading from the stream.
*/
exports.readChunkStrict = async function readChunkStrict(stream, size) {
const chunk = await readChunk(stream, size)
if (chunk === null) {

View File

@@ -8,8 +8,8 @@
Installation of the [npm package](https://npmjs.org/package/@vates/task):
```sh
npm install --save @vates/task
```
> npm install --save @vates/task
```
## Usage

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@vates/toggle-scripts):
```sh
npm install --save @vates/toggle-scripts
```
> npm install --save @vates/toggle-scripts
```
## Usage

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/async-map):
```sh
npm install --save @xen-orchestra/async-map
```
> npm install --save @xen-orchestra/async-map
```
## Usage

View File

@@ -8,8 +8,8 @@
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/audit-core):
```sh
npm install --save @xen-orchestra/audit-core
```
> npm install --save @xen-orchestra/audit-core
```
## Contributions

View File

@@ -8,8 +8,8 @@
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/backups-cli):
```sh
npm install --global @xen-orchestra/backups-cli
```
> npm install --global @xen-orchestra/backups-cli
```
## Usage

View File

@@ -7,7 +7,7 @@
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"dependencies": {
"@xen-orchestra/async-map": "^0.1.2",
"@xen-orchestra/backups": "^0.29.6",
"@xen-orchestra/backups": "^0.29.5",
"@xen-orchestra/fs": "^3.3.1",
"filenamify": "^4.1.0",
"getopts": "^2.2.5",

View File

@@ -8,8 +8,8 @@
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/backups):
```sh
npm install --save @xen-orchestra/backups
```
> npm install --save @xen-orchestra/backups
```
## Contributions

View File

@@ -258,9 +258,6 @@ exports.importDeltaVm = defer(async function importDeltaVm(
$defer.onFailure(() => newVdi.$destroy())
await newVdi.update_other_config(TAG_COPY_SRC, vdi.uuid)
if (vdi.virtual_size > newVdi.virtual_size) {
await newVdi.$callAsync('resize', vdi.virtual_size)
}
} else if (vdiRef === vmRecord.suspend_VDI) {
// suspendVDI has already created
newVdi = suspendVdi

View File

@@ -1,6 +1,7 @@
'use strict'
const { finished, PassThrough } = require('node:stream')
const eos = require('end-of-stream')
const { PassThrough } = require('stream')
const { debug } = require('@xen-orchestra/log').createLogger('xo:backups:forkStreamUnpipe')
@@ -8,29 +9,29 @@ const { debug } = require('@xen-orchestra/log').createLogger('xo:backups:forkStr
//
// in case of error in the new readable stream, it will simply be unpiped
// from the original one
exports.forkStreamUnpipe = function forkStreamUnpipe(source) {
const { forks = 0 } = source
source.forks = forks + 1
exports.forkStreamUnpipe = function forkStreamUnpipe(stream) {
const { forks = 0 } = stream
stream.forks = forks + 1
debug('forking', { forks: source.forks })
debug('forking', { forks: stream.forks })
const fork = new PassThrough()
source.pipe(fork)
finished(source, { writable: false }, error => {
const proxy = new PassThrough()
stream.pipe(proxy)
eos(stream, error => {
if (error !== undefined) {
debug('error on original stream, destroying fork', { error })
fork.destroy(error)
proxy.destroy(error)
}
})
finished(fork, { readable: false }, error => {
debug('end of stream, unpiping', { error, forks: --source.forks })
eos(proxy, error => {
debug('end of stream, unpiping', { error, forks: --stream.forks })
source.unpipe(fork)
stream.unpipe(proxy)
if (source.forks === 0) {
if (stream.forks === 0) {
debug('no more forks, destroying original stream')
source.destroy(new Error('no more consumers for this stream'))
stream.destroy(new Error('no more consumers for this stream'))
}
})
return fork
return proxy
}

View File

@@ -8,7 +8,7 @@
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"version": "0.29.6",
"version": "0.29.5",
"engines": {
"node": ">=14.6"
},
@@ -23,7 +23,7 @@
"@vates/decorate-with": "^2.0.0",
"@vates/disposable": "^0.1.4",
"@vates/fuse-vhd": "^1.0.0",
"@vates/nbd-client": "^1.0.1",
"@vates/nbd-client": "*",
"@vates/parse-duration": "^0.1.1",
"@xen-orchestra/async-map": "^0.1.2",
"@xen-orchestra/fs": "^3.3.1",
@@ -32,6 +32,7 @@
"compare-versions": "^5.0.1",
"d3-time-format": "^3.0.0",
"decorator-synchronized": "^0.6.0",
"end-of-stream": "^1.4.4",
"fs-extra": "^11.1.0",
"golike-defer": "^0.5.1",
"limit-concurrency-decorator": "^0.5.0",

View File

@@ -7,8 +7,6 @@ const ignoreErrors = require('promise-toolbox/ignoreErrors')
const { asyncMap } = require('@xen-orchestra/async-map')
const { chainVhd, checkVhdChain, openVhd, VhdAbstract } = require('vhd-lib')
const { createLogger } = require('@xen-orchestra/log')
const { decorateClass } = require('@vates/decorate-with')
const { defer } = require('golike-defer')
const { dirname } = require('path')
const { formatFilenameDate } = require('../_filenameDate.js')
@@ -24,7 +22,7 @@ const NbdClient = require('@vates/nbd-client')
const { debug, warn, info } = createLogger('xo:backups:DeltaBackupWriter')
class DeltaBackupWriter extends MixinBackupWriter(AbstractDeltaWriter) {
exports.DeltaBackupWriter = class DeltaBackupWriter extends MixinBackupWriter(AbstractDeltaWriter) {
async checkBaseVdis(baseUuidToSrcVdi) {
const { handler } = this._adapter
const backup = this._backup
@@ -135,7 +133,7 @@ class DeltaBackupWriter extends MixinBackupWriter(AbstractDeltaWriter) {
}
}
async _transfer($defer, { timestamp, deltaExport }) {
async _transfer({ timestamp, deltaExport }) {
const adapter = this._adapter
const backup = this._backup
@@ -212,11 +210,6 @@ class DeltaBackupWriter extends MixinBackupWriter(AbstractDeltaWriter) {
debug('got NBD info', { nbdInfo, vdi: id, path })
nbdClient = new NbdClient(nbdInfo)
await nbdClient.connect()
// this will inform the xapi that we don't need this anymore
// and will detach the vdi from dom0
$defer(() => nbdClient.disconnect())
info('NBD client ready', { vdi: id, path })
} catch (error) {
nbdClient = undefined
@@ -255,6 +248,3 @@ class DeltaBackupWriter extends MixinBackupWriter(AbstractDeltaWriter) {
// TODO: run cleanup?
}
}
exports.DeltaBackupWriter = decorateClass(DeltaBackupWriter, {
_transfer: defer,
})

View File

@@ -8,8 +8,8 @@
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/cr-seed-cli):
```sh
npm install --global @xen-orchestra/cr-seed-cli
```
> npm install --global @xen-orchestra/cr-seed-cli
```
## Contributions

View File

@@ -18,7 +18,7 @@
"preferGlobal": true,
"dependencies": {
"golike-defer": "^0.5.1",
"xen-api": "^1.2.5"
"xen-api": "^1.2.2"
},
"scripts": {
"postversion": "npm publish"

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/cron):
```sh
npm install --save @xen-orchestra/cron
```
> npm install --save @xen-orchestra/cron
```
## Usage

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/defined):
```sh
npm install --save @xen-orchestra/defined
```
> npm install --save @xen-orchestra/defined
```
## Contributions

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/emit-async):
```sh
npm install --save @xen-orchestra/emit-async
```
> npm install --save @xen-orchestra/emit-async
```
## Usage

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/fs):
```sh
npm install --global @xen-orchestra/fs
```
> npm install --global @xen-orchestra/fs
```
## Contributions

View File

@@ -24,14 +24,12 @@
"d3-time-format": "^4.1.0",
"decorator-synchronized": "^0.6.0",
"echarts": "^5.3.3",
"highlight.js": "^11.6.0",
"human-format": "^1.0.0",
"json-rpc-2.0": "^1.3.0",
"json5": "^2.2.1",
"limit-concurrency-decorator": "^0.5.0",
"lodash-es": "^4.17.21",
"make-error": "^1.3.6",
"markdown-it": "^13.0.1",
"pinia": "^2.0.14",
"placement.js": "^1.0.0-beta.5",
"vue": "^3.2.37",

View File

@@ -16,8 +16,8 @@ a {
color: var(--color-extra-blue-base);
}
code, code * {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
code {
font-family: monospace;
}
.card-view {

View File

@@ -1,114 +0,0 @@
<template>
<div ref="rootElement" class="app-markdown" v-html="html" />
</template>
<script lang="ts" setup>
import { type Ref, computed, ref } from "vue";
import { useEventListener } from "@vueuse/core";
import "highlight.js/styles/github-dark.css";
import { markdown } from "@/libs/markdown";
const rootElement = ref() as Ref<HTMLElement>;
const props = defineProps<{
content: string;
}>();
const html = computed(() => markdown.render(props.content ?? ""));
useEventListener(
rootElement,
"click",
(event: MouseEvent) => {
const target = event.target as HTMLElement;
if (!target.classList.contains("copy-button")) {
return;
}
const copyable =
target.parentElement!.querySelector<HTMLElement>(".copyable");
if (copyable !== null) {
navigator.clipboard.writeText(copyable.innerText);
}
},
{ capture: true }
);
</script>
<style lang="postcss" scoped>
.app-markdown {
font-size: 1.6rem;
:deep() {
p,
h1,
h2,
h3,
h4,
h5,
h6,
pre {
margin: 1rem 0;
}
pre {
width: 100%;
padding: 0.8rem 1.4rem;
border-radius: 1rem;
}
code:not(.hljs-code) {
background-color: var(--background-color-extra-blue);
padding: 0.3rem 0.6rem;
border-radius: 0.6rem;
}
ul,
ol {
margin: revert;
padding-left: 2rem;
list-style-type: revert;
}
table {
border-spacing: 0;
th,
td {
padding: 0.5rem 1rem;
}
thead th {
border-bottom: 2px solid var(--color-blue-scale-400);
background-color: var(--background-color-secondary);
}
tbody td {
border-bottom: 1px solid var(--color-blue-scale-400);
}
}
.copy-button {
font-size: 1.6rem;
font-weight: 400;
position: absolute;
z-index: 1;
right: 1rem;
cursor: pointer;
color: white;
border: none;
background-color: transparent;
&:hover {
color: var(--color-extra-blue-base);
}
&:active {
color: var(--color-extra-blue-d20);
}
}
}
}
</style>

View File

@@ -1,41 +0,0 @@
<template>
<pre class="code-highlight hljs"><code v-html="codeAsHtml"></code></pre>
</template>
<script lang="ts" setup>
import HLJS from "highlight.js";
import { computed } from "vue";
import "highlight.js/styles/github-dark.css";
const props = withDefaults(
defineProps<{
code?: any;
lang?: string;
}>(),
{ lang: "typescript" }
);
const codeAsText = computed(() => {
switch (typeof props.code) {
case "string":
return props.code;
case "function":
return String(props.code);
default:
return JSON.stringify(props.code, undefined, 2);
}
});
const codeAsHtml = computed(
() => HLJS.highlight(codeAsText.value, { language: props.lang }).value
);
</script>
<style lang="postcss" scoped>
.code-highlight {
display: inline-block;
padding: 0.3rem 0.6rem;
text-align: left;
border-radius: 0.6rem;
}
</style>

View File

@@ -7,7 +7,7 @@
@remove="emit('removeSort', property)"
>
<span class="property">
<UiIcon :icon="isAscending ? faCaretUp : faCaretDown" />
<FontAwesomeIcon :icon="isAscending ? faCaretUp : faCaretDown" />
{{ property }}
</span>
</UiFilter>
@@ -17,7 +17,7 @@
</UiActionButton>
</UiFilterGroup>
<UiModal v-if="isOpen" :icon="faSort" @submit.prevent="handleSubmit">
<UiModal v-if="isOpen" @submit.prevent="handleSubmit" :icon="faSort">
<div class="form-widgets">
<FormWidget :label="$t('sort-by')">
<select v-model="newSortProperty">
@@ -48,14 +48,7 @@
</template>
<script lang="ts" setup>
import FormWidget from "@/components/FormWidget.vue";
import UiActionButton from "@/components/ui/UiActionButton.vue";
import UiButton from "@/components/ui/UiButton.vue";
import UiFilter from "@/components/ui/UiFilter.vue";
import UiFilterGroup from "@/components/ui/UiFilterGroup.vue";
import UiIcon from "@/components/ui/UiIcon.vue";
import UiModal from "@/components/ui/UiModal.vue";
import useModal from "@/composables/modal.composable";
import { ref } from "vue";
import type { ActiveSorts, Sorts } from "@/types/sort";
import {
faCaretDown,
@@ -63,7 +56,13 @@ import {
faPlus,
faSort,
} from "@fortawesome/free-solid-svg-icons";
import { ref } from "vue";
import FormWidget from "@/components/FormWidget.vue";
import UiButton from "@/components/ui/UiButton.vue";
import UiActionButton from "@/components/ui/UiActionButton.vue";
import UiFilter from "@/components/ui/UiFilter.vue";
import UiFilterGroup from "@/components/ui/UiFilterGroup.vue";
import UiModal from "@/components/ui/UiModal.vue";
import useModal from "@/composables/modal.composable";
defineProps<{
availableSorts: Sorts;

View File

@@ -2,7 +2,7 @@
<th>
<div class="content">
<span class="label">
<UiIcon :icon="icon" />
<FontAwesomeIcon v-if="icon" :icon="icon" />
<slot />
</span>
</div>
@@ -10,7 +10,6 @@
</template>
<script lang="ts" setup>
import UiIcon from "@/components/ui/UiIcon.vue";
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
defineProps<{

View File

@@ -8,7 +8,7 @@
<span class="widget">
<span v-if="before || $slots.before" class="before">
<slot name="before">
<UiIcon v-if="isIcon(before)" :icon="before" fixed-width />
<FontAwesomeIcon v-if="isIcon(before)" :icon="before" fixed-width />
<template v-else>{{ before }}</template>
</slot>
</span>
@@ -17,7 +17,7 @@
</span>
<span v-if="after || $slots.after" class="after">
<slot name="after">
<UiIcon v-if="isIcon(after)" :icon="after" fixed-width />
<FontAwesomeIcon v-if="isIcon(after)" :icon="after" fixed-width />
<template v-else>{{ after }}</template>
</slot>
</span>
@@ -26,7 +26,6 @@
</template>
<script lang="ts" setup>
import UiIcon from "@/components/ui/UiIcon.vue";
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
defineProps<{

View File

@@ -1,10 +1,9 @@
<template>
<UiIcon :class="className" :icon="icon" class="power-state-icon" />
<FontAwesomeIcon class="power-state-icon" :class="className" :icon="icon" />
</template>
<script lang="ts" setup>
import UiIcon from "@/components/ui/UiIcon.vue";
import type { PowerState } from "@/libs/xen-api";
import { computed } from "vue";
import {
faMoon,
faPause,
@@ -12,7 +11,7 @@ import {
faQuestion,
faStop,
} from "@fortawesome/free-solid-svg-icons";
import { computed } from "vue";
import type { PowerState } from "@/libs/xen-api";
const props = defineProps<{
state: PowerState;
@@ -30,7 +29,7 @@ const icon = computed(() => icons[props.state] ?? faQuestion);
const className = computed(() => `state-${props.state.toLocaleLowerCase()}`);
</script>
<style lang="postcss" scoped>
<style scoped lang="postcss">
.power-state-icon {
color: var(--color-extra-blue-d60);

View File

@@ -1,29 +0,0 @@
<template>
<RouterLink
v-slot="{ isActive, href }"
:to="disabled || isTabBarDisabled ? '' : to"
custom
>
<UiTab :active="isActive" :disabled="disabled" :href="href" tag="a">
<slot />
</UiTab>
</RouterLink>
</template>
<script lang="ts" setup>
import { type ComputedRef, computed, inject } from "vue";
import type { RouteLocationRaw } from "vue-router";
import UiTab from "@/components/ui/UiTab.vue";
const props = defineProps<{
to: RouteLocationRaw;
disabled?: boolean;
}>();
const isTabBarDisabled = inject<ComputedRef<boolean>>(
"isTabBarDisabled",
computed(() => false)
);
</script>
<style lang="postcss" scoped></style>

View File

@@ -1,29 +1,16 @@
<template>
<div class="ui-tab-bar">
<div class="tab-bar">
<slot />
</div>
</template>
<script lang="ts" setup>
import { computed, provide } from "vue";
const props = defineProps<{
disabled?: boolean;
}>();
provide(
"isTabBarDisabled",
computed(() => props.disabled)
);
</script>
<style lang="postcss" scoped>
.ui-tab-bar {
.tab-bar {
display: flex;
align-items: stretch;
height: 6.5rem;
border-bottom: 1px solid var(--color-blue-scale-400);
background-color: var(--background-color-primary);
border-bottom: 1px solid var(--color-blue-scale-400);
max-width: 100%;
overflow: auto;
}

View File

@@ -0,0 +1,54 @@
<template>
<span v-if="disabled" class="tab-bar-item disabled">
<slot />
</span>
<RouterLink v-else class="tab-bar-item" v-bind="$props">
<slot />
</RouterLink>
</template>
<script lang="ts" setup>
import type { RouterLinkProps } from "vue-router";
// https://vuejs.org/api/sfc-script-setup.html#type-only-props-emit-declarations
interface Props extends RouterLinkProps {
disabled?: boolean;
}
defineProps<Props>();
</script>
<style lang="postcss" scoped>
.tab-bar-item {
font-size: 1.8rem;
font-weight: 600;
display: flex;
align-items: center;
padding: 0 1.2em;
text-decoration: none;
text-transform: uppercase;
color: var(--color-blue-scale-100);
border-bottom: 2px solid transparent;
&:hover:not(.disabled) {
border-bottom-color: var(--color-extra-blue-base);
background-color: var(--background-color-secondary);
}
&:active:not(.disabled) {
color: var(--color-extra-blue-base);
border-bottom-color: var(--color-extra-blue-base);
background-color: var(--background-color-secondary);
}
&.router-link-active {
color: var(--color-extra-blue-base);
border-bottom-color: var(--color-extra-blue-base);
background-color: var(--background-color-primary);
}
&.disabled {
color: var(--color-blue-scale-400);
}
}
</style>

View File

@@ -1,6 +1,6 @@
<template>
<div class="title-bar">
<UiIcon :icon="icon" class="icon" />
<FontAwesomeIcon :icon="icon" class="icon" />
<div class="title">
<slot />
</div>
@@ -11,7 +11,6 @@
</template>
<script lang="ts" setup>
import UiIcon from "@/components/ui/UiIcon.vue";
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
defineProps<{

View File

@@ -20,8 +20,8 @@
</template>
<script lang="ts" setup>
import { computed, inject } from "vue";
import { percent } from "@/libs/utils";
import { computed, type ComputedRef, inject } from "vue";
const props = defineProps<{
total: number;
@@ -34,9 +34,7 @@ const freePercent = computed(() =>
percent(props.total - props.used, props.total)
);
const valueFormatter = inject("valueFormatter") as ComputedRef<
(value: number) => string
>;
const valueFormatter = inject("valueFormatter") as (value: number) => string;
</script>
<style lang="postcss" scoped>

View File

@@ -33,17 +33,8 @@ const props = defineProps<{
maxValue?: number;
}>();
const valueFormatter = computed(() => {
const formatter = props.valueFormatter;
return (value: OptionDataValue | OptionDataValue[]) => {
if (formatter) {
return formatter(value as number);
}
return value.toString();
};
});
const valueFormatter = (value: OptionDataValue | OptionDataValue[]) =>
props.valueFormatter?.(value as number) ?? `${value}`;
provide("valueFormatter", valueFormatter);
@@ -65,7 +56,7 @@ const option = computed<EChartsOption>(() => ({
data: props.data.map((series) => series.label),
},
tooltip: {
valueFormatter: valueFormatter.value,
valueFormatter,
},
xAxis: {
type: "time",
@@ -79,9 +70,9 @@ const option = computed<EChartsOption>(() => ({
yAxis: {
type: "value",
axisLabel: {
formatter: valueFormatter.value,
formatter: valueFormatter,
},
max: props.maxValue ?? Y_AXIS_MAX_VALUE,
max: () => props.maxValue ?? Y_AXIS_MAX_VALUE,
},
series: props.data.map((series, index) => ({
type: "line",

View File

@@ -6,7 +6,6 @@
>
<input
v-model="value"
:class="{ indeterminate: type === 'checkbox' && value === undefined }"
:disabled="isLabelDisabled || disabled"
:type="type === 'radio' ? 'radio' : 'checkbox'"
class="input"
@@ -33,7 +32,7 @@ import {
inject,
ref,
} from "vue";
import { faCheck, faCircle, faMinus } from "@fortawesome/free-solid-svg-icons";
import { faCheck, faCircle } from "@fortawesome/free-solid-svg-icons";
import { useVModel } from "@vueuse/core";
import UiIcon from "@/components/ui/UiIcon.vue";
@@ -54,17 +53,7 @@ const value = useVModel(props, "modelValue", emit);
const type = inject<"checkbox" | "radio" | "toggle">("inputType", "checkbox");
const hasLabel = inject("hasLabel", false);
const isLabelDisabled = inject("isLabelDisabled", ref(false));
const icon = computed(() => {
if (type !== "checkbox") {
return faCircle;
}
if (value.value === undefined) {
return faMinus;
}
return faCheck;
});
const icon = computed(() => (type === "checkbox" ? faCheck : faCircle));
</script>
<style lang="postcss" scoped>
@@ -85,11 +74,6 @@ const icon = computed(() => {
.form-checkbox {
--checkbox-border-radius: 0.25em;
--checkbox-icon-size: 1em;
.input.indeterminate + .fake-checkbox > .icon {
opacity: 1;
color: var(--color-blue-scale-300);
}
}
.form-checkbox,
@@ -125,8 +109,8 @@ const icon = computed(() => {
}
.icon {
transition: transform 0.125s ease-in-out;
transform: translateX(-0.7em);
transition: transform 0.125s ease-in-out;
}
.input:checked + .fake-checkbox > .icon {
@@ -156,12 +140,12 @@ const icon = computed(() => {
align-items: center;
justify-content: center;
height: 1.25em;
transition: background-color 0.125s ease-in-out,
border-color 0.125s ease-in-out;
border: var(--checkbox-border-width) solid var(--border-color);
border-radius: var(--checkbox-border-radius);
background-color: var(--background-color);
box-shadow: var(--shadow-100);
transition: background-color 0.125s ease-in-out,
border-color 0.125s ease-in-out;
--border-color: var(--color-blue-scale-400);
}

View File

@@ -1,6 +1,15 @@
<template>
<span :class="wrapperClass" v-bind="wrapperAttrs">
<template v-if="inputType === 'select'">
<input
v-if="!isSelect"
v-model="value"
:class="inputClass"
:disabled="disabled || isLabelDisabled"
class="input"
ref="inputElement"
v-bind="$attrs"
/>
<template v-else>
<select
v-model="value"
:class="inputClass"
@@ -15,24 +24,6 @@
<UiIcon :fixed-width="false" :icon="faAngleDown" />
</span>
</template>
<textarea
v-else-if="inputType === 'textarea'"
ref="textarea"
v-model="value"
:class="inputClass"
:disabled="disabled || isLabelDisabled"
class="textarea"
v-bind="$attrs"
/>
<input
v-else
v-model="value"
:class="inputClass"
:disabled="disabled || isLabelDisabled"
class="input"
ref="inputElement"
v-bind="$attrs"
/>
<span v-if="before !== undefined" class="before">
<template v-if="typeof before === 'string'">{{ before }}</template>
<UiIcon v-else :icon="before" class="before" />
@@ -52,19 +43,18 @@ export default {
</script>
<script lang="ts" setup>
import { isEmpty } from "lodash-es";
import {
type HTMLAttributes,
type InputHTMLAttributes,
computed,
inject,
nextTick,
ref,
watch,
} from "vue";
import type { Color } from "@/types";
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
import { faAngleDown } from "@fortawesome/free-solid-svg-icons";
import { useTextareaAutosize, useVModel } from "@vueuse/core";
import { useVModel } from "@vueuse/core";
import UiIcon from "@/components/ui/UiIcon.vue";
// Temporary workaround for https://github.com/vuejs/core/issues/4294
@@ -89,10 +79,8 @@ const emit = defineEmits<{
}>();
const value = useVModel(props, "modelValue", emit);
const isEmpty = computed(
() => props.modelValue == null || String(props.modelValue).trim() === ""
);
const inputType = inject("inputType", "input");
const empty = computed(() => isEmpty(props.modelValue));
const isSelect = inject("isSelect", false);
const isLabelDisabled = inject("isLabelDisabled", ref(false));
const color = inject(
"color",
@@ -100,10 +88,10 @@ const color = inject(
);
const wrapperClass = computed(() => [
`form-${inputType}`,
isSelect ? "form-select" : "form-input",
{
disabled: props.disabled || isLabelDisabled.value,
empty: isEmpty.value,
empty: empty.value,
},
]);
@@ -116,12 +104,6 @@ const inputClass = computed(() => [
},
]);
const { textarea, triggerResize } = useTextareaAutosize();
watch(value, () => nextTick(() => triggerResize()), {
immediate: true,
});
const focus = () => inputElement.value.focus();
defineExpose({
@@ -131,13 +113,12 @@ defineExpose({
<style lang="postcss" scoped>
.form-input,
.form-select,
.form-textarea {
.form-select {
display: inline-grid;
align-items: stretch;
--before-width: v-bind('beforeWidth || "1.75em"');
--after-width: v-bind('afterWidth || "1.625em"');
--before-width: v-bind('beforeWidth ?? "1.75em"');
--after-width: v-bind('afterWidth ?? "1.625em"');
--caret-width: 1.5em;
--text-color: var(--color-blue-scale-100);
@@ -151,8 +132,7 @@ defineExpose({
}
}
.form-input,
.form-textarea {
.form-input {
grid-template-columns: var(--before-width) auto var(--after-width);
}
@@ -165,10 +145,8 @@ defineExpose({
}
.input,
.textarea,
.select {
font-size: 1em;
width: 100%;
height: 2em;
margin: 0;
color: var(--text-color);
@@ -257,19 +235,8 @@ defineExpose({
}
}
.textarea {
height: auto;
min-height: 2em;
}
.input {
padding: 0;
}
.input,
.textarea {
padding-right: 0.625em;
padding-left: 0.625em;
padding: 0 0.625em 0 0.625em;
&.has-before {
padding-left: calc(var(--before-width) + 0.25em);

View File

@@ -1,93 +0,0 @@
<template>
<UiModal
@submit.prevent="saveJson"
:color="isJsonValid ? 'success' : 'error'"
v-if="isCodeModalOpen"
:icon="faCode"
@close="closeCodeModal"
>
<FormTextarea class="modal-textarea" v-model="editedJson" />
<template #buttons>
<UiButton transparent @click="formatJson">{{ $t("reformat") }}</UiButton>
<UiButton outlined @click="closeCodeModal">{{ $t("cancel") }}</UiButton>
<UiButton :disabled="!isJsonValid" type="submit"
>{{ $t("save") }}
</UiButton>
</template>
</UiModal>
<FormInput
@click="openCodeModal"
:model-value="jsonValue"
:before="faCode"
readonly
/>
</template>
<script lang="ts" setup>
import FormInput from "@/components/form/FormInput.vue";
import FormTextarea from "@/components/form/FormTextarea.vue";
import UiButton from "@/components/ui/UiButton.vue";
import UiModal from "@/components/ui/UiModal.vue";
import useModal from "@/composables/modal.composable";
import { faCode } from "@fortawesome/free-solid-svg-icons";
import { useVModel, whenever } from "@vueuse/core";
import { computed, ref } from "vue";
const props = defineProps<{
modelValue: any;
}>();
const emit = defineEmits<{
(event: "update:modelValue", value: any): void;
}>();
const model = useVModel(props, "modelValue", emit);
const {
open: openCodeModal,
close: closeCodeModal,
isOpen: isCodeModalOpen,
} = useModal();
const jsonValue = computed(() => JSON.stringify(model.value, undefined, 2));
const isJsonValid = computed(() => {
try {
JSON.parse(editedJson.value);
return true;
} catch {
return false;
}
});
const formatJson = () => {
if (!isJsonValid.value) {
return;
}
editedJson.value = JSON.stringify(JSON.parse(editedJson.value), undefined, 2);
};
const saveJson = () => {
if (!isJsonValid.value) {
return;
}
formatJson();
model.value = JSON.parse(editedJson.value);
closeCodeModal();
};
whenever(isCodeModalOpen, () => (editedJson.value = jsonValue.value));
const editedJson = ref();
</script>
<style lang="postcss" scoped>
:deep(.modal-textarea) {
min-width: 50rem;
min-height: 20rem;
}
</style>

View File

@@ -8,7 +8,7 @@
import { provide } from "vue";
import FormInput from "@/components/form/FormInput.vue";
provide("inputType", "select");
provide("isSelect", true);
</script>
<style lang="postcss" scoped></style>

View File

@@ -1,12 +0,0 @@
<template>
<FormInput />
</template>
<script lang="ts" setup>
import { provide } from "vue";
import FormInput from "@/components/form/FormInput.vue";
provide("inputType", "textarea");
</script>
<style lang="postcss" scoped></style>

View File

@@ -1,13 +1,12 @@
<template>
<div class="infra-action">
<slot>
<UiIcon :icon="icon" fixed-width />
<FontAwesomeIcon :icon="icon" fixed-width />
</slot>
</div>
</template>
<script lang="ts" setup>
import UiIcon from "@/components/ui/UiIcon.vue";
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
defineProps<{

View File

@@ -8,7 +8,7 @@
v-bind="$attrs"
>
<a :href="href" class="link" @click="navigate">
<UiIcon :icon="icon" class="icon" />
<FontAwesomeIcon :icon="icon" class="icon" />
<div class="text">
<slot />
</div>
@@ -21,9 +21,8 @@
</template>
<script lang="ts" setup>
import UiIcon from "@/components/ui/UiIcon.vue";
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
import type { RouteLocationRaw } from "vue-router";
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
defineProps<{
icon: IconDefinition;

View File

@@ -2,7 +2,7 @@
<li class="infra-loading-item">
<div class="infra-item-label-placeholder">
<div class="link-placeholder">
<UiIcon :icon="icon" class="icon" />
<FontAwesomeIcon :icon="icon" class="icon" />
<div class="loader">&nbsp;</div>
</div>
</div>
@@ -10,7 +10,6 @@
</template>
<script lang="ts" setup>
import UiIcon from "@/components/ui/UiIcon.vue";
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
defineProps<{

View File

@@ -1,40 +1,67 @@
<template>
<UiTabBar :disabled="!isReady">
<RouterTab :to="{ name: 'pool.dashboard', params: { uuid: poolUuid } }">
<TabBar>
<TabBarItem
:disabled="!isReady"
:to="{ name: 'pool.dashboard', params: { uuid: poolUuid } }"
>
{{ $t("dashboard") }}
</RouterTab>
<RouterTab :to="{ name: 'pool.alarms', params: { uuid: poolUuid } }">
</TabBarItem>
<TabBarItem
:disabled="!isReady"
:to="{ name: 'pool.alarms', params: { uuid: poolUuid } }"
>
{{ $t("alarms") }}
</RouterTab>
<RouterTab :to="{ name: 'pool.stats', params: { uuid: poolUuid } }">
</TabBarItem>
<TabBarItem
:disabled="!isReady"
:to="{ name: 'pool.stats', params: { uuid: poolUuid } }"
>
{{ $t("stats") }}
</RouterTab>
<RouterTab :to="{ name: 'pool.system', params: { uuid: poolUuid } }">
</TabBarItem>
<TabBarItem
:disabled="!isReady"
:to="{ name: 'pool.system', params: { uuid: poolUuid } }"
>
{{ $t("system") }}
</RouterTab>
<RouterTab :to="{ name: 'pool.network', params: { uuid: poolUuid } }">
</TabBarItem>
<TabBarItem
:disabled="!isReady"
:to="{ name: 'pool.network', params: { uuid: poolUuid } }"
>
{{ $t("network") }}
</RouterTab>
<RouterTab :to="{ name: 'pool.storage', params: { uuid: poolUuid } }">
</TabBarItem>
<TabBarItem
:disabled="!isReady"
:to="{ name: 'pool.storage', params: { uuid: poolUuid } }"
>
{{ $t("storage") }}
</RouterTab>
<RouterTab :to="{ name: 'pool.tasks', params: { uuid: poolUuid } }">
</TabBarItem>
<TabBarItem
:disabled="!isReady"
:to="{ name: 'pool.tasks', params: { uuid: poolUuid } }"
>
{{ $t("tasks") }}
</RouterTab>
<RouterTab :to="{ name: 'pool.hosts', params: { uuid: poolUuid } }">
</TabBarItem>
<TabBarItem
:disabled="!isReady"
:to="{ name: 'pool.hosts', params: { uuid: poolUuid } }"
>
{{ $t("hosts") }}
</RouterTab>
<RouterTab :to="{ name: 'pool.vms', params: { uuid: poolUuid } }">
</TabBarItem>
<TabBarItem
:disabled="!isReady"
:to="{ name: 'pool.vms', params: { uuid: poolUuid } }"
>
{{ $t("vms") }}
</RouterTab>
</UiTabBar>
</TabBarItem>
</TabBar>
</template>
<script lang="ts" setup>
import { storeToRefs } from "pinia";
import { computed } from "vue";
import RouterTab from "@/components/RouterTab.vue";
import UiTabBar from "@/components/ui/UiTabBar.vue";
import TabBar from "@/components/TabBar.vue";
import TabBarItem from "@/components/TabBarItem.vue";
import { usePoolStore } from "@/stores/pool.store";
const poolStore = usePoolStore();

View File

@@ -1,67 +0,0 @@
<template>
<tr class="finished-task-row" :class="{ finished: !isPending }">
<td>{{ task.name_label }}</td>
<td>
<RouterLink
:to="{
name: 'host.dashboard',
params: { uuid: host.uuid },
}"
>
{{ host.name_label }}
</RouterLink>
</td>
<td>
<UiProgressBar v-if="isPending" :max-value="1" :value="task.progress" />
</td>
<td>
<RelativeTime v-if="isPending" :date="createdAt" />
<template v-else>{{ $d(createdAt, "datetime_short") }}</template>
</td>
<td>
<template v-if="finishedAt !== undefined">
{{ $d(finishedAt, "datetime_short") }}
</template>
<RelativeTime
v-else-if="isPending && estimatedEndAt !== Infinity"
:date="estimatedEndAt"
/>
</td>
</tr>
</template>
<script lang="ts" setup>
import RelativeTime from "@/components/RelativeTime.vue";
import UiProgressBar from "@/components/ui/UiProgressBar.vue";
import { parseDateTime } from "@/libs/utils";
import type { XenApiTask } from "@/libs/xen-api";
import { useHostStore } from "@/stores/host.store";
import { computed } from "vue";
const props = defineProps<{
isPending?: boolean;
task: XenApiTask;
}>();
const { getRecord: getHost } = useHostStore();
const createdAt = computed(() => parseDateTime(props.task.created));
const host = computed(() => getHost(props.task.resident_on));
const estimatedEndAt = computed(
() =>
createdAt.value +
(new Date().getTime() - createdAt.value) / props.task.progress
);
const finishedAt = computed(() =>
props.isPending ? undefined : parseDateTime(props.task.finished)
);
</script>
<style lang="postcss" scoped>
.finished {
opacity: 0.5;
}
</style>

View File

@@ -1,32 +0,0 @@
<template>
<UiTable class="tasks-table">
<template #header>
<th>{{ $t("name") }}</th>
<th>{{ $t("object") }}</th>
<th>{{ $t("task.progress") }}</th>
<th>{{ $t("task.started") }}</th>
<th>{{ $t("task.estimated-end") }}</th>
</template>
<TaskRow
v-for="task in pendingTasks"
:key="task.uuid"
:task="task"
is-pending
/>
<TaskRow v-for="task in finishedTasks" :key="task.uuid" :task="task" />
</UiTable>
</template>
<script lang="ts" setup>
import TaskRow from "@/components/tasks/TaskRow.vue";
import UiTable from "@/components/ui/UiTable.vue";
import type { XenApiTask } from "@/libs/xen-api";
defineProps<{
pendingTasks: XenApiTask[];
finishedTasks: XenApiTask[];
}>();
</script>
<style lang="postcss" scoped></style>

View File

@@ -5,7 +5,7 @@
:type="type || 'button'"
class="ui-button"
>
<UiSpinner v-if="isBusy" />
<span v-if="isBusy" class="loader" />
<template v-else>
<UiIcon :icon="icon" class="icon" />
<slot />
@@ -14,7 +14,6 @@
</template>
<script lang="ts" setup>
import UiSpinner from "@/components/ui/UiSpinner.vue";
import { computed, inject, unref } from "vue";
import type { Color } from "@/types";
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";

View File

@@ -1,51 +0,0 @@
<template>
<span :class="color" class="ui-counter">
<span :class="{ overflow: value > 99 }">
{{ value }}
</span>
</span>
</template>
<script lang="ts" setup>
import type { Color } from "@/types";
defineProps<{
value: number;
color?: Color;
}>();
</script>
<style lang="postcss" scoped>
.ui-counter {
font-weight: 700;
display: inline-flex;
align-items: center;
justify-content: center;
width: var(--size);
height: var(--size);
color: var(--color-blue-scale-500);
border-radius: calc(var(--size) / 2);
background-color: var(--color-blue-scale-300);
--size: 1.75em;
.overflow {
font-size: 0.8em;
}
&.info {
background-color: var(--color-extra-blue-base);
}
&.success {
background-color: var(--color-green-infra-base);
}
&.warning {
background-color: var(--color-orange-world-base);
}
&.error {
background-color: var(--color-red-vates-base);
}
}
</style>

View File

@@ -4,13 +4,12 @@
<slot />
</span>
<span class="remove" @click.stop="emit('remove')">
<UiIcon :icon="faRemove" />
<FontAwesomeIcon :icon="faRemove" class="icon" />
</span>
</span>
</template>
<script lang="ts" setup>
import UiIcon from "@/components/ui/UiIcon.vue";
import { faRemove } from "@fortawesome/free-solid-svg-icons";
const emit = defineEmits<{
@@ -40,7 +39,6 @@ const emit = defineEmits<{
display: flex;
align-items: center;
justify-content: center;
&:hover {
opacity: 0.7;
}
@@ -56,7 +54,6 @@ const emit = defineEmits<{
width: 2.8rem;
margin: 0.2rem;
background-color: var(--color-extra-blue-l40);
&:hover {
background-color: var(--color-red-vates-l20);
}

View File

@@ -1,19 +1,19 @@
<template>
<UiSpinner v-if="busy" class="ui-icon" />
<FontAwesomeIcon
v-else-if="icon !== undefined"
v-if="icon !== undefined"
:icon="icon"
:spin="busy"
class="ui-icon"
:fixed-width="fixedWidth"
/>
</template>
<script lang="ts" setup>
import UiSpinner from "@/components/ui/UiSpinner.vue";
import { computed } from "vue";
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { faSpinner } from "@fortawesome/free-solid-svg-icons";
withDefaults(
const props = withDefaults(
defineProps<{
busy?: boolean;
icon?: IconDefinition;
@@ -21,6 +21,8 @@ withDefaults(
}>(),
{ fixedWidth: true }
);
const icon = computed(() => (props.busy ? faSpinner : props.icon));
</script>
<style lang="postcss" scoped></style>

View File

@@ -8,11 +8,11 @@
>
<div class="container">
<span v-if="onClose" class="close-icon" @click="emit('close')">
<UiIcon :icon="faXmark" />
<FontAwesomeIcon :icon="faXmark" />
</span>
<div v-if="icon || $slots.icon" class="modal-icon">
<slot name="icon">
<UiIcon :icon="icon" />
<FontAwesomeIcon :icon="icon" />
</slot>
</div>
<UiTitle v-if="$slots.title" type="h4">
@@ -34,7 +34,6 @@
<script lang="ts" setup>
import UiButtonGroup from "@/components/ui/UiButtonGroup.vue";
import UiIcon from "@/components/ui/UiIcon.vue";
import UiTitle from "@/components/ui/UiTitle.vue";
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
import { faXmark } from "@fortawesome/free-solid-svg-icons";

View File

@@ -1,68 +0,0 @@
<template>
<component
:is="tag"
:class="{ active, disabled: disabled || isTabBarDisabled }"
class="ui-tab"
>
<slot />
</component>
</template>
<script lang="ts" setup>
import { type ComputedRef, computed, inject } from "vue";
withDefaults(
defineProps<{
disabled?: boolean;
active?: boolean;
tag?: string;
}>(),
{ tag: "span" }
);
const isTabBarDisabled = inject<ComputedRef<boolean>>(
"isTabBarDisabled",
computed(() => false)
);
</script>
<style lang="postcss" scoped>
.ui-tab {
font-size: 1.8rem;
font-weight: 600;
display: flex;
align-items: center;
padding: 0 1.2em;
text-decoration: none;
text-transform: uppercase;
color: var(--color-blue-scale-100);
border-bottom: 2px solid transparent;
&.disabled {
pointer-events: none;
color: var(--color-blue-scale-400);
}
&:not(.disabled) {
cursor: pointer;
&:hover {
cursor: pointer;
border-bottom-color: var(--color-extra-blue-base);
background-color: var(--background-color-secondary);
}
&:active {
color: var(--color-extra-blue-base);
border-bottom-color: var(--color-extra-blue-base);
background-color: var(--background-color-secondary);
}
&.active {
color: var(--color-extra-blue-base);
border-bottom-color: var(--color-extra-blue-base);
background-color: var(--background-color-primary);
}
}
}
</style>

View File

@@ -18,8 +18,8 @@
border-spacing: 0;
}
:deep(th),
:deep(td) {
:slotted(th),
:slotted(td) {
padding: 1rem;
border-top: 1px solid lightgrey;
border-right: 1px solid lightgrey;
@@ -30,14 +30,14 @@
}
}
:deep(.header-row th) {
:slotted(.header-row th) {
color: var(--color-extra-blue-base);
font-size: 1.4rem;
font-weight: 400;
text-transform: uppercase;
}
:deep(.body td) {
:slotted(.body td) {
font-weight: 400;
font-size: 1.6rem;
line-height: 2.4rem;

View File

@@ -1,18 +1,13 @@
import type { MaybeRef } from "@vueuse/core";
import { differenceBy } from "lodash-es";
import { type Ref, ref, unref, watch } from "vue";
export default function useArrayRemovedItemsHistory<T>(
list: Ref<T[]>,
iteratee: (item: T) => unknown = (item) => item,
options: {
limit?: MaybeRef<number>;
onRemove?: (items: T[]) => any[];
} = {}
limit = Infinity,
iteratee: (item: T) => unknown = (item) => item
) {
const currentList: Ref<T[]> = ref([]);
const history: Ref<T[]> = ref([]);
const { limit = Infinity, onRemove = (items) => items } = options;
watch(
list,
@@ -24,10 +19,10 @@ export default function useArrayRemovedItemsHistory<T>(
watch(currentList, (nextList, previousList) => {
const removedItems = differenceBy(previousList, nextList, iteratee);
history.value.push(...onRemove(removedItems));
history.value.push(...removedItems);
const currentLimit = unref(limit);
if (history.value.length > currentLimit) {
history.value = history.value.slice(-currentLimit);
history.value.slice(-currentLimit);
}
});

View File

@@ -1,64 +0,0 @@
import HLJS from "highlight.js";
import MarkdownIt from "markdown-it";
export const markdown = new MarkdownIt();
markdown.set({
highlight: (str: string, lang: string) => {
const code = highlight(str, lang);
return `<pre class="hljs"><button class="copy-button" type="button">Copy</button><code class="hljs-code">${code}</code></pre>`;
},
});
function highlight(str: string, lang: string) {
switch (lang) {
case "vue-template": {
const indented = str
.trim()
.split("\n")
.map((s) => ` ${s}`)
.join("\n");
return wrap(indented, "template");
}
case "vue-script":
return wrap(str.trim(), "script");
case "vue-style":
return wrap(str.trim(), "style");
default: {
if (HLJS.getLanguage(lang) !== undefined) {
return copyable(HLJS.highlight(str, { language: lang }).value);
}
return copyable(markdown.utils.escapeHtml(str));
}
}
}
function wrap(str: string, tag: "template" | "script" | "style") {
let openTag;
let code;
switch (tag) {
case "template":
openTag = "<template>";
code = HLJS.highlight(str, { language: "xml" }).value;
break;
case "script":
openTag = '<script lang="ts" setup>';
code = HLJS.highlight(str, { language: "typescript" }).value;
break;
case "style":
openTag = '<style lang="postcss" scoped>';
code = HLJS.highlight(str, { language: "scss" }).value;
break;
}
const openTagHtml = HLJS.highlight(openTag, { language: "xml" }).value;
const closeTagHtml = HLJS.highlight(`</${tag}>`, { language: "xml" }).value;
return `${openTagHtml}${copyable(code)}${closeTagHtml}`;
}
function copyable(code: string) {
return `<div class="copyable">${code}</div>`;
}

View File

@@ -56,7 +56,6 @@ export function getFilterIcon(filter: Filter | undefined) {
}
export function parseDateTime(dateTime: string) {
dateTime = dateTime.replace(/(-|\.\d{3})/g, ""); // Allow toISOString() date-time format
const date = utcParse("%Y%m%dT%H:%M:%SZ")(dateTime);
if (date === null) {
throw new RangeError(

View File

@@ -115,15 +115,6 @@ export type XenApiVmMetrics = XenApiRecord;
export type XenApiVmGuestMetrics = XenApiRecord;
export interface XenApiTask extends XenApiRecord {
name_label: string;
resident_on: string;
created: string;
finished: string;
status: string;
progress: number;
}
type WatchCallbackResult = {
id: string;
class: ObjectType;

View File

@@ -36,14 +36,12 @@
"log-out": "Log out",
"login": "Login",
"migrate": "Migrate",
"name": "Name",
"network": "Network",
"network-download": "Download",
"network-throughput": "Network throughput",
"network-upload": "Upload",
"news": "News",
"news-name": "{name} news",
"object": "Object",
"object-not-found": "Object {id} can't be found…",
"or": "Or",
"page-not-found": "This page is not to be found…",
@@ -55,7 +53,6 @@
"property": "Property",
"ram-usage": "RAM usage",
"reboot": "Reboot",
"reformat": "Reformat",
"relative-time": {
"day": "1 day | {n} days",
"future": "In {str}",
@@ -68,14 +65,13 @@
"year": "1 year | {n} years"
},
"resume": "Resume",
"save": "Save",
"send-us-feedback": "Send us feedback",
"settings": "Settings",
"shutdown": "Shutdown",
"snapshot": "Snapshot",
"sort-by": "Sort by",
"stacked-cpu-usage": "Stacked CPU usage",
"stacked-ram-usage": "Stacked RAM usage",
"stacked-ram-usage": "Stacked memory usage",
"start": "Start",
"start-on-host": "Start on specific host",
"stats": "Stats",
@@ -85,12 +81,6 @@
"suspend": "Suspend",
"switch-theme": "Switch theme",
"system": "System",
"task": {
"estimated-end": "Estimated end",
"page-title": "Tasks | (1) Tasks | ({n}) Tasks",
"progress": "Progress",
"started": "Started"
},
"tasks": "Tasks",
"theme-auto": "Auto",
"theme-dark": "Dark",

View File

@@ -36,14 +36,12 @@
"log-out": "Se déconnecter",
"login": "Connexion",
"migrate": "Migrer",
"name": "Nom",
"network": "Réseau",
"network-download": "Descendant",
"network-throughput": "Débit du réseau",
"network-upload": "Montant",
"news": "Actualités",
"news-name": "Actualités {name}",
"object": "Objet",
"object-not-found": "L'objet {id} est introuvable…",
"or": "Ou",
"page-not-found": "Cette page est introuvable…",
@@ -55,7 +53,6 @@
"property": "Propriété",
"ram-usage": "Utilisation de la RAM",
"reboot": "Redémarrer",
"reformat": "Reformater",
"relative-time": {
"day": "1 jour | {n} jours",
"future": "Dans {str}",
@@ -68,14 +65,13 @@
"year": "1 an | {n} ans"
},
"resume": "Reprendre",
"save": "Enregistrer",
"send-us-feedback": "Envoyez-nous vos commentaires",
"settings": "Paramètres",
"shutdown": "Arrêter",
"snapshot": "Instantané",
"sort-by": "Trier par",
"stacked-cpu-usage": "Utilisation CPU empilée",
"stacked-ram-usage": "Utilisation RAM empilée",
"stacked-ram-usage": "Utilisation de la mémoire empilée",
"start": "Démarrer",
"start-on-host": "Démarrer sur un hôte spécifique",
"stats": "Stats",
@@ -85,12 +81,6 @@
"suspend": "Suspendre",
"switch-theme": "Changer de thème",
"system": "Système",
"task": {
"estimated-end": "Fin estimée",
"page-title": "Tâches | (1) Tâches | ({n}) Tâches",
"progress": "Progression",
"started": "Démarré"
},
"tasks": "Tâches",
"theme-auto": "Auto",
"theme-dark": "Sombre",

View File

@@ -3,11 +3,13 @@ import { createApp } from "vue";
import App from "@/App.vue";
import i18n from "@/i18n";
import router from "@/router";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
const app = createApp(App);
app.use(i18n);
app.use(createPinia());
app.use(router);
app.component("FontAwesomeIcon", FontAwesomeIcon);
app.mount("#root");

View File

@@ -1,7 +0,0 @@
import { defineStore } from "pinia";
import type { XenApiTask } from "@/libs/xen-api";
import { createRecordContext } from "@/stores/index";
export const useTaskStore = defineStore("task", () =>
createRecordContext<XenApiTask>("task")
);

View File

@@ -10,7 +10,6 @@ import { useHostStore } from "@/stores/host.store";
import { usePoolStore } from "@/stores/pool.store";
import { useRecordsStore } from "@/stores/records.store";
import { useSrStore } from "@/stores/storage.store";
import { useTaskStore } from "@/stores/task.store";
import { useVmGuestMetricsStore } from "@/stores/vm-guest-metrics.store";
import { useVmMetricsStore } from "@/stores/vm-metrics.store";
import { useVmStore } from "@/stores/vm.store";
@@ -87,9 +86,6 @@ export const useXenApiStore = defineStore("xen-api", () => {
srStore.init(),
]);
const taskStore = useTaskStore();
taskStore.init();
const consoleStore = useConsoleStore();
consoleStore.init();
}

View File

@@ -126,7 +126,6 @@ onMounted(() => {
.item {
margin: 0;
padding: 0.5rem;
overflow: hidden;
}
@media (min-width: 768px) {

View File

@@ -1,82 +1 @@
<template>
<UiCard>
<UiTitle class="title-with-counter" type="h4">
{{ $t("tasks") }}
<UiCounter :value="pendingTasks.length" color="info" />
</UiTitle>
<TasksTable :pending-tasks="pendingTasks" :finished-tasks="finishedTasks" />
<UiSpinner class="loader" v-if="!isReady" />
</UiCard>
</template>
<script lang="ts" setup>
import TasksTable from "@/components/tasks/TasksTable.vue";
import UiCard from "@/components/ui/UiCard.vue";
import UiCounter from "@/components/ui/UiCounter.vue";
import UiSpinner from "@/components/ui/UiSpinner.vue";
import UiTitle from "@/components/ui/UiTitle.vue";
import useArrayRemovedItemsHistory from "@/composables/array-removed-items-history.composable";
import useCollectionFilter from "@/composables/collection-filter.composable";
import useCollectionSorter from "@/composables/collection-sorter.composable";
import useFilteredCollection from "@/composables/filtered-collection.composable";
import useSortedCollection from "@/composables/sorted-collection.composable";
import type { XenApiTask } from "@/libs/xen-api";
import { useTaskStore } from "@/stores/task.store";
import { useTitle } from "@vueuse/core";
import { storeToRefs } from "pinia";
import { computed } from "vue";
import { useI18n } from "vue-i18n";
const { allRecords, isReady } = storeToRefs(useTaskStore());
const { t } = useI18n();
const { compareFn } = useCollectionSorter<XenApiTask>({
initialSorts: ["-created"],
});
const allTasks = useSortedCollection(allRecords, compareFn);
const { predicate } = useCollectionFilter({
initialFilters: ["!name_label:|(SR.scan host.call_plugin)", "status:pending"],
});
const pendingTasks = useFilteredCollection<XenApiTask>(allTasks, predicate);
const finishedTasks = useArrayRemovedItemsHistory(
allTasks,
(task) => task.uuid,
{
limit: 50,
onRemove: (tasks) =>
tasks.map((task) => ({
...task,
finished: new Date().toISOString(),
})),
}
);
useTitle(
computed(() => t("task.page-title", { n: pendingTasks.value.length }))
);
</script>
<style lang="postcss" scoped>
.title-with-counter {
display: flex;
align-items: center;
margin-bottom: 1rem;
gap: 0.5rem;
.ui-counter {
font-size: 1.4rem;
}
}
.loader {
color: var(--color-extra-blue-base);
display: block;
font-size: 4rem;
margin: 2rem auto 0;
}
</style>
<template>Tasks (coming soon)</template>

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/log):
```sh
npm install --save @xen-orchestra/log
```
> npm install --save @xen-orchestra/log
```
## Usage

View File

@@ -8,8 +8,8 @@
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/mixin):
```sh
npm install --save @xen-orchestra/mixin
```
> npm install --save @xen-orchestra/mixin
```
## Usage

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/mixins):
```sh
npm install --save @xen-orchestra/mixins
```
> npm install --save @xen-orchestra/mixins
```
## Contributions

View File

@@ -25,7 +25,7 @@ For safety reasons, the proxy requires authentication to be used.
Use the authentication token:
```console
```
$ cat ~/.config/xo-proxy/config.z-auto.json
{"authenticationToken":"J0BgKritQgPxoyZrBJ5ViafQfLk06YoyFwC3fmfO5wU"}
```
@@ -48,7 +48,7 @@ https://user:password@xo.company.lan
Or create a dedicated token with `xo-cli`:
```console
```
$ xo-cli --createToken xoa.company.lan admin@admin.net
Password: ********
Successfully logged with admin@admin.net

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/openflow):
```sh
npm install --save @xen-orchestra/openflow
```
> npm install --save @xen-orchestra/openflow
```
## Usage

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/proxy-cli):
```sh
npm install --global @xen-orchestra/proxy-cli
```
> npm install --global @xen-orchestra/proxy-cli
```
## Usage

View File

@@ -67,44 +67,38 @@ ${pkg.name} v${pkg.version}`
// sequence path of the current call
const callPath = []
let url
let { token } = opts
if (opts.url !== '') {
url = new URL(opts.url)
const { username } = url
if (username !== '') {
token = username
url.username = ''
}
} else {
url = new URL('https://localhost/')
if (opts.host !== '') {
url.host = opts.host
} else {
const { hostname = 'localhost', port } = config?.http?.listen?.https ?? {}
url.hostname = hostname
url.port = port
}
}
url = new URL('/api/v1', url)
const baseRequest = {
headers: {
'content-type': 'application/json',
cookie: `authenticationToken=${token}`,
},
method: 'POST',
pathname: '/api/v1',
rejectUnauthorized: false,
}
let { token } = opts
if (opts.url !== '') {
const { protocol, host, username } = new URL(opts.url)
Object.assign(baseRequest, { protocol, host })
if (username !== '') {
token = username
}
} else {
baseRequest.protocol = 'https:'
if (opts.host !== '') {
baseRequest.host = opts.host
} else {
const { hostname = 'localhost', port } = config?.http?.listen?.https ?? {}
baseRequest.hostname = hostname
baseRequest.port = port
}
}
baseRequest.headers.cookie = `authenticationToken=${token}`
const call = async ({ method, params }) => {
if (callPath.length !== 0) {
process.stderr.write(`\n${colors.bold(`--- call #${callPath.join('.')}`)} ---\n\n`)
}
const response = await hrp(url, {
...baseRequest,
const response = await hrp.post(baseRequest, {
body: format.request(0, method, params),
})

View File

@@ -32,7 +32,7 @@
"content-type": "^1.0.4",
"cson-parser": "^4.0.7",
"getopts": "^2.2.3",
"http-request-plus": "^1.0.0",
"http-request-plus": "^0.14.0",
"json-rpc-protocol": "^0.13.1",
"promise-toolbox": "^0.21.0",
"pumpify": "^2.0.1",

View File

@@ -1,7 +1,7 @@
{
"private": true,
"name": "@xen-orchestra/proxy",
"version": "0.26.13",
"version": "0.26.10",
"license": "AGPL-3.0-or-later",
"description": "XO Proxy used to remotely execute backup jobs",
"keywords": [
@@ -32,7 +32,7 @@
"@vates/decorate-with": "^2.0.0",
"@vates/disposable": "^0.1.4",
"@xen-orchestra/async-map": "^0.1.2",
"@xen-orchestra/backups": "^0.29.6",
"@xen-orchestra/backups": "^0.29.5",
"@xen-orchestra/fs": "^3.3.1",
"@xen-orchestra/log": "^0.6.0",
"@xen-orchestra/mixin": "^0.1.0",
@@ -46,7 +46,7 @@
"get-stream": "^6.0.0",
"getopts": "^2.2.3",
"golike-defer": "^0.5.1",
"http-server-plus": "^1.0.0",
"http-server-plus": "^0.12.0",
"http2-proxy": "^5.0.53",
"json-rpc-protocol": "^0.13.1",
"jsonrpc-websocket-client": "^0.7.2",
@@ -60,7 +60,7 @@
"source-map-support": "^0.5.16",
"stoppable": "^1.0.6",
"xdg-basedir": "^5.1.0",
"xen-api": "^1.2.5",
"xen-api": "^1.2.2",
"xo-common": "^0.8.0"
},
"devDependencies": {

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/self-signed):
```sh
npm install --save @xen-orchestra/self-signed
```
> npm install --save @xen-orchestra/self-signed
```
## Usage

View File

@@ -8,8 +8,8 @@
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/template):
```sh
npm install --save @xen-orchestra/template
```
> npm install --save @xen-orchestra/template
```
## Usage

View File

@@ -35,7 +35,7 @@
"form-data": "^4.0.0",
"fs-extra": "^11.1.0",
"get-stream": "^6.0.0",
"http-request-plus": "^1.0.0",
"http-request-plus": "^0.14.0",
"human-format": "^1.0.0",
"lodash": "^4.17.4",
"pretty-ms": "^7.0.0",
@@ -43,7 +43,7 @@
"pw": "^0.0.4",
"xdg-basedir": "^4.0.0",
"xo-lib": "^0.11.1",
"xo-vmdk-to-vhd": "^2.5.3"
"xo-vmdk-to-vhd": "^2.5.2"
},
"devDependencies": {
"@babel/cli": "^7.0.0",

View File

@@ -230,15 +230,10 @@ export async function upload(args) {
)
formData.append('file', input, { filename: 'file', knownLength: length })
try {
const response = await hrp(url.toString(), { body: formData, headers: formData.getHeaders(), method: 'POST' })
return await response.text()
return await hrp.post(url.toString(), { body: formData, headers: formData.getHeaders() }).readAll('utf-8')
} catch (e) {
console.log('ERROR', e)
const { response } = e
if (response !== undefined) {
console.log('ERROR content', await response.text())
}
console.log('ERROR content', await e.response.readAll('utf-8'))
throw e
}
}

View File

@@ -88,16 +88,35 @@ export default class VhdEsxiSeSparse extends VhdAbstract {
async readHeaderAndFooter() {
const buffer = await this.#read(0, 2048)
strictEqual(buffer.readBigInt64LE(0), 0xcafebaben)
for (let i = 0; i < 2048 / 8; i++) {
console.log(i, '> ', buffer.readBigInt64LE(8 * i).toString(16), buffer.readBigInt64LE(8 * i))
}
strictEqual(readInt64(buffer, 1), 0x200000001) // version 2.1
const capacity = readInt64(buffer, 2)
const grain_size = readInt64(buffer, 3)
const grain_table_size = readInt64(buffer, 4)
const flags = readInt64(buffer, 5)
const grain_dir_offset = readInt64(buffer, 16)
const grain_dir_size = readInt64(buffer, 17)
const grain_tables_offset = readInt64(buffer, 18)
const grain_tables_size = readInt64(buffer, 19)
this.#grainOffset = readInt64(buffer, 24)
console.log({
capacity,
grain_size,
grain_table_size,
flags,
grain_dir_offset,
grain_dir_size,
grain_tables_offset,
grain_tables_size,
grainSize: this.#grainSize,
})
this.#grainSize = grain_size * 512 // 8 sectors / 4KB default
this.#grainTableOffset = grain_tables_offset * 512
this.#grainTableSize = grain_tables_size * 512
@@ -112,10 +131,12 @@ export default class VhdEsxiSeSparse extends VhdAbstract {
}
async readBlockAllocationTable() {
console.log('READ BLOCK ALLOCATION', this.#grainTableSize)
const CHUNK_SIZE = 64 * 512
strictEqual(this.#grainTableSize % CHUNK_SIZE, 0)
console.log(' will read ', this.#grainTableSize / CHUNK_SIZE, 'table')
for (let chunkIndex = 0, grainIndex = 0; chunkIndex < this.#grainTableSize / CHUNK_SIZE; chunkIndex++) {
process.stdin.write('.')
const start = chunkIndex * CHUNK_SIZE + this.#grainTableOffset
@@ -130,11 +151,15 @@ export default class VhdEsxiSeSparse extends VhdAbstract {
break
}
if (entry > 3n) {
const intIndex = +(((entry & 0x0fff000000000000n) >> 48n) | ((entry & 0x0000ffffffffffffn) << 12n))
let pos = intIndex * this.#grainSize + CHUNK_SIZE * chunkIndex + this.#grainOffset
console.log({ indexInChunk, grainIndex, intIndex, pos })
this.#grainMap.set(grainIndex)
grainIndex++
}
}
}
console.log('found', this.#grainMap.size)
// read grain directory and the grain tables
const nbBlocks = this.header.maxTableEntries
@@ -164,7 +189,7 @@ export default class VhdEsxiSeSparse extends VhdAbstract {
strictEqual(graintable.length, 4096 * 4)
// we have no guaranty that data are order or contiguous
// let's construct ranges to limit the number of queries
let rangeStart, offsetStart, offsetEnd
let rangeStart, offsetStart, offsetEnd, lastOffset
const changeRange = async (index, offset) => {
if (offsetStart !== undefined) {

View File

@@ -0,0 +1,16 @@
import Esxi from './esxi.mjs'
const host = '10.10.0.62'
const user = 'root'
const password = 'vateslab'
const sslVerify = false
console.log(Esxi)
const esxi = new Esxi(host, user, password, sslVerify)
console.log(esxi)
esxi.on('ready', async function () {
//const metadata = await esxi.getTransferableVmMetadata('4')
//console.log('metadata', metadata)
const res = await esxi.powerOn(9)
console.log(res)
})

View File

@@ -12,7 +12,7 @@
"vhd-lib": "^4.2.0"
},
"engines": {
"node": ">=14"
"node": ">=16"
},
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/vmware-explorer",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",

View File

@@ -50,7 +50,7 @@ export default function parseVmsd(text) {
// remove the " around value
set(parsed, key.split('.'), val?.substring(1, val.length - 1))
})
if (parsed.snapshot?.current === undefined) {
if (parsed.snapshot?.current == undefined) {
return
}

View File

@@ -8,8 +8,8 @@
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/xapi):
```sh
npm install --save @xen-orchestra/xapi
```
> npm install --save @xen-orchestra/xapi
```
## Contributions

View File

@@ -18,7 +18,7 @@ The feature is opt-in via a tag on the VM: `xo:notify-on-snapshot`.
By default, it will be an HTTPS request on the port `1727`, on the first IP address reported by the VM.
If the _VM Tools_ (i.e. management agent) are not installed on the VM or if you wish to use another URL, you can specify this in the tag: `xo:notify-on-snapshot=<URL>`.
If the _VM Tools_ (i.e. management agent) are not installed on the VM or if you which to use another URL, you can specify this in the tag: `xo:notify-on-snapshot=<URL>`.
To guarantee the request comes from XO, a secret must be provided in the `xo-server`'s (and `xo-proxy` if relevant) configuration:
@@ -27,20 +27,11 @@ To guarantee the request comes from XO, a secret must be provided in the `xo-ser
syncHookSecret = 'unique long string to ensure the request comes from XO'
```
XO will waits for the request to be answered before starting the snapshot, but will not wait longer than _1 minute_ by default. This timeout can be changed in the configuration as well:
```toml
[xapiOptions]
# Timeout in milliseconds
#
# Default: 60e3
syncHookTimeout = 300e3 # 5 minutes
```
## Specification
If the request fails for any reasons (including the timeout described in the above section), XO will go ahead with snapshot immediately.
XO will waits for the request to be answered before starting the snapshot, but will not wait longer than _1 minute_.
If the request fails for any reason, XO will go ahead with snapshot immediately.
```http
GET /sync HTTP/1.1
@@ -152,7 +143,7 @@ main().catch(console.warn)
You can run it manually for testing:
```console
$ node index.cjs
```
> node index.cjs
Server is listening on https://[::]:1727
```

View File

@@ -15,7 +15,7 @@
"node": ">=14"
},
"peerDependencies": {
"xen-api": "^1.2.5"
"xen-api": "^1.2.2"
},
"scripts": {
"postversion": "npm publish --access public"
@@ -26,7 +26,7 @@
"@xen-orchestra/log": "^0.6.0",
"d3-time-format": "^3.0.0",
"golike-defer": "^0.5.1",
"http-request-plus": "^1.0.0",
"http-request-plus": "^0.14.0",
"json-rpc-protocol": "^0.13.2",
"lodash": "^4.17.15",
"promise-toolbox": "^0.21.0",

View File

@@ -1,44 +1,5 @@
# ChangeLog
## **5.79.2** (2023-02-20)
<img id="latest" src="https://badgen.net/badge/channel/latest/yellow" alt="Channel: latest" />
### Bug fixes
- [Disk import] Fixes ` Cannot read properties of null (reading "length")` error
- [Continuous Replication] Work-around _premature close_ error
### Released packages
- xen-api 1.2.5
- @xen-orchestra/proxy 0.26.13
- xo-server 5.109.3
## **5.79.1** (2023-02-17)
### Bug fixes
- [Continuous Replication] Fix `VDI_IO_ERROR` when after a VDI has been resized
- [REST API] Fix VDI import
- Fix failing imports (REST API and web UI) [Forum#58146](https://xcp-ng.org/forum/post/58146)
- [Pool/License] Fix license expiration on license binding modal (PR [#6666](https://github.com/vatesfr/xen-orchestra/pull/6666))
- [NBD Backup] Fix VDI not disconnecting from control domain (PR [#6660](https://github.com/vatesfr/xen-orchestra/pull/6660))
- [NBD Backup] Improve performance by avoid unnecessary VDI transfers
- [Home/Pool] Do not check for support on non `XCP-ng` pool (PR [#6661](https://github.com/vatesfr/xen-orchestra/pull/6661))
- [VMDK/OVA import] Fix error importing a VMDK or an OVA generated from XO (PR [#6669](https://github.com/vatesfr/xen-orchestra/pull/6669))
### Released packages
- xen-api 1.2.4
- @vates/nbd-client 1.0.1
- @xen-orchestra/backups 0.29.6
- @xen-orchestra/proxy 0.26.12
- xo-vmdk-to-vhd 2.5.3
- xo-cli 0.14.4
- xo-server 5.109.2
- xo-server-transport-email 0.6.1
- xo-web 5.111.1
## **5.79.0** (2023-01-31)
### Highlights
@@ -97,7 +58,7 @@
## **5.78.0** (2022-12-20)
<img id="stable" src="https://badgen.net/badge/channel/stable/green" alt="Channel: stable" />
<img id="latest" src="https://badgen.net/badge/channel/latest/yellow" alt="Channel: latest" />
### Highlights
@@ -121,6 +82,8 @@
## **5.77.2** (2022-12-12)
<img id="stable" src="https://badgen.net/badge/channel/stable/green" alt="Channel: stable" />
### Bug fixes
- [Backups] Fixes most of the _unexpected number of entries in backup cache_ errors

View File

@@ -27,6 +27,4 @@
<!--packages-start-->
- xo-cli minor
<!--packages-end-->

View File

@@ -4,7 +4,7 @@ FROM ubuntu:xenial
# https://qastack.fr/programming/25899912/how-to-install-nvm-in-docker
RUN apt-get update
RUN apt-get install -y curl qemu-utils vmdk-stream-converter git libxml2-utils libfuse2 nbdkit
RUN apt-get install -y curl qemu-utils blktap-utils vmdk-stream-converter git libxml2-utils libfuse2 nbdkit
ENV NVM_DIR /usr/local/nvm
RUN mkdir -p /usr/local/nvm
RUN cd /usr/local/nvm

View File

@@ -134,8 +134,8 @@ This CLI is mainly used as a debug tool, there's no 100% guarantee on its stabil
### Usage
```console
$ xo-cli --help
```
> xo-cli --help
Usage:
xo-cli --register [--allowUnauthorized] [--expiresIn duration] <XO-Server URL> <username> [<password>]
@@ -177,8 +177,8 @@ Usage:
#### Register your XO instance
```console
$ xo-cli --register http://xo.my-company.net admin@admin.net admin
```
> xo-cli --register http://xo.my-company.net admin@admin.net admin
Successfully logged with admin@admin.net
```
@@ -188,27 +188,27 @@ Note: only a token will be saved in the configuration file.
Prints all objects:
```sh
xo-cli --list-objects
```
> xo-cli --list-objects
```
It is possible to filter on object properties, for instance to print
all VM templates:
```sh
xo-cli --list-objects type=VM-template
```
> xo-cli --list-objects type=VM-template
```
#### List available commands
```sh
xo-cli --list-commands
```
> xo-cli --list-commands
```
Commands can be filtered using patterns:
```sh
xo-cli --list-commands '{user,group}.*'
```
> xo-cli --list-commands '{user,group}.*'
```
#### Execute a command
@@ -217,8 +217,8 @@ The same syntax is used for all commands: `xo-cli <command> <param name>=<value>
E.g., adding a new server:
```console
$ xo-cli server.add host=my.server.net username=root password=secret-password
```
> xo-cli server.add host=my.server.net username=root password=secret-password
42
```
@@ -228,20 +228,20 @@ Parameters (except `true` and `false` which are correctly parsed as
booleans) are assumed to be strings. For other types, you may use JSON
encoding by prefixing with `json:`:
```sh
xo-cli foo.bar baz='json:[1, 2, 3]'
```
> xo-cli foo.bar baz='json:[1, 2, 3]'
```
##### VM export
```sh
xo-cli vm.export vm=a01667e0-8e29-49fc-a550-17be4226783c @=vm.xva
```
> xo-cli vm.export vm=a01667e0-8e29-49fc-a550-17be4226783c @=vm.xva
```
##### VM import
```sh
xo-cli vm.import sr=60a6939e-8b0a-4352-9954-5bde44bcdf7d @=vm.xva
```
> xo-cli vm.import sr=60a6939e-8b0a-4352-9954-5bde44bcdf7d @=vm.xva
```
> Note: `xo-cli` only supports the import of XVA files. It will not import OVA files. To import OVA images, you must use the XOA web UI or use `xo-upload-ova` [available here](https://github.com/vatesfr/xen-orchestra/blob/master/@xen-orchestra/upload-ova/README.md#xo-upload-ova).

Some files were not shown because too many files have changed in this diff Show More