Compare commits

..

19 Commits

Author SHA1 Message Date
Thierry
b8f741cb29 feat(lite): use new defineSlots macro from Vue 3.3 2023-07-12 10:52:11 +02:00
Julien Fontanet
4502590bb0 fix(xapi/VM_create): work-around HVM multiplier issues (#6935)
Fixes zammad#15189
2023-07-12 10:29:14 +02:00
Thierry Goettelmann
6d440a5af5 feat(lite/components): rewrite FormInputWrapper (#6918)
Rewrite `FormInputWrapper` and update `FormInput` to match Figma design.

Slotted input will change color according to passed `warning` and/or `error` message.
2023-07-12 10:20:33 +02:00
Julien Fontanet
0840b4c359 fix(xo-server/rest-api): VDI export via NBD 2023-07-12 10:18:45 +02:00
Julien Fontanet
696ee7dbe5 fix(CHANGELOG): add 5.84.0 highlights
Introduced by f32742225
2023-07-12 10:06:15 +02:00
Thierry Goettelmann
5e23e356ce chore(lite): bundling, dynamic import, optimizations (#6910)
Added dynamic imports for views and components.

Extracted to their own bundle:
- Vue related libs (vue, vue-router, pinia etc.)
- Lodash
- Charts

Removed `vite-plugin-pages` package.

Optimize highlight/markdown loading.
2023-07-12 10:05:09 +02:00
Thierry Goettelmann
c705051a89 chore(lite): use injection keys (#6898)
Using injection keys for `provide`/`inject` to prevent errors and code
repetition.
2023-07-11 14:56:03 +02:00
Julien Fontanet
ce2b918a29 fix(xo-server): xoData ESModule import
Introduced by c3e0308ad
2023-07-10 18:15:05 +02:00
Julien Fontanet
df740b1e8e test(backups): fix issues introduced by conversion to ESM
Introduced by 1005e295b
2023-07-10 16:58:12 +02:00
Julien Fontanet
c3e0308ad0 chore(xapi): convert to ESM
BREAKING CHANGE
2023-07-10 16:45:31 +02:00
Julien Fontanet
1005e295b2 chore(backups): convert to ESM
BREAKING CHANGE
2023-07-10 16:45:13 +02:00
Julien Fontanet
b3cf58b8c0 fix(complex-matcher): import specific lodash functions (#6904)
This reduces the bundled size when the library is used in a bundled app.
2023-07-10 16:01:54 +02:00
Julien Fontanet
2652c87917 feat(xo-web/backup/restore): can open raw log (#6936) 2023-07-07 11:20:49 +02:00
Thierry Goettelmann
9e0b5575a4 feat(lite/component): new component FormSection (#6926)
Can take a `collapsible` prop in conjunction of a `collapsed` prop.
If `collapsible` is set to `true`, the style is changed and clicking the section
header will toggle the content visibility.
Collapse status updates are sent via the `update:collapsed` event.
This allows to use `collapsed` as a model, e.g.:
`<FormSection collapsible v-model:collapsed="collapsed">`
2023-07-06 15:55:05 +02:00
Thierry Goettelmann
56c089dc01 feat(lite/stories): identify models with an icon and tooltip (#6927)
When a prop is a model, add an indicator (icon + tooltip) to identify it as such.
2023-07-06 15:49:15 +02:00
Julien Fontanet
3b94da1790 docs(supported_hosts): clearer symbol to indicate EOL 2023-07-06 10:41:52 +02:00
Julien Fontanet
ec39a8e9fe docs(supported_hosts): issues on XenServer 7.2 2023-07-06 10:39:16 +02:00
Julien Fontanet
6339f971ca docs(supported_hosts): Citrix Hypervisor → XenServer
The name has been reverted back to XenServer.
2023-07-06 10:33:21 +02:00
Pierre Donias
2978ad1486 feat(lite): 0.1.1 (#6930) 2023-07-03 15:58:17 +02:00
172 changed files with 1548 additions and 1289 deletions

View File

@@ -1,5 +1,5 @@
import { asyncMap } from '@xen-orchestra/async-map'
import { RemoteAdapter } from '@xen-orchestra/backups/RemoteAdapter.js'
import { RemoteAdapter } from '@xen-orchestra/backups/RemoteAdapter.mjs'
import { getSyncedHandler } from '@xen-orchestra/fs'
import getopts from 'getopts'
import { basename, dirname } from 'path'

View File

@@ -1,10 +1,8 @@
'use strict'
import { Metadata } from './_runners/Metadata.mjs'
import { VmsRemote } from './_runners/VmsRemote.mjs'
import { VmsXapi } from './_runners/VmsXapi.mjs'
const { Metadata } = require('./_runners/Metadata.js')
const { VmsRemote } = require('./_runners/VmsRemote.js')
const { VmsXapi } = require('./_runners/VmsXapi.js')
exports.createRunner = function createRunner(opts) {
export function createRunner(opts) {
const { type } = opts.job
switch (type) {
case 'backup':

View File

@@ -1,8 +1,6 @@
'use strict'
import { asyncMap } from '@xen-orchestra/async-map'
const { asyncMap } = require('@xen-orchestra/async-map')
exports.DurablePartition = class DurablePartition {
export class DurablePartition {
// private resource API is used exceptionally to be able to separate resource creation and release
#partitionDisposers = {}

View File

@@ -1,8 +1,6 @@
'use strict'
import { Task } from './Task.mjs'
const { Task } = require('./Task')
exports.HealthCheckVmBackup = class HealthCheckVmBackup {
export class HealthCheckVmBackup {
#restoredVm
#timeout
#xapi

View File

@@ -1,13 +1,11 @@
'use strict'
import assert from 'node:assert'
const assert = require('assert')
import { formatFilenameDate } from './_filenameDate.mjs'
import { importIncrementalVm } from './_incrementalVm.mjs'
import { Task } from './Task.mjs'
import { watchStreamSize } from './_watchStreamSize.mjs'
const { formatFilenameDate } = require('./_filenameDate.js')
const { importIncrementalVm } = require('./_incrementalVm.js')
const { Task } = require('./Task.js')
const { watchStreamSize } = require('./_watchStreamSize.js')
exports.ImportVmBackup = class ImportVmBackup {
export class ImportVmBackup {
constructor({ adapter, metadata, srUuid, xapi, settings: { newMacAddresses, mapVdisSrs = {} } = {} }) {
this._adapter = adapter
this._importIncrementalVmSettings = { newMacAddresses, mapVdisSrs }

View File

@@ -1,43 +1,38 @@
'use strict'
import { asyncEach } from '@vates/async-each'
import { asyncMap, asyncMapSettled } from '@xen-orchestra/async-map'
import { compose } from '@vates/compose'
import { createLogger } from '@xen-orchestra/log'
import { createVhdDirectoryFromStream, openVhd, VhdAbstract, VhdDirectory, VhdSynthetic } from 'vhd-lib'
import { decorateMethodsWith } from '@vates/decorate-with'
import { deduped } from '@vates/disposable/deduped.js'
import { dirname, join, normalize, resolve } from 'node:path'
import { execFile } from 'child_process'
import { mount } from '@vates/fuse-vhd'
import { readdir, lstat } from 'node:fs/promises'
import { synchronized } from 'decorator-synchronized'
import { v4 as uuidv4 } from 'uuid'
import { ZipFile } from 'yazl'
import Disposable from 'promise-toolbox/Disposable'
import fromCallback from 'promise-toolbox/fromCallback'
import fromEvent from 'promise-toolbox/fromEvent'
import groupBy from 'lodash/groupBy.js'
import pDefer from 'promise-toolbox/defer'
import pickBy from 'lodash/pickBy.js'
import zlib from 'zlib'
const { asyncMap, asyncMapSettled } = require('@xen-orchestra/async-map')
const { synchronized } = require('decorator-synchronized')
const Disposable = require('promise-toolbox/Disposable')
const fromCallback = require('promise-toolbox/fromCallback')
const fromEvent = require('promise-toolbox/fromEvent')
const pDefer = require('promise-toolbox/defer')
const groupBy = require('lodash/groupBy.js')
const pickBy = require('lodash/pickBy.js')
const { dirname, join, normalize, resolve } = require('path')
const { createLogger } = require('@xen-orchestra/log')
const { createVhdDirectoryFromStream, openVhd, VhdAbstract, VhdDirectory, VhdSynthetic } = require('vhd-lib')
const { deduped } = require('@vates/disposable/deduped.js')
const { decorateMethodsWith } = require('@vates/decorate-with')
const { compose } = require('@vates/compose')
const { execFile } = require('child_process')
const { readdir, lstat } = require('fs-extra')
const { v4: uuidv4 } = require('uuid')
const { ZipFile } = require('yazl')
const zlib = require('zlib')
import { BACKUP_DIR } from './_getVmBackupDir.mjs'
import { cleanVm } from './_cleanVm.mjs'
import { formatFilenameDate } from './_filenameDate.mjs'
import { getTmpDir } from './_getTmpDir.mjs'
import { isMetadataFile } from './_backupType.mjs'
import { isValidXva } from './_isValidXva.mjs'
import { listPartitions, LVM_PARTITION_TYPE } from './_listPartitions.mjs'
import { lvs, pvs } from './_lvm.mjs'
import { watchStreamSize } from './_watchStreamSize.mjs'
const { BACKUP_DIR } = require('./_getVmBackupDir.js')
const { cleanVm } = require('./_cleanVm.js')
const { formatFilenameDate } = require('./_filenameDate.js')
const { getTmpDir } = require('./_getTmpDir.js')
const { isMetadataFile } = require('./_backupType.js')
const { isValidXva } = require('./_isValidXva.js')
const { listPartitions, LVM_PARTITION_TYPE } = require('./_listPartitions.js')
const { lvs, pvs } = require('./_lvm.js')
const { watchStreamSize } = require('./_watchStreamSize')
// @todo : this import is marked extraneous , sould be fixed when lib is published
const { mount } = require('@vates/fuse-vhd')
const { asyncEach } = require('@vates/async-each')
export const DIR_XO_CONFIG_BACKUPS = 'xo-config-backups'
const DIR_XO_CONFIG_BACKUPS = 'xo-config-backups'
exports.DIR_XO_CONFIG_BACKUPS = DIR_XO_CONFIG_BACKUPS
const DIR_XO_POOL_METADATA_BACKUPS = 'xo-pool-metadata-backups'
exports.DIR_XO_POOL_METADATA_BACKUPS = DIR_XO_POOL_METADATA_BACKUPS
export const DIR_XO_POOL_METADATA_BACKUPS = 'xo-pool-metadata-backups'
const { debug, warn } = createLogger('xo:backups:RemoteAdapter')
@@ -76,7 +71,7 @@ const debounceResourceFactory = factory =>
return this._debounceResource(factory.apply(this, arguments))
}
class RemoteAdapter {
export class RemoteAdapter {
constructor(
handler,
{ debounceResource = res => res, dirMode, vhdDirectoryCompression, useGetDiskLegacy = false } = {}
@@ -669,7 +664,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 +673,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 })
}
}
@@ -840,5 +830,3 @@ decorateMethodsWith(RemoteAdapter, {
getPartition: Disposable.factory,
})
exports.RemoteAdapter = RemoteAdapter

View File

@@ -1,11 +1,9 @@
'use strict'
import { join, resolve } from 'node:path/posix'
const { join, resolve } = require('node:path/posix')
import { DIR_XO_POOL_METADATA_BACKUPS } from './RemoteAdapter.mjs'
import { PATH_DB_DUMP } from './_runners/_PoolMetadataBackup.mjs'
const { DIR_XO_POOL_METADATA_BACKUPS } = require('./RemoteAdapter.js')
const { PATH_DB_DUMP } = require('./_runners/_PoolMetadataBackup.js')
exports.RestoreMetadataBackup = class RestoreMetadataBackup {
export class RestoreMetadataBackup {
constructor({ backupId, handler, xapi }) {
this._backupId = backupId
this._handler = handler

View File

@@ -1,7 +1,5 @@
'use strict'
const CancelToken = require('promise-toolbox/CancelToken')
const Zone = require('node-zone')
import CancelToken from 'promise-toolbox/CancelToken'
import Zone from 'node-zone'
const logAfterEnd = log => {
const error = new Error('task has already ended')
@@ -30,7 +28,7 @@ const serializeError = error =>
const $$task = Symbol('@xen-orchestra/backups/Task')
class Task {
export class Task {
static get cancelToken() {
const task = Zone.current.data[$$task]
return task !== undefined ? task.#cancelToken : CancelToken.none
@@ -151,7 +149,6 @@ class Task {
})
}
}
exports.Task = Task
for (const method of ['info', 'warning']) {
Task[method] = (...args) => Zone.current.data[$$task]?.[method](...args)

View File

@@ -1,6 +0,0 @@
'use strict'
exports.isMetadataFile = filename => filename.endsWith('.json')
exports.isVhdFile = filename => filename.endsWith('.vhd')
exports.isXvaFile = filename => filename.endsWith('.xva')
exports.isXvaSumFile = filename => filename.endsWith('.xva.checksum')

View File

@@ -0,0 +1,4 @@
export const isMetadataFile = filename => filename.endsWith('.json')
export const isVhdFile = filename => filename.endsWith('.vhd')
export const isXvaFile = filename => filename.endsWith('.xva')
export const isXvaSumFile = filename => filename.endsWith('.xva.checksum')

View File

@@ -1,25 +1,25 @@
'use strict'
import { createLogger } from '@xen-orchestra/log'
import { catchGlobalErrors } from '@xen-orchestra/log/configure'
const logger = require('@xen-orchestra/log').createLogger('xo:backups:worker')
import Disposable from 'promise-toolbox/Disposable'
import ignoreErrors from 'promise-toolbox/ignoreErrors'
import { compose } from '@vates/compose'
import { createCachedLookup } from '@vates/cached-dns.lookup'
import { createDebounceResource } from '@vates/disposable/debounceResource.js'
import { createRunner } from './Backup.mjs'
import { decorateMethodsWith } from '@vates/decorate-with'
import { deduped } from '@vates/disposable/deduped.js'
import { getHandler } from '@xen-orchestra/fs'
import { parseDuration } from '@vates/parse-duration'
import { Xapi } from '@xen-orchestra/xapi'
require('@xen-orchestra/log/configure').catchGlobalErrors(logger)
import { RemoteAdapter } from './RemoteAdapter.mjs'
import { Task } from './Task.mjs'
require('@vates/cached-dns.lookup').createCachedLookup().patchGlobal()
const Disposable = require('promise-toolbox/Disposable')
const ignoreErrors = require('promise-toolbox/ignoreErrors')
const { compose } = require('@vates/compose')
const { createDebounceResource } = require('@vates/disposable/debounceResource.js')
const { decorateMethodsWith } = require('@vates/decorate-with')
const { deduped } = require('@vates/disposable/deduped.js')
const { getHandler } = require('@xen-orchestra/fs')
const { createRunner } = require('./Backup.js')
const { parseDuration } = require('@vates/parse-duration')
const { Xapi } = require('@xen-orchestra/xapi')
const { RemoteAdapter } = require('./RemoteAdapter.js')
const { Task } = require('./Task.js')
createCachedLookup().patchGlobal()
const logger = createLogger('xo:backups:worker')
catchGlobalErrors(logger)
const { debug } = logger
class BackupWorker {

View File

@@ -1,13 +1,11 @@
'use strict'
const cancelable = require('promise-toolbox/cancelable')
const CancelToken = require('promise-toolbox/CancelToken')
import cancelable from 'promise-toolbox/cancelable'
import CancelToken from 'promise-toolbox/CancelToken'
// Similar to `Promise.all` + `map` but pass a cancel token to the callback
//
// If any of the executions fails, the cancel token will be triggered and the
// first reason will be rejected.
exports.cancelableMap = cancelable(async function cancelableMap($cancelToken, iterable, callback) {
export const cancelableMap = cancelable(async function cancelableMap($cancelToken, iterable, callback) {
const { cancel, token } = CancelToken.source([$cancelToken])
try {
return await Promise.all(

View File

@@ -1,19 +1,19 @@
'use strict'
import test from 'test'
import { strict as assert } from 'node:assert'
const { beforeEach, afterEach, test, describe } = require('test')
const assert = require('assert').strict
import tmp from 'tmp'
import fs from 'fs-extra'
import * as uuid from 'uuid'
import { getHandler } from '@xen-orchestra/fs'
import { pFromCallback } from 'promise-toolbox'
import { RemoteAdapter } from './RemoteAdapter.mjs'
import { VHDFOOTER, VHDHEADER } from './tests.fixtures.mjs'
import { VhdFile, Constants, VhdDirectory, VhdAbstract } from 'vhd-lib'
import { checkAliases } from './_cleanVm.mjs'
import { dirname, basename } from 'node:path'
import { rimraf } from 'rimraf'
const tmp = require('tmp')
const fs = require('fs-extra')
const uuid = require('uuid')
const { getHandler } = require('@xen-orchestra/fs')
const { pFromCallback } = require('promise-toolbox')
const { RemoteAdapter } = require('./RemoteAdapter')
const { VHDFOOTER, VHDHEADER } = require('./tests.fixtures.js')
const { VhdFile, Constants, VhdDirectory, VhdAbstract } = require('vhd-lib')
const { checkAliases } = require('./_cleanVm')
const { dirname, basename } = require('path')
const { rimraf } = require('rimraf')
const { beforeEach, afterEach, describe } = test
let tempDir, adapter, handler, jobId, vdiId, basePath, relativePath
const rootPath = 'xo-vm-backups/VMUUID/'

View File

@@ -1,28 +1,37 @@
'use strict'
import * as UUID from 'uuid'
import sum from 'lodash/sum.js'
import { asyncMap } from '@xen-orchestra/async-map'
import { Constants, openVhd, VhdAbstract, VhdFile } from 'vhd-lib'
import { isVhdAlias, resolveVhdAlias } from 'vhd-lib/aliases.js'
import { dirname, resolve } from 'node:path'
import { isMetadataFile, isVhdFile, isXvaFile, isXvaSumFile } from './_backupType.mjs'
import { limitConcurrency } from 'limit-concurrency-decorator'
import { mergeVhdChain } from 'vhd-lib/merge.js'
import { Task } from './Task.mjs'
import { Disposable } from 'promise-toolbox'
import handlerPath from '@xen-orchestra/fs/path'
const sum = require('lodash/sum')
const UUID = require('uuid')
const { asyncMap } = require('@xen-orchestra/async-map')
const { Constants, openVhd, VhdAbstract } = require('vhd-lib')
const { isVhdAlias, resolveVhdAlias } = require('vhd-lib/aliases')
const { dirname, resolve } = require('path')
const { DISK_TYPES } = Constants
const { isMetadataFile, isVhdFile, isXvaFile, isXvaSumFile } = require('./_backupType.js')
const { limitConcurrency } = require('limit-concurrency-decorator')
const { mergeVhdChain } = require('vhd-lib/merge')
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)
}
}
)
@@ -107,7 +116,7 @@ const listVhds = async (handler, vmDir, logWarn) => {
return { vhds, interruptedVhds, aliases }
}
async function checkAliases(
export async function checkAliases(
aliasPaths,
targetDataRepository,
{ handler, logInfo = noop, logWarn = console.warn, remove = false }
@@ -166,11 +175,9 @@ async function checkAliases(
})
}
exports.checkAliases = checkAliases
const defaultMergeLimiter = limitConcurrency(1)
exports.cleanVm = async function cleanVm(
export async function cleanVm(
vmDir,
{
fixMetadata,
@@ -524,6 +531,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 +553,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

@@ -1,8 +0,0 @@
'use strict'
const { utcFormat, utcParse } = require('d3-time-format')
// Format a date in ISO 8601 in a safe way to be used in filenames
// (even on Windows).
exports.formatFilenameDate = utcFormat('%Y%m%dT%H%M%SZ')
exports.parseFilenameDate = utcParse('%Y%m%dT%H%M%SZ')

View File

@@ -0,0 +1,6 @@
import { utcFormat, utcParse } from 'd3-time-format'
// Format a date in ISO 8601 in a safe way to be used in filenames
// (even on Windows).
export const formatFilenameDate = utcFormat('%Y%m%dT%H%M%SZ')
export const parseFilenameDate = utcParse('%Y%m%dT%H%M%SZ')

View File

@@ -1,6 +1,4 @@
'use strict'
// returns all entries but the last retention-th
exports.getOldEntries = function getOldEntries(retention, entries) {
export function getOldEntries(retention, entries) {
return entries === undefined ? [] : retention > 0 ? entries.slice(0, -retention) : entries
}

View File

@@ -1,13 +1,11 @@
'use strict'
const Disposable = require('promise-toolbox/Disposable')
const { join } = require('path')
const { mkdir, rmdir } = require('fs-extra')
const { tmpdir } = require('os')
import Disposable from 'promise-toolbox/Disposable'
import { join } from 'node:path'
import { mkdir, rmdir } from 'node:fs/promises'
import { tmpdir } from 'os'
const MAX_ATTEMPTS = 3
exports.getTmpDir = async function getTmpDir() {
export async function getTmpDir() {
for (let i = 0; true; ++i) {
const path = join(tmpdir(), Math.random().toString(36).slice(2))
try {

View File

@@ -1,8 +0,0 @@
'use strict'
const BACKUP_DIR = 'xo-vm-backups'
exports.BACKUP_DIR = BACKUP_DIR
exports.getVmBackupDir = function getVmBackupDir(uuid) {
return `${BACKUP_DIR}/${uuid}`
}

View File

@@ -0,0 +1,5 @@
export const BACKUP_DIR = 'xo-vm-backups'
export function getVmBackupDir(uuid) {
return `${BACKUP_DIR}/${uuid}`
}

View File

@@ -1,24 +1,20 @@
'use strict'
import find from 'lodash/find.js'
import groupBy from 'lodash/groupBy.js'
import ignoreErrors from 'promise-toolbox/ignoreErrors'
import omit from 'lodash/omit.js'
import { asyncMap } from '@xen-orchestra/async-map'
import { CancelToken } from 'promise-toolbox'
import { compareVersions } from 'compare-versions'
import { createVhdStreamWithLength } from 'vhd-lib'
import { defer } from 'golike-defer'
const find = require('lodash/find.js')
const groupBy = require('lodash/groupBy.js')
const ignoreErrors = require('promise-toolbox/ignoreErrors')
const omit = require('lodash/omit.js')
const { asyncMap } = require('@xen-orchestra/async-map')
const { CancelToken } = require('promise-toolbox')
const { compareVersions } = require('compare-versions')
const { createVhdStreamWithLength } = require('vhd-lib')
const { defer } = require('golike-defer')
import { cancelableMap } from './_cancelableMap.mjs'
import { Task } from './Task.mjs'
import pick from 'lodash/pick.js'
const { cancelableMap } = require('./_cancelableMap.js')
const { Task } = require('./Task.js')
const pick = require('lodash/pick.js')
export const TAG_BASE_DELTA = 'xo:base_delta'
const TAG_BASE_DELTA = 'xo:base_delta'
exports.TAG_BASE_DELTA = TAG_BASE_DELTA
const TAG_COPY_SRC = 'xo:copy_of'
exports.TAG_COPY_SRC = TAG_COPY_SRC
export const TAG_COPY_SRC = 'xo:copy_of'
const ensureArray = value => (value === undefined ? [] : Array.isArray(value) ? value : [value])
const resolveUuid = async (xapi, cache, uuid, type) => {
@@ -33,7 +29,7 @@ const resolveUuid = async (xapi, cache, uuid, type) => {
return ref
}
exports.exportIncrementalVm = async function exportIncrementalVm(
export async function exportIncrementalVm(
vm,
baseVm,
{
@@ -143,7 +139,7 @@ exports.exportIncrementalVm = async function exportIncrementalVm(
)
}
exports.importIncrementalVm = defer(async function importIncrementalVm(
export const importIncrementalVm = defer(async function importIncrementalVm(
$defer,
incrementalVm,
sr,

View File

@@ -1,6 +1,4 @@
'use strict'
const assert = require('assert')
import assert from 'node:assert'
const COMPRESSED_MAGIC_NUMBERS = [
// https://tools.ietf.org/html/rfc1952.html#page-5
@@ -47,7 +45,7 @@ const isValidTar = async (handler, size, fd) => {
}
// TODO: find an heuristic for compressed files
async function isValidXva(path) {
export async function isValidXva(path) {
const handler = this._handler
// size is longer when encrypted + reading part of an encrypted file is not implemented
@@ -74,6 +72,5 @@ async function isValidXva(path) {
return true
}
}
exports.isValidXva = isValidXva
const noop = Function.prototype

View File

@@ -1,9 +1,7 @@
'use strict'
const fromCallback = require('promise-toolbox/fromCallback')
const { createLogger } = require('@xen-orchestra/log')
const { createParser } = require('parse-pairs')
const { execFile } = require('child_process')
import fromCallback from 'promise-toolbox/fromCallback'
import { createLogger } from '@xen-orchestra/log'
import { createParser } from 'parse-pairs'
import { execFile } from 'child_process'
const { debug } = createLogger('xo:backups:listPartitions')
@@ -24,8 +22,7 @@ const IGNORED_PARTITION_TYPES = {
0x82: true, // swap
}
const LVM_PARTITION_TYPE = 0x8e
exports.LVM_PARTITION_TYPE = LVM_PARTITION_TYPE
export const LVM_PARTITION_TYPE = 0x8e
const parsePartxLine = createParser({
keyTransform: key => (key === 'UUID' ? 'id' : key.toLowerCase()),
@@ -33,7 +30,7 @@ const parsePartxLine = createParser({
})
// returns an empty array in case of a non-partitioned disk
exports.listPartitions = async function listPartitions(devicePath) {
export async function listPartitions(devicePath) {
const parts = await fromCallback(execFile, 'partx', [
'--bytes',
'--output=NR,START,SIZE,NAME,UUID,TYPE',

View File

@@ -1,8 +1,6 @@
'use strict'
const fromCallback = require('promise-toolbox/fromCallback')
const { createParser } = require('parse-pairs')
const { execFile } = require('child_process')
import fromCallback from 'promise-toolbox/fromCallback'
import { createParser } from 'parse-pairs'
import { execFile } from 'child_process'
// ===================================================================
@@ -29,5 +27,5 @@ const makeFunction =
.map(Array.isArray(fields) ? parse : line => parse(line)[fields])
}
exports.lvs = makeFunction('lvs')
exports.pvs = makeFunction('pvs')
export const lvs = makeFunction('lvs')
export const pvs = makeFunction('pvs')

View File

@@ -1,22 +1,20 @@
'use strict'
import { asyncMap } from '@xen-orchestra/async-map'
import Disposable from 'promise-toolbox/Disposable'
import ignoreErrors from 'promise-toolbox/ignoreErrors'
const { asyncMap } = require('@xen-orchestra/async-map')
const Disposable = require('promise-toolbox/Disposable')
const ignoreErrors = require('promise-toolbox/ignoreErrors')
const { extractIdsFromSimplePattern } = require('../extractIdsFromSimplePattern.js')
const { PoolMetadataBackup } = require('./_PoolMetadataBackup.js')
const { XoMetadataBackup } = require('./_XoMetadataBackup.js')
const { DEFAULT_SETTINGS, Abstract } = require('./_Abstract.js')
const { runTask } = require('./_runTask.js')
const { getAdaptersByRemote } = require('./_getAdaptersByRemote.js')
import { extractIdsFromSimplePattern } from '../extractIdsFromSimplePattern.mjs'
import { PoolMetadataBackup } from './_PoolMetadataBackup.mjs'
import { XoMetadataBackup } from './_XoMetadataBackup.mjs'
import { DEFAULT_SETTINGS, Abstract } from './_Abstract.mjs'
import { runTask } from './_runTask.mjs'
import { getAdaptersByRemote } from './_getAdaptersByRemote.mjs'
const DEFAULT_METADATA_SETTINGS = {
retentionPoolMetadata: 0,
retentionXoMetadata: 0,
}
exports.Metadata = class MetadataBackupRunner extends Abstract {
export const Metadata = class MetadataBackupRunner extends Abstract {
_computeBaseSettings(config, job) {
const baseSettings = { ...DEFAULT_SETTINGS }
Object.assign(baseSettings, DEFAULT_METADATA_SETTINGS, config.defaultSettings, config.metadata?.defaultSettings)

View File

@@ -1,17 +1,15 @@
'use strict'
import { asyncMapSettled } from '@xen-orchestra/async-map'
import Disposable from 'promise-toolbox/Disposable'
import { limitConcurrency } from 'limit-concurrency-decorator'
const { asyncMapSettled } = require('@xen-orchestra/async-map')
const Disposable = require('promise-toolbox/Disposable')
const { limitConcurrency } = require('limit-concurrency-decorator')
const { extractIdsFromSimplePattern } = require('../extractIdsFromSimplePattern.js')
const { Task } = require('../Task.js')
const createStreamThrottle = require('./_createStreamThrottle.js')
const { DEFAULT_SETTINGS, Abstract } = require('./_Abstract.js')
const { runTask } = require('./_runTask.js')
const { getAdaptersByRemote } = require('./_getAdaptersByRemote.js')
const { FullRemote } = require('./_vmRunners/FullRemote.js')
const { IncrementalRemote } = require('./_vmRunners/IncrementalRemote.js')
import { extractIdsFromSimplePattern } from '../extractIdsFromSimplePattern.mjs'
import { Task } from '../Task.mjs'
import createStreamThrottle from './_createStreamThrottle.mjs'
import { DEFAULT_SETTINGS, Abstract } from './_Abstract.mjs'
import { runTask } from './_runTask.mjs'
import { getAdaptersByRemote } from './_getAdaptersByRemote.mjs'
import { FullRemote } from './_vmRunners/FullRemote.mjs'
import { IncrementalRemote } from './_vmRunners/IncrementalRemote.mjs'
const DEFAULT_REMOTE_VM_SETTINGS = {
concurrency: 2,
@@ -27,7 +25,7 @@ const DEFAULT_REMOTE_VM_SETTINGS = {
vmTimeout: 0,
}
exports.VmsRemote = class RemoteVmsBackupRunner extends Abstract {
export const VmsRemote = class RemoteVmsBackupRunner extends Abstract {
_computeBaseSettings(config, job) {
const baseSettings = { ...DEFAULT_SETTINGS }
Object.assign(baseSettings, DEFAULT_REMOTE_VM_SETTINGS, config.defaultSettings, config.vm?.defaultSettings)

View File

@@ -1,17 +1,15 @@
'use strict'
import { asyncMapSettled } from '@xen-orchestra/async-map'
import Disposable from 'promise-toolbox/Disposable'
import { limitConcurrency } from 'limit-concurrency-decorator'
const { asyncMapSettled } = require('@xen-orchestra/async-map')
const Disposable = require('promise-toolbox/Disposable')
const { limitConcurrency } = require('limit-concurrency-decorator')
const { extractIdsFromSimplePattern } = require('../extractIdsFromSimplePattern.js')
const { Task } = require('../Task.js')
const createStreamThrottle = require('./_createStreamThrottle.js')
const { DEFAULT_SETTINGS, Abstract } = require('./_Abstract.js')
const { runTask } = require('./_runTask.js')
const { getAdaptersByRemote } = require('./_getAdaptersByRemote.js')
const { IncrementalXapi } = require('./_vmRunners/IncrementalXapi.js')
const { FullXapi } = require('./_vmRunners/FullXapi.js')
import { extractIdsFromSimplePattern } from '../extractIdsFromSimplePattern.mjs'
import { Task } from '../Task.mjs'
import createStreamThrottle from './_createStreamThrottle.mjs'
import { DEFAULT_SETTINGS, Abstract } from './_Abstract.mjs'
import { runTask } from './_runTask.mjs'
import { getAdaptersByRemote } from './_getAdaptersByRemote.mjs'
import { IncrementalXapi } from './_vmRunners/IncrementalXapi.mjs'
import { FullXapi } from './_vmRunners/FullXapi.mjs'
const DEFAULT_XAPI_VM_SETTINGS = {
bypassVdiChainsCheck: false,
@@ -36,7 +34,7 @@ const DEFAULT_XAPI_VM_SETTINGS = {
vmTimeout: 0,
}
exports.VmsXapi = class VmsXapiBackupRunner extends Abstract {
export const VmsXapi = class VmsXapiBackupRunner extends Abstract {
_computeBaseSettings(config, job) {
const baseSettings = { ...DEFAULT_SETTINGS }
Object.assign(baseSettings, DEFAULT_XAPI_VM_SETTINGS, config.defaultSettings, config.vm?.defaultSettings)

View File

@@ -1,17 +1,15 @@
'use strict'
import Disposable from 'promise-toolbox/Disposable'
import pTimeout from 'promise-toolbox/timeout'
import { compileTemplate } from '@xen-orchestra/template'
import { runTask } from './_runTask.mjs'
import { RemoteTimeoutError } from './_RemoteTimeoutError.mjs'
const Disposable = require('promise-toolbox/Disposable')
const pTimeout = require('promise-toolbox/timeout')
const { compileTemplate } = require('@xen-orchestra/template')
const { runTask } = require('./_runTask.js')
const { RemoteTimeoutError } = require('./_RemoteTimeoutError.js')
exports.DEFAULT_SETTINGS = {
export const DEFAULT_SETTINGS = {
getRemoteTimeout: 300e3,
reportWhen: 'failure',
}
exports.Abstract = class AbstractRunner {
export const Abstract = class AbstractRunner {
constructor({ config, getAdapter, getConnectedRecord, job, schedule }) {
this._config = config
this._getRecord = getConnectedRecord

View File

@@ -1,16 +1,13 @@
'use strict'
import { asyncMap } from '@xen-orchestra/async-map'
const { asyncMap } = require('@xen-orchestra/async-map')
import { DIR_XO_POOL_METADATA_BACKUPS } from '../RemoteAdapter.mjs'
import { forkStreamUnpipe } from './_forkStreamUnpipe.mjs'
import { formatFilenameDate } from '../_filenameDate.mjs'
import { Task } from '../Task.mjs'
const { DIR_XO_POOL_METADATA_BACKUPS } = require('../RemoteAdapter.js')
const { forkStreamUnpipe } = require('./_forkStreamUnpipe.js')
const { formatFilenameDate } = require('../_filenameDate.js')
const { Task } = require('../Task.js')
export const PATH_DB_DUMP = '/pool/xmldbdump'
const PATH_DB_DUMP = '/pool/xmldbdump'
exports.PATH_DB_DUMP = PATH_DB_DUMP
exports.PoolMetadataBackup = class PoolMetadataBackup {
export class PoolMetadataBackup {
constructor({ config, job, pool, remoteAdapters, schedule, settings }) {
this._config = config
this._job = job

View File

@@ -1,8 +1,6 @@
'use strict'
class RemoteTimeoutError extends Error {
export class RemoteTimeoutError extends Error {
constructor(remoteId) {
super('timeout while getting the remote ' + remoteId)
this.remoteId = remoteId
}
}
exports.RemoteTimeoutError = RemoteTimeoutError

View File

@@ -1,13 +1,11 @@
'use strict'
import { asyncMap } from '@xen-orchestra/async-map'
import { join } from '@xen-orchestra/fs/path'
const { asyncMap } = require('@xen-orchestra/async-map')
const { join } = require('@xen-orchestra/fs/path')
import { DIR_XO_CONFIG_BACKUPS } from '../RemoteAdapter.mjs'
import { formatFilenameDate } from '../_filenameDate.mjs'
import { Task } from '../Task.mjs'
const { DIR_XO_CONFIG_BACKUPS } = require('../RemoteAdapter.js')
const { formatFilenameDate } = require('../_filenameDate.js')
const { Task } = require('../Task.js')
exports.XoMetadataBackup = class XoMetadataBackup {
export class XoMetadataBackup {
constructor({ config, job, remoteAdapters, schedule, settings }) {
this._config = config
this._job = job

View File

@@ -1,12 +1,10 @@
'use strict'
const { pipeline } = require('node:stream')
const { ThrottleGroup } = require('@kldzj/stream-throttle')
const identity = require('lodash/identity.js')
import { pipeline } from 'node:stream'
import { ThrottleGroup } from '@kldzj/stream-throttle'
import identity from 'lodash/identity.js'
const noop = Function.prototype
module.exports = function createStreamThrottle(rate) {
export default function createStreamThrottle(rate) {
if (rate === 0) {
return identity
}

View File

@@ -1,14 +1,13 @@
'use strict'
import { createLogger } from '@xen-orchestra/log'
import { finished, PassThrough } from 'node:stream'
const { finished, PassThrough } = require('node:stream')
const { debug } = require('@xen-orchestra/log').createLogger('xo:backups:forkStreamUnpipe')
const { debug } = createLogger('xo:backups:forkStreamUnpipe')
// create a new readable stream from an existing one which may be piped later
//
// in case of error in the new readable stream, it will simply be unpiped
// from the original one
exports.forkStreamUnpipe = function forkStreamUnpipe(source) {
export function forkStreamUnpipe(source) {
const { forks = 0 } = source
source.forks = forks + 1

View File

@@ -1,9 +1,7 @@
'use strict'
const getAdaptersByRemote = adapters => {
export function getAdaptersByRemote(adapters) {
const adaptersByRemote = {}
adapters.forEach(({ adapter, remoteId }) => {
adaptersByRemote[remoteId] = adapter
})
return adaptersByRemote
}
exports.getAdaptersByRemote = getAdaptersByRemote

View File

@@ -1,6 +0,0 @@
'use strict'
const { Task } = require('../Task.js')
const noop = Function.prototype
const runTask = (...args) => Task.run(...args).catch(noop) // errors are handled by logs
exports.runTask = runTask

View File

@@ -0,0 +1,5 @@
import { Task } from '../Task.mjs'
const noop = Function.prototype
export const runTask = (...args) => Task.run(...args).catch(noop) // errors are handled by logs

View File

@@ -1,14 +1,12 @@
'use strict'
import { decorateMethodsWith } from '@vates/decorate-with'
import { defer } from 'golike-defer'
import { AbstractRemote } from './_AbstractRemote.mjs'
import { FullRemoteWriter } from '../_writers/FullRemoteWriter.mjs'
import { forkStreamUnpipe } from '../_forkStreamUnpipe.mjs'
import { watchStreamSize } from '../../_watchStreamSize.mjs'
import { Task } from '../../Task.mjs'
const { decorateMethodsWith } = require('@vates/decorate-with')
const { defer } = require('golike-defer')
const { AbstractRemote } = require('./_AbstractRemote')
const { FullRemoteWriter } = require('../_writers/FullRemoteWriter')
const { forkStreamUnpipe } = require('../_forkStreamUnpipe')
const { watchStreamSize } = require('../../_watchStreamSize')
const { Task } = require('../../Task')
class FullRemoteVmBackupRunner extends AbstractRemote {
export const FullRemote = class FullRemoteVmBackupRunner extends AbstractRemote {
_getRemoteWriter() {
return FullRemoteWriter
}
@@ -47,7 +45,6 @@ class FullRemoteVmBackupRunner extends AbstractRemote {
}
}
exports.FullRemote = FullRemoteVmBackupRunner
decorateMethodsWith(FullRemoteVmBackupRunner, {
decorateMethodsWith(FullRemote, {
_run: defer,
})

View File

@@ -1,16 +1,14 @@
'use strict'
import { createLogger } from '@xen-orchestra/log'
const { createLogger } = require('@xen-orchestra/log')
const { forkStreamUnpipe } = require('../_forkStreamUnpipe.js')
const { FullRemoteWriter } = require('../_writers/FullRemoteWriter.js')
const { FullXapiWriter } = require('../_writers/FullXapiWriter.js')
const { watchStreamSize } = require('../../_watchStreamSize.js')
const { AbstractXapi } = require('./_AbstractXapi.js')
import { forkStreamUnpipe } from '../_forkStreamUnpipe.mjs'
import { FullRemoteWriter } from '../_writers/FullRemoteWriter.mjs'
import { FullXapiWriter } from '../_writers/FullXapiWriter.mjs'
import { watchStreamSize } from '../../_watchStreamSize.mjs'
import { AbstractXapi } from './_AbstractXapi.mjs'
const { debug } = createLogger('xo:backups:FullXapiVmBackup')
exports.FullXapi = class FullXapiVmBackupRunner extends AbstractXapi {
export const FullXapi = class FullXapiVmBackupRunner extends AbstractXapi {
_getWriters() {
return [FullRemoteWriter, FullXapiWriter]
}

View File

@@ -1,15 +1,14 @@
'use strict'
const assert = require('node:assert')
import { asyncEach } from '@vates/async-each'
import { decorateMethodsWith } from '@vates/decorate-with'
import { defer } from 'golike-defer'
import assert from 'node:assert'
import isVhdDifferencingDisk from 'vhd-lib/isVhdDifferencingDisk.js'
import mapValues from 'lodash/mapValues.js'
const { decorateMethodsWith } = require('@vates/decorate-with')
const { defer } = require('golike-defer')
const { mapValues } = require('lodash')
const { Task } = require('../../Task')
const { AbstractRemote } = require('./_AbstractRemote')
const { IncrementalRemoteWriter } = require('../_writers/IncrementalRemoteWriter')
const { forkDeltaExport } = require('./_forkDeltaExport')
const isVhdDifferencingDisk = require('vhd-lib/isVhdDifferencingDisk')
const { asyncEach } = require('@vates/async-each')
import { AbstractRemote } from './_AbstractRemote.mjs'
import { forkDeltaExport } from './_forkDeltaExport.mjs'
import { IncrementalRemoteWriter } from '../_writers/IncrementalRemoteWriter.mjs'
import { Task } from '../../Task.mjs'
class IncrementalRemoteVmBackupRunner extends AbstractRemote {
_getRemoteWriter() {
@@ -61,7 +60,7 @@ class IncrementalRemoteVmBackupRunner extends AbstractRemote {
}
}
exports.IncrementalRemote = IncrementalRemoteVmBackupRunner
export const IncrementalRemote = IncrementalRemoteVmBackupRunner
decorateMethodsWith(IncrementalRemoteVmBackupRunner, {
_run: defer,
})

View File

@@ -1,28 +1,26 @@
'use strict'
import { asyncEach } from '@vates/async-each'
import { asyncMap } from '@xen-orchestra/async-map'
import { createLogger } from '@xen-orchestra/log'
import { pipeline } from 'node:stream'
import findLast from 'lodash/findLast.js'
import isVhdDifferencingDisk from 'vhd-lib/isVhdDifferencingDisk.js'
import keyBy from 'lodash/keyBy.js'
import mapValues from 'lodash/mapValues.js'
import vhdStreamValidator from 'vhd-lib/vhdStreamValidator.js'
const findLast = require('lodash/findLast.js')
const keyBy = require('lodash/keyBy.js')
const mapValues = require('lodash/mapValues.js')
const vhdStreamValidator = require('vhd-lib/vhdStreamValidator.js')
const { asyncMap } = require('@xen-orchestra/async-map')
const { createLogger } = require('@xen-orchestra/log')
const { pipeline } = require('node:stream')
const { IncrementalRemoteWriter } = require('../_writers/IncrementalRemoteWriter.js')
const { IncrementalXapiWriter } = require('../_writers/IncrementalXapiWriter.js')
const { exportIncrementalVm } = require('../../_incrementalVm.js')
const { Task } = require('../../Task.js')
const { watchStreamSize } = require('../../_watchStreamSize.js')
const { AbstractXapi } = require('./_AbstractXapi.js')
const { forkDeltaExport } = require('./_forkDeltaExport.js')
const isVhdDifferencingDisk = require('vhd-lib/isVhdDifferencingDisk')
const { asyncEach } = require('@vates/async-each')
import { AbstractXapi } from './_AbstractXapi.mjs'
import { exportIncrementalVm } from '../../_incrementalVm.mjs'
import { forkDeltaExport } from './_forkDeltaExport.mjs'
import { IncrementalRemoteWriter } from '../_writers/IncrementalRemoteWriter.mjs'
import { IncrementalXapiWriter } from '../_writers/IncrementalXapiWriter.mjs'
import { Task } from '../../Task.mjs'
import { watchStreamSize } from '../../_watchStreamSize.mjs'
const { debug } = createLogger('xo:backups:IncrementalXapiVmBackup')
const noop = Function.prototype
exports.IncrementalXapi = class IncrementalXapiVmBackupRunner extends AbstractXapi {
export const IncrementalXapi = class IncrementalXapiVmBackupRunner extends AbstractXapi {
_getWriters() {
return [IncrementalRemoteWriter, IncrementalXapiWriter]
}

View File

@@ -1,8 +1,6 @@
'use strict'
const { asyncMap } = require('@xen-orchestra/async-map')
const { createLogger } = require('@xen-orchestra/log')
const { Task } = require('../../Task.js')
import { asyncMap } from '@xen-orchestra/async-map'
import { createLogger } from '@xen-orchestra/log'
import { Task } from '../../Task.mjs'
const { debug, warn } = createLogger('xo:backups:AbstractVmRunner')
@@ -19,7 +17,7 @@ const asyncEach = async (iterable, fn, thisArg = iterable) => {
}
}
exports.Abstract = class AbstractVmBackupRunner {
export const Abstract = class AbstractVmBackupRunner {
// calls fn for each function, warns of any errors, and throws only if there are no writers left
async _callWriters(fn, step, parallel = true) {
const writers = this._writers

View File

@@ -1,11 +1,11 @@
'use strict'
const { Abstract } = require('./_Abstract')
import { asyncEach } from '@vates/async-each'
import { Disposable } from 'promise-toolbox'
const { getVmBackupDir } = require('../../_getVmBackupDir')
const { asyncEach } = require('@vates/async-each')
const { Disposable } = require('promise-toolbox')
import { getVmBackupDir } from '../../_getVmBackupDir.mjs'
exports.AbstractRemote = class AbstractRemoteVmBackupRunner extends Abstract {
import { Abstract } from './_Abstract.mjs'
export const AbstractRemote = class AbstractRemoteVmBackupRunner extends Abstract {
constructor({
config,
job,

View File

@@ -1,18 +1,16 @@
'use strict'
import assert from 'node:assert'
import groupBy from 'lodash/groupBy.js'
import ignoreErrors from 'promise-toolbox/ignoreErrors'
import { asyncMap } from '@xen-orchestra/async-map'
import { decorateMethodsWith } from '@vates/decorate-with'
import { defer } from 'golike-defer'
import { formatDateTime } from '@xen-orchestra/xapi'
const assert = require('assert')
const groupBy = require('lodash/groupBy.js')
const ignoreErrors = require('promise-toolbox/ignoreErrors')
const { asyncMap } = require('@xen-orchestra/async-map')
const { decorateMethodsWith } = require('@vates/decorate-with')
const { defer } = require('golike-defer')
const { formatDateTime } = require('@xen-orchestra/xapi')
import { getOldEntries } from '../../_getOldEntries.mjs'
import { Task } from '../../Task.mjs'
import { Abstract } from './_Abstract.mjs'
const { getOldEntries } = require('../../_getOldEntries.js')
const { Task } = require('../../Task.js')
const { Abstract } = require('./_Abstract.js')
class AbstractXapiVmBackupRunner extends Abstract {
export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
constructor({
config,
getSnapshotNameLabel,
@@ -271,8 +269,7 @@ class AbstractXapiVmBackupRunner extends Abstract {
await this._healthCheck()
}
}
exports.AbstractXapi = AbstractXapiVmBackupRunner
decorateMethodsWith(AbstractXapiVmBackupRunner, {
decorateMethodsWith(AbstractXapi, {
run: defer,
})

View File

@@ -1,12 +0,0 @@
'use strict'
const { mapValues } = require('lodash')
const { forkStreamUnpipe } = require('../_forkStreamUnpipe')
exports.forkDeltaExport = function forkDeltaExport(deltaExport) {
return Object.create(deltaExport, {
streams: {
value: mapValues(deltaExport.streams, forkStreamUnpipe),
},
})
}

View File

@@ -0,0 +1,11 @@
import mapValues from 'lodash/mapValues.js'
import { forkStreamUnpipe } from '../_forkStreamUnpipe.mjs'
export function forkDeltaExport(deltaExport) {
return Object.create(deltaExport, {
streams: {
value: mapValues(deltaExport.streams, forkStreamUnpipe),
},
})
}

View File

@@ -1,13 +1,11 @@
'use strict'
import { formatFilenameDate } from '../../_filenameDate.mjs'
import { getOldEntries } from '../../_getOldEntries.mjs'
import { Task } from '../../Task.mjs'
const { formatFilenameDate } = require('../../_filenameDate.js')
const { getOldEntries } = require('../../_getOldEntries.js')
const { Task } = require('../../Task.js')
import { MixinRemoteWriter } from './_MixinRemoteWriter.mjs'
import { AbstractFullWriter } from './_AbstractFullWriter.mjs'
const { MixinRemoteWriter } = require('./_MixinRemoteWriter.js')
const { AbstractFullWriter } = require('./_AbstractFullWriter.js')
exports.FullRemoteWriter = class FullRemoteWriter extends MixinRemoteWriter(AbstractFullWriter) {
export class FullRemoteWriter extends MixinRemoteWriter(AbstractFullWriter) {
constructor(props) {
super(props)

View File

@@ -1,18 +1,16 @@
'use strict'
import ignoreErrors from 'promise-toolbox/ignoreErrors'
import { asyncMap, asyncMapSettled } from '@xen-orchestra/async-map'
import { formatDateTime } from '@xen-orchestra/xapi'
const ignoreErrors = require('promise-toolbox/ignoreErrors')
const { asyncMap, asyncMapSettled } = require('@xen-orchestra/async-map')
const { formatDateTime } = require('@xen-orchestra/xapi')
import { formatFilenameDate } from '../../_filenameDate.mjs'
import { getOldEntries } from '../../_getOldEntries.mjs'
import { Task } from '../../Task.mjs'
const { formatFilenameDate } = require('../../_filenameDate.js')
const { getOldEntries } = require('../../_getOldEntries.js')
const { Task } = require('../../Task.js')
import { AbstractFullWriter } from './_AbstractFullWriter.mjs'
import { MixinXapiWriter } from './_MixinXapiWriter.mjs'
import { listReplicatedVms } from './_listReplicatedVms.mjs'
const { AbstractFullWriter } = require('./_AbstractFullWriter.js')
const { MixinXapiWriter } = require('./_MixinXapiWriter.js')
const { listReplicatedVms } = require('./_listReplicatedVms.js')
exports.FullXapiWriter = class FullXapiWriter extends MixinXapiWriter(AbstractFullWriter) {
export class FullXapiWriter extends MixinXapiWriter(AbstractFullWriter) {
constructor(props) {
super(props)

View File

@@ -1,29 +1,27 @@
'use strict'
import assert from 'node:assert'
import mapValues from 'lodash/mapValues.js'
import ignoreErrors from 'promise-toolbox/ignoreErrors'
import { asyncEach } from '@vates/async-each'
import { asyncMap } from '@xen-orchestra/async-map'
import { chainVhd, checkVhdChain, openVhd, VhdAbstract } from 'vhd-lib'
import { createLogger } from '@xen-orchestra/log'
import { decorateClass } from '@vates/decorate-with'
import { defer } from 'golike-defer'
import { dirname } from 'node:path'
const assert = require('assert')
const mapValues = require('lodash/mapValues.js')
const ignoreErrors = require('promise-toolbox/ignoreErrors')
const { asyncEach } = require('@vates/async-each')
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')
import { formatFilenameDate } from '../../_filenameDate.mjs'
import { getOldEntries } from '../../_getOldEntries.mjs'
import { Task } from '../../Task.mjs'
const { formatFilenameDate } = require('../../_filenameDate.js')
const { getOldEntries } = require('../../_getOldEntries.js')
const { Task } = require('../../Task.js')
const { MixinRemoteWriter } = require('./_MixinRemoteWriter.js')
const { AbstractIncrementalWriter } = require('./_AbstractIncrementalWriter.js')
const { checkVhd } = require('./_checkVhd.js')
const { packUuid } = require('./_packUuid.js')
const { Disposable } = require('promise-toolbox')
import { MixinRemoteWriter } from './_MixinRemoteWriter.mjs'
import { AbstractIncrementalWriter } from './_AbstractIncrementalWriter.mjs'
import { checkVhd } from './_checkVhd.mjs'
import { packUuid } from './_packUuid.mjs'
import { Disposable } from 'promise-toolbox'
const { warn } = createLogger('xo:backups:DeltaBackupWriter')
class IncrementalRemoteWriter extends MixinRemoteWriter(AbstractIncrementalWriter) {
export class IncrementalRemoteWriter extends MixinRemoteWriter(AbstractIncrementalWriter) {
async checkBaseVdis(baseUuidToSrcVdi) {
const { handler } = this._adapter
const adapter = this._adapter
@@ -205,7 +203,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,12 +230,12 @@ 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?
}
}
exports.IncrementalRemoteWriter = decorateClass(IncrementalRemoteWriter, {
decorateClass(IncrementalRemoteWriter, {
_transfer: defer,
})

View File

@@ -1,19 +1,17 @@
'use strict'
import { asyncMap, asyncMapSettled } from '@xen-orchestra/async-map'
import ignoreErrors from 'promise-toolbox/ignoreErrors'
import { formatDateTime } from '@xen-orchestra/xapi'
const { asyncMap, asyncMapSettled } = require('@xen-orchestra/async-map')
const ignoreErrors = require('promise-toolbox/ignoreErrors')
const { formatDateTime } = require('@xen-orchestra/xapi')
import { formatFilenameDate } from '../../_filenameDate.mjs'
import { getOldEntries } from '../../_getOldEntries.mjs'
import { importIncrementalVm, TAG_COPY_SRC } from '../../_incrementalVm.mjs'
import { Task } from '../../Task.mjs'
const { formatFilenameDate } = require('../../_filenameDate.js')
const { getOldEntries } = require('../../_getOldEntries.js')
const { importIncrementalVm, TAG_COPY_SRC } = require('../../_incrementalVm.js')
const { Task } = require('../../Task.js')
import { AbstractIncrementalWriter } from './_AbstractIncrementalWriter.mjs'
import { MixinXapiWriter } from './_MixinXapiWriter.mjs'
import { listReplicatedVms } from './_listReplicatedVms.mjs'
const { AbstractIncrementalWriter } = require('./_AbstractIncrementalWriter.js')
const { MixinXapiWriter } = require('./_MixinXapiWriter.js')
const { listReplicatedVms } = require('./_listReplicatedVms.js')
exports.IncrementalXapiWriter = class IncrementalXapiWriter extends MixinXapiWriter(AbstractIncrementalWriter) {
export class IncrementalXapiWriter extends MixinXapiWriter(AbstractIncrementalWriter) {
async checkBaseVdis(baseUuidToSrcVdi, baseVm) {
const sr = this._sr
const replicatedVm = listReplicatedVms(sr.$xapi, this._job.id, sr.uuid, this._vmUuid).find(

View File

@@ -1,8 +1,6 @@
'use strict'
import { AbstractWriter } from './_AbstractWriter.mjs'
const { AbstractWriter } = require('./_AbstractWriter.js')
exports.AbstractFullWriter = class AbstractFullWriter extends AbstractWriter {
export class AbstractFullWriter extends AbstractWriter {
async run({ timestamp, sizeContainer, stream, vm, vmSnapshot }) {
try {
return await this._run({ timestamp, sizeContainer, stream, vm, vmSnapshot })

View File

@@ -1,8 +1,6 @@
'use strict'
import { AbstractWriter } from './_AbstractWriter.mjs'
const { AbstractWriter } = require('./_AbstractWriter.js')
exports.AbstractIncrementalWriter = class AbstractIncrementalWriter extends AbstractWriter {
export class AbstractIncrementalWriter extends AbstractWriter {
checkBaseVdis(baseUuidToSrcVdi, baseVm) {
throw new Error('Not implemented')
}

View File

@@ -1,9 +1,7 @@
'use strict'
import { formatFilenameDate } from '../../_filenameDate.mjs'
import { getVmBackupDir } from '../../_getVmBackupDir.mjs'
const { formatFilenameDate } = require('../../_filenameDate')
const { getVmBackupDir } = require('../../_getVmBackupDir')
exports.AbstractWriter = class AbstractWriter {
export class AbstractWriter {
constructor({ config, healthCheckSr, job, vmUuid, scheduleId, settings }) {
this._config = config
this._healthCheckSr = healthCheckSr

View File

@@ -1,19 +1,17 @@
'use strict'
import { createLogger } from '@xen-orchestra/log'
import { join } from 'node:path'
import assert from 'node:assert'
const { createLogger } = require('@xen-orchestra/log')
const { join } = require('path')
const assert = require('assert')
const { formatFilenameDate } = require('../../_filenameDate.js')
const { getVmBackupDir } = require('../../_getVmBackupDir.js')
const { HealthCheckVmBackup } = require('../../HealthCheckVmBackup.js')
const { ImportVmBackup } = require('../../ImportVmBackup.js')
const { Task } = require('../../Task.js')
const MergeWorker = require('../../merge-worker/index.js')
import { formatFilenameDate } from '../../_filenameDate.mjs'
import { getVmBackupDir } from '../../_getVmBackupDir.mjs'
import { HealthCheckVmBackup } from '../../HealthCheckVmBackup.mjs'
import { ImportVmBackup } from '../../ImportVmBackup.mjs'
import { Task } from '../../Task.mjs'
import * as MergeWorker from '../../merge-worker/index.mjs'
const { info, warn } = createLogger('xo:backups:MixinBackupWriter')
exports.MixinRemoteWriter = (BaseClass = Object) =>
export const MixinRemoteWriter = (BaseClass = Object) =>
class MixinRemoteWriter extends BaseClass {
#lock

View File

@@ -1,12 +1,10 @@
'use strict'
import { extractOpaqueRef } from '@xen-orchestra/xapi'
import assert from 'node:assert/strict'
const { extractOpaqueRef } = require('@xen-orchestra/xapi')
import { HealthCheckVmBackup } from '../../HealthCheckVmBackup.mjs'
import { Task } from '../../Task.mjs'
const { Task } = require('../../Task')
const assert = require('node:assert/strict')
const { HealthCheckVmBackup } = require('../../HealthCheckVmBackup')
exports.MixinXapiWriter = (BaseClass = Object) =>
export const MixinXapiWriter = (BaseClass = Object) =>
class MixinXapiWriter extends BaseClass {
constructor({ sr, ...rest }) {
super(rest)

View File

@@ -1,8 +0,0 @@
'use strict'
const openVhd = require('vhd-lib').openVhd
const Disposable = require('promise-toolbox/Disposable')
exports.checkVhd = async function checkVhd(handler, path) {
await Disposable.use(openVhd(handler, path), () => {})
}

View File

@@ -0,0 +1,6 @@
import { openVhd } from 'vhd-lib'
import Disposable from 'promise-toolbox/Disposable'
export async function checkVhd(handler, path) {
await Disposable.use(openVhd(handler, path), () => {})
}

View File

@@ -1,5 +1,3 @@
'use strict'
const getReplicatedVmDatetime = vm => {
const { 'xo:backup:datetime': datetime = vm.name_label.slice(-17, -1) } = vm.other_config
return datetime
@@ -7,7 +5,7 @@ const getReplicatedVmDatetime = vm => {
const compareReplicatedVmDatetime = (a, b) => (getReplicatedVmDatetime(a) < getReplicatedVmDatetime(b) ? -1 : 1)
exports.listReplicatedVms = function listReplicatedVms(xapi, scheduleOrJobId, srUuid, vmUuid) {
export function listReplicatedVms(xapi, scheduleOrJobId, srUuid, vmUuid) {
const { all } = xapi.objects
const vms = {}
for (const key in all) {

View File

@@ -1,7 +1,5 @@
'use strict'
const PARSE_UUID_RE = /-/g
exports.packUuid = function packUuid(uuid) {
export function packUuid(uuid) {
return Buffer.from(uuid.replace(PARSE_UUID_RE, ''), 'hex')
}

View File

@@ -1,6 +1,4 @@
'use strict'
exports.watchStreamSize = function watchStreamSize(stream, container = { size: 0 }) {
export function watchStreamSize(stream, container = { size: 0 }) {
stream.on('data', data => {
container.size += data.length
})

View File

@@ -1,6 +1,4 @@
'use strict'
exports.extractIdsFromSimplePattern = function extractIdsFromSimplePattern(pattern) {
export function extractIdsFromSimplePattern(pattern) {
if (pattern === undefined) {
return []
}

View File

@@ -1,7 +1,5 @@
'use strict'
const mapValues = require('lodash/mapValues.js')
const { dirname } = require('path')
import mapValues from 'lodash/mapValues.js'
import { dirname } from 'node:path'
function formatVmBackup(backup) {
return {
@@ -31,6 +29,6 @@ function formatVmBackup(backup) {
}
// format all backups as returned by RemoteAdapter#listAllVmBackups()
exports.formatVmBackups = function formatVmBackups(backupsByVM) {
export function formatVmBackups(backupsByVM) {
return mapValues(backupsByVM, backups => backups.map(formatVmBackup))
}

View File

@@ -2,19 +2,17 @@
// eslint-disable-next-line eslint-comments/disable-enable-pair
/* eslint-disable n/shebang */
'use strict'
import { catchGlobalErrors } from '@xen-orchestra/log/configure'
import { createLogger } from '@xen-orchestra/log'
import { getSyncedHandler } from '@xen-orchestra/fs'
import { join } from 'node:path'
import Disposable from 'promise-toolbox/Disposable'
import min from 'lodash/min.js'
const { catchGlobalErrors } = require('@xen-orchestra/log/configure')
const { createLogger } = require('@xen-orchestra/log')
const { getSyncedHandler } = require('@xen-orchestra/fs')
const { join } = require('path')
const Disposable = require('promise-toolbox/Disposable')
const min = require('lodash/min')
import { getVmBackupDir } from '../_getVmBackupDir.mjs'
import { RemoteAdapter } from '../RemoteAdapter.mjs'
const { getVmBackupDir } = require('../_getVmBackupDir.js')
const { RemoteAdapter } = require('../RemoteAdapter.js')
const { CLEAN_VM_QUEUE } = require('./index.js')
import { CLEAN_VM_QUEUE } from './index.mjs'
// -------------------------------------------------------------------

View File

@@ -1,13 +1,12 @@
'use strict'
import { join } from 'node:path'
import { spawn } from 'child_process'
import { check } from 'proper-lockfile'
const { join, resolve } = require('path')
const { spawn } = require('child_process')
const { check } = require('proper-lockfile')
export const CLEAN_VM_QUEUE = '/xo-vm-backups/.queue/clean-vm/'
const CLEAN_VM_QUEUE = (exports.CLEAN_VM_QUEUE = '/xo-vm-backups/.queue/clean-vm/')
const CLI_PATH = new URL('cli.mjs', import.meta.url).pathname
const CLI_PATH = resolve(__dirname, 'cli.js')
exports.run = async function runMergeWorker(remotePath) {
export const run = async function runMergeWorker(remotePath) {
try {
// TODO: find a way to pass the acquire the lock and then pass it down the worker
if (await check(join(remotePath, CLEAN_VM_QUEUE))) {

View File

@@ -14,7 +14,7 @@
},
"scripts": {
"postversion": "npm publish --access public",
"test-integration": "node--test *.integ.js"
"test-integration": "node--test *.integ.mjs"
},
"dependencies": {
"@kldzj/stream-throttle": "^1.1.1",
@@ -33,7 +33,6 @@
"compare-versions": "^5.0.1",
"d3-time-format": "^3.0.0",
"decorator-synchronized": "^0.6.0",
"fs-extra": "^11.1.0",
"golike-defer": "^0.5.1",
"limit-concurrency-decorator": "^0.5.0",
"lodash": "^4.17.20",
@@ -47,6 +46,7 @@
"yazl": "^2.5.1"
},
"devDependencies": {
"fs-extra": "^11.1.0",
"rimraf": "^5.0.1",
"sinon": "^15.0.1",
"test": "^3.2.1",

View File

@@ -1,8 +1,6 @@
'use strict'
import { DIR_XO_CONFIG_BACKUPS, DIR_XO_POOL_METADATA_BACKUPS } from './RemoteAdapter.mjs'
const { DIR_XO_CONFIG_BACKUPS, DIR_XO_POOL_METADATA_BACKUPS } = require('./RemoteAdapter.js')
exports.parseMetadataBackupId = function parseMetadataBackupId(backupId) {
export function parseMetadataBackupId(backupId) {
const [dir, ...rest] = backupId.split('/')
if (dir === DIR_XO_CONFIG_BACKUPS) {
const [scheduleId, timestamp] = rest

View File

@@ -1,14 +1,11 @@
'use strict'
const path = require('path')
const { createLogger } = require('@xen-orchestra/log')
const { fork } = require('child_process')
import { createLogger } from '@xen-orchestra/log'
import { fork } from 'child_process'
const { warn } = createLogger('xo:backups:backupWorker')
const PATH = path.resolve(__dirname, '_backupWorker.js')
const PATH = new URL('_backupWorker.mjs', import.meta.url).pathname
exports.runBackupWorker = function runBackupWorker(params, onLog) {
export function runBackupWorker(params, onLog) {
return new Promise((resolve, reject) => {
const worker = fork(PATH)

View File

@@ -1,7 +1,5 @@
'use strict'
// a valid footer of a 2
exports.VHDFOOTER = {
export const VHDFOOTER = {
cookie: 'conectix',
features: 2,
fileFormatVersion: 65536,
@@ -20,7 +18,7 @@ exports.VHDFOOTER = {
hidden: '',
reserved: '',
}
exports.VHDHEADER = {
export const VHDHEADER = {
cookie: 'cxsparse',
dataOffset: undefined,
tableOffset: 2048,

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",
@@ -57,7 +57,6 @@
"postcss-nested": "^6.0.0",
"typescript": "^4.9.3",
"vite": "^4.3.8",
"vite-plugin-pages": "^0.29.1",
"vue-tsc": "^1.6.5"
},
"private": true,

View File

@@ -21,9 +21,14 @@ import AccountButton from "@/components/AccountButton.vue";
import UiIcon from "@/components/ui/icon/UiIcon.vue";
import { useNavigationStore } from "@/stores/navigation.store";
import { useUiStore } from "@/stores/ui.store";
import type { SlotDefinition } from "@/types";
import { faBars } from "@fortawesome/free-solid-svg-icons";
import { storeToRefs } from "pinia";
defineSlots<{
default: SlotDefinition;
}>();
const uiStore = useUiStore();
const { isMobile } = storeToRefs(uiStore);

View File

@@ -5,7 +5,6 @@
<script lang="ts" setup>
import markdown from "@/libs/markdown";
import { useEventListener } from "@vueuse/core";
import "highlight.js/styles/github-dark.css";
import { computed, type Ref, ref } from "vue";
const rootElement = ref() as Ref<HTMLElement>;

View File

@@ -3,14 +3,13 @@
</template>
<script lang="ts" setup>
import HLJS from "highlight.js";
import { type AcceptedLanguage, highlight } from "@/libs/highlight";
import { computed } from "vue";
import "highlight.js/styles/github-dark.css";
const props = withDefaults(
defineProps<{
code?: any;
lang?: string;
lang?: AcceptedLanguage;
}>(),
{ lang: "typescript" }
);
@@ -27,7 +26,7 @@ const codeAsText = computed(() => {
});
const codeAsHtml = computed(
() => HLJS.highlight(codeAsText.value, { language: props.lang }).value
() => highlight(codeAsText.value, { language: props.lang }).value
);
</script>

View File

@@ -43,6 +43,7 @@
</template>
<script lang="ts" setup>
import type { SlotDefinition } from "@/types";
import { computed, toRef, watch } from "vue";
import type { Filters } from "@/types/filter";
import type { Sorts } from "@/types/sort";
@@ -55,6 +56,11 @@ import useFilteredCollection from "@/composables/filtered-collection.composable"
import useMultiSelect from "@/composables/multi-select.composable";
import useSortedCollection from "@/composables/sorted-collection.composable";
defineSlots<{
"head-row": SlotDefinition;
"body-row": SlotDefinition<{ item: any }>;
}>();
const props = defineProps<{
modelValue?: string[];
availableFilters?: Filters;

View File

@@ -11,8 +11,13 @@
<script lang="ts" setup>
import UiIcon from "@/components/ui/icon/UiIcon.vue";
import type { SlotDefinition } from "@/types";
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
defineSlots<{
default: SlotDefinition;
}>();
defineProps<{
icon?: IconDefinition;
}>();

View File

@@ -13,10 +13,15 @@
>
import UiSpinner from "@/components/ui/UiSpinner.vue";
import type { XenApiRecord } from "@/libs/xen-api";
import type { SlotDefinition } from "@/types";
import ObjectNotFoundView from "@/views/ObjectNotFoundView.vue";
import { computed } from "vue";
import { useRouter } from "vue-router";
defineSlots<{
default: SlotDefinition;
}>();
const props = defineProps<{
isReady: boolean;
uuidChecker: (uuid: I) => boolean;

View File

@@ -11,17 +11,23 @@
</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";
import type { SlotDefinition } from "@/types";
import { IK_TAB_BAR_DISABLED } from "@/types/injection-keys";
import { computed, inject } from "vue";
import type { RouteLocationRaw } from "vue-router";
defineSlots<{
default: SlotDefinition;
}>();
defineProps<{
to: RouteLocationRaw;
disabled?: boolean;
}>();
const isTabBarDisabled = inject<ComputedRef<boolean>>(
"isTabBarDisabled",
const isTabBarDisabled = inject(
IK_TAB_BAR_DISABLED,
computed(() => false)
);
</script>

View File

@@ -12,8 +12,14 @@
<script lang="ts" setup>
import UiIcon from "@/components/ui/icon/UiIcon.vue";
import type { SlotDefinition } from "@/types";
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
defineSlots<{
default: SlotDefinition;
actions: SlotDefinition;
}>();
defineProps<{
icon: IconDefinition;
}>();

View File

@@ -31,7 +31,7 @@ import { faServer } from "@fortawesome/free-solid-svg-icons";
import UiModal from "@/components/ui/UiModal.vue";
import UiButton from "@/components/ui/UiButton.vue";
import { computed, ref, watch } from "vue";
import { difference } from "lodash";
import { difference } from "lodash-es";
import { useHostStore } from "@/stores/host.store";
const { records: hosts } = useHostStore().subscribe();

View File

@@ -26,8 +26,13 @@
import UiProgressBar from "@/components/ui/progress/UiProgressBar.vue";
import UiProgressLegend from "@/components/ui/progress/UiProgressLegend.vue";
import UiCardSpinner from "@/components/ui/UiCardSpinner.vue";
import type { SlotDefinition } from "@/types";
import { computed } from "vue";
defineSlots<{
footer: SlotDefinition<{ totalPercent: number }>;
}>();
interface Data {
id: string;
value: number;

View File

@@ -1,61 +0,0 @@
<template>
<div class="chart-summary">
<div>
<div class="label">{{ $t("total-used") }}</div>
<div>
{{ usedPercent }}%
<br />
{{ valueFormatter(used) }}
</div>
</div>
<div>
<div class="label">{{ $t("total-free") }}</div>
<div>
{{ freePercent }}%
<br />
{{ valueFormatter(total - used) }}
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { percent } from "@/libs/utils";
import { computed, type ComputedRef, inject } from "vue";
const props = defineProps<{
total: number;
used: number;
}>();
const usedPercent = computed(() => percent(props.used, props.total));
const freePercent = computed(() =>
percent(props.total - props.used, props.total)
);
const valueFormatter = inject("valueFormatter") as ComputedRef<
(value: number) => string
>;
</script>
<style lang="postcss" scoped>
.chart-summary {
font-size: 1.4rem;
font-weight: 700;
display: flex;
margin-top: 2rem;
color: var(--color-blue-scale-200);
gap: 4rem;
& > div {
display: flex;
flex: 1;
justify-content: space-between;
}
}
.label {
text-transform: uppercase;
}
</style>

View File

@@ -6,11 +6,12 @@
</template>
<script lang="ts" setup>
import UiCard from "@/components/ui/UiCard.vue";
import type { SlotDefinition } from "@/types";
import type { LinearChartData, ValueFormatter } from "@/types/chart";
import { IK_CHART_VALUE_FORMATTER } from "@/types/injection-keys";
import { utcFormat } from "d3-time-format";
import type { EChartsOption } from "echarts";
import { computed, provide } from "vue";
import VueCharts from "vue-echarts";
import type { LinearChartData } from "@/types/chart";
import { LineChart } from "echarts/charts";
import {
GridComponent,
@@ -20,32 +21,36 @@ import {
} from "echarts/components";
import { use } from "echarts/core";
import { CanvasRenderer } from "echarts/renderers";
import type { OptionDataValue } from "echarts/types/src/util/types";
import UiCard from "@/components/ui/UiCard.vue";
import { computed, provide } from "vue";
import VueCharts from "vue-echarts";
const Y_AXIS_MAX_VALUE = 200;
defineSlots<{
summary: SlotDefinition;
}>();
const props = defineProps<{
title?: string;
subtitle?: string;
data: LinearChartData;
valueFormatter?: (value: number) => string;
valueFormatter?: ValueFormatter;
maxValue?: number;
}>();
const valueFormatter = computed(() => {
const valueFormatter = computed<ValueFormatter>(() => {
const formatter = props.valueFormatter;
return (value: OptionDataValue | OptionDataValue[]) => {
if (formatter) {
return formatter(value as number);
return (value) => {
if (formatter === undefined) {
return value.toString();
}
return value.toString();
return formatter(value);
};
});
provide("valueFormatter", valueFormatter);
provide(IK_CHART_VALUE_FORMATTER, valueFormatter);
use([
CanvasRenderer,
@@ -65,7 +70,7 @@ const option = computed<EChartsOption>(() => ({
data: props.data.map((series) => series.label),
},
tooltip: {
valueFormatter: valueFormatter.value,
valueFormatter: (v) => valueFormatter.value(v as number),
},
xAxis: {
type: "time",

View File

@@ -118,12 +118,20 @@ import {
ModelParam,
type Param,
} from "@/libs/story/story-param";
import type { SlotDefinition } from "@/types";
import { faSliders } from "@fortawesome/free-solid-svg-icons";
import "highlight.js/styles/github-dark.css";
import { uniqueId, upperFirst } from "lodash-es";
import { computed, reactive, ref, watch, watchEffect } from "vue";
import { useRoute } from "vue-router";
defineSlots<{
default: SlotDefinition<{
properties: Record<string, any>;
settings: Record<string, any>;
}>;
}>();
const tab = (tab: TAB, params: Param[]) =>
reactive({
onClick: () => (selectedTab.value = tab),
@@ -273,13 +281,9 @@ const documentation = ref();
const route = useRoute();
const mdPaths = import.meta.glob("../../stories/*.md", { as: "raw" });
if (route.meta.storyMdPath !== undefined && route.meta.storyMdPath in mdPaths) {
mdPaths[route.meta.storyMdPath]().then((md) => {
documentation.value = md;
});
}
route.meta.storyMdLoader?.().then((md) => {
documentation.value = md;
});
const applyPreset = (preset: {
props?: Record<string, any>;

View File

@@ -41,8 +41,18 @@
</template>
<script lang="ts" setup>
import type { SlotDefinition } from "@/types";
const moonDistance = 384400;
defineSlots<{
default: SlotDefinition;
"named-slot": SlotDefinition;
"named-scoped-slot": SlotDefinition<{
moonDistance: number;
}>;
}>();
withDefaults(
defineProps<{
imString: string;

View File

@@ -4,7 +4,13 @@
</table>
</template>
<script lang="ts" setup></script>
<script lang="ts" setup>
import type { SlotDefinition } from "@/types";
defineSlots<{
default: SlotDefinition;
}>();
</script>
<style lang="postcss" scoped>
.story-params-table {

View File

@@ -29,6 +29,13 @@
>
<th class="name">
{{ param.getFullName() }}
<sup
v-if="param.isVModel()"
v-tooltip="`[Model] Can be used with ${param.getVModelDirective()}`"
class="v-model-indicator"
>
<UiIcon :icon="faRepeat" />
</sup>
</th>
<td>
<CodeHighlight :code="param.getTypeLabel()" />
@@ -86,8 +93,9 @@ import UiIcon from "@/components/ui/icon/UiIcon.vue";
import UiModal from "@/components/ui/UiModal.vue";
import useModal from "@/composables/modal.composable";
import useSortedCollection from "@/composables/sorted-collection.composable";
import { vTooltip } from "@/directives/tooltip.directive";
import type { PropParam } from "@/libs/story/story-param";
import { faClose } from "@fortawesome/free-solid-svg-icons";
import { faClose, faRepeat } from "@fortawesome/free-solid-svg-icons";
import { useVModel } from "@vueuse/core";
import { toRef } from "vue";
@@ -173,4 +181,8 @@ const {
color: var(--color-green-infra-base);
}
}
.v-model-indicator {
color: var(--color-green-infra-base);
}
</style>

View File

@@ -81,4 +81,10 @@ const model = useVModel(props, "modelValue", emit);
display: flex;
gap: 1rem;
}
.form-select,
.form-input,
.form-json {
font-size: 1.4rem;
}
</style>

View File

@@ -19,7 +19,12 @@
</template>
<script lang="ts" setup>
import { type HTMLAttributes, computed, inject, ref } from "vue";
import {
IK_FORM_HAS_LABEL,
IK_FORM_LABEL_DISABLED,
IK_CHECKBOX_TYPE,
} from "@/types/injection-keys";
import { type HTMLAttributes, computed, inject } from "vue";
import { faCheck, faCircle, faMinus } from "@fortawesome/free-solid-svg-icons";
import { useVModel } from "@vueuse/core";
import UiIcon from "@/components/ui/icon/UiIcon.vue";
@@ -37,9 +42,15 @@ const emit = defineEmits<{
}>();
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 type = inject(IK_CHECKBOX_TYPE, "checkbox");
const hasLabel = inject(
IK_FORM_HAS_LABEL,
computed(() => false)
);
const isLabelDisabled = inject(
IK_FORM_LABEL_DISABLED,
computed(() => false)
);
const icon = computed(() => {
if (type !== "checkbox") {
return faCircle;

View File

@@ -1,13 +1,14 @@
<template>
<span :class="wrapperClass" v-bind="wrapperAttrs">
<span :class="wrapperClass" class="container" v-bind="wrapperAttrs">
<template v-if="inputType === 'select'">
<select
:id="id"
ref="inputElement"
v-model="value"
:class="inputClass"
:disabled="disabled || isLabelDisabled"
:required="required"
class="select"
ref="inputElement"
v-bind="$attrs"
>
<slot />
@@ -18,6 +19,7 @@
</template>
<textarea
v-else-if="inputType === 'textarea'"
:id="id"
ref="textarea"
v-model="value"
:class="inputClass"
@@ -28,12 +30,13 @@
/>
<input
v-else
:id="id"
ref="inputElement"
v-model="value"
:class="inputClass"
:disabled="disabled || isLabelDisabled"
:required="required"
class="input"
ref="inputElement"
v-bind="$attrs"
/>
<span v-if="before !== undefined" class="before">
@@ -48,24 +51,35 @@
</template>
<script lang="ts" setup>
import UiIcon from "@/components/ui/icon/UiIcon.vue";
import type { Color, SlotDefinition } from "@/types";
import {
IK_FORM_INPUT_COLOR,
IK_FORM_LABEL_DISABLED,
IK_INPUT_ID,
IK_INPUT_TYPE,
} from "@/types/injection-keys";
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
import { faAngleDown } from "@fortawesome/free-solid-svg-icons";
import { useTextareaAutosize, useVModel } from "@vueuse/core";
import {
type HTMLAttributes,
computed,
type HTMLAttributes,
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 UiIcon from "@/components/ui/icon/UiIcon.vue";
defineOptions({ inheritAttrs: false });
defineSlots<{
default: SlotDefinition;
}>();
const props = withDefaults(
defineProps<{
id?: string;
modelValue?: any;
color?: Color;
before?: IconDefinition | string;
@@ -90,17 +104,20 @@ const value = useVModel(props, "modelValue", emit);
const isEmpty = computed(
() => props.modelValue == null || String(props.modelValue).trim() === ""
);
const inputType = inject("inputType", "input");
const isLabelDisabled = inject("isLabelDisabled", ref(false));
const inputType = inject(IK_INPUT_TYPE, "input");
const isLabelDisabled = inject(
IK_FORM_LABEL_DISABLED,
computed(() => false)
);
const parentColor = inject(
"color",
IK_FORM_INPUT_COLOR,
computed(() => undefined)
);
const wrapperClass = computed(() => [
`form-${inputType}`,
{
disabled: props.disabled || isLabelDisabled.value,
disabled: props.disabled === true || isLabelDisabled.value,
empty: isEmpty.value,
},
]);
@@ -114,6 +131,10 @@ const inputClass = computed(() => [
},
]);
const parentId = inject(IK_INPUT_ID, undefined);
const id = computed(() => props.id ?? parentId?.value);
const { textarea, triggerResize } = useTextareaAutosize();
watch(value, () => nextTick(() => triggerResize()), {
@@ -128,11 +149,16 @@ defineExpose({
</script>
<style lang="postcss" scoped>
.container {
font-size: 2rem;
}
.form-input,
.form-select,
.form-textarea {
display: inline-grid;
display: grid;
align-items: stretch;
max-width: 30em;
--before-width: v-bind('beforeWidth || "1.75em"');
--after-width: v-bind('afterWidth || "1.625em"');
@@ -167,11 +193,11 @@ defineExpose({
.select {
font-size: 1em;
width: 100%;
height: 2em;
height: 3em;
margin: 0;
color: var(--text-color);
border: 0.0625em solid var(--border-color);
border-radius: 0.5em;
border: 0.05em solid var(--border-color);
border-radius: 0.4em;
outline: none;
background-color: var(--background-color);
box-shadow: var(--shadow-100);

View File

@@ -1,96 +1,140 @@
<template>
<div class="wrapper">
<label
v-if="$slots.label"
class="form-label"
:class="{ disabled, ...formInputWrapperClass }"
<div class="form-input-wrapper">
<div
v-if="label !== undefined || learnMoreUrl !== undefined"
class="label-container"
>
<label :for="id" class="label">
<UiIcon :icon="icon" />
{{ label }}
</label>
<a
v-if="learnMoreUrl !== undefined"
:href="learnMoreUrl"
class="learn-more-url"
target="_blank"
>
<UiIcon :icon="faInfoCircle" />
<span>{{ $t("learn-more") }}</span>
</a>
</div>
<div class="input-container">
<slot />
</label>
<slot />
<p v-if="hasError || hasWarning" :class="formInputWrapperClass">
<UiIcon :icon="faCircleExclamation" v-if="hasError" />{{
error ?? warning
}}
</p>
</div>
<div class="messages-container">
<div v-if="warning !== undefined" class="warning">
{{ warning }}
</div>
<div v-if="error !== undefined" class="error">
{{ error }}
</div>
<div v-if="help !== undefined" class="help">
{{ help }}
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, provide, useSlots } from "vue";
import { faCircleExclamation } from "@fortawesome/free-solid-svg-icons";
import UiIcon from "@/components/ui/icon/UiIcon.vue";
const slots = useSlots();
import type { Color } from "@/types";
import {
IK_FORM_INPUT_COLOR,
IK_FORM_LABEL_DISABLED,
IK_INPUT_ID,
} from "@/types/injection-keys";
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
import { uniqueId } from "lodash-es";
import { computed, provide } from "vue";
const props = defineProps<{
disabled?: boolean;
error?: string;
label?: string;
id?: string;
icon?: IconDefinition;
learnMoreUrl?: string;
warning?: string;
error?: string;
help?: string;
disabled?: boolean;
}>();
provide("hasLabel", slots.label !== undefined);
provide(
"isLabelDisabled",
computed(() => props.disabled)
);
const id = computed(() => props.id ?? uniqueId("form-input-"));
provide(IK_INPUT_ID, id);
const hasError = computed(
() => props.error !== undefined && props.error.trim() !== ""
);
const hasWarning = computed(
() => props.warning !== undefined && props.warning.trim() !== ""
);
const color = computed<Color | undefined>(() => {
if (props.error !== undefined && props.error.trim() !== "") {
return "error";
}
if (props.warning !== undefined && props.warning.trim() !== "") {
return "warning";
}
return undefined;
});
provide(IK_FORM_INPUT_COLOR, color);
provide(
"color",
computed(() =>
hasError.value ? "error" : hasWarning.value ? "warning" : undefined
)
IK_FORM_LABEL_DISABLED,
computed(() => props.disabled ?? false)
);
const formInputWrapperClass = computed(() => ({
error: hasError.value,
warning: !hasError.value && hasWarning.value,
}));
</script>
<style lang="postcss" scoped>
.wrapper {
.form-input-wrapper {
max-width: 60rem;
}
.label-container {
display: flex;
flex-direction: column;
}
.wrapper :deep(.input) {
margin-bottom: 1rem;
}
.form-label {
font-size: 1.6rem;
display: inline-flex;
justify-content: space-between;
align-items: center;
gap: 0.625em;
}
&.disabled {
cursor: not-allowed;
color: var(--color-blue-scale-300);
.label {
text-transform: uppercase;
font-weight: 700;
color: var(--color-blue-scale-100);
font-size: 1.4rem;
padding: 1rem 0;
}
.messages-container {
margin-top: 1rem;
}
.warning,
.error,
.help,
.learn-more-url {
font-size: 1.3rem;
line-height: 150%;
margin: 0.5rem 0;
}
.learn-more-url {
display: flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
color: var(--color-extra-blue-base);
& > span {
text-decoration: underline;
}
}
p.error,
p.warning {
font-size: 0.65em;
margin-bottom: 1rem;
}
.error {
color: var(--color-red-vates-base);
}
.warning {
color: var(--color-orange-world-base);
}
p svg {
margin-right: 0.4em;
.error {
color: var(--color-red-vates-base);
}
.help {
color: var(--color-blue-scale-300);
}
</style>

View File

@@ -3,8 +3,9 @@
</template>
<script lang="ts" setup>
import { provide } from "vue";
import FormCheckbox from "@/components/form/FormCheckbox.vue";
import { IK_CHECKBOX_TYPE } from "@/types/injection-keys";
import { provide } from "vue";
provide("inputType", "radio");
provide(IK_CHECKBOX_TYPE, "radio");
</script>

View File

@@ -0,0 +1,92 @@
<template>
<div :class="{ collapsible }" class="form-section">
<fieldset class="fieldset">
<legend class="legend" @click="toggleCollapse">
{{ label }}
<UiIcon :icon="icon" class="collapse-icon" />
</legend>
<div v-if="!isCollapsed" class="content">
<slot />
</div>
</fieldset>
</div>
</template>
<script lang="ts" setup>
import UiIcon from "@/components/ui/icon/UiIcon.vue";
import { faChevronDown, faChevronUp } from "@fortawesome/free-solid-svg-icons";
import { useVModel, whenever } from "@vueuse/core";
import { computed } from "vue";
const props = defineProps<{
label: string;
collapsible?: boolean;
collapsed?: boolean;
}>();
const emit = defineEmits<{
(event: "update:collapsed", value: boolean): void;
}>();
const isCollapsed = useVModel(props, "collapsed", emit);
const toggleCollapse = () => {
if (props.collapsible) {
isCollapsed.value = !isCollapsed.value;
}
};
const icon = computed(() => {
if (!props.collapsible) {
return undefined;
}
return isCollapsed.value ? faChevronDown : faChevronUp;
});
whenever(
() => !props.collapsible,
() => (isCollapsed.value = false)
);
</script>
<style lang="postcss" scoped>
.collapsible {
padding: 1rem 1.5rem;
background-color: var(--background-color-extra-blue);
border-radius: 0.8rem;
}
.fieldset {
border: none;
margin: 0;
padding: 0;
}
.legend {
display: flex;
align-items: center;
justify-content: space-between;
color: var(--color-extra-blue-base);
border: none;
border-bottom: 1px solid var(--color-extra-blue-base);
width: 100%;
font-size: 2rem;
font-weight: 500;
padding-bottom: 1rem;
.collapsible & {
color: var(--color-blue-scale-100);
padding-bottom: 0;
cursor: pointer;
}
}
.content {
padding: 1.5rem 0;
}
.collapse-icon {
color: var(--color-extra-blue-base);
}
</style>

View File

@@ -5,10 +5,16 @@
</template>
<script lang="ts" setup>
import { provide } from "vue";
import FormInput from "@/components/form/FormInput.vue";
import type { SlotDefinition } from "@/types";
import { IK_INPUT_TYPE } from "@/types/injection-keys";
import { provide } from "vue";
provide("inputType", "select");
defineSlots<{
default: SlotDefinition;
}>();
provide(IK_INPUT_TYPE, "select");
</script>
<style lang="postcss" scoped></style>

View File

@@ -3,10 +3,11 @@
</template>
<script lang="ts" setup>
import { provide } from "vue";
import FormInput from "@/components/form/FormInput.vue";
import { IK_INPUT_TYPE } from "@/types/injection-keys";
import { provide } from "vue";
provide("inputType", "textarea");
provide(IK_INPUT_TYPE, "textarea");
</script>
<style lang="postcss" scoped></style>

View File

@@ -3,8 +3,9 @@
</template>
<script lang="ts" setup>
import { provide } from "vue";
import FormCheckbox from "@/components/form/FormCheckbox.vue";
import { IK_CHECKBOX_TYPE } from "@/types/injection-keys";
import { provide } from "vue";
provide("inputType", "toggle");
provide(IK_CHECKBOX_TYPE, "toggle");
</script>

View File

@@ -8,8 +8,13 @@
<script lang="ts" setup>
import UiIcon from "@/components/ui/icon/UiIcon.vue";
import type { SlotDefinition } from "@/types";
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
defineSlots<{
default: SlotDefinition;
}>();
defineProps<{
icon?: IconDefinition;
}>();

View File

@@ -24,10 +24,16 @@
import UiIcon from "@/components/ui/icon/UiIcon.vue";
import { vTooltip } from "@/directives/tooltip.directive";
import { hasEllipsis } from "@/libs/utils";
import type { SlotDefinition } from "@/types";
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
import { computed, ref } from "vue";
import type { RouteLocationRaw } from "vue-router";
defineSlots<{
default: SlotDefinition;
actions: SlotDefinition;
}>();
defineProps<{
icon: IconDefinition;
route: RouteLocationRaw;

View File

@@ -1,6 +1,6 @@
<template>
<slot :is-open="isOpen" :open="open" name="trigger" />
<Teleport to="body" :disabled="!shouldTeleport">
<Teleport :disabled="!shouldTeleport" to="body">
<ul
v-if="!hasTrigger || isOpen"
ref="menu"
@@ -14,10 +14,24 @@
</template>
<script lang="ts" setup>
import { IK_MENU_TELEPORTED } from "@/types/injection-keys";
import placementJs, { type Options } from "placement.js";
import { inject, nextTick, provide, ref, toRef, unref, useSlots } from "vue";
import type { SlotDefinition } from "@/types";
import {
IK_CLOSE_MENU,
IK_MENU_DISABLED,
IK_MENU_HORIZONTAL,
IK_MENU_TELEPORTED,
} from "@/types/injection-keys";
import { onClickOutside, unrefElement, whenever } from "@vueuse/core";
import placementJs, { type Options } from "placement.js";
import { computed, inject, nextTick, provide, ref, useSlots } from "vue";
defineSlots<{
default: SlotDefinition;
trigger: SlotDefinition<{
isOpen: boolean;
open: (event: MouseEvent) => void;
}>;
}>();
const props = defineProps<{
horizontal?: boolean;
@@ -33,9 +47,18 @@ defineOptions({
const slots = useSlots();
const isOpen = ref(false);
const menu = ref();
const isParentHorizontal = inject("isMenuHorizontal", undefined);
provide("isMenuHorizontal", toRef(props, "horizontal"));
provide("isMenuDisabled", toRef(props, "disabled"));
const isParentHorizontal = inject(
IK_MENU_HORIZONTAL,
computed(() => false)
);
provide(
IK_MENU_HORIZONTAL,
computed(() => props.horizontal ?? false)
);
provide(
IK_MENU_DISABLED,
computed(() => props.disabled ?? false)
);
let clearClickOutsideEvent: (() => void) | undefined;
const hasTrigger = useSlots().trigger !== undefined;
@@ -50,9 +73,8 @@ whenever(
() => !isOpen.value,
() => clearClickOutsideEvent?.()
);
if (slots.trigger && !inject("closeMenu", false)) {
provide("closeMenu", () => (isOpen.value = false));
if (slots.trigger && inject(IK_CLOSE_MENU, undefined) === undefined) {
provide(IK_CLOSE_MENU, () => (isOpen.value = false));
}
const open = (event: MouseEvent) => {
@@ -74,7 +96,7 @@ const open = (event: MouseEvent) => {
placementJs(event.currentTarget as HTMLElement, unrefElement(menu), {
placement:
props.placement ??
(unref(isParentHorizontal) !== false ? "bottom-start" : "right-start"),
(isParentHorizontal.value ? "bottom-start" : "right-start"),
});
});
};

View File

@@ -10,7 +10,7 @@
>
<slot />
</MenuTrigger>
<AppMenu v-else shadow :disabled="isDisabled">
<AppMenu v-else :disabled="isDisabled" shadow>
<template #trigger="{ open, isOpen }">
<MenuTrigger
:active="isOpen"
@@ -33,13 +33,23 @@
</template>
<script lang="ts" setup>
import { computed, inject, ref, unref } from "vue";
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
import { faAngleDown, faAngleRight } from "@fortawesome/free-solid-svg-icons";
import { noop } from "@vueuse/core";
import AppMenu from "@/components/menu/AppMenu.vue";
import MenuTrigger from "@/components/menu/MenuTrigger.vue";
import UiIcon from "@/components/ui/icon/UiIcon.vue";
import type { SlotDefinition } from "@/types";
import {
IK_CLOSE_MENU,
IK_MENU_DISABLED,
IK_MENU_HORIZONTAL,
} from "@/types/injection-keys";
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
import { faAngleDown, faAngleRight } from "@fortawesome/free-solid-svg-icons";
import { computed, inject, ref } from "vue";
defineSlots<{
default: SlotDefinition;
submenu?: SlotDefinition;
}>();
const props = defineProps<{
icon?: IconDefinition;
@@ -48,17 +58,25 @@ const props = defineProps<{
busy?: boolean;
}>();
const isParentHorizontal = inject("isMenuHorizontal", false);
const isMenuDisabled = inject("isMenuDisabled", false);
const isDisabled = computed(() => props.disabled || unref(isMenuDisabled));
const isParentHorizontal = inject(
IK_MENU_HORIZONTAL,
computed(() => false)
);
const isMenuDisabled = inject(
IK_MENU_DISABLED,
computed(() => false)
);
const isDisabled = computed(
() => props.disabled === true || isMenuDisabled.value
);
const submenuIcon = computed(() =>
unref(isParentHorizontal) ? faAngleDown : faAngleRight
isParentHorizontal.value ? faAngleDown : faAngleRight
);
const isHandlingClick = ref(false);
const isBusy = computed(() => isHandlingClick.value || props.busy);
const closeMenu = inject("closeMenu", noop);
const isBusy = computed(() => isHandlingClick.value || props.busy === true);
const closeMenu = inject(IK_CLOSE_MENU, undefined);
const handleClick = async () => {
if (isDisabled.value || isBusy.value) {
@@ -68,7 +86,7 @@ const handleClick = async () => {
isHandlingClick.value = true;
try {
await props.onClick?.();
closeMenu();
closeMenu?.();
} finally {
isHandlingClick.value = false;
}

View File

@@ -3,9 +3,13 @@
</template>
<script lang="ts" setup>
import { inject } from "vue";
import { IK_MENU_HORIZONTAL } from "@/types/injection-keys";
import { computed, inject } from "vue";
const horizontal = inject("isParentMenuHorizontal", false);
const horizontal = inject(
IK_MENU_HORIZONTAL,
computed(() => false)
);
</script>
<style lang="postcss" scoped>

View File

@@ -6,9 +6,14 @@
</template>
<script lang="ts" setup>
import type { SlotDefinition } from "@/types";
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
import UiIcon from "@/components/ui/icon/UiIcon.vue";
defineSlots<{
default: SlotDefinition;
}>();
defineProps<{
active?: boolean;
busy?: boolean;

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