Compare commits

..

2 Commits

Author SHA1 Message Date
Thierry
677a9c958c feat(lite/component): add support for nested modals 2023-07-04 08:59:16 +02:00
Pierre Donias
2978ad1486 feat(lite): 0.1.1 (#6930) 2023-07-03 15:58:17 +02:00
13 changed files with 109 additions and 81 deletions

View File

@@ -669,7 +669,7 @@ class RemoteAdapter {
const handler = this._handler
if (this.useVhdDirectory()) {
const dataPath = `${dirname(path)}/data/${uuidv4()}.vhd`
const sizes = await createVhdDirectoryFromStream(handler, dataPath, input, {
const size = await createVhdDirectoryFromStream(handler, dataPath, input, {
concurrency: writeBlockConcurrency,
compression: this.#getCompressionType(),
async validator() {
@@ -678,14 +678,9 @@ class RemoteAdapter {
},
})
await VhdAbstract.createAlias(handler, path, dataPath)
return sizes
return size
} else {
const size = this.outputStream(path, input, { checksum, validator })
return {
compressedSize: size,
sourceSize: size,
writtenSize: size,
}
return this.outputStream(path, input, { checksum, validator })
}
}

View File

@@ -3,7 +3,7 @@
const sum = require('lodash/sum')
const UUID = require('uuid')
const { asyncMap } = require('@xen-orchestra/async-map')
const { Constants, openVhd, VhdAbstract } = require('vhd-lib')
const { Constants, openVhd, VhdAbstract, VhdFile } = require('vhd-lib')
const { isVhdAlias, resolveVhdAlias } = require('vhd-lib/aliases')
const { dirname, resolve } = require('path')
const { DISK_TYPES } = Constants
@@ -15,14 +15,24 @@ const { Task } = require('./Task.js')
const { Disposable } = require('promise-toolbox')
const handlerPath = require('@xen-orchestra/fs/path')
// checking the size of a vhd directory is costly
// 1 Http Query per 1000 blocks
// we only check size of all the vhd are VhdFiles
function shouldComputeVhdsSize(handler, vhds) {
if (handler.isEncrypted) {
return false
}
return vhds.every(vhd => vhd instanceof VhdFile)
}
const computeVhdsSize = (handler, vhdPaths) =>
Disposable.use(
vhdPaths.map(vhdPath => openVhd(handler, vhdPath)),
async vhds => {
await Promise.all(vhds.map(vhd => vhd.readBlockAllocationTable()))
// get file size for vhdfile, computed size from bat for vhd directory
const sizes = await asyncMap(vhds, vhd => vhd.streamSize())
return sum(sizes)
if (shouldComputeVhdsSize(handler, vhds)) {
const sizes = await asyncMap(vhds, vhd => vhd.getSize())
return sum(sizes)
}
}
)
@@ -524,6 +534,11 @@ exports.cleanVm = async function cleanVm(
const linkedVhds = Object.keys(vhds).map(key => resolve('/', vmDir, vhds[key]))
fileSystemSize = await computeVhdsSize(handler, linkedVhds)
// the size is not computed in some cases (e.g. VhdDirectory)
if (fileSystemSize === undefined) {
return
}
// don't warn if the size has changed after a merge
if (!merged && fileSystemSize !== size) {
// FIXME: figure out why it occurs so often and, once fixed, log the real problems with `logWarn`
@@ -541,8 +556,6 @@ exports.cleanVm = async function cleanVm(
// systematically update size after a merge
if ((merged || fixMetadata) && size !== fileSystemSize) {
// @todo add a cumulatedTransferSize property ?
// @todo update writtenSize, compressedSize
metadata.size = fileSystemSize
mustRegenerateCache = true
try {

View File

@@ -205,7 +205,7 @@ class IncrementalRemoteWriter extends MixinRemoteWriter(AbstractIncrementalWrite
// TODO remove when this has been done before the export
await checkVhd(handler, parentPath)
}
// @todo : sum per property
transferSize += await adapter.writeVhd(path, deltaExport.streams[`${id}.vhd`], {
// no checksum for VHDs, because they will be invalidated by
// merges and chainings
@@ -232,7 +232,7 @@ class IncrementalRemoteWriter extends MixinRemoteWriter(AbstractIncrementalWrite
return { size: transferSize }
})
metadataContent.size = size // @todo: transferSize
metadataContent.size = size
this._metadataFileName = await adapter.writeVmBackupMetadata(vm.uuid, metadataContent)
// TODO: run cleanup?

View File

@@ -1,6 +1,8 @@
# ChangeLog
## **0.2.0**
## **next**
## **0.1.1** (2023-07-03)
- Invalidate sessionId token after logout (PR [#6480](https://github.com/vatesfr/xen-orchestra/pull/6480))
- Settings page (PR [#6418](https://github.com/vatesfr/xen-orchestra/pull/6418))

View File

@@ -1,6 +1,6 @@
{
"name": "@xen-orchestra/lite",
"version": "0.1.0",
"version": "0.1.1",
"scripts": {
"dev": "GIT_HEAD=$(git rev-parse HEAD) vite",
"build": "run-p type-check build-only",

View File

@@ -1,12 +1,13 @@
<template>
<Teleport to="body">
<form
<Teleport :disabled="isNested" to="body">
<component
:is="isNested ? 'div' : 'form'"
:class="className"
class="ui-modal"
v-bind="$attrs"
@click.self="emit('close')"
@click.self="!isNested && emit('close')"
>
<div class="container">
<div :class="{ nested: isNested }" class="container">
<span v-if="onClose" class="close-icon" @click="emit('close')">
<UiIcon :icon="faXmark" />
</span>
@@ -24,22 +25,23 @@
<div v-if="$slots.default" class="content">
<slot />
</div>
<UiButtonGroup :color="color">
<UiButtonGroup v-if="!isNested" :color="color">
<slot name="buttons" />
</UiButtonGroup>
</div>
</form>
</component>
</Teleport>
</template>
<script lang="ts" setup>
import UiButtonGroup from "@/components/ui/UiButtonGroup.vue";
import UiIcon from "@/components/ui/icon/UiIcon.vue";
import UiButtonGroup from "@/components/ui/UiButtonGroup.vue";
import UiTitle from "@/components/ui/UiTitle.vue";
import { IK_MODAL_NESTED } from "@/types/injection-keys";
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
import { faXmark } from "@fortawesome/free-solid-svg-icons";
import { useMagicKeys, whenever } from "@vueuse/core";
import { computed } from "vue";
import { computed, inject, provide } from "vue";
const props = withDefaults(
defineProps<{
@@ -54,27 +56,39 @@ const emit = defineEmits<{
(event: "close"): void;
}>();
const isNested = inject(IK_MODAL_NESTED, false);
provide(IK_MODAL_NESTED, true);
const { escape } = useMagicKeys();
whenever(escape, () => emit("close"));
const className = computed(() => {
return [`color-${props.color}`, { "has-icon": props.icon !== undefined }];
return [
`color-${props.color}`,
{
"has-icon": props.icon !== undefined,
nested: isNested,
},
];
});
</script>
<style lang="postcss" scoped>
.ui-modal {
position: fixed;
z-index: 2;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
overflow: auto;
align-items: center;
justify-content: center;
background-color: #00000080;
&:not(.nested) {
background-color: #00000080;
position: fixed;
z-index: 2;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
}
.color-success {
@@ -103,11 +117,23 @@ const className = computed(() => {
flex-direction: column;
justify-content: center;
min-width: 40rem;
padding: 4.2rem;
text-align: center;
border-radius: 1rem;
background-color: var(--modal-background-color);
box-shadow: var(--shadow-400);
margin: 1rem 2rem;
&.nested {
width: 100%;
}
&:not(.nested) {
box-shadow: var(--shadow-400);
padding: 4.2rem;
}
}
.container > div:last-child {
padding-bottom: 1rem;
}
.close-icon {

View File

@@ -1,5 +1,6 @@
<template>
<ComponentStory
v-slot="{ properties, settings }"
:params="[
colorProp(),
iconProp(),
@@ -11,17 +12,31 @@
slot('buttons').help('Meant to receive UiButton components'),
setting('title').preset('Modal Title').widget(),
setting('subtitle').preset('Modal Subtitle').widget(),
setting('nested_modal').widget(boolean()),
]"
v-slot="{ properties, settings }"
>
<UiButton type="button" @click="open">Open Modal</UiButton>
<UiModal v-bind="properties" v-if="isOpen">
<UiModal v-if="isOpen" v-bind="properties">
<template #title>{{ settings.title }}</template>
<template #subtitle>{{ settings.subtitle }}</template>
<template #buttons>
<UiButton @click="close">Discard</UiButton>
</template>
<template v-if="settings.nested_modal">
<UiModal :icon="faWarning" color="warning">
<template #title>Warning</template>
<template #subtitle> This is a warning "nested" modal.</template>
<UiModal :icon="faInfoCircle" color="info">
<template #title>Info</template>
<template #subtitle> This is an info "nested" modal.</template>
</UiModal>
</UiModal>
<UiModal :icon="faCheck" color="success">
<template #title>Success</template>
<template #subtitle> This is a success "deep nested" modal.</template>
</UiModal>
</template>
</UiModal>
</ComponentStory>
</template>
@@ -38,6 +53,12 @@ import {
setting,
slot,
} from "@/libs/story/story-param";
import {
faCheck,
faInfoCircle,
faWarning,
} from "@fortawesome/free-solid-svg-icons";
import { boolean } from "@/libs/story/story-widget";
const { open, close, isOpen } = useModal();
</script>

View File

@@ -1,3 +1,4 @@
import type { InjectionKey } from "vue";
export const IK_MENU_TELEPORTED = Symbol() as InjectionKey<boolean>;
export const IK_MODAL_NESTED = Symbol() as InjectionKey<boolean>;

View File

@@ -168,12 +168,7 @@ exports.VhdDirectory = class VhdDirectory extends VhdAbstract {
// in case of VhdDirectory, we want to create the file if it does not exists
const flags = this._opts?.flags === 'r+' ? 'w' : this._opts?.flags
const compressed = await this.#compressor.compress(buffer)
const writtenSize = await this._handler.outputFile(this.#getChunkPath(partName), compressed, { flags })
return {
size: buffer.length,
compressedSize: compressed.length,
writtenSize,
}
return this._handler.outputFile(this.#getChunkPath(partName), compressed, { flags })
}
// put block in subdirectories to limit impact when doing directory listing
@@ -233,7 +228,7 @@ exports.VhdDirectory = class VhdDirectory extends VhdAbstract {
footer.checksum = checksumStruct(rawFooter, fuFooter)
debug(`Write footer (checksum=${footer.checksum}). (data=${rawFooter.toString('hex')})`)
return await this._writeChunk('footer', rawFooter)
await this._writeChunk('footer', rawFooter)
}
async writeHeader() {
@@ -241,9 +236,8 @@ exports.VhdDirectory = class VhdDirectory extends VhdAbstract {
const rawHeader = fuHeader.pack(header)
header.checksum = checksumStruct(rawHeader, fuHeader)
debug(`Write header (checksum=${header.checksum}). (data=${rawHeader.toString('hex')})`)
const sizes = await this._writeChunk('header', rawHeader)
await this._writeChunk('header', rawHeader)
await this.#writeChunkFilters()
return sizes
}
writeBlockAllocationTable() {
@@ -291,9 +285,8 @@ exports.VhdDirectory = class VhdDirectory extends VhdAbstract {
}
async writeEntireBlock(block) {
const sizes = await this._writeChunk(this.#getBlockPath(block.id), block.buffer)
await this._writeChunk(this.#getBlockPath(block.id), block.buffer)
setBitmap(this.#blockTable, block.id)
return sizes
}
async _readParentLocatorData(id) {
@@ -301,9 +294,8 @@ exports.VhdDirectory = class VhdDirectory extends VhdAbstract {
}
async _writeParentLocatorData(id, data) {
const sizes = await this._writeChunk('parentLocatorEntry' + id, data)
await this._writeChunk('parentLocatorEntry' + id, data)
this.header.parentLocatorEntry[id].platformDataOffset = 0
return sizes
}
async #writeChunkFilters() {

View File

@@ -463,7 +463,7 @@ exports.VhdFile = class VhdFile extends VhdAbstract {
}
}
async streamSize() {
async getSize() {
return await this._handler.getSize(this._path)
}
}

View File

@@ -10,16 +10,6 @@ const { warn } = createLogger('vhd-lib:createVhdDirectoryFromStream')
const buildVhd = Disposable.wrap(async function* (handler, path, inputStream, { concurrency, compression }) {
const vhd = yield VhdDirectory.create(handler, path, { compression })
const sizes = {
compressedSize: 0,
sourceSize: 0,
writtenSize: 0,
}
const updateSums = ({ writtenSize, compressedSize, sourceSize }) => {
sizes.writtenSize += writtenSize ?? 0
sizes.compressedSize += compressedSize ?? 0
sizes.sourceSize += sourceSize ?? 0
}
await asyncEach(
parseVhdStream(inputStream),
async function (item) {
@@ -31,10 +21,10 @@ const buildVhd = Disposable.wrap(async function* (handler, path, inputStream, {
vhd.header = item.header
break
case 'parentLocator':
updateSums(await vhd.writeParentLocator({ ...item, data: item.buffer }))
await vhd.writeParentLocator({ ...item, data: item.buffer })
break
case 'block':
updateSums(await vhd.writeEntireBlock(item))
await vhd.writeEntireBlock(item)
break
case 'bat':
// it exists but I don't care
@@ -46,18 +36,9 @@ const buildVhd = Disposable.wrap(async function* (handler, path, inputStream, {
{
concurrency,
}
)(await Promise.all([vhd.writeFooter(), vhd.writeHeader(), vhd.writeBlockAllocationTable()])).forEach(
([footer, header, bat]) => {
updateSums(footer)
updateSums(header)
updateSums(bat)
}
)
const vhdSize = vhd.streamSize()
return {
...sizes,
vhdSize,
}
await Promise.all([vhd.writeFooter(), vhd.writeHeader(), vhd.writeBlockAllocationTable()])
return vhd.streamSize()
})
exports.createVhdDirectoryFromStream = async function createVhdDirectoryFromStream(
@@ -67,11 +48,11 @@ exports.createVhdDirectoryFromStream = async function createVhdDirectoryFromStre
{ validator, concurrency = 16, compression } = {}
) {
try {
const sizes = await buildVhd(handler, path, inputStream, { concurrency, compression })
const size = await buildVhd(handler, path, inputStream, { concurrency, compression })
if (validator !== undefined) {
await validator.call(this, path)
}
return sizes
return size
} catch (error) {
// cleanup on error
await handler.rmtree(path).catch(warn)

View File

@@ -142,11 +142,9 @@ const COLUMNS = [
return
}
if (operationTask.message === 'transfer' && vmTransferSize === undefined) {
// @todo handle if size is an object
vmTransferSize = operationTask.result?.size
}
if (operationTask.message === 'merge' && vmMergeSize === undefined) {
// @todo handle if size is an object
vmMergeSize = operationTask.result?.size
}

View File

@@ -330,7 +330,6 @@ const SrTask = ({ children, className, task }) => (
)
const TransferMergeTask = ({ className, task }) => {
// @todo : handle case when size is an object
const size = defined(() => task.result.size, 0)
if (task.status === 'success' && size === 0 && task.warnings?.length === 0) {
return null