mirror of
https://github.com/vatesfr/xen-orchestra.git
synced 2026-11-10 01:15:35 -06:00
Compare commits
36 Commits
debug_time
...
feat/qa-te
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
591c552e3e | ||
|
|
9cdaf6bb41 | ||
|
|
9b4e1aee3e | ||
|
|
ab7b2fc7a9 | ||
|
|
9cdd8e9d45 | ||
|
|
14e5352a40 | ||
|
|
d955ffc4d1 | ||
|
|
f812eaaeb6 | ||
|
|
814bc96a8b | ||
|
|
9ef53890b8 | ||
|
|
8b4505a2d5 | ||
|
|
a68c9534e9 | ||
|
|
46b0dc67bb | ||
|
|
249137793f | ||
|
|
96e7e23f40 | ||
|
|
6c3e3e781d | ||
|
|
bcb0a7041f | ||
|
|
db0d009d5b | ||
|
|
de1c231873 | ||
|
|
4235644950 | ||
|
|
7942d6a84a | ||
|
|
c5faec4cf6 | ||
|
|
c9149618f8 | ||
|
|
438415a882 | ||
|
|
ed78cc10cf | ||
|
|
1c18215bd1 | ||
|
|
7cdb792bfe | ||
|
|
0601716fa4 | ||
|
|
d53d2fc1c2 | ||
|
|
11ff21097f | ||
|
|
828c15cbb4 | ||
|
|
798732e62a | ||
|
|
1bfcc50b23 | ||
|
|
c5d791a7de | ||
|
|
3293e69cc2 | ||
|
|
a0193085f5 |
15
.eslintrc.js
15
.eslintrc.js
@@ -85,6 +85,21 @@ module.exports = {
|
||||
XO_LITE_GIT_HEAD: true,
|
||||
},
|
||||
rules: {
|
||||
'import/extensions': [
|
||||
'error',
|
||||
'ignorePackages',
|
||||
{
|
||||
pattern: { ts: 'always', vue: 'always' },
|
||||
checkTypeImports: true,
|
||||
// '@core/*' and '@/*' look like scoped packages to the plugin, which 'ignorePackages' would skip
|
||||
pathGroupOverrides: [
|
||||
{ pattern: '@core/packages/*', action: 'ignore' }, // barrel files
|
||||
{ pattern: '@core/icons', action: 'ignore' },
|
||||
{ pattern: '@core/**', action: 'enforce' },
|
||||
{ pattern: '@/**', action: 'enforce' },
|
||||
],
|
||||
},
|
||||
],
|
||||
'import/order': [
|
||||
'error',
|
||||
{
|
||||
|
||||
@@ -5,3 +5,7 @@
|
||||
# Generated files
|
||||
/@xen-orchestra/web/src/route-map.d.ts
|
||||
/@xen-orchestra/lite/src/route-map.d.ts
|
||||
|
||||
# Docusaurus MDX content: prettier's markdown parser corrupts JSX
|
||||
# comments ({/* … */} becomes {/_ … _/}) and other MDX constructs
|
||||
/docs/docs/**/*.md
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@vates/multi-key-map": "^0.2.0",
|
||||
"@xen-orchestra/async-map": "^0.1.3",
|
||||
"@xen-orchestra/async-map": "^0.1.4",
|
||||
"@xen-orchestra/log": "^0.7.2",
|
||||
"ensure-array": "^1.0.0"
|
||||
},
|
||||
|
||||
@@ -21,11 +21,11 @@
|
||||
"dependencies": {
|
||||
"@vates/async-each": "^1.0.3",
|
||||
"@vates/read-chunk": "^1.2.1",
|
||||
"@xen-orchestra/async-map": "^0.1.3",
|
||||
"@xen-orchestra/async-map": "^0.1.4",
|
||||
"@xen-orchestra/disk-transform": "^1.3.3",
|
||||
"@xen-orchestra/log": "^0.7.2",
|
||||
"promise-toolbox": "^0.21.0",
|
||||
"xen-api": "^4.8.0"
|
||||
"xen-api": "^4.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tap": "^18.7.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@vates/types",
|
||||
"private": false,
|
||||
"version": "1.30.1",
|
||||
"version": "1.30.2",
|
||||
"main": "./dist/index.mjs",
|
||||
"exports": {
|
||||
".": {
|
||||
|
||||
@@ -328,10 +328,11 @@ export type XoApp = {
|
||||
xo: Record<XoBackupRepository['id'], XoConfigBackupArchive[]>
|
||||
pool: Record<XoBackupRepository['id'], Record<XoPool['id'], XoPoolBackupArchive[]>>
|
||||
}>
|
||||
/** `null` when the listing of a backup repository failed */
|
||||
listVmBackupsNg(
|
||||
backupRepositoryIds: XoBackupRepository['id'][],
|
||||
opts?: { _forceRefresh?: boolean; vmId: XoVm['id'] }
|
||||
): Promise<Record<XoBackupRepository['id'], Record<XoVm['id'], XoVmBackupArchive[]>>>
|
||||
opts?: { _forceRefresh?: boolean; vmId?: XoVm['id'] }
|
||||
): Promise<Record<XoBackupRepository['id'], Record<XoVm['id'], XoVmBackupArchive[]> | null>>
|
||||
pingRemote(id: XoBackupRepository['id']): Promise<{ success: true }>
|
||||
/** Allow to add a new server in the DB (XCP-ng/XenServer) */
|
||||
registerXenServer(
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"main": "dist/index.mjs",
|
||||
"name": "@xen-orchestra/acl",
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/acl",
|
||||
"version": "1.5.0",
|
||||
"version": "1.6.0",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"private": false,
|
||||
"type": "module",
|
||||
@@ -30,7 +30,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.19.0",
|
||||
"@vates/types": "^1.30.1",
|
||||
"@vates/types": "^1.30.2",
|
||||
"rimraf": "^6.0.1",
|
||||
"typescript": "~5.6",
|
||||
"typescript-eslint": "^8.61.0"
|
||||
|
||||
@@ -13,11 +13,11 @@ const wrapCall = (fn, arg, thisArg) => {
|
||||
*
|
||||
* WARNING: Does not handle plain objects
|
||||
*
|
||||
* @template Item,This
|
||||
* @template Item, Result, [This=Iterable<Item>]
|
||||
* @param {Iterable<Item>} iterable
|
||||
* @param {(this: This, item: Item) => (Item | PromiseLike<Item>)} mapFn
|
||||
* @param {This} [thisArg]
|
||||
* @returns {Promise<Item[]>}
|
||||
* @param {(this: This, item: Item) => (Result | PromiseLike<Result>)} mapFn
|
||||
* @param {This} [thisArg] defaults to `iterable`
|
||||
* @returns {Promise<Result[]>}
|
||||
*/
|
||||
exports.asyncMap = function asyncMap(iterable, mapFn, thisArg = iterable) {
|
||||
return Promise.all(Array.from(iterable, mapFn, thisArg))
|
||||
@@ -26,11 +26,11 @@ exports.asyncMap = function asyncMap(iterable, mapFn, thisArg = iterable) {
|
||||
/**
|
||||
* Like `asyncMap` but wait for all promises to settle before rejecting
|
||||
*
|
||||
* @template Item,This
|
||||
* @template Item, Result, [This=Iterable<Item>]
|
||||
* @param {Iterable<Item>} iterable
|
||||
* @param {(this: This, item: Item) => (Item | PromiseLike<Item>)} mapFn
|
||||
* @param {This} [thisArg]
|
||||
* @returns {Promise<Item[]>}
|
||||
* @param {(this: This, item: Item) => (Result | PromiseLike<Result>)} mapFn
|
||||
* @param {This} [thisArg] defaults to `iterable`
|
||||
* @returns {Promise<Result[]>}
|
||||
*/
|
||||
exports.asyncMapSettled = function asyncMapSettled(iterable, mapFn, thisArg = iterable) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "@xen-orchestra/async-map",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.4",
|
||||
"license": "ISC",
|
||||
"description": "Promise.all + map for all iterables",
|
||||
"keywords": [
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"tar-stream": "^3.1.8",
|
||||
"@types/tar-stream": "^3.1.4",
|
||||
"tmp": "^0.2.1",
|
||||
"@vates/types": "^1.30.1"
|
||||
"@vates/types": "^1.30.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@xen-orchestra/log": "^0.7.1",
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"dependencies": {
|
||||
"@vates/async-each": "^1.0.3",
|
||||
"@xen-orchestra/async-map": "^0.1.3",
|
||||
"@xen-orchestra/backups": "^0.73.10",
|
||||
"@xen-orchestra/async-map": "^0.1.4",
|
||||
"@xen-orchestra/backups": "^0.74.0",
|
||||
"@xen-orchestra/fs": "^4.9.3",
|
||||
"filenamify": "^6.0.0",
|
||||
"getopts": "^2.2.5",
|
||||
@@ -28,7 +28,7 @@
|
||||
"scripts": {
|
||||
"postversion": "npm publish --access public"
|
||||
},
|
||||
"version": "1.1.13",
|
||||
"version": "1.1.14",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"author": {
|
||||
"name": "Vates SAS",
|
||||
|
||||
@@ -16,42 +16,15 @@ import { toQcow2Stream } from '@xen-orchestra/qcow2'
|
||||
|
||||
const ensureArray = value => (value === undefined ? [] : Array.isArray(value) ? value : [value])
|
||||
|
||||
const orderedMemoryLimits = ['memory_static_min', 'memory_dynamic_min', 'memory_dynamic_max', 'memory_static_max']
|
||||
|
||||
// The dynamic memory range MUST respect this inequality at any moment: static_min <= dynamic_min <= dynamic_max <= static_max.
|
||||
// We must update these properties in the right order to avoid XAPI error.
|
||||
// The order depends on the values. It can be an increase, a decrease or a mix of both, so any order could be required.
|
||||
// VM.set_memory_limits sets all four fields atomically, so there is no intermediate state for XAPI to reject
|
||||
export async function updateMemoryFields(xapi, targetVm, vmRecord) {
|
||||
const memoryValues = {}
|
||||
for (const key of orderedMemoryLimits) {
|
||||
memoryValues[key] = {
|
||||
currentValue: targetVm[key],
|
||||
newValue: vmRecord[key] ?? targetVm[key],
|
||||
}
|
||||
}
|
||||
const staticMin = vmRecord.memory_static_min ?? targetVm.memory_static_min
|
||||
const staticMax = vmRecord.memory_static_max ?? targetVm.memory_static_max
|
||||
const dynamicMin = vmRecord.memory_dynamic_min ?? targetVm.memory_dynamic_min
|
||||
const dynamicMax = vmRecord.memory_dynamic_max ?? targetVm.memory_dynamic_max
|
||||
|
||||
while (await updateNextMemoryField(xapi, memoryValues, targetVm.$ref)) {
|
||||
/* execute until all memory fields are updated */
|
||||
}
|
||||
}
|
||||
|
||||
// Update one more memory field if needed, then return a boolean describing if a field was updated or if all fields are up to date
|
||||
async function updateNextMemoryField(xapi, memoryValues, vmRef) {
|
||||
for (let i = 0; i < orderedMemoryLimits.length; i++) {
|
||||
const currentField = memoryValues[orderedMemoryLimits[i]]
|
||||
const nextField = i === orderedMemoryLimits.length - 1 ? undefined : memoryValues[orderedMemoryLimits[i + 1]]
|
||||
if (
|
||||
currentField.newValue !== currentField.currentValue &&
|
||||
(nextField === undefined || currentField.newValue <= nextField.currentValue)
|
||||
) {
|
||||
// no need to check that previousField.currentValue <= currentField.newValue, as we can deduce it
|
||||
await xapi.setField('VM', vmRef, orderedMemoryLimits[i], currentField.newValue)
|
||||
await xapi.barrier()
|
||||
currentField.currentValue = currentField.newValue
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
await xapi.call('VM.set_memory_limits', targetVm.$ref, staticMin, staticMax, dynamicMin, dynamicMax)
|
||||
}
|
||||
|
||||
export async function exportIncrementalVm(
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
import { describe, it } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
|
||||
import { updateMemoryFields } from './_incrementalVm.mjs'
|
||||
|
||||
const MOCK_REF = 'mockRef'
|
||||
|
||||
function mockXapiVm(initialMemoryValues) {
|
||||
const res = {
|
||||
...initialMemoryValues,
|
||||
updates: [],
|
||||
barrier: () => {},
|
||||
setField: (type, ref, field, value) => {
|
||||
assert(type === 'VM')
|
||||
assert(ref === MOCK_REF)
|
||||
res.updates.push({ field, value })
|
||||
res[field] = value
|
||||
assert(res.memory_static_min <= res.memory_dynamic_min)
|
||||
assert(res.memory_dynamic_min <= res.memory_dynamic_max)
|
||||
assert(res.memory_dynamic_max <= res.memory_static_max)
|
||||
},
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
const tests = [
|
||||
{
|
||||
label: 'handles a memory increase',
|
||||
targetVm: {
|
||||
memory_static_min: 2,
|
||||
memory_dynamic_min: 4,
|
||||
memory_dynamic_max: 4,
|
||||
memory_static_max: 8,
|
||||
},
|
||||
vmRecord: {
|
||||
memory_static_min: 16,
|
||||
memory_dynamic_min: 16,
|
||||
memory_dynamic_max: 16,
|
||||
memory_static_max: 16,
|
||||
},
|
||||
expectedUpdates: [
|
||||
{ field: 'memory_static_max', value: 16 },
|
||||
{ field: 'memory_dynamic_max', value: 16 },
|
||||
{ field: 'memory_dynamic_min', value: 16 },
|
||||
{ field: 'memory_static_min', value: 16 },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'handles a memory decrease',
|
||||
targetVm: {
|
||||
memory_static_min: 4,
|
||||
memory_dynamic_min: 8,
|
||||
memory_dynamic_max: 16,
|
||||
memory_static_max: 16,
|
||||
},
|
||||
vmRecord: {
|
||||
memory_static_min: 2,
|
||||
memory_dynamic_min: 2,
|
||||
memory_dynamic_max: 2,
|
||||
memory_static_max: 2,
|
||||
},
|
||||
expectedUpdates: [
|
||||
{ field: 'memory_static_min', value: 2 },
|
||||
{ field: 'memory_dynamic_min', value: 2 },
|
||||
{ field: 'memory_dynamic_max', value: 2 },
|
||||
{ field: 'memory_static_max', value: 2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'handles a more complex case',
|
||||
targetVm: {
|
||||
memory_static_min: 1,
|
||||
memory_dynamic_min: 2,
|
||||
memory_dynamic_max: 8,
|
||||
memory_static_max: 16,
|
||||
},
|
||||
vmRecord: {
|
||||
memory_static_min: 4,
|
||||
memory_dynamic_min: 4,
|
||||
memory_dynamic_max: 4,
|
||||
memory_static_max: 4,
|
||||
},
|
||||
expectedUpdates: [
|
||||
{ field: 'memory_dynamic_min', value: 4 },
|
||||
{ field: 'memory_static_min', value: 4 },
|
||||
{ field: 'memory_dynamic_max', value: 4 },
|
||||
{ field: 'memory_static_max', value: 4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'avoids updating identical value',
|
||||
targetVm: {
|
||||
memory_static_min: 1,
|
||||
memory_dynamic_min: 2,
|
||||
memory_dynamic_max: 4,
|
||||
memory_static_max: 8,
|
||||
},
|
||||
vmRecord: {
|
||||
memory_static_min: 1,
|
||||
memory_dynamic_min: 2,
|
||||
memory_dynamic_max: 4,
|
||||
memory_static_max: 8,
|
||||
},
|
||||
expectedUpdates: [],
|
||||
},
|
||||
]
|
||||
|
||||
describe('updateMemoryFields() should update in the right order', () => {
|
||||
for (const test of tests) {
|
||||
it(test.label, async () => {
|
||||
const mock = mockXapiVm(test.targetVm)
|
||||
test.targetVm.$ref = MOCK_REF
|
||||
await updateMemoryFields(mock, test.targetVm, test.vmRecord)
|
||||
|
||||
assert.equal(mock.updates.length, test.expectedUpdates.length)
|
||||
for (let i = 0; i < mock.updates.length; i++) {
|
||||
assert.equal(mock.updates[i].value, test.expectedUpdates[i].value)
|
||||
assert.equal(mock.updates[i].field, test.expectedUpdates[i].field)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -9,7 +9,9 @@ import { getAdaptersByRemote } from './_getAdaptersByRemote.mjs'
|
||||
import { IncrementalXapi } from './_vmRunners/IncrementalXapi.mjs'
|
||||
import { FullXapi } from './_vmRunners/FullXapi.mjs'
|
||||
import { Throttle } from '@vates/generator-toolbox'
|
||||
import { asyncEach } from '@vates/async-each'
|
||||
import createStreamThrottle from './_createStreamThrottle.mjs'
|
||||
import { selectSynchronizedSnapshotVms } from './_selectSynchronizedSnapshotVms.mjs'
|
||||
|
||||
const noop = Function.prototype
|
||||
|
||||
@@ -31,7 +33,9 @@ const DEFAULT_XAPI_VM_SETTINGS = {
|
||||
nRetriesVmBackupFailures: 0,
|
||||
offlineBackup: false,
|
||||
offlineSnapshot: false,
|
||||
snapshotConcurrency: 2,
|
||||
snapshotRetention: 0,
|
||||
synchronizedSnapshot: false,
|
||||
timeout: 0,
|
||||
useNbd: false,
|
||||
unconditionalSnapshot: false,
|
||||
@@ -47,6 +51,16 @@ export const VmsXapi = class VmsXapiBackupRunner extends Abstract {
|
||||
return baseSettings
|
||||
}
|
||||
|
||||
_getVmBackup(jobMode, opts) {
|
||||
if (jobMode === 'delta') {
|
||||
return new IncrementalXapi(opts)
|
||||
} else if (jobMode === 'full') {
|
||||
return new FullXapi(opts)
|
||||
}
|
||||
|
||||
throw new Error(`Job mode ${jobMode} not implemented`)
|
||||
}
|
||||
|
||||
async run() {
|
||||
const job = this._job
|
||||
|
||||
@@ -95,7 +109,81 @@ export const VmsXapi = class VmsXapiBackupRunner extends Abstract {
|
||||
const allSettings = this._job.settings
|
||||
const baseSettings = this._baseSettings
|
||||
|
||||
const queue = new Set(vmIds)
|
||||
const preTakenTimestampByVmId = {}
|
||||
const failedSnapshotByVmId = {}
|
||||
if (settings.synchronizedSnapshot) {
|
||||
await Task.run({ properties: { name: 'snapshot VMs' } }, async () => {
|
||||
await Disposable.use(
|
||||
Disposable.all(vmIds.map(vmId => this._getRecord('VM', vmId).catch(noop))),
|
||||
async vms => {
|
||||
// remove vms that failed (already handled)
|
||||
vms = vms.filter(_ => _ !== undefined)
|
||||
|
||||
const batchIds = selectSynchronizedSnapshotVms(settings.synchronizedSnapshot, vms)
|
||||
|
||||
await asyncEach(
|
||||
[...vms].filter(vm => batchIds.has(vm.uuid)),
|
||||
async vm => {
|
||||
const vmSettings = { ...settings, ...allSettings[vm.uuid] }
|
||||
const opts = {
|
||||
baseSettings,
|
||||
config,
|
||||
getSnapshotNameLabel,
|
||||
healthCheckSr,
|
||||
job,
|
||||
remoteAdapters,
|
||||
schedule,
|
||||
settings: vmSettings,
|
||||
srs,
|
||||
throttleGenerator,
|
||||
throttleStream,
|
||||
vm,
|
||||
}
|
||||
|
||||
let vmBackup
|
||||
try {
|
||||
vmBackup = this._getVmBackup(job.mode, opts)
|
||||
if (
|
||||
!vmBackup._settings.offlineBackup &&
|
||||
!vmBackup._settings.offlineSnapshot &&
|
||||
(await vmBackup._mustDoSnapshot())
|
||||
) {
|
||||
await vmBackup._prepareAndSnapshot()
|
||||
preTakenTimestampByVmId[vm.uuid] = vmBackup.timestamp
|
||||
}
|
||||
} catch (error) {
|
||||
failedSnapshotByVmId[vm.uuid] = error
|
||||
if (vmBackup !== undefined) {
|
||||
try {
|
||||
await vmBackup._fetchJobSnapshots()
|
||||
await vmBackup._removeUnusedSnapshots()
|
||||
await vmBackup._cleanMetadata()
|
||||
} catch (cleanupError) {
|
||||
// Best effort cleanup
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
concurrency: settings.snapshotConcurrency,
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const snapshotedVmIds = new Set(vmIds.filter(id => !(id in failedSnapshotByVmId)))
|
||||
Object.entries(failedSnapshotByVmId).forEach(([vmId, error]) => {
|
||||
Task.run(
|
||||
{
|
||||
properties: { id: vmId, name: 'backup VM', type: 'VM' },
|
||||
},
|
||||
() => Promise.reject(error)
|
||||
).catch(noop)
|
||||
})
|
||||
|
||||
const queue = new Set(snapshotedVmIds)
|
||||
const taskByVmId = {}
|
||||
const nTriesByVmId = {}
|
||||
|
||||
@@ -155,21 +243,16 @@ export const VmsXapi = class VmsXapiBackupRunner extends Abstract {
|
||||
schedule,
|
||||
settings: vmSettings,
|
||||
srs,
|
||||
// when set, the batch phase already snapshotted this VM; the
|
||||
// runner re-finds that snapshot by its metadata (see _snapshot)
|
||||
synchronizedSnapshotTimestamp: preTakenTimestampByVmId[vmUuid],
|
||||
throttleGenerator,
|
||||
throttleStream,
|
||||
vm,
|
||||
}
|
||||
|
||||
let vmBackup
|
||||
if (job.mode === 'delta') {
|
||||
vmBackup = new IncrementalXapi(opts)
|
||||
} else {
|
||||
if (job.mode === 'full') {
|
||||
vmBackup = new FullXapi(opts)
|
||||
} else {
|
||||
throw new Error(`Job mode ${job.mode} not implemented`)
|
||||
}
|
||||
}
|
||||
const vmBackup = this._getVmBackup(job.mode, opts)
|
||||
|
||||
return vmBackup.run().catch(error => {
|
||||
taskError = error
|
||||
})
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import { asyncMap } from '@xen-orchestra/async-map'
|
||||
import { createLogger } from '@xen-orchestra/log'
|
||||
import { Task } from '@vates/task'
|
||||
|
||||
import { DIR_XO_POOL_METADATA_BACKUPS } from '../RemoteAdapter.mjs'
|
||||
import { forkStreamUnpipe } from './_forkStreamUnpipe.mjs'
|
||||
import { formatFilenameDate } from '../_filenameDate.mjs'
|
||||
|
||||
const { debug } = createLogger('xo:backups:PoolMetadataBackup')
|
||||
|
||||
export const PATH_DB_DUMP = '/pool/xmldbdump'
|
||||
|
||||
export class PoolMetadataBackup {
|
||||
@@ -34,22 +31,7 @@ export class PoolMetadataBackup {
|
||||
const poolDir = `${DIR_XO_POOL_METADATA_BACKUPS}/${schedule.id}/${pool.$id}`
|
||||
const dir = `${poolDir}/${formatFilenameDate(timestamp)}`
|
||||
|
||||
const tasks = Object.values(pool.$xapi.objects.indexes.type.task ?? {})
|
||||
const pendingTasks = tasks.filter(task => task.status === 'pending')
|
||||
debug('pool tasks before export', {
|
||||
total: tasks.length,
|
||||
pending: pendingTasks.length,
|
||||
oldestPendingCreated: pendingTasks.map(task => task.created).sort()[0],
|
||||
})
|
||||
|
||||
const stream = (await this._exportPoolMetadata()).body
|
||||
const startedAt = Date.now()
|
||||
let bytes = 0
|
||||
stream.once('data', () => debug('first byte received', { afterMs: Date.now() - startedAt }))
|
||||
stream.on('data', chunk => (bytes += chunk.length))
|
||||
stream.once('end', () => debug('stream ended', { bytes, durationMs: Date.now() - startedAt }))
|
||||
stream.once('error', error => debug('stream errored', { error, bytes, durationMs: Date.now() - startedAt }))
|
||||
|
||||
const fileName = `${dir}/data`
|
||||
|
||||
const metadata = JSON.stringify(
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Task } from '@vates/task'
|
||||
|
||||
export function selectSynchronizedSnapshotVms(synchronizedSnapshot, vms) {
|
||||
if (synchronizedSnapshot === false) {
|
||||
return new Set()
|
||||
}
|
||||
|
||||
let matches
|
||||
if (typeof synchronizedSnapshot === 'string') {
|
||||
matches = vms.filter(vm => vm.tags.includes(synchronizedSnapshot))
|
||||
} else if (synchronizedSnapshot === true) {
|
||||
matches = vms
|
||||
} else {
|
||||
Task.warning('unsupported synchronizedSnapshot option, expected a boolean or a tag name', { synchronizedSnapshot })
|
||||
return new Set()
|
||||
}
|
||||
|
||||
return new Set(matches.map(vm => vm.uuid))
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { describe, it } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
|
||||
import { selectSynchronizedSnapshotVms } from './_selectSynchronizedSnapshotVms.mjs'
|
||||
|
||||
// Helper mirroring the minimal VM record shape the selector needs.
|
||||
const vm = (uuid, tags = []) => ({ uuid, tags })
|
||||
|
||||
// `selectSynchronizedSnapshotVms(synchronizedSnapshot, vms)` must return a Set
|
||||
// of the UUIDs that should be snapshotted together in the batch phase.
|
||||
// - false -> feature off, no VM is batched
|
||||
// - true -> every VM is batched, regardless of tags
|
||||
// - '<tagName>' -> only VMs carrying that exact tag are batched
|
||||
const tests = [
|
||||
{
|
||||
label: 'false disables batching',
|
||||
setting: false,
|
||||
vms: [vm('a', ['prod']), vm('b')],
|
||||
expected: [],
|
||||
},
|
||||
{
|
||||
label: 'true batches every VM regardless of tags',
|
||||
setting: true,
|
||||
vms: [vm('a', ['prod']), vm('b')],
|
||||
expected: ['a', 'b'],
|
||||
},
|
||||
{
|
||||
label: 'tag batches only the VMs carrying it',
|
||||
setting: 'prod',
|
||||
vms: [vm('a', ['prod']), vm('b', ['dev']), vm('c', ['prod', 'other'])],
|
||||
expected: ['a', 'c'],
|
||||
},
|
||||
{
|
||||
label: 'tag carried by no VM yields an empty batch',
|
||||
setting: 'prod',
|
||||
vms: [vm('a', ['dev']), vm('b')],
|
||||
expected: [],
|
||||
},
|
||||
{
|
||||
label: 'tag carried by every VM batches all of them',
|
||||
setting: 'prod',
|
||||
vms: [vm('a', ['prod']), vm('b', ['prod'])],
|
||||
expected: ['a', 'b'],
|
||||
},
|
||||
{
|
||||
label: 'empty VM list yields an empty batch',
|
||||
setting: true,
|
||||
vms: [],
|
||||
expected: [],
|
||||
},
|
||||
{
|
||||
label: 'empty-string tag matches no VM when none carries an empty tag',
|
||||
setting: '',
|
||||
vms: [vm('a', ['prod'])],
|
||||
expected: [],
|
||||
},
|
||||
{
|
||||
label: 'non boolean or string yiels an empty batch',
|
||||
setting: 1,
|
||||
vms: [vm('a', ['prod'])],
|
||||
expected: [],
|
||||
},
|
||||
]
|
||||
|
||||
describe('selectSynchronizedSnapshotVms()', () => {
|
||||
for (const { label, setting, vms, expected } of tests) {
|
||||
it(label, () => {
|
||||
const result = selectSynchronizedSnapshotVms(setting, vms)
|
||||
assert.ok(result instanceof Set, 'result must be a Set')
|
||||
assert.deepEqual([...result].sort(), [...expected].sort())
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -80,14 +80,14 @@ export const FullXapi = class FullXapiVmBackupRunner extends AbstractXapi {
|
||||
|
||||
const sizeContainer = watchStreamSize(stream)
|
||||
|
||||
const timestamp = Date.now()
|
||||
const transferStart = Date.now()
|
||||
await this._callWriters(
|
||||
writer =>
|
||||
writer.run({
|
||||
maxStreamLength,
|
||||
sizeContainer,
|
||||
stream: forkStreamUnpipe(stream),
|
||||
timestamp,
|
||||
timestamp: this.timestamp,
|
||||
vm,
|
||||
vmSnapshot: exportedVm,
|
||||
}),
|
||||
@@ -95,8 +95,7 @@ export const FullXapi = class FullXapiVmBackupRunner extends AbstractXapi {
|
||||
)
|
||||
|
||||
const { size } = sizeContainer
|
||||
const end = Date.now()
|
||||
const duration = end - timestamp
|
||||
const duration = Date.now() - transferStart
|
||||
debug('transfer complete', {
|
||||
duration,
|
||||
speed: duration !== 0 ? (size * 1e3) / 1024 / 1024 / duration : 0,
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
DELTA_CHAIN_LENGTH,
|
||||
EXPORTED_SUCCESSFULLY,
|
||||
setVmDeltaChainLength,
|
||||
markExportSuccessfull,
|
||||
} from '../../_otherConfig.mjs'
|
||||
import { ThrottledDisk, SynchronizedDisk } from '@xen-orchestra/disk-transform'
|
||||
import { AggregatedIncrementalRemoteWriter } from '../_writers/AggregatedIncrementalRemoteWriter.mjs'
|
||||
@@ -68,7 +67,6 @@ export const IncrementalXapi = class IncrementalXapiVmBackupRunner extends Abstr
|
||||
|
||||
// @todo : reimplement throttle
|
||||
|
||||
const timestamp = Date.now()
|
||||
await this._callWriters(
|
||||
writer =>
|
||||
writer.transfer({
|
||||
@@ -78,7 +76,7 @@ export const IncrementalXapi = class IncrementalXapiVmBackupRunner extends Abstr
|
||||
// clean; the tip's flag lets the next run skip re-probing
|
||||
includeNonNbdQcow2Fix: true,
|
||||
isVhdDifferencing,
|
||||
timestamp,
|
||||
timestamp: this.timestamp,
|
||||
vm,
|
||||
vmSnapshot: exportedVm,
|
||||
}),
|
||||
@@ -91,11 +89,6 @@ export const IncrementalXapi = class IncrementalXapiVmBackupRunner extends Abstr
|
||||
await setVmDeltaChainLength(this._xapi, exportedVm.$ref, (this._deltaChainLength ?? 0) + 1)
|
||||
}
|
||||
|
||||
// not the case if offlineBackup
|
||||
if (exportedVm.is_a_snapshot) {
|
||||
await markExportSuccessfull(this._xapi, exportedVm.$ref)
|
||||
}
|
||||
|
||||
await this._callWriters(writer => writer.cleanup(), 'writer.cleanup()')
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { asyncEach } from '@vates/async-each'
|
||||
import { decorateMethodsWith } from '@vates/decorate-with'
|
||||
import { defer } from 'golike-defer'
|
||||
import { Task } from '@vates/task'
|
||||
import { formatDateTime } from '@xen-orchestra/xapi'
|
||||
|
||||
import { getOldEntries } from '../../_getOldEntries.mjs'
|
||||
import { Abstract } from './_Abstract.mjs'
|
||||
@@ -16,9 +17,11 @@ import {
|
||||
JOB_ID,
|
||||
SCHEDULE_ID,
|
||||
VM_UUID,
|
||||
EXPORTED_SUCCESSFULLY,
|
||||
resetVmOtherConfig,
|
||||
setVmOtherConfig,
|
||||
setVmSnapshotContentKeys,
|
||||
markExportSuccessfull,
|
||||
} from '../../_otherConfig.mjs'
|
||||
|
||||
const { warn, info } = createLogger('xo:backups:AbstractXapi')
|
||||
@@ -36,6 +39,7 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
|
||||
schedule,
|
||||
settings,
|
||||
srs,
|
||||
synchronizedSnapshotTimestamp,
|
||||
throttleGenerator,
|
||||
throttleStream,
|
||||
vm,
|
||||
@@ -63,9 +67,9 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
|
||||
|
||||
// VM (snapshot) that is really exported
|
||||
this._exportedVm = undefined
|
||||
this._synchronizedSnapshotTimestamp = synchronizedSnapshotTimestamp
|
||||
this._vm = vm
|
||||
|
||||
this._baseVdis = undefined
|
||||
this._getSnapshotNameLabel = getSnapshotNameLabel
|
||||
this._isIncremental = job.mode === 'delta'
|
||||
this._healthCheckSr = healthCheckSr
|
||||
@@ -180,6 +184,27 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
|
||||
}
|
||||
|
||||
async _snapshot() {
|
||||
if (this._synchronizedSnapshotTimestamp !== undefined) {
|
||||
const datetime = formatDateTime(this._synchronizedSnapshotTimestamp)
|
||||
const candidates = this._vm.$snapshots.filter(
|
||||
snapshot =>
|
||||
snapshot?.other_config[JOB_ID] === this._jobId &&
|
||||
snapshot?.other_config[DATETIME] === datetime &&
|
||||
!snapshot.other_config[EXPORTED_SUCCESSFULLY]
|
||||
)
|
||||
if (candidates.length === 1) {
|
||||
this._exportedVm = candidates[0]
|
||||
this.timestamp = this._synchronizedSnapshotTimestamp
|
||||
return
|
||||
}
|
||||
|
||||
warn('expected exactly one synchronized snapshot to reuse, taking a fresh one', {
|
||||
vm: this._vm.uuid,
|
||||
datetime,
|
||||
count: candidates.length,
|
||||
})
|
||||
}
|
||||
|
||||
const vm = this._vm
|
||||
const xapi = this._xapi
|
||||
|
||||
@@ -396,7 +421,20 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
|
||||
await xapi.barrier()
|
||||
// ensure cached object are up to date
|
||||
this._jobSnapshotVdis = this._jobSnapshotVdis.map(vdi => xapi.getObject(vdi.$ref))
|
||||
const disklessVmSnapshots = this._disklessJobSnapshotVms.map(vm => xapi.getObject(vm.$ref))
|
||||
let disklessVmSnapshots = this._disklessJobSnapshotVms.map(vm => xapi.getObject(vm.$ref))
|
||||
|
||||
// The synchronized snapshot for this run is taken up-front by the batch
|
||||
// phase but transferred later. Until it has been exported, hide it from
|
||||
// retention so it is neither reclaimed nor allowed to steal the delta
|
||||
// base's "most recent" protection. This makes the pre-transfer pass behave
|
||||
// like a normal run, where the snapshot does not exist yet.
|
||||
if (this._synchronizedSnapshotTimestamp !== undefined) {
|
||||
const datetime = formatDateTime(this._synchronizedSnapshotTimestamp)
|
||||
const isInFlightSyncSnapshot = ({ other_config }) =>
|
||||
other_config[DATETIME] === datetime && !other_config[EXPORTED_SUCCESSFULLY]
|
||||
this._jobSnapshotVdis = this._jobSnapshotVdis.filter(vdi => !isInFlightSyncSnapshot(vdi))
|
||||
disklessVmSnapshots = disklessVmSnapshots.filter(vm => !isInFlightSyncSnapshot(vm))
|
||||
}
|
||||
|
||||
// get the datetime of the most recent snapshot across both VDI and diskless VM snapshots
|
||||
const lastSnapshotDateTime = [...this._jobSnapshotVdis, ...disklessVmSnapshots]
|
||||
@@ -464,6 +502,7 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
|
||||
vm = vdiVm
|
||||
}
|
||||
}
|
||||
|
||||
if (vm?.$ref !== undefined) {
|
||||
return xapi.VM_destroy(vm.$ref)
|
||||
} else {
|
||||
@@ -493,6 +532,7 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
|
||||
if (this.job.mode === 'delta' && datetime === lastSnapshotDateTime) {
|
||||
return
|
||||
}
|
||||
|
||||
await xapi.VM_destroy(snapshotPerDatetime[datetime])
|
||||
})
|
||||
})
|
||||
@@ -551,6 +591,11 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
|
||||
async _prepareAndSnapshot() {
|
||||
await this._cleanMetadata()
|
||||
await this._snapshot()
|
||||
}
|
||||
|
||||
async run($defer) {
|
||||
const settings = this._settings
|
||||
assert(
|
||||
@@ -614,6 +659,10 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
|
||||
if (this._writers.size !== 0) {
|
||||
await this._copy()
|
||||
}
|
||||
// not the case if offlineBackup
|
||||
if (this._exportedVm.is_a_snapshot) {
|
||||
await markExportSuccessfull(this._xapi, this._exportedVm.$ref)
|
||||
}
|
||||
} finally {
|
||||
if (startAfter) {
|
||||
ignoreErrors.call(vm.$callAsync('start', false, false))
|
||||
|
||||
@@ -0,0 +1,327 @@
|
||||
import { describe, it } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
|
||||
import { formatDateTime } from '@xen-orchestra/xapi'
|
||||
|
||||
import { AbstractXapi } from './_AbstractXapi.mjs'
|
||||
import { DATETIME, EXPORTED_SUCCESSFULLY, JOB_ID, SCHEDULE_ID } from '../../_otherConfig.mjs'
|
||||
|
||||
// Build an AbstractXapi instance without running its heavy constructor, then
|
||||
// assign only the fields the method under test reads.
|
||||
const makeRunner = props => Object.assign(Object.create(AbstractXapi.prototype), props)
|
||||
|
||||
describe('_snapshot() synchronized-snapshot reuse guard', () => {
|
||||
it('reuses the batch snapshot, found by metadata, without snapshotting again', async () => {
|
||||
let mustDoSnapshotCalls = 0
|
||||
const timestamp = 1717200000000
|
||||
// the snapshot taken by the batch phase, identified by its other_config
|
||||
const batchSnapshot = {
|
||||
$ref: 'batch-snapshot-ref',
|
||||
other_config: {
|
||||
[JOB_ID]: 'job-1',
|
||||
[SCHEDULE_ID]: 'schedule-1',
|
||||
[DATETIME]: formatDateTime(timestamp),
|
||||
},
|
||||
}
|
||||
const runner = makeRunner({
|
||||
_synchronizedSnapshotTimestamp: timestamp,
|
||||
_jobId: 'job-1',
|
||||
scheduleId: 'schedule-1',
|
||||
_vm: { uuid: 'vm-uuid', $snapshots: [batchSnapshot] },
|
||||
_xapi: { barrier: async () => {} },
|
||||
// if the metadata lookup fails, _snapshot() falls through to here
|
||||
_mustDoSnapshot: async () => {
|
||||
mustDoSnapshotCalls++
|
||||
return false
|
||||
},
|
||||
})
|
||||
|
||||
await runner._snapshot()
|
||||
|
||||
assert.equal(mustDoSnapshotCalls, 0, '_mustDoSnapshot() should not be called when the batch snapshot is found')
|
||||
assert.equal(runner._exportedVm, batchSnapshot, '_exportedVm should be the batch snapshot found by metadata')
|
||||
assert.equal(runner.timestamp, timestamp, 'timestamp should be the synchronized snapshot timestamp')
|
||||
})
|
||||
|
||||
it('takes a fresh snapshot when the batch snapshot can no longer be found', async () => {
|
||||
let mustDoSnapshotCalls = 0
|
||||
const vm = { uuid: 'vm-uuid', $snapshots: [] } // the pre-taken snapshot is gone
|
||||
const runner = makeRunner({
|
||||
_synchronizedSnapshotTimestamp: 1717200000000,
|
||||
_jobId: 'job-1',
|
||||
scheduleId: 'schedule-1',
|
||||
_vm: vm,
|
||||
_xapi: { barrier: async () => {} },
|
||||
_settings: {},
|
||||
_mustDoSnapshot: async () => {
|
||||
mustDoSnapshotCalls++
|
||||
return false
|
||||
},
|
||||
})
|
||||
|
||||
await runner._snapshot()
|
||||
|
||||
assert.equal(
|
||||
mustDoSnapshotCalls,
|
||||
1,
|
||||
'a missing batch snapshot should not be reused: _snapshot() should fall through and take a fresh snapshot'
|
||||
)
|
||||
assert.equal(
|
||||
runner._exportedVm,
|
||||
vm,
|
||||
'_exportedVm should fall back to the freshly-snapshotted VM, not stay on a stale/missing batch snapshot'
|
||||
)
|
||||
})
|
||||
|
||||
it('without a pre-taken snapshot and no snapshot needed, exports the live VM', async () => {
|
||||
let mustDoSnapshotCalls = 0
|
||||
const vm = { uuid: 'vm-uuid' }
|
||||
const runner = makeRunner({
|
||||
_exportedVm: undefined,
|
||||
_vm: vm,
|
||||
_xapi: {},
|
||||
_settings: {},
|
||||
_mustDoSnapshot: async () => {
|
||||
mustDoSnapshotCalls++
|
||||
return false
|
||||
},
|
||||
})
|
||||
|
||||
await runner._snapshot()
|
||||
|
||||
assert.equal(mustDoSnapshotCalls, 1, '_mustDoSnapshot() should be called once when no snapshot was pre-taken')
|
||||
assert.equal(runner._exportedVm, vm, '_exportedVm should be set to the live VM when no snapshot is needed')
|
||||
assert.equal(typeof runner.timestamp, 'number', 'timestamp should be set when exporting the live VM')
|
||||
})
|
||||
})
|
||||
|
||||
describe('_removeUnusedSnapshots() protects the pre-taken synchronized snapshot', () => {
|
||||
const OLD_DATETIME = '20240101T00:00:00Z'
|
||||
const SYNC_TIMESTAMP = 1717200000000
|
||||
const SYNC_DATETIME = formatDateTime(SYNC_TIMESTAMP)
|
||||
|
||||
// Full-mode job with snapshotRetention 0, so retention wants to remove every
|
||||
// snapshot. `vdi-fresh` is the snapshot taken up-front by the synchronized
|
||||
// batch phase (identified by SYNC_TIMESTAMP); until it is exported it must be
|
||||
// hidden from retention.
|
||||
const makeRemoveRunner = ({ exported }) => {
|
||||
const destroyed = []
|
||||
|
||||
const snapshotVm = ($ref, name_label) => ({
|
||||
$ref,
|
||||
name_label,
|
||||
is_control_domain: false,
|
||||
$snapshot_of: 'live-vm-ref',
|
||||
other_config: {},
|
||||
})
|
||||
const freshSnapshotVm = snapshotVm('vm-fresh', 'fresh')
|
||||
const oldSnapshotVm = snapshotVm('vm-old', 'old')
|
||||
|
||||
const vdi = ($ref, datetime, snapshotVmRecord, isExported = false) => ({
|
||||
$ref,
|
||||
other_config: {
|
||||
[DATETIME]: datetime,
|
||||
[SCHEDULE_ID]: 'schedule-1',
|
||||
...(isExported ? { [EXPORTED_SUCCESSFULLY]: 'true' } : {}),
|
||||
},
|
||||
$VBDs: [{ $VM: snapshotVmRecord }],
|
||||
})
|
||||
// the old snapshot was exported by a previous run
|
||||
const oldVdi = vdi('vdi-old', OLD_DATETIME, oldSnapshotVm, true)
|
||||
const freshVdi = vdi('vdi-fresh', SYNC_DATETIME, freshSnapshotVm, exported)
|
||||
|
||||
const registry = { 'vdi-old': oldVdi, 'vdi-fresh': freshVdi }
|
||||
|
||||
const runner = makeRunner({
|
||||
_synchronizedSnapshotTimestamp: SYNC_TIMESTAMP,
|
||||
_vm: { uuid: 'live-uuid', $snapshots: [] },
|
||||
_baseSettings: { snapshotRetention: 0 },
|
||||
_jobSnapshotVdis: [oldVdi, freshVdi],
|
||||
_disklessJobSnapshotVms: [],
|
||||
job: { mode: 'full', settings: {} },
|
||||
_xapi: {
|
||||
barrier: async () => {},
|
||||
getObject: ref => registry[ref],
|
||||
VM_destroy: async ref => {
|
||||
destroyed.push(ref)
|
||||
},
|
||||
VDI_destroy: async ref => {
|
||||
destroyed.push(ref)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return { runner, destroyed }
|
||||
}
|
||||
|
||||
it('does not destroy the pre-taken snapshot before it has been transferred', async () => {
|
||||
const { runner, destroyed } = makeRemoveRunner({ exported: false })
|
||||
|
||||
await runner._removeUnusedSnapshots()
|
||||
|
||||
assert.deepEqual(destroyed, ['vm-old'], 'only the older snapshot should be removed')
|
||||
})
|
||||
|
||||
it('destroys the snapshot once it has been transferred (retention 0, no leak)', async () => {
|
||||
const { runner, destroyed } = makeRemoveRunner({ exported: true })
|
||||
|
||||
await runner._removeUnusedSnapshots()
|
||||
|
||||
assert.deepEqual(destroyed.sort(), ['vm-fresh', 'vm-old'], 'both snapshots should be removed after transfer')
|
||||
})
|
||||
})
|
||||
|
||||
describe('_removeUnusedSnapshots() in the synchronized batch pre-snapshot state', () => {
|
||||
it('delta mode: keeps the base snapshot when the fresh one has not been taken yet', async () => {
|
||||
const destroyed = []
|
||||
|
||||
const BASE_DATETIME = '2024-06-01T00:00:00Z'
|
||||
const baseSnapshotVm = {
|
||||
$ref: 'vm-base',
|
||||
name_label: 'base',
|
||||
is_control_domain: false,
|
||||
$snapshot_of: 'live-vm-ref',
|
||||
// exported successfully by the previous run
|
||||
other_config: { [EXPORTED_SUCCESSFULLY]: 'true' },
|
||||
}
|
||||
const baseVdi = {
|
||||
$ref: 'vdi-base',
|
||||
other_config: { [DATETIME]: BASE_DATETIME, [SCHEDULE_ID]: 'schedule-1' },
|
||||
$VBDs: [{ $VM: baseSnapshotVm }],
|
||||
}
|
||||
const registry = { 'vdi-base': baseVdi }
|
||||
|
||||
const runner = makeRunner({
|
||||
// batch pre-snapshot state: the synchronized snapshot has not been taken yet
|
||||
_exportedVm: undefined,
|
||||
_vm: { uuid: 'live-uuid', $snapshots: [] },
|
||||
_baseSettings: { snapshotRetention: 0 },
|
||||
_jobSnapshotVdis: [baseVdi],
|
||||
_disklessJobSnapshotVms: [],
|
||||
job: { mode: 'delta', settings: {} },
|
||||
_xapi: {
|
||||
barrier: async () => {},
|
||||
getObject: ref => registry[ref],
|
||||
VM_destroy: async ref => {
|
||||
destroyed.push(ref)
|
||||
},
|
||||
VDI_destroy: async ref => {
|
||||
destroyed.push(ref)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await runner._removeUnusedSnapshots()
|
||||
|
||||
assert.deepEqual(destroyed, [], 'the base snapshot must be kept as the delta base for the upcoming transfer')
|
||||
})
|
||||
})
|
||||
|
||||
describe('_removeUnusedSnapshots() reclaims orphan / CBT snapshot VDIs (no attached VM)', () => {
|
||||
// Guards the `else` branch: snapshot VDIs that are not attached to any user VM
|
||||
// (e.g. CBT metadata, orphans) must be reclaimed via VDI_destroy, not VM_destroy.
|
||||
it('destroys snapshot VDIs directly when they are not attached to any VM', async () => {
|
||||
const vmDestroyed = []
|
||||
const vdiDestroyed = []
|
||||
|
||||
const orphanVdi = {
|
||||
$ref: 'vdi-orphan',
|
||||
other_config: { [DATETIME]: '2024-01-01T00:00:00Z', [SCHEDULE_ID]: 'schedule-1' },
|
||||
$VBDs: [], // not attached to any VM
|
||||
}
|
||||
const registry = { 'vdi-orphan': orphanVdi }
|
||||
|
||||
const runner = makeRunner({
|
||||
_exportedVm: undefined,
|
||||
_vm: { uuid: 'live-uuid', $snapshots: [] },
|
||||
_baseSettings: { snapshotRetention: 0 },
|
||||
_jobSnapshotVdis: [orphanVdi],
|
||||
_disklessJobSnapshotVms: [],
|
||||
job: { mode: 'full', settings: {} },
|
||||
_xapi: {
|
||||
barrier: async () => {},
|
||||
getObject: ref => registry[ref],
|
||||
VM_destroy: async ref => {
|
||||
vmDestroyed.push(ref)
|
||||
},
|
||||
VDI_destroy: async ref => {
|
||||
vdiDestroyed.push(ref)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await runner._removeUnusedSnapshots()
|
||||
|
||||
assert.deepEqual(vdiDestroyed, ['vdi-orphan'], 'the orphan VDI should be reclaimed via VDI_destroy')
|
||||
assert.deepEqual(vmDestroyed, [], 'no VM should be destroyed for an orphan VDI')
|
||||
})
|
||||
})
|
||||
|
||||
describe('_removeUnusedSnapshots() diskless VM snapshots', () => {
|
||||
const OLD_DATETIME = '20240101T00:00:00Z'
|
||||
const SYNC_TIMESTAMP = 1717200000000
|
||||
const SYNC_DATETIME = formatDateTime(SYNC_TIMESTAMP)
|
||||
|
||||
// A diskless VM's backup snapshots are tracked as VM snapshots (no VDIs to
|
||||
// anchor them). `dl-fresh` is the snapshot the synchronized batch pre-took,
|
||||
// identified by SYNC_TIMESTAMP.
|
||||
const makeDisklessRunner = ({ exported, mode = 'full' }) => {
|
||||
const destroyed = []
|
||||
|
||||
const oldSnap = { $ref: 'dl-old', other_config: { [DATETIME]: OLD_DATETIME, [SCHEDULE_ID]: 'schedule-1' } }
|
||||
const freshSnap = { $ref: 'dl-fresh', other_config: { [DATETIME]: SYNC_DATETIME, [SCHEDULE_ID]: 'schedule-1' } }
|
||||
if (exported) freshSnap.other_config[EXPORTED_SUCCESSFULLY] = 'true'
|
||||
const registry = { 'dl-old': oldSnap, 'dl-fresh': freshSnap }
|
||||
|
||||
const runner = makeRunner({
|
||||
_synchronizedSnapshotTimestamp: SYNC_TIMESTAMP,
|
||||
_vm: { uuid: 'live-uuid', $snapshots: [] },
|
||||
_baseSettings: { snapshotRetention: 0 },
|
||||
_jobSnapshotVdis: [],
|
||||
_disklessJobSnapshotVms: [oldSnap, freshSnap],
|
||||
job: { mode, settings: {} },
|
||||
_xapi: {
|
||||
barrier: async () => {},
|
||||
getObject: ref => registry[ref],
|
||||
VM_destroy: async ref => {
|
||||
destroyed.push(ref)
|
||||
},
|
||||
VDI_destroy: async ref => {
|
||||
destroyed.push(ref)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return { runner, destroyed }
|
||||
}
|
||||
|
||||
it('does not destroy the pre-taken diskless snapshot before it has been transferred', async () => {
|
||||
const { runner, destroyed } = makeDisklessRunner({ exported: false })
|
||||
|
||||
await runner._removeUnusedSnapshots()
|
||||
|
||||
assert.deepEqual(destroyed, ['dl-old'], 'only the older diskless snapshot should be removed')
|
||||
})
|
||||
|
||||
it('destroys the diskless snapshot once it has been transferred (retention 0, no leak)', async () => {
|
||||
const { runner, destroyed } = makeDisklessRunner({ exported: true })
|
||||
|
||||
await runner._removeUnusedSnapshots()
|
||||
|
||||
assert.deepEqual(
|
||||
destroyed.sort(),
|
||||
['dl-fresh', 'dl-old'],
|
||||
'both diskless snapshots should be removed after transfer'
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps the most recent diskless snapshot in delta mode (base for next delta)', async () => {
|
||||
// Even after transfer (exported), delta mode must retain the latest
|
||||
// snapshot so the next run can compute its delta against it.
|
||||
const { runner, destroyed } = makeDisklessRunner({ exported: true, mode: 'delta' })
|
||||
|
||||
await runner._removeUnusedSnapshots()
|
||||
|
||||
assert.deepEqual(destroyed, ['dl-old'], 'the most recent diskless snapshot should be kept in delta mode')
|
||||
})
|
||||
})
|
||||
@@ -8,7 +8,7 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||
},
|
||||
"version": "0.73.10",
|
||||
"version": "0.74.0",
|
||||
"engines": {
|
||||
"node": ">=14.18"
|
||||
},
|
||||
@@ -30,8 +30,8 @@
|
||||
"@vates/nbd-client": "^3.5.0",
|
||||
"@vates/parse-duration": "^0.1.1",
|
||||
"@vates/task": "^0.7.0",
|
||||
"@vates/types": "^1.30.1",
|
||||
"@xen-orchestra/async-map": "^0.1.3",
|
||||
"@vates/types": "^1.30.2",
|
||||
"@xen-orchestra/async-map": "^0.1.4",
|
||||
"@xen-orchestra/disk-transform": "^1.3.3",
|
||||
"@xen-orchestra/fs": "^4.9.3",
|
||||
"@xen-orchestra/log": "^0.7.2",
|
||||
@@ -52,11 +52,11 @@
|
||||
"parse-pairs": "^2.0.0",
|
||||
"promise-toolbox": "^0.21.0",
|
||||
"proper-lockfile": "^4.1.2",
|
||||
"tar": "^7.5.3",
|
||||
"tar": "^7.5.19",
|
||||
"uuid": "^9.0.0",
|
||||
"value-matcher": "^0.2.0",
|
||||
"vhd-lib": "^4.16.1",
|
||||
"xen-api": "^4.8.0",
|
||||
"xen-api": "^4.9.0",
|
||||
"yazl": "^2.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -67,7 +67,7 @@
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@xen-orchestra/xapi": "^8.10.2"
|
||||
"@xen-orchestra/xapi": "^8.10.3"
|
||||
},
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"author": {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"preferGlobal": true,
|
||||
"dependencies": {
|
||||
"golike-defer": "^0.5.1",
|
||||
"xen-api": "^4.8.0"
|
||||
"xen-api": "^4.9.0"
|
||||
},
|
||||
"scripts": {
|
||||
"postversion": "npm publish"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "@xen-orchestra/disk-cli",
|
||||
"version": "2.2.0",
|
||||
"version": "2.2.1",
|
||||
"description": "CLI tool to inspect and manage disks in backup repositories",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
@@ -20,7 +20,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@vates/async-each": "^1.0.3",
|
||||
"@xen-orchestra/backups": "^0.73.10",
|
||||
"@xen-orchestra/backups": "^0.74.0",
|
||||
"@xen-orchestra/backup-archive": "^1.1.1",
|
||||
"@xen-orchestra/disk-transform": "^1.3.3",
|
||||
"@xen-orchestra/fs": "^4.9.3",
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
"xo-immutable-remote": "./dist/protectRemotes.mjs"
|
||||
},
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"version": "3.0.4",
|
||||
"version": "3.0.5",
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vates/async-each": "^1.0.3",
|
||||
"@xen-orchestra/backups": "^0.73.10",
|
||||
"@xen-orchestra/backups": "^0.74.0",
|
||||
"@xen-orchestra/log": "^0.7.2",
|
||||
"app-conf": "^3.0.0",
|
||||
"execa": "^5.0.0",
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# ChangeLog
|
||||
|
||||
## **next**
|
||||
|
||||
- [Pool/networks] Add the possibility to create new network or bonded network (PR [#10145](https://github.com/vatesfr/xen-orchestra/pull/10145))
|
||||
- [VM] Add VDIs page with table and side panel (PR [#10269](https://github.com/vatesfr/xen-orchestra/pull/10269))
|
||||
- [Host/Network] Add ability to rescan physical network interfaces (PIFs) (PR [#10147](https://github.com/vatesfr/xen-orchestra/pull/10147))
|
||||
- Fix inconsistent spacing in side panel cards (PR [#10279](https://github.com/vatesfr/xen-orchestra/pull/10279))
|
||||
- Fix missing collapse buttons on pools and hosts in the tree sidebar (added `hasChildren` prop) (PR [#10244](https://github.com/vatesfr/xen-orchestra/pull/10244))
|
||||
|
||||
## **0.24.0** (2026-07-30)
|
||||
|
||||
- [Host/dashboard] Switch CPU and RAM panels order to match Pool dashboard layout (PR [#10059](https://github.com/vatesfr/xen-orchestra/pull/10059))
|
||||
|
||||
@@ -23,18 +23,21 @@
|
||||
"@fortawesome/vue-fontawesome": "^3.0.8",
|
||||
"@intlify/unplugin-vue-i18n": "^6.0.3",
|
||||
"@novnc/novnc": "~1.5.0",
|
||||
"@regle/core": "^1.26.0",
|
||||
"@regle/rules": "^1.26.0",
|
||||
"@regle/schemas": "^1.20.2",
|
||||
"@tsconfig/node18": "^18.2.4",
|
||||
"@types/d3-time-format": "^4.0.3",
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^18.19.80",
|
||||
"@vates/types": "^1.30.1",
|
||||
"@vates/types": "^1.30.2",
|
||||
"@vitejs/plugin-vue": "^6.0.0",
|
||||
"@vue/tsconfig": "^0.9.0",
|
||||
"@vueuse/core": "^14.0.0",
|
||||
"@vueuse/math": "^14.0.0",
|
||||
"@vueuse/shared": "^14.0.0",
|
||||
"@xen-orchestra/web-core": "^0.58.0",
|
||||
"@xen-orchestra/web-core": "^0.59.0",
|
||||
"complex-matcher": "^1.1.1",
|
||||
"d3-time-format": "^4.1.0",
|
||||
"decorator-synchronized": "^0.6.0",
|
||||
|
||||
@@ -19,13 +19,13 @@
|
||||
import AppHeader from '@/components/AppHeader.vue'
|
||||
import AppLogin from '@/components/AppLogin.vue'
|
||||
import AppNavigation from '@/components/AppNavigation.vue'
|
||||
import { useUnreachableHosts } from '@/composables/unreachable-hosts.composable'
|
||||
import { usePoolStore } from '@/stores/xen-api/pool.store'
|
||||
import { useXenApiStore } from '@/stores/xen-api.store'
|
||||
import { useUnreachableHosts } from '@/composables/unreachable-hosts.composable.ts'
|
||||
import { usePoolStore } from '@/stores/xen-api/pool.store.ts'
|
||||
import { useXenApiStore } from '@/stores/xen-api.store.ts'
|
||||
import VtsOverlayList from '@core/components/overlay/VtsOverlayList.vue'
|
||||
import VtsTooltipList from '@core/components/tooltip-list/VtsTooltipList.vue'
|
||||
import { useChartTheme } from '@core/composables/chart-theme.composable'
|
||||
import { useUiStore } from '@core/stores/ui.store'
|
||||
import { useChartTheme } from '@core/composables/chart-theme.composable.ts'
|
||||
import { useUiStore } from '@core/stores/ui.store.ts'
|
||||
import { useActiveElement, useMagicKeys, whenever } from '@vueuse/core'
|
||||
import { logicAnd } from '@vueuse/math'
|
||||
import { computed } from 'vue'
|
||||
|
||||
@@ -26,11 +26,11 @@
|
||||
import AccountMenu from '@/components/account-menu/AccountMenu.vue'
|
||||
import PoolOverrideWarning from '@/components/PoolOverrideWarning.vue'
|
||||
import XoaButton from '@/components/XoaButton.vue'
|
||||
import { useNavigationStore } from '@/stores/navigation.store'
|
||||
import { useNavigationStore } from '@/stores/navigation.store.ts'
|
||||
import { usePoolStore } from '@/stores/xen-api/pool.store.ts'
|
||||
import UiButtonIcon from '@core/components/ui/button-icon/UiButtonIcon.vue'
|
||||
import UiLogoText from '@core/components/ui/logo-text/UiLogoText.vue'
|
||||
import { useUiStore } from '@core/stores/ui.store'
|
||||
import { useUiStore } from '@core/stores/ui.store.ts'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@@ -42,9 +42,9 @@
|
||||
import FormInput from '@/components/form/FormInput.vue'
|
||||
import LoginError from '@/components/LoginError.vue'
|
||||
import PoolOverrideWarning from '@/components/PoolOverrideWarning.vue'
|
||||
import type { XenApiError } from '@/libs/xen-api/xen-api.types'
|
||||
import { usePageTitleStore } from '@/stores/page-title.store'
|
||||
import { useXenApiStore } from '@/stores/xen-api.store'
|
||||
import type { XenApiError } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import { usePageTitleStore } from '@/stores/page-title.store.ts'
|
||||
import { useXenApiStore } from '@/stores/xen-api.store.ts'
|
||||
import VtsIcon from '@core/components/icon/VtsIcon.vue'
|
||||
import VtsInputWrapper from '@core/components/input-wrapper/VtsInputWrapper.vue'
|
||||
import UiButton from '@core/components/ui/button/UiButton.vue'
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import markdown from '@/libs/markdown'
|
||||
import markdown from '@/libs/markdown.ts'
|
||||
import { useEventListener } from '@vueuse/core'
|
||||
import { computed, type Ref, ref } from 'vue'
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<script lang="ts" setup>
|
||||
import StoryMenu from '@/components/component-story/StoryMenu.vue'
|
||||
import InfraPoolList from '@/components/infra/InfraPoolList.vue'
|
||||
import { useNavigationStore } from '@/stores/navigation.store'
|
||||
import { useNavigationStore } from '@/stores/navigation.store.ts'
|
||||
import { onClickOutside, tryOnMounted, useTimeoutFn, watchImmediate } from '@vueuse/core'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { type AcceptedLanguage, highlight } from '@/libs/highlight'
|
||||
import { type AcceptedLanguage, highlight } from '@/libs/highlight.ts'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = withDefaults(
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
import UiActionButton from '@/components/ui/UiActionButton.vue'
|
||||
import UiFilter from '@/components/ui/UiFilter.vue'
|
||||
import UiFilterGroup from '@/components/ui/UiFilterGroup.vue'
|
||||
import type { Filters } from '@/types/filter'
|
||||
import type { Filters } from '@/types/filter.ts'
|
||||
import { useOverlay } from '@core/packages/overlay/use-overlay.ts'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import UiActionButton from '@/components/ui/UiActionButton.vue'
|
||||
import { buildComplexMatcherNode } from '@/libs/complex-matcher.utils'
|
||||
import type { Filter, FilterComparisons, FilterComparisonType, Filters, FilterType, NewFilter } from '@/types/filter'
|
||||
import { buildComplexMatcherNode } from '@/libs/complex-matcher.utils.ts'
|
||||
import type { Filter, FilterComparisons, FilterComparisonType, Filters, FilterType, NewFilter } from '@/types/filter.ts'
|
||||
import VtsSelect from '@core/components/select/VtsSelect.vue'
|
||||
import UiInput from '@core/components/ui/input/UiInput.vue'
|
||||
import { useFormSelect } from '@core/packages/form-select'
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
import UiActionButton from '@/components/ui/UiActionButton.vue'
|
||||
import UiFilter from '@/components/ui/UiFilter.vue'
|
||||
import UiFilterGroup from '@/components/ui/UiFilterGroup.vue'
|
||||
import type { ActiveSorts, Sorts } from '@/types/sort'
|
||||
import type { ActiveSorts, Sorts } from '@/types/sort.ts'
|
||||
import VtsIcon from '@core/components/icon/VtsIcon.vue'
|
||||
import { useOverlay } from '@core/packages/overlay/use-overlay.ts'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { XenApiPatchWithHostRefs } from '@/composables/host-patches.composable'
|
||||
import type { XenApiPatchWithHostRefs } from '@/composables/host-patches.composable.ts'
|
||||
import VtsRow from '@core/components/table/VtsRow.vue'
|
||||
import VtsTable from '@core/components/table/VtsTable.vue'
|
||||
import { useTableState } from '@core/composables/table-state.composable'
|
||||
import { useUiStore } from '@core/stores/ui.store'
|
||||
import { usePatchColumns } from '@core/tables/column-sets/patch-columns'
|
||||
import { useTableState } from '@core/composables/table-state.composable.ts'
|
||||
import { useUiStore } from '@core/stores/ui.store.ts'
|
||||
import { usePatchColumns } from '@core/tables/column-sets/patch-columns.ts'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { XenApiError } from '@/libs/xen-api/xen-api.types'
|
||||
import type { XenApiError } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import VtsIcon from '@core/components/icon/VtsIcon.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
</template>
|
||||
|
||||
<script generic="T extends ObjectType" lang="ts" setup>
|
||||
import type { ObjectType, ObjectTypeToRecord } from '@/libs/xen-api/xen-api.types'
|
||||
import type { XapiContext } from '@/stores/xen-api/create-xapi-store-config'
|
||||
import { useHostStore } from '@/stores/xen-api/host.store'
|
||||
import { usePoolStore } from '@/stores/xen-api/pool.store'
|
||||
import { useSrStore } from '@/stores/xen-api/sr.store'
|
||||
import { useVmStore } from '@/stores/xen-api/vm.store'
|
||||
import type { ObjectType, ObjectTypeToRecord } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import type { XapiContext } from '@/stores/xen-api/create-xapi-store-config.ts'
|
||||
import { useHostStore } from '@/stores/xen-api/host.store.ts'
|
||||
import { usePoolStore } from '@/stores/xen-api/pool.store.ts'
|
||||
import { useSrStore } from '@/stores/xen-api/sr.store.ts'
|
||||
import { useVmStore } from '@/stores/xen-api/vm.store.ts'
|
||||
import UiLoader from '@core/components/ui/loader/UiLoader.vue'
|
||||
import { computed, watch } from 'vue'
|
||||
import { type RouteLocationTyped, type RouteRecordName } from 'vue-router'
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</template>
|
||||
|
||||
<script generic="T extends XenApiRecord<ObjectType>, I extends T['uuid']" lang="ts" setup>
|
||||
import type { ObjectType, XenApiRecord } from '@/libs/xen-api/xen-api.types'
|
||||
import type { ObjectType, XenApiRecord } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import ObjectNotFoundView from '@/pages/object-not-found.vue'
|
||||
import UiLoader from '@core/components/ui/loader/UiLoader.vue'
|
||||
import { computed } from 'vue'
|
||||
|
||||
@@ -27,9 +27,9 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useXenApiStore } from '@/stores/xen-api.store'
|
||||
import { useXenApiStore } from '@/stores/xen-api.store.ts'
|
||||
import VtsIcon from '@core/components/icon/VtsIcon.vue'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive.ts'
|
||||
import { useSessionStorage } from '@vueuse/core'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { VM_POWER_STATE } from '@/libs/xen-api/xen-api.enums'
|
||||
import { VM_POWER_STATE } from '@/libs/xen-api/xen-api.enums.ts'
|
||||
import type { IconName } from '@core/icons'
|
||||
import VtsIcon from '@core/components/icon/VtsIcon.vue'
|
||||
import { computed } from 'vue'
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import TabItem from '@core/components/tab/TabItem.vue'
|
||||
import { useDisabled } from '@core/composables/disabled.composable'
|
||||
import { useDisabled } from '@core/composables/disabled.composable.ts'
|
||||
import type { RouteLocationRaw } from 'vue-router'
|
||||
|
||||
const props = defineProps<{
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { PRIMARY_ADDRESS_TYPE } from '@/libs/xen-api/xen-api.enums'
|
||||
import { usePoolStore } from '@/stores/xen-api/pool.store'
|
||||
import type { PRIMARY_ADDRESS_TYPE } from '@/libs/xen-api/xen-api.enums.ts'
|
||||
import { usePoolStore } from '@/stores/xen-api/pool.store.ts'
|
||||
import UiButton from '@core/components/ui/button/UiButton.vue'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@@ -31,11 +31,11 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useXenApiStore } from '@/stores/xen-api.store'
|
||||
import { useXenApiStore } from '@/stores/xen-api.store.ts'
|
||||
import MenuItem from '@core/components/menu/MenuItem.vue'
|
||||
import MenuList from '@core/components/menu/MenuList.vue'
|
||||
import UiAccountMenuButton from '@core/components/ui/account-menu-button/UiAccountMenuButton.vue'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive.ts'
|
||||
import { nextTick } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
@@ -87,7 +87,7 @@ import {
|
||||
isSlotParam,
|
||||
ModelParam,
|
||||
type Param,
|
||||
} from '@/libs/story/story-param'
|
||||
} from '@/libs/story/story-param.ts'
|
||||
import VtsIcon from '@core/components/icon/VtsIcon.vue'
|
||||
import MenuItem from '@core/components/menu/MenuItem.vue'
|
||||
import MenuList from '@core/components/menu/MenuList.vue'
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<script lang="ts" setup>
|
||||
import CodeHighlight from '@/components/CodeHighlight.vue'
|
||||
import StoryParamsTable from '@/components/component-story/StoryParamsTable.vue'
|
||||
import type { EventParam } from '@/libs/story/story-param'
|
||||
import type { EventParam } from '@/libs/story/story-param.ts'
|
||||
|
||||
defineProps<{
|
||||
params: EventParam[]
|
||||
|
||||
@@ -83,10 +83,10 @@
|
||||
import CodeHighlight from '@/components/CodeHighlight.vue'
|
||||
import StoryParamsTable from '@/components/component-story/StoryParamsTable.vue'
|
||||
import StoryWidget from '@/components/component-story/StoryWidget.vue'
|
||||
import useSortedCollection from '@/composables/sorted-collection.composable'
|
||||
import type { PropParam } from '@/libs/story/story-param'
|
||||
import useSortedCollection from '@/composables/sorted-collection.composable.ts'
|
||||
import type { PropParam } from '@/libs/story/story-param.ts'
|
||||
import VtsIcon from '@core/components/icon/VtsIcon.vue'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive.ts'
|
||||
import { useOverlay } from '@core/packages/overlay/use-overlay.ts'
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { toRef } from 'vue'
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<script lang="ts" setup>
|
||||
import StoryParamsTable from '@/components/component-story/StoryParamsTable.vue'
|
||||
import StoryWidget from '@/components/component-story/StoryWidget.vue'
|
||||
import type { SettingParam } from '@/libs/story/story-param'
|
||||
import type { SettingParam } from '@/libs/story/story-param.ts'
|
||||
import { useVModel } from '@vueuse/core'
|
||||
|
||||
const props = defineProps<{
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<script lang="ts" setup>
|
||||
import CodeHighlight from '@/components/CodeHighlight.vue'
|
||||
import StoryParamsTable from '@/components/component-story/StoryParamsTable.vue'
|
||||
import type { SlotParam } from '@/libs/story/story-param'
|
||||
import type { SlotParam } from '@/libs/story/story-param.ts'
|
||||
|
||||
defineProps<{
|
||||
params: SlotParam[]
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
isSelectWidget,
|
||||
isTextWidget,
|
||||
type Widget,
|
||||
} from '@/libs/story/story-widget'
|
||||
} from '@/libs/story/story-widget.ts'
|
||||
import { useFormSelect } from '@core/packages/form-select'
|
||||
import { computed, defineAsyncComponent } from 'vue'
|
||||
|
||||
|
||||
@@ -20,13 +20,13 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useContext } from '@/composables/context.composable'
|
||||
import { ColorContext } from '@/context'
|
||||
import type { Color } from '@/types'
|
||||
import { IK_INPUT_ID, IK_INPUT_TYPE } from '@/types/injection-keys'
|
||||
import { useContext } from '@/composables/context.composable.ts'
|
||||
import { ColorContext } from '@/context.ts'
|
||||
import type { Color } from '@/types/index.ts'
|
||||
import { IK_INPUT_ID, IK_INPUT_TYPE } from '@/types/injection-keys.ts'
|
||||
import type { IconName } from '@core/icons'
|
||||
import VtsIcon from '@core/components/icon/VtsIcon.vue'
|
||||
import { useDisabled } from '@core/composables/disabled.composable'
|
||||
import { useDisabled } from '@core/composables/disabled.composable.ts'
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { computed, type HTMLAttributes, inject, ref, useAttrs } from 'vue'
|
||||
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { XenApiHost } from '@/libs/xen-api/xen-api.types'
|
||||
import { useHostMetricsStore } from '@/stores/xen-api/host-metrics.store'
|
||||
import { usePoolStore } from '@/stores/xen-api/pool.store'
|
||||
import type { XenApiHost } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import { useHostMetricsStore } from '@/stores/xen-api/host-metrics.store.ts'
|
||||
import { usePoolStore } from '@/stores/xen-api/pool.store.ts'
|
||||
import VtsIcon from '@core/components/icon/VtsIcon.vue'
|
||||
import VtsObjectIcon from '@core/components/object-icon/VtsObjectIcon.vue'
|
||||
import UiHeadBar from '@core/components/ui/head-bar/UiHeadBar.vue'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive.ts'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
|
||||
@@ -65,10 +65,10 @@ import VtsQuickInfoCard from '@core/components/quick-info-card/VtsQuickInfoCard.
|
||||
import VtsTag from '@core/components/tag/VtsTag.vue'
|
||||
import UiLink from '@core/components/ui/link/UiLink.vue'
|
||||
import UiTagsList from '@core/components/ui/tag/UiTagsList.vue'
|
||||
import useRelativeTime from '@core/composables/relative-time.composable'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive'
|
||||
import { formatSizeRaw } from '@core/utils/size.util'
|
||||
import { parseDateTime } from '@core/utils/time.util'
|
||||
import useRelativeTime from '@core/composables/relative-time.composable.ts'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive.ts'
|
||||
import { formatSizeRaw } from '@core/utils/size.util.ts'
|
||||
import { parseDateTime } from '@core/utils/time.util.ts'
|
||||
import { logicAnd } from '@vueuse/math'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@@ -36,7 +36,7 @@ import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
import UiCardNumbers from '@core/components/ui/card-numbers/UiCardNumbers.vue'
|
||||
import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
|
||||
import { formatSizeRaw } from '@core/utils/size.util'
|
||||
import { formatSizeRaw } from '@core/utils/size.util.ts'
|
||||
import { logicAnd } from '@vueuse/math'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@@ -20,7 +20,7 @@ import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
import UiCardNumbers from '@core/components/ui/card-numbers/UiCardNumbers.vue'
|
||||
import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
|
||||
import { useItemCounter } from '@core/composables/item-counter.composable'
|
||||
import { useItemCounter } from '@core/composables/item-counter.composable.ts'
|
||||
import { VM_POWER_STATE } from '@vates/types'
|
||||
import { logicAnd, useSum } from '@vueuse/math'
|
||||
import { computed } from 'vue'
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<VtsSidePanel :has-selection="!!pif" @close="emit('close')">
|
||||
<VtsSidePanel :has-selection="!!pif" class="host-pif-side-panel" @close="emit('close')">
|
||||
<template v-if="pif" #default>
|
||||
<!-- PIF -->
|
||||
<UiCard class="card">
|
||||
<UiPanelCard>
|
||||
<VtsCardObjectTitle :id="pif.uuid" :label="isBond ? t('bond') : t('pif')" />
|
||||
<div class="content">
|
||||
<!-- NETWORK -->
|
||||
@@ -75,9 +75,9 @@
|
||||
</template>
|
||||
</VtsCardRowKeyValue>
|
||||
</div>
|
||||
</UiCard>
|
||||
</UiPanelCard>
|
||||
<!-- NETWORK INFORMATION -->
|
||||
<UiCard class="card">
|
||||
<UiPanelCard>
|
||||
<UiCardTitle>{{ t('network-information') }}</UiCardTitle>
|
||||
<div class="content">
|
||||
<!-- IP ADDRESSES -->
|
||||
@@ -180,9 +180,9 @@
|
||||
</VtsCardRowKeyValue>
|
||||
</div>
|
||||
</div>
|
||||
</UiCard>
|
||||
</UiPanelCard>
|
||||
<!-- PROPERTIES -->
|
||||
<UiCard class="card">
|
||||
<UiPanelCard>
|
||||
<UiCardTitle>{{ t('properties') }}</UiCardTitle>
|
||||
<div class="content">
|
||||
<!-- MTU -->
|
||||
@@ -219,16 +219,16 @@
|
||||
</template>
|
||||
</VtsCardRowKeyValue>
|
||||
</div>
|
||||
</UiCard>
|
||||
</UiPanelCard>
|
||||
</template>
|
||||
</VtsSidePanel>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { XenApiPif } from '@/libs/xen-api/xen-api.types'
|
||||
import { useNetworkStore } from '@/stores/xen-api/network.store'
|
||||
import { usePifMetricsStore } from '@/stores/xen-api/pif-metrics.store'
|
||||
import { usePifStore } from '@/stores/xen-api/pif.store'
|
||||
import type { XenApiPif } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import { useNetworkStore } from '@/stores/xen-api/network.store.ts'
|
||||
import { usePifMetricsStore } from '@/stores/xen-api/pif-metrics.store.ts'
|
||||
import { usePifStore } from '@/stores/xen-api/pif.store.ts'
|
||||
import VtsCardRowKeyValue from '@core/components/card/VtsCardRowKeyValue.vue'
|
||||
import VtsCardObjectTitle from '@core/components/card-object-title/VtsCardObjectTitle.vue'
|
||||
import VtsCopyAllMenuItem from '@core/components/copy-all-menu-item/VtsCopyAllMenuItem.vue'
|
||||
@@ -237,10 +237,10 @@ import VtsIcon from '@core/components/icon/VtsIcon.vue'
|
||||
import VtsSidePanel from '@core/components/panel/VtsSidePanel.vue'
|
||||
import VtsStatus from '@core/components/status/VtsStatus.vue'
|
||||
import VtsTag from '@core/components/tag/VtsTag.vue'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
|
||||
import UiPanelCard from '@core/components/ui/panel-card/UiPanelCard.vue'
|
||||
import UiTagsList from '@core/components/ui/tag/UiTagsList.vue'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive.ts'
|
||||
import humanFormat from 'human-format'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
@@ -317,9 +317,7 @@ const speed = computed(() => {
|
||||
</script>
|
||||
|
||||
<style scoped lang="postcss">
|
||||
.card {
|
||||
gap: 1.6rem;
|
||||
|
||||
.host-pif-side-panel {
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
<div class="host-pif-table">
|
||||
<UiTitle>
|
||||
{{ t('pifs') }}
|
||||
<template #action>
|
||||
<slot name="title-actions" />
|
||||
</template>
|
||||
</UiTitle>
|
||||
<div class="container">
|
||||
<UiQuerySearchBar @search="(value: string) => (searchQuery = value)" />
|
||||
@@ -22,18 +25,18 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { XenApiNetwork, XenApiPif } from '@/libs/xen-api/xen-api.types'
|
||||
import { useNetworkStore } from '@/stores/xen-api/network.store'
|
||||
import { usePifStore } from '@/stores/xen-api/pif.store'
|
||||
import type { XenApiNetwork, XenApiPif } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import { useNetworkStore } from '@/stores/xen-api/network.store.ts'
|
||||
import { usePifStore } from '@/stores/xen-api/pif.store.ts'
|
||||
import VtsRow from '@core/components/table/VtsRow.vue'
|
||||
import VtsTable from '@core/components/table/VtsTable.vue'
|
||||
import UiQuerySearchBar from '@core/components/ui/query-search-bar/UiQuerySearchBar.vue'
|
||||
import UiTitle from '@core/components/ui/title/UiTitle.vue'
|
||||
import { usePagination } from '@core/composables/pagination.composable'
|
||||
import { useRouteQuery } from '@core/composables/route-query.composable'
|
||||
import { useTableState } from '@core/composables/table-state.composable'
|
||||
import { usePagination } from '@core/composables/pagination.composable.ts'
|
||||
import { useRouteQuery } from '@core/composables/route-query.composable.ts'
|
||||
import { useTableState } from '@core/composables/table-state.composable.ts'
|
||||
import { icon } from '@core/icons'
|
||||
import { usePifColumns } from '@core/tables/column-sets/pif-columns'
|
||||
import { usePifColumns } from '@core/tables/column-sets/pif-columns.ts'
|
||||
import { logicNot } from '@vueuse/math'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
@@ -42,6 +45,10 @@ const { pifs } = defineProps<{
|
||||
pifs: XenApiPif[]
|
||||
}>()
|
||||
|
||||
defineSlots<{
|
||||
'title-actions'(): any
|
||||
}>()
|
||||
|
||||
const { isReady, hasError } = usePifStore().subscribe()
|
||||
const { getByOpaqueRef } = useNetworkStore().subscribe()
|
||||
const { getPifStatus } = usePifStore().subscribe()
|
||||
|
||||
@@ -70,7 +70,7 @@ import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
import UiLink from '@core/components/ui/link/UiLink.vue'
|
||||
import UiTagsList from '@core/components/ui/tag/UiTagsList.vue'
|
||||
import UiTitle from '@core/components/ui/title/UiTitle.vue'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive.ts'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<VtsTreeItem v-if="host !== undefined" :expanded="isExpanded" class="infra-host-item" :node-id="`host:${host.uuid}`">
|
||||
<VtsTreeItem
|
||||
v-if="host !== undefined"
|
||||
:expanded="isExpanded"
|
||||
has-children
|
||||
class="infra-host-item"
|
||||
:node-id="`host:${host.uuid}`"
|
||||
>
|
||||
<UiTreeItemLabel
|
||||
:route="{ name: '/host/[uuid]', params: { uuid: host.uuid } }"
|
||||
icon="object:host"
|
||||
@@ -40,11 +46,11 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import InfraVmItems from '@/components/infra/InfraVmItems.vue'
|
||||
import type { XenApiHost } from '@/libs/xen-api/xen-api.types'
|
||||
import type { XenApiHost } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import HostTreeActions from '@/modules/host/components/actions/HostTreeActions.vue'
|
||||
import { useHostStore } from '@/stores/xen-api/host.store'
|
||||
import { usePoolStore } from '@/stores/xen-api/pool.store'
|
||||
import { useVmStore } from '@/stores/xen-api/vm.store'
|
||||
import { useHostStore } from '@/stores/xen-api/host.store.ts'
|
||||
import { usePoolStore } from '@/stores/xen-api/pool.store.ts'
|
||||
import { useVmStore } from '@/stores/xen-api/vm.store.ts'
|
||||
import VtsIcon from '@core/components/icon/VtsIcon.vue'
|
||||
import MenuList from '@core/components/menu/MenuList.vue'
|
||||
import VtsTreeItem from '@core/components/tree/VtsTreeItem.vue'
|
||||
@@ -52,7 +58,7 @@ import VtsTreeList from '@core/components/tree/VtsTreeList.vue'
|
||||
import UiButtonIcon from '@core/components/ui/button-icon/UiButtonIcon.vue'
|
||||
import UiCounter from '@core/components/ui/counter/UiCounter.vue'
|
||||
import UiTreeItemLabel from '@core/components/ui/tree-item-label/UiTreeItemLabel.vue'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive.ts'
|
||||
import { useToggle } from '@vueuse/shared'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import InfraHostItem from '@/components/infra/InfraHostItem.vue'
|
||||
import { useHostStore } from '@/stores/xen-api/host.store'
|
||||
import { useHostStore } from '@/stores/xen-api/host.store.ts'
|
||||
import VtsTreeItemError from '@core/components/tree/VtsTreeItemError.vue'
|
||||
import VtsTreeLoadingItem from '@core/components/tree/VtsTreeLoadingItem.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<VtsTreeList class="infra-pool-list">
|
||||
<VtsTreeItem :expanded="isExpanded" :node-id="`pool:${pool?.uuid}`">
|
||||
<VtsTreeItem :expanded="isExpanded" has-children :node-id="`pool:${pool?.uuid}`">
|
||||
<VtsTreeItemError v-if="hasError">
|
||||
{{ t('error-no-data') }}
|
||||
</VtsTreeItemError>
|
||||
@@ -41,7 +41,7 @@
|
||||
import InfraHostItems from '@/components/infra/InfraHostItems.vue'
|
||||
import InfraVmItems from '@/components/infra/InfraVmItems.vue'
|
||||
import PoolTreeActions from '@/modules/pool/components/actions/PoolTreeActions.vue'
|
||||
import { usePoolStore } from '@/stores/xen-api/pool.store'
|
||||
import { usePoolStore } from '@/stores/xen-api/pool.store.ts'
|
||||
import MenuList from '@core/components/menu/MenuList.vue'
|
||||
import VtsTreeItem from '@core/components/tree/VtsTreeItem.vue'
|
||||
import VtsTreeItemError from '@core/components/tree/VtsTreeItemError.vue'
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { VM_POWER_STATE } from '@/libs/xen-api/xen-api.enums'
|
||||
import type { XenApiVm } from '@/libs/xen-api/xen-api.types'
|
||||
import { useVmStore } from '@/stores/xen-api/vm.store'
|
||||
import type { VM_POWER_STATE } from '@/libs/xen-api/xen-api.enums.ts'
|
||||
import type { XenApiVm } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import { useVmStore } from '@/stores/xen-api/vm.store.ts'
|
||||
import VtsObjectIcon from '@core/components/object-icon/VtsObjectIcon.vue'
|
||||
import VtsTreeItem from '@core/components/tree/VtsTreeItem.vue'
|
||||
import UiTreeItemLabel from '@core/components/ui/tree-item-label/UiTreeItemLabel.vue'
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import InfraVmItem from '@/components/infra/InfraVmItem.vue'
|
||||
import type { XenApiHost } from '@/libs/xen-api/xen-api.types'
|
||||
import { useVmStore } from '@/stores/xen-api/vm.store'
|
||||
import type { XenApiHost } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import { useVmStore } from '@/stores/xen-api/vm.store.ts'
|
||||
import VtsTreeItemError from '@core/components/tree/VtsTreeItemError.vue'
|
||||
import VtsTreeLoadingItem from '@core/components/tree/VtsTreeLoadingItem.vue'
|
||||
import { computed } from 'vue'
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
<script lang="ts" setup>
|
||||
import CollectionFilterRow from '@/components/CollectionFilterRow.vue'
|
||||
import UiBadge from '@/components/ui/UiBadge.vue'
|
||||
import { getFilterIcon } from '@/libs/utils'
|
||||
import type { Filters, NewFilter } from '@/types/filter'
|
||||
import { getFilterIcon } from '@/libs/utils.ts'
|
||||
import type { Filters, NewFilter } from '@/types/filter.ts'
|
||||
import VtsOverlayButton from '@core/components/overlay/VtsOverlayButton.vue'
|
||||
import VtsOverlayCancelButton from '@core/components/overlay/VtsOverlayCancelButton.vue'
|
||||
import VtsOverlayConfirmButton from '@core/components/overlay/VtsOverlayConfirmButton.vue'
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { NewSort, Sorts } from '@/types/sort'
|
||||
import type { NewSort, Sorts } from '@/types/sort.ts'
|
||||
import VtsOverlayCancelButton from '@core/components/overlay/VtsOverlayCancelButton.vue'
|
||||
import VtsOverlayConfirmButton from '@core/components/overlay/VtsOverlayConfirmButton.vue'
|
||||
import VtsSelect from '@core/components/select/VtsSelect.vue'
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { XenApiVm } from '@/libs/xen-api/xen-api.types'
|
||||
import { useVmStore } from '@/stores/xen-api/vm.store'
|
||||
import type { XenApiVm } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import { useVmStore } from '@/stores/xen-api/vm.store.ts'
|
||||
import VtsOverlayConfirmButton from '@core/components/overlay/VtsOverlayConfirmButton.vue'
|
||||
import UiModal from '@core/components/ui/modal/UiModal.vue'
|
||||
import { computed } from 'vue'
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { VM_COMPRESSION_TYPE } from '@/libs/xen-api/xen-api.enums'
|
||||
import { VM_COMPRESSION_TYPE } from '@/libs/xen-api/xen-api.enums.ts'
|
||||
import VtsInputWrapper from '@core/components/input-wrapper/VtsInputWrapper.vue'
|
||||
import VtsOverlayCancelButton from '@core/components/overlay/VtsOverlayCancelButton.vue'
|
||||
import VtsOverlayConfirmButton from '@core/components/overlay/VtsOverlayConfirmButton.vue'
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { XenApiHost } from '@/libs/xen-api/xen-api.types'
|
||||
import type { XenApiHost } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import VtsInputWrapper from '@core/components/input-wrapper/VtsInputWrapper.vue'
|
||||
import VtsOverlayCancelButton from '@core/components/overlay/VtsOverlayCancelButton.vue'
|
||||
import VtsOverlayConfirmButton from '@core/components/overlay/VtsOverlayConfirmButton.vue'
|
||||
|
||||
@@ -21,14 +21,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { XenApiNetwork } from '@/libs/xen-api/xen-api.types'
|
||||
import type { NetworkInterface, VmState } from '@/types/new-vm'
|
||||
import type { XenApiNetwork } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import type { NetworkInterface, VmState } from '@/types/new-vm.ts'
|
||||
import VtsRow from '@core/components/table/VtsRow.vue'
|
||||
import VtsTable from '@core/components/table/VtsTable.vue'
|
||||
import UiButton from '@core/components/ui/button/UiButton.vue'
|
||||
import UiTableCell from '@core/components/ui/table-cell/UiTableCell.vue'
|
||||
import { useFormSelect } from '@core/packages/form-select'
|
||||
import { useNewVmNetworkColumns } from '@core/tables/column-sets/new-vm-network-columns'
|
||||
import { useNewVmNetworkColumns } from '@core/tables/column-sets/new-vm-network-columns.ts'
|
||||
import { toRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
|
||||
@@ -24,15 +24,15 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { XenApiSr } from '@/libs/xen-api/xen-api.types'
|
||||
import type { Vdi, VmState } from '@/types/new-vm'
|
||||
import type { XenApiSr } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import type { Vdi, VmState } from '@/types/new-vm.ts'
|
||||
import VtsRow from '@core/components/table/VtsRow.vue'
|
||||
import VtsTable from '@core/components/table/VtsTable.vue'
|
||||
import UiButton from '@core/components/ui/button/UiButton.vue'
|
||||
import UiTableCell from '@core/components/ui/table-cell/UiTableCell.vue'
|
||||
import { useFormSelect } from '@core/packages/form-select'
|
||||
import { useNewVmSrColumns } from '@core/tables/column-sets/new-vm-sr-columns'
|
||||
import { renderBodyCell } from '@core/tables/helpers/render-body-cell'
|
||||
import { useNewVmSrColumns } from '@core/tables/column-sets/new-vm-sr-columns.ts'
|
||||
import { renderBodyCell } from '@core/tables/helpers/render-body-cell.ts'
|
||||
import { toRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
|
||||
@@ -22,14 +22,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { XenApiPif } from '@/libs/xen-api/xen-api.types'
|
||||
import { useHostMetricsStore } from '@/stores/xen-api/host-metrics.store'
|
||||
import { useHostStore } from '@/stores/xen-api/host.store'
|
||||
import { usePifStore } from '@/stores/xen-api/pif.store'
|
||||
import type { XenApiPif } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import { useHostMetricsStore } from '@/stores/xen-api/host-metrics.store.ts'
|
||||
import { useHostStore } from '@/stores/xen-api/host.store.ts'
|
||||
import { usePifStore } from '@/stores/xen-api/pif.store.ts'
|
||||
import VtsObjectIcon from '@core/components/object-icon/VtsObjectIcon.vue'
|
||||
import VtsStatus from '@core/components/status/VtsStatus.vue'
|
||||
import UiButtonIcon from '@core/components/ui/button-icon/UiButtonIcon.vue'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive.ts'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { usePoolStore } from '@/stores/xen-api/pool.store'
|
||||
import { usePoolStore } from '@/stores/xen-api/pool.store.ts'
|
||||
import UiHeadBar from '@core/components/ui/head-bar/UiHeadBar.vue'
|
||||
import UiLink from '@core/components/ui/link/UiLink.vue'
|
||||
import { computed } from 'vue'
|
||||
|
||||
@@ -35,23 +35,23 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ObjectLink from '@/components/ObjectLink.vue'
|
||||
import type { RawObjectType } from '@/libs/xen-api/xen-api.types'
|
||||
import { rawTypeToType } from '@/libs/xen-api/xen-api.utils'
|
||||
import { useAlarmStore } from '@/stores/xen-api/alarm.store'
|
||||
import type { XenApiAlarm } from '@/types/xen-api'
|
||||
import type { RawObjectType } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import { rawTypeToType } from '@/libs/xen-api/xen-api.utils.ts'
|
||||
import { useAlarmStore } from '@/stores/xen-api/alarm.store.ts'
|
||||
import type { XenApiAlarm } from '@/types/xen-api.ts'
|
||||
import VtsRow from '@core/components/table/VtsRow.vue'
|
||||
import VtsTable from '@core/components/table/VtsTable.vue'
|
||||
import UiButton from '@core/components/ui/button/UiButton.vue'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
|
||||
import UiCounter from '@core/components/ui/counter/UiCounter.vue'
|
||||
import { useTableState } from '@core/composables/table-state.composable'
|
||||
import { useTableState } from '@core/composables/table-state.composable.ts'
|
||||
import { defineColumn, defineColumns } from '@core/packages/table'
|
||||
import { useDateColumn } from '@core/tables/column-definitions/date-column'
|
||||
import { usePercentColumn } from '@core/tables/column-definitions/percent-column'
|
||||
import { useTextColumn } from '@core/tables/column-definitions/text-column'
|
||||
import { renderBodyCell } from '@core/tables/helpers/render-body-cell'
|
||||
import { renderHeadCell } from '@core/tables/helpers/render-head-cell'
|
||||
import { useDateColumn } from '@core/tables/column-definitions/date-column.ts'
|
||||
import { usePercentColumn } from '@core/tables/column-definitions/percent-column.ts'
|
||||
import { useTextColumn } from '@core/tables/column-definitions/text-column.ts'
|
||||
import { renderBodyCell } from '@core/tables/helpers/render-body-cell.ts'
|
||||
import { renderHeadCell } from '@core/tables/helpers/render-head-cell.ts'
|
||||
import { logicNot } from '@vueuse/math'
|
||||
import { h } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ACTIVE_STATES } from '@/libs/utils'
|
||||
import { useHostStore } from '@/stores/xen-api/host.store'
|
||||
import { useVmMetricsStore } from '@/stores/xen-api/vm-metrics.store'
|
||||
import { useVmStore } from '@/stores/xen-api/vm.store'
|
||||
import { ACTIVE_STATES } from '@/libs/utils.ts'
|
||||
import { useHostStore } from '@/stores/xen-api/host.store.ts'
|
||||
import { useVmMetricsStore } from '@/stores/xen-api/vm-metrics.store.ts'
|
||||
import { useVmStore } from '@/stores/xen-api/vm.store.ts'
|
||||
import VtsProgressBar from '@core/components/progress-bar/VtsProgressBar.vue'
|
||||
import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
<script lang="ts" setup>
|
||||
import HostsCpuUsage from '@/components/pool/dashboard/cpuUsage/HostsCpuUsage.vue'
|
||||
import VmsCpuUsage from '@/components/pool/dashboard/cpuUsage/VmsCpuUsage.vue'
|
||||
import type { Stat } from '@/composables/fetch-stats.composable'
|
||||
import { useHostStore } from '@/stores/xen-api/host.store'
|
||||
import { useVmStore } from '@/stores/xen-api/vm.store'
|
||||
import type { Stat } from '@/composables/fetch-stats.composable.ts'
|
||||
import { useHostStore } from '@/stores/xen-api/host.store.ts'
|
||||
import { useVmStore } from '@/stores/xen-api/vm.store.ts'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
|
||||
import UiLoader from '@core/components/ui/loader/UiLoader.vue'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive.ts'
|
||||
import type { XapiHostStatsRaw, XapiVmStatsRaw } from '@vates/types/common'
|
||||
import { computed, type ComputedRef, inject } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import HostPatchesTable from '@/components/HostPatchesTable.vue'
|
||||
import { useHostPatches } from '@/composables/host-patches.composable'
|
||||
import { useHostStore } from '@/stores/xen-api/host.store'
|
||||
import { useHostPatches } from '@/composables/host-patches.composable.ts'
|
||||
import { useHostStore } from '@/stores/xen-api/host.store.ts'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { formatSize } from '@/libs/utils'
|
||||
import { RRD_STEP_FROM_STRING } from '@/libs/xapi-stats'
|
||||
import { useHostStore } from '@/stores/xen-api/host.store'
|
||||
import { IK_HOST_LAST_WEEK_STATS } from '@/types/injection-keys'
|
||||
import type { LinearChartData } from '@core/types/chart'
|
||||
import { formatSize } from '@/libs/utils.ts'
|
||||
import { RRD_STEP_FROM_STRING } from '@/libs/xapi-stats.ts'
|
||||
import { useHostStore } from '@/stores/xen-api/host.store.ts'
|
||||
import { IK_HOST_LAST_WEEK_STATS } from '@/types/injection-keys.ts'
|
||||
import type { LinearChartData } from '@core/types/chart.ts'
|
||||
import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
<script lang="ts" setup>
|
||||
import HostsRamUsage from '@/components/pool/dashboard/ramUsage/HostsRamUsage.vue'
|
||||
import VmsRamUsage from '@/components/pool/dashboard/ramUsage/VmsRamUsage.vue'
|
||||
import type { Stat } from '@/composables/fetch-stats.composable'
|
||||
import { useHostStore } from '@/stores/xen-api/host.store'
|
||||
import { useVmStore } from '@/stores/xen-api/vm.store'
|
||||
import type { Stat } from '@/composables/fetch-stats.composable.ts'
|
||||
import { useHostStore } from '@/stores/xen-api/host.store.ts'
|
||||
import { useVmStore } from '@/stores/xen-api/vm.store.ts'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
|
||||
import UiLoader from '@core/components/ui/loader/UiLoader.vue'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive.ts'
|
||||
import type { XapiHostStatsRaw, XapiVmStatsRaw } from '@vates/types/common'
|
||||
import type { ComputedRef } from 'vue'
|
||||
import { computed, inject } from 'vue'
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import PoolDashboardStatusItem from '@/components/pool/dashboard/PoolDashboardStatusItem.vue'
|
||||
import { useHostMetricsStore } from '@/stores/xen-api/host-metrics.store'
|
||||
import { useVmStore } from '@/stores/xen-api/vm.store'
|
||||
import { useHostMetricsStore } from '@/stores/xen-api/host-metrics.store.ts'
|
||||
import { useVmStore } from '@/stores/xen-api/vm.store.ts'
|
||||
import VtsDivider from '@core/components/divider/VtsDivider.vue'
|
||||
import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { N_ITEMS } from '@/pages/pool/[uuid]/dashboard.vue'
|
||||
import { useSrStore } from '@/stores/xen-api/sr.store'
|
||||
import { useSrStore } from '@/stores/xen-api/sr.store.ts'
|
||||
import VtsProgressBarGroup, {
|
||||
type ProgressBarGroupItem,
|
||||
} from '@core/components/progress-bar-group/VtsProgressBarGroup.vue'
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import TasksTable from '@/components/tasks/TasksTable.vue'
|
||||
import { useTaskStore } from '@/stores/xen-api/task.store'
|
||||
import { useTaskStore } from '@/stores/xen-api/task.store.ts'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
|
||||
import UiCounter from '@core/components/ui/counter/UiCounter.vue'
|
||||
|
||||
@@ -12,12 +12,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useStatStatus } from '@/composables/stat-status.composable'
|
||||
import { getAvgCpuUsage } from '@/libs/utils'
|
||||
import { useStatStatus } from '@/composables/stat-status.composable.ts'
|
||||
import { getAvgCpuUsage } from '@/libs/utils.ts'
|
||||
import { N_ITEMS } from '@/pages/pool/[uuid]/dashboard.vue'
|
||||
import { useHostStore } from '@/stores/xen-api/host.store'
|
||||
import { IK_HOST_STATS } from '@/types/injection-keys'
|
||||
import type { StatData } from '@/types/stat'
|
||||
import { useHostStore } from '@/stores/xen-api/host.store.ts'
|
||||
import { IK_HOST_STATS } from '@/types/injection-keys.ts'
|
||||
import type { StatData } from '@/types/stat.ts'
|
||||
import VtsProgressBarGroup from '@core/components/progress-bar-group/VtsProgressBarGroup.vue'
|
||||
import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
|
||||
import UiCardSubtitle from '@core/components/ui/card-subtitle/UiCardSubtitle.vue'
|
||||
|
||||
@@ -18,10 +18,10 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { RRD_STEP_FROM_STRING } from '@/libs/xapi-stats'
|
||||
import { useHostStore } from '@/stores/xen-api/host.store'
|
||||
import { IK_HOST_LAST_WEEK_STATS } from '@/types/injection-keys'
|
||||
import type { LinearChartData, ValueFormatter } from '@core/types/chart'
|
||||
import { RRD_STEP_FROM_STRING } from '@/libs/xapi-stats.ts'
|
||||
import { useHostStore } from '@/stores/xen-api/host.store.ts'
|
||||
import { IK_HOST_LAST_WEEK_STATS } from '@/types/injection-keys.ts'
|
||||
import type { LinearChartData, ValueFormatter } from '@core/types/chart.ts'
|
||||
import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useStatStatus } from '@/composables/stat-status.composable'
|
||||
import { getAvgCpuUsage } from '@/libs/utils'
|
||||
import { useStatStatus } from '@/composables/stat-status.composable.ts'
|
||||
import { getAvgCpuUsage } from '@/libs/utils.ts'
|
||||
import { N_ITEMS } from '@/pages/pool/[uuid]/dashboard.vue'
|
||||
import { useVmStore } from '@/stores/xen-api/vm.store'
|
||||
import { IK_VM_STATS } from '@/types/injection-keys'
|
||||
import type { StatData } from '@/types/stat'
|
||||
import { useVmStore } from '@/stores/xen-api/vm.store.ts'
|
||||
import { IK_VM_STATS } from '@/types/injection-keys.ts'
|
||||
import type { StatData } from '@/types/stat.ts'
|
||||
import VtsProgressBarGroup from '@core/components/progress-bar-group/VtsProgressBarGroup.vue'
|
||||
import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
|
||||
import UiCardSubtitle from '@core/components/ui/card-subtitle/UiCardSubtitle.vue'
|
||||
|
||||
@@ -12,12 +12,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useStatStatus } from '@/composables/stat-status.composable'
|
||||
import { parseRamUsage } from '@/libs/utils'
|
||||
import { useStatStatus } from '@/composables/stat-status.composable.ts'
|
||||
import { parseRamUsage } from '@/libs/utils.ts'
|
||||
import { N_ITEMS } from '@/pages/pool/[uuid]/dashboard.vue'
|
||||
import { useHostStore } from '@/stores/xen-api/host.store'
|
||||
import { IK_HOST_STATS } from '@/types/injection-keys'
|
||||
import type { StatData } from '@/types/stat'
|
||||
import { useHostStore } from '@/stores/xen-api/host.store.ts'
|
||||
import { IK_HOST_STATS } from '@/types/injection-keys.ts'
|
||||
import type { StatData } from '@/types/stat.ts'
|
||||
import VtsProgressBarGroup from '@core/components/progress-bar-group/VtsProgressBarGroup.vue'
|
||||
import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
|
||||
import UiCardSubtitle from '@core/components/ui/card-subtitle/UiCardSubtitle.vue'
|
||||
|
||||
@@ -18,12 +18,12 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import SizeStatsSummary from '@/components/ui/SizeStatsSummary.vue'
|
||||
import { formatSize } from '@/libs/utils'
|
||||
import { RRD_STEP_FROM_STRING } from '@/libs/xapi-stats'
|
||||
import { useHostMetricsStore } from '@/stores/xen-api/host-metrics.store'
|
||||
import { useHostStore } from '@/stores/xen-api/host.store'
|
||||
import { IK_HOST_LAST_WEEK_STATS } from '@/types/injection-keys'
|
||||
import type { LinearChartData } from '@core/types/chart'
|
||||
import { formatSize } from '@/libs/utils.ts'
|
||||
import { RRD_STEP_FROM_STRING } from '@/libs/xapi-stats.ts'
|
||||
import { useHostMetricsStore } from '@/stores/xen-api/host-metrics.store.ts'
|
||||
import { useHostStore } from '@/stores/xen-api/host.store.ts'
|
||||
import { IK_HOST_LAST_WEEK_STATS } from '@/types/injection-keys.ts'
|
||||
import type { LinearChartData } from '@core/types/chart.ts'
|
||||
import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
|
||||
|
||||
@@ -12,12 +12,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useStatStatus } from '@/composables/stat-status.composable'
|
||||
import { parseRamUsage } from '@/libs/utils'
|
||||
import { useStatStatus } from '@/composables/stat-status.composable.ts'
|
||||
import { parseRamUsage } from '@/libs/utils.ts'
|
||||
import { N_ITEMS } from '@/pages/pool/[uuid]/dashboard.vue'
|
||||
import { useVmStore } from '@/stores/xen-api/vm.store'
|
||||
import { IK_VM_STATS } from '@/types/injection-keys'
|
||||
import type { StatData } from '@/types/stat'
|
||||
import { useVmStore } from '@/stores/xen-api/vm.store.ts'
|
||||
import { IK_VM_STATS } from '@/types/injection-keys.ts'
|
||||
import type { StatData } from '@/types/stat.ts'
|
||||
import VtsProgressBarGroup from '@core/components/progress-bar-group/VtsProgressBarGroup.vue'
|
||||
import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
|
||||
import UiCardSubtitle from '@core/components/ui/card-subtitle/UiCardSubtitle.vue'
|
||||
|
||||
@@ -26,15 +26,15 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { XenApiNetwork } from '@/libs/xen-api/xen-api.types'
|
||||
import { useNetworkStore } from '@/stores/xen-api/network.store'
|
||||
import type { XenApiNetwork } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import { useNetworkStore } from '@/stores/xen-api/network.store.ts'
|
||||
import VtsRow from '@core/components/table/VtsRow.vue'
|
||||
import VtsTable from '@core/components/table/VtsTable.vue'
|
||||
import UiQuerySearchBar from '@core/components/ui/query-search-bar/UiQuerySearchBar.vue'
|
||||
import UiTitle from '@core/components/ui/title/UiTitle.vue'
|
||||
import { usePagination } from '@core/composables/pagination.composable'
|
||||
import { useRouteQuery } from '@core/composables/route-query.composable'
|
||||
import { useTableState } from '@core/composables/table-state.composable'
|
||||
import { usePagination } from '@core/composables/pagination.composable.ts'
|
||||
import { useRouteQuery } from '@core/composables/route-query.composable.ts'
|
||||
import { useTableState } from '@core/composables/table-state.composable.ts'
|
||||
import { useNetworkColumns } from '@core/tables/column-sets/network-columns.ts'
|
||||
import { logicNot } from '@vueuse/math'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<VtsSidePanel :has-selection="!!network" @close="emit('close')">
|
||||
<VtsSidePanel :has-selection="!!network" class="pool-network-side-panel" @close="emit('close')">
|
||||
<template v-if="network" #actions>
|
||||
<MenuList placement="bottom-end">
|
||||
<template #trigger="{ open, isOpen }">
|
||||
@@ -18,7 +18,7 @@
|
||||
</MenuList>
|
||||
</template>
|
||||
<template v-if="network" #default>
|
||||
<UiCard class="card-container">
|
||||
<UiPanelCard>
|
||||
<VtsCardObjectTitle :id="network.uuid" :label="network.name_label" />
|
||||
<div class="content">
|
||||
<!-- DESCRIPTION -->
|
||||
@@ -63,8 +63,8 @@
|
||||
<template #value>{{ networkDefaultLockingMode }}</template>
|
||||
</VtsCardRowKeyValue>
|
||||
</div>
|
||||
</UiCard>
|
||||
<UiCard v-if="pifsCount && pifsCount > 0" class="card-container">
|
||||
</UiPanelCard>
|
||||
<UiPanelCard v-if="pifsCount && pifsCount > 0">
|
||||
<div class="typo-body-bold">
|
||||
{{ t('pifs') }}
|
||||
<UiCounter :value="pifsCount" variant="primary" size="small" accent="neutral" />
|
||||
@@ -88,7 +88,7 @@
|
||||
<PifRow v-for="pif in pifs" :key="pif.uuid" :pif />
|
||||
</tbody>
|
||||
</table>
|
||||
</UiCard>
|
||||
</UiPanelCard>
|
||||
</template>
|
||||
</VtsSidePanel>
|
||||
</template>
|
||||
@@ -104,8 +104,8 @@ import MenuItem from '@core/components/menu/MenuItem.vue'
|
||||
import MenuList from '@core/components/menu/MenuList.vue'
|
||||
import VtsSidePanel from '@core/components/panel/VtsSidePanel.vue'
|
||||
import UiButtonIcon from '@core/components/ui/button-icon/UiButtonIcon.vue'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
import UiCounter from '@core/components/ui/counter/UiCounter.vue'
|
||||
import UiPanelCard from '@core/components/ui/panel-card/UiPanelCard.vue'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive.ts'
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
import { computed } from 'vue'
|
||||
@@ -147,11 +147,7 @@ const { copy, copied, isSupported: isClipboardSupported } = useClipboard({ sourc
|
||||
</script>
|
||||
|
||||
<style scoped lang="postcss">
|
||||
.card-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.6rem;
|
||||
|
||||
.pool-network-side-panel {
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -2,30 +2,15 @@
|
||||
<div class="pool-networks-table">
|
||||
<UiTitle>
|
||||
{{ t('networks') }}
|
||||
<template #action>
|
||||
<slot name="title-actions" />
|
||||
</template>
|
||||
</UiTitle>
|
||||
<div class="container">
|
||||
<div class="table-actions">
|
||||
<UiQuerySearchBar @search="value => (searchQuery = value)" />
|
||||
<UiTableActions :title="t('table-actions')">
|
||||
<UiButton
|
||||
v-tooltip="copied && t('copied')"
|
||||
:disabled="!isClipboardSupported"
|
||||
:left-icon="copied ? 'fa:check-circle' : 'action:copy'"
|
||||
variant="tertiary"
|
||||
accent="brand"
|
||||
size="medium"
|
||||
@click="selectedNetworkIds.length > 0 && copy()"
|
||||
>
|
||||
{{ t('action:copy-info-json') }}
|
||||
</UiButton>
|
||||
</UiTableActions>
|
||||
</div>
|
||||
<UiQuerySearchBar @search="value => (searchQuery = value)" />
|
||||
<VtsTable :state :pagination-bindings sticky="right">
|
||||
<thead>
|
||||
<tr>
|
||||
<VtsHeaderCell>
|
||||
<UiCheckbox v-model="areAllNetworksSelected" accent="brand" />
|
||||
</VtsHeaderCell>
|
||||
<HeadCells />
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -35,9 +20,6 @@
|
||||
:key="network.uuid"
|
||||
:selected="selectedNetworkId === network.uuid"
|
||||
>
|
||||
<UiTableCell>
|
||||
<UiCheckbox v-model="selectedNetworkIds" :value="network.uuid" accent="brand" />
|
||||
</UiTableCell>
|
||||
<BodyCells :item="network" />
|
||||
</VtsRow>
|
||||
</tbody>
|
||||
@@ -46,36 +28,31 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
<script lang="ts" setup>
|
||||
import type { XenApiNetwork } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import { useNetworkStore } from '@/stores/xen-api/network.store.ts'
|
||||
import { usePifMetricsStore } from '@/stores/xen-api/pif-metrics.store.ts'
|
||||
import { usePifStore } from '@/stores/xen-api/pif.store.ts'
|
||||
import VtsHeaderCell from '@core/components/table/cells/VtsHeaderCell.vue'
|
||||
import VtsRow from '@core/components/table/VtsRow.vue'
|
||||
import VtsTable from '@core/components/table/VtsTable.vue'
|
||||
import UiButton from '@core/components/ui/button/UiButton.vue'
|
||||
import UiCheckbox from '@core/components/ui/checkbox/UiCheckbox.vue'
|
||||
import UiQuerySearchBar from '@core/components/ui/query-search-bar/UiQuerySearchBar.vue'
|
||||
import UiTableActions from '@core/components/ui/table-actions/UiTableActions.vue'
|
||||
import UiTableCell from '@core/components/ui/table-cell/UiTableCell.vue'
|
||||
import UiTitle from '@core/components/ui/title/UiTitle.vue'
|
||||
import { usePagination } from '@core/composables/pagination.composable.ts'
|
||||
import { useRouteQuery } from '@core/composables/route-query.composable.ts'
|
||||
import useMultiSelect from '@core/composables/table/multi-select.composable.ts'
|
||||
import { useTableState } from '@core/composables/table-state.composable.ts'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive.ts'
|
||||
import { useNetworkColumns } from '@core/tables/column-sets/network-columns.ts'
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
import { logicNot } from '@vueuse/math'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { networks } = defineProps<{
|
||||
const { networks, busy, error } = defineProps<{
|
||||
networks: XenApiNetwork[]
|
||||
busy?: boolean
|
||||
error?: boolean
|
||||
}>()
|
||||
|
||||
defineSlots<{
|
||||
'title-actions'(): any
|
||||
}>()
|
||||
|
||||
const { isReady, hasError } = useNetworkStore().subscribe()
|
||||
const { records: pifs } = usePifStore().subscribe()
|
||||
const { getPifCarrier } = usePifMetricsStore().subscribe()
|
||||
|
||||
@@ -128,29 +105,9 @@ const filteredNetworks = computed(() => {
|
||||
|
||||
const { pageRecords: paginatedNetworks, paginationBindings } = usePagination('networks', filteredNetworks)
|
||||
|
||||
const { selected: selectedNetworkIds, areAllSelected: areAllNetworksSelected } = useMultiSelect(
|
||||
computed(() => networks.map(network => network.uuid)),
|
||||
computed(() => paginatedNetworks.value.map(network => network.uuid))
|
||||
)
|
||||
|
||||
const selectedNetworksAsJson = computed(() => {
|
||||
const selectedNetworks = networks.filter(network => selectedNetworkIds.value.includes(network.uuid))
|
||||
|
||||
return JSON.stringify(selectedNetworks)
|
||||
})
|
||||
|
||||
const {
|
||||
copy,
|
||||
copied,
|
||||
isSupported: isClipboardSupported,
|
||||
} = useClipboard({
|
||||
source: selectedNetworksAsJson,
|
||||
legacy: true,
|
||||
})
|
||||
|
||||
const state = useTableState({
|
||||
busy: logicNot(isReady),
|
||||
error: hasError,
|
||||
busy: () => busy,
|
||||
error: () => error,
|
||||
empty: () =>
|
||||
networks.length === 0
|
||||
? t('no-network-detected')
|
||||
@@ -179,7 +136,7 @@ const { HeadCells, BodyCells } = useNetworkColumns({
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="postcss">
|
||||
<style lang="postcss" scoped>
|
||||
.pool-networks-table,
|
||||
.container {
|
||||
display: flex;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { XenApiPool } from '@/libs/xen-api/xen-api.types'
|
||||
import type { XenApiPool } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import VtsTabularKeyValueList from '@core/components/tabular-key-value-list/VtsTabularKeyValueList.vue'
|
||||
import VtsTabularKeyValueRow from '@core/components/tabular-key-value-row/VtsTabularKeyValueRow.vue'
|
||||
import VtsTag from '@core/components/tag/VtsTag.vue'
|
||||
|
||||
@@ -42,8 +42,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { XenApiPool } from '@/libs/xen-api/xen-api.types'
|
||||
import { usePoolStore } from '@/stores/xen-api/pool.store'
|
||||
import type { XenApiPool } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import { usePoolStore } from '@/stores/xen-api/pool.store.ts'
|
||||
import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
|
||||
import VtsStatus from '@core/components/status/VtsStatus.vue'
|
||||
import VtsTabularKeyValueList from '@core/components/tabular-key-value-list/VtsTabularKeyValueList.vue'
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { XenApiNetwork, XenApiPool } from '@/libs/xen-api/xen-api.types'
|
||||
import { useNetworkStore } from '@/stores/xen-api/network.store'
|
||||
import type { XenApiNetwork, XenApiPool } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import { useNetworkStore } from '@/stores/xen-api/network.store.ts'
|
||||
import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
|
||||
import VtsTabularKeyValueList from '@core/components/tabular-key-value-list/VtsTabularKeyValueList.vue'
|
||||
import VtsTabularKeyValueRow from '@core/components/tabular-key-value-row/VtsTabularKeyValueRow.vue'
|
||||
|
||||
@@ -58,9 +58,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { XenApiPool, XenApiSr } from '@/libs/xen-api/xen-api.types'
|
||||
import { useSrStore } from '@/stores/xen-api/sr.store'
|
||||
import { useVdiStore } from '@/stores/xen-api/vdi.store'
|
||||
import type { XenApiPool, XenApiSr } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import { useSrStore } from '@/stores/xen-api/sr.store.ts'
|
||||
import { useVdiStore } from '@/stores/xen-api/vdi.store.ts'
|
||||
import VtsIcon from '@core/components/icon/VtsIcon.vue'
|
||||
import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
|
||||
import VtsTabularKeyValueList from '@core/components/tabular-key-value-list/VtsTabularKeyValueList.vue'
|
||||
|
||||
@@ -17,19 +17,19 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { XenApiTask } from '@/libs/xen-api/xen-api.types'
|
||||
import { useHostStore } from '@/stores/xen-api/host.store'
|
||||
import { useTaskStore } from '@/stores/xen-api/task.store'
|
||||
import type { XenApiTask } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import { useHostStore } from '@/stores/xen-api/host.store.ts'
|
||||
import { useTaskStore } from '@/stores/xen-api/task.store.ts'
|
||||
import VtsRow from '@core/components/table/VtsRow.vue'
|
||||
import VtsTable from '@core/components/table/VtsTable.vue'
|
||||
import { useTableState } from '@core/composables/table-state.composable'
|
||||
import { useTableState } from '@core/composables/table-state.composable.ts'
|
||||
import { defineColumns } from '@core/packages/table'
|
||||
import { useDateColumn } from '@core/tables/column-definitions/date-column'
|
||||
import { useLinkColumn } from '@core/tables/column-definitions/link-column'
|
||||
import { useProgressBarColumn } from '@core/tables/column-definitions/progress-bar-column'
|
||||
import { useTextColumn } from '@core/tables/column-definitions/text-column'
|
||||
import { renderBodyCell } from '@core/tables/helpers/render-body-cell'
|
||||
import { parseDateTime } from '@core/utils/time.util'
|
||||
import { useDateColumn } from '@core/tables/column-definitions/date-column.ts'
|
||||
import { useLinkColumn } from '@core/tables/column-definitions/link-column.ts'
|
||||
import { useProgressBarColumn } from '@core/tables/column-definitions/progress-bar-column.ts'
|
||||
import { useTextColumn } from '@core/tables/column-definitions/text-column.ts'
|
||||
import { renderBodyCell } from '@core/tables/helpers/render-body-cell.ts'
|
||||
import { parseDateTime } from '@core/utils/time.util.ts'
|
||||
import { logicNot } from '@vueuse/math'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { formatSize, percent } from '@/libs/utils'
|
||||
import { formatSize, percent } from '@/libs/utils.ts'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<script lang="ts" setup>
|
||||
import type { IconName } from '@core/icons'
|
||||
import VtsIcon from '@core/components/icon/VtsIcon.vue'
|
||||
import { useDisabled } from '@core/composables/disabled.composable'
|
||||
import { useDisabled } from '@core/composables/disabled.composable.ts'
|
||||
|
||||
const props = defineProps<{
|
||||
busy?: boolean
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { IK_CARD_GROUP_VERTICAL } from '@/types/injection-keys'
|
||||
import { IK_CARD_GROUP_VERTICAL } from '@/types/injection-keys.ts'
|
||||
import { inject, provide } from 'vue'
|
||||
|
||||
const vertical = inject(IK_CARD_GROUP_VERTICAL, false)
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { isVmOperationPending } from '@/libs/vm'
|
||||
import { VM_OPERATION, VM_POWER_STATE } from '@/libs/xen-api/xen-api.enums'
|
||||
import type { XenApiVm } from '@/libs/xen-api/xen-api.types'
|
||||
import { useVmStore } from '@/stores/xen-api/vm.store'
|
||||
import { useXenApiStore } from '@/stores/xen-api.store'
|
||||
import { isVmOperationPending } from '@/libs/vm.ts'
|
||||
import { VM_OPERATION, VM_POWER_STATE } from '@/libs/xen-api/xen-api.enums.ts'
|
||||
import type { XenApiVm } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import { useVmStore } from '@/stores/xen-api/vm.store.ts'
|
||||
import { useXenApiStore } from '@/stores/xen-api.store.ts'
|
||||
import MenuItem from '@core/components/menu/MenuItem.vue'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive.ts'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user