Compare commits
3 Commits
feat_nbd_d
...
feat_check
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
29dba3bbd7 | ||
|
|
616f6fb31a | ||
|
|
fbfa34f945 |
@@ -33,7 +33,7 @@
|
||||
"test": "node--test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"sinon": "^16.0.0",
|
||||
"sinon": "^15.0.1",
|
||||
"tap": "^16.3.0",
|
||||
"test": "^3.2.1"
|
||||
}
|
||||
|
||||
@@ -13,15 +13,12 @@ describe('decorateWith', () => {
|
||||
const expectedFn = Function.prototype
|
||||
const newFn = () => {}
|
||||
|
||||
const decorator = decorateWith(
|
||||
function wrapper(fn, ...args) {
|
||||
assert.deepStrictEqual(fn, expectedFn)
|
||||
assert.deepStrictEqual(args, expectedArgs)
|
||||
const decorator = decorateWith(function wrapper(fn, ...args) {
|
||||
assert.deepStrictEqual(fn, expectedFn)
|
||||
assert.deepStrictEqual(args, expectedArgs)
|
||||
|
||||
return newFn
|
||||
},
|
||||
...expectedArgs
|
||||
)
|
||||
return newFn
|
||||
}, ...expectedArgs)
|
||||
|
||||
const descriptor = {
|
||||
configurable: true,
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"ensure-array": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"sinon": "^16.0.0",
|
||||
"sinon": "^15.0.1",
|
||||
"test": "^3.2.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"fuse-native": "^2.2.6",
|
||||
"lru-cache": "^7.14.0",
|
||||
"promise-toolbox": "^0.21.0",
|
||||
"vhd-lib": "^4.6.1"
|
||||
"vhd-lib": "^4.5.0"
|
||||
},
|
||||
"scripts": {
|
||||
"postversion": "npm publish --access public"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import assert from 'node:assert'
|
||||
import { Socket } from 'node:net'
|
||||
import { connect } from 'node:tls'
|
||||
import { fromCallback, pRetry, pDelay, pTimeout, pFromCallback } from 'promise-toolbox'
|
||||
import { fromCallback, pRetry, pDelay, pTimeout } from 'promise-toolbox'
|
||||
import { readChunkStrict } from '@vates/read-chunk'
|
||||
import { createLogger } from '@xen-orchestra/log'
|
||||
|
||||
@@ -112,22 +112,18 @@ export default class NbdClient {
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
warn('will try to disconnect', { serverAddress: this.#serverAddress })
|
||||
if (!this.#connected) {
|
||||
warn('was already disconnected', { serverAddress: this.#serverAddress })
|
||||
return
|
||||
}
|
||||
warn('will really disconnect', { serverAddress: this.#serverAddress })
|
||||
|
||||
const buffer = Buffer.alloc(28)
|
||||
buffer.writeInt32BE(NBD_REQUEST_MAGIC, 0) // it is a nbd request
|
||||
buffer.writeInt16BE(0, 4) // no command flags for a disconnect
|
||||
buffer.writeInt16BE(NBD_CMD_DISC, 6) // we want to disconnect from nbd server
|
||||
warn('will send end buffer', { serverAddress: this.#serverAddress })
|
||||
this.#connected = false // optimistically mark as disconnected to ensure we don' send another disconnection while handling this one
|
||||
await pFromCallback(cb => this.#serverSocket.end(buffer, cb))
|
||||
warn('end buffer sent', { serverAddress: this.#serverAddress })
|
||||
await this.#write(buffer)
|
||||
await this.#serverSocket.destroy()
|
||||
this.#serverSocket = undefined
|
||||
this.#connected = false
|
||||
}
|
||||
|
||||
#clearReconnectPromise = () => {
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"@xen-orchestra/async-map": "^0.1.2",
|
||||
"@xen-orchestra/log": "^0.6.0",
|
||||
"promise-toolbox": "^0.21.0",
|
||||
"xen-api": "^1.3.6"
|
||||
"xen-api": "^1.3.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tap": "^16.3.0",
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"license": "ISC",
|
||||
"version": "0.1.0",
|
||||
"engines": {
|
||||
"node": ">=12.3"
|
||||
"node": ">=10"
|
||||
},
|
||||
"scripts": {
|
||||
"postversion": "npm publish --access public",
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"test": "node--test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"sinon": "^16.0.0",
|
||||
"sinon": "^15.0.1",
|
||||
"test": "^3.2.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"dependencies": {
|
||||
"@xen-orchestra/async-map": "^0.1.2",
|
||||
"@xen-orchestra/backups": "^0.43.0",
|
||||
"@xen-orchestra/fs": "^4.1.0",
|
||||
"@xen-orchestra/backups": "^0.40.0",
|
||||
"@xen-orchestra/fs": "^4.0.1",
|
||||
"filenamify": "^6.0.0",
|
||||
"getopts": "^2.2.5",
|
||||
"lodash": "^4.17.15",
|
||||
@@ -27,7 +27,7 @@
|
||||
"scripts": {
|
||||
"postversion": "npm publish --access public"
|
||||
},
|
||||
"version": "1.0.13",
|
||||
"version": "1.0.10",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"author": {
|
||||
"name": "Vates SAS",
|
||||
|
||||
@@ -683,11 +683,17 @@ export class RemoteAdapter {
|
||||
|
||||
async outputStream(path, input, { checksum = true, validator = noop } = {}) {
|
||||
const container = watchStreamSize(input)
|
||||
await this._handler.outputStream(path, input, {
|
||||
const handler = this._handler
|
||||
await handler.outputStream(path, input, {
|
||||
checksum,
|
||||
dirMode: this._dirMode,
|
||||
async validator() {
|
||||
async validator(tmpPath) {
|
||||
await input.task
|
||||
// size on file system can be bigger when encrypted ( IV + alignment padding)
|
||||
const size = await handler.getSize(tmpPath, { exact: false })
|
||||
if (Math.abs(size - container.size) > handler.getSizeApproximationMargin()) {
|
||||
return false
|
||||
}
|
||||
return validator.apply(this, arguments)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -41,7 +41,6 @@ export async function exportIncrementalVm(
|
||||
fullVdisRequired = new Set(),
|
||||
|
||||
disableBaseTags = false,
|
||||
preferNbd,
|
||||
} = {}
|
||||
) {
|
||||
// refs of VM's VDIs → base's VDIs.
|
||||
@@ -89,7 +88,6 @@ export async function exportIncrementalVm(
|
||||
baseRef: baseVdi?.$ref,
|
||||
cancelToken,
|
||||
format: 'vhd',
|
||||
preferNbd,
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -41,7 +41,6 @@ export const IncrementalXapi = class IncrementalXapiVmBackupRunner extends Abstr
|
||||
|
||||
const deltaExport = await exportIncrementalVm(exportedVm, baseVm, {
|
||||
fullVdisRequired,
|
||||
preferNbd: this._settings.preferNbd,
|
||||
})
|
||||
// since NBD is network based, if one disk use nbd , all the disk use them
|
||||
// except the suspended VDI
|
||||
|
||||
@@ -4,7 +4,6 @@ import { Disposable } from 'promise-toolbox'
|
||||
import { getVmBackupDir } from '../../_getVmBackupDir.mjs'
|
||||
|
||||
import { Abstract } from './_Abstract.mjs'
|
||||
import { extractIdsFromSimplePattern } from '../../extractIdsFromSimplePattern.mjs'
|
||||
|
||||
export const AbstractRemote = class AbstractRemoteVmBackupRunner extends Abstract {
|
||||
constructor({
|
||||
@@ -35,8 +34,7 @@ export const AbstractRemote = class AbstractRemoteVmBackupRunner extends Abstrac
|
||||
this._writers = writers
|
||||
|
||||
const RemoteWriter = this._getRemoteWriter()
|
||||
extractIdsFromSimplePattern(job.remotes).forEach(remoteId => {
|
||||
const adapter = remoteAdapters[remoteId]
|
||||
Object.entries(remoteAdapters).forEach(([remoteId, adapter]) => {
|
||||
const targetSettings = {
|
||||
...settings,
|
||||
...allSettings[remoteId],
|
||||
|
||||
@@ -31,11 +31,6 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
|
||||
throw new Error('cannot backup a VM created by this very job')
|
||||
}
|
||||
|
||||
const currentOperations = Object.values(vm.current_operations)
|
||||
if (currentOperations.some(_ => _ === 'migrate_send' || _ === 'pool_migrate')) {
|
||||
throw new Error('cannot backup a VM currently being migrated')
|
||||
}
|
||||
|
||||
this.config = config
|
||||
this.job = job
|
||||
this.remoteAdapters = remoteAdapters
|
||||
@@ -261,15 +256,7 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
|
||||
}
|
||||
|
||||
if (this._writers.size !== 0) {
|
||||
const { pool_migrate = null, migrate_send = null } = this._exportedVm.blocked_operations
|
||||
|
||||
const reason = 'VM migration is blocked during backup'
|
||||
await this._exportedVm.update_blocked_operations({ pool_migrate: reason, migrate_send: reason })
|
||||
try {
|
||||
await this._copy()
|
||||
} finally {
|
||||
await this._exportedVm.update_blocked_operations({ pool_migrate, migrate_send })
|
||||
}
|
||||
await this._copy()
|
||||
}
|
||||
} finally {
|
||||
if (startAfter) {
|
||||
|
||||
@@ -2,21 +2,18 @@
|
||||
// eslint-disable-next-line eslint-comments/disable-enable-pair
|
||||
/* eslint-disable n/shebang */
|
||||
|
||||
import { asyncEach } from '@vates/async-each'
|
||||
import { catchGlobalErrors } from '@xen-orchestra/log/configure'
|
||||
import { createLogger } from '@xen-orchestra/log'
|
||||
import { getSyncedHandler } from '@xen-orchestra/fs'
|
||||
import { join } from 'node:path'
|
||||
import { load as loadConfig } from 'app-conf'
|
||||
import Disposable from 'promise-toolbox/Disposable'
|
||||
import min from 'lodash/min.js'
|
||||
|
||||
import { getVmBackupDir } from '../_getVmBackupDir.mjs'
|
||||
import { RemoteAdapter } from '../RemoteAdapter.mjs'
|
||||
|
||||
import { CLEAN_VM_QUEUE } from './index.mjs'
|
||||
|
||||
const APP_NAME = 'xo-merge-worker'
|
||||
const APP_DIR = new URL('.', import.meta.url).pathname
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
catchGlobalErrors(createLogger('xo:backups:mergeWorker'))
|
||||
@@ -37,7 +34,6 @@ const main = Disposable.wrap(async function* main(args) {
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
const entries = await handler.list(CLEAN_VM_QUEUE)
|
||||
if (entries.length !== 0) {
|
||||
entries.sort()
|
||||
return entries
|
||||
}
|
||||
await new Promise(timeoutResolver)
|
||||
@@ -46,47 +42,38 @@ const main = Disposable.wrap(async function* main(args) {
|
||||
|
||||
let taskFiles
|
||||
while ((taskFiles = await listRetry()) !== undefined) {
|
||||
const { concurrency } = await loadConfig(APP_NAME, {
|
||||
appDir: APP_DIR,
|
||||
ignoreUnknownFormats: true,
|
||||
})
|
||||
await asyncEach(
|
||||
taskFiles,
|
||||
async taskFileBasename => {
|
||||
const previousTaskFile = join(CLEAN_VM_QUEUE, taskFileBasename)
|
||||
const taskFile = join(CLEAN_VM_QUEUE, '_' + taskFileBasename)
|
||||
const taskFileBasename = min(taskFiles)
|
||||
const previousTaskFile = join(CLEAN_VM_QUEUE, taskFileBasename)
|
||||
const taskFile = join(CLEAN_VM_QUEUE, '_' + taskFileBasename)
|
||||
|
||||
// move this task to the end
|
||||
try {
|
||||
await handler.rename(previousTaskFile, taskFile)
|
||||
} catch (error) {
|
||||
// this error occurs if the task failed too many times (i.e. too many `_` prefixes)
|
||||
// there is nothing more that can be done
|
||||
if (error.code === 'ENAMETOOLONG') {
|
||||
await handler.unlink(previousTaskFile)
|
||||
}
|
||||
// move this task to the end
|
||||
try {
|
||||
await handler.rename(previousTaskFile, taskFile)
|
||||
} catch (error) {
|
||||
// this error occurs if the task failed too many times (i.e. too many `_` prefixes)
|
||||
// there is nothing more that can be done
|
||||
if (error.code === 'ENAMETOOLONG') {
|
||||
await handler.unlink(previousTaskFile)
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
|
||||
try {
|
||||
const vmDir = getVmBackupDir(String(await handler.readFile(taskFile)))
|
||||
try {
|
||||
await adapter.cleanVm(vmDir, { merge: true, logInfo: info, logWarn: warn, remove: true })
|
||||
} catch (error) {
|
||||
// consider the clean successful if the VM dir is missing
|
||||
if (error.code !== 'ENOENT') {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const vmDir = getVmBackupDir(String(await handler.readFile(taskFile)))
|
||||
try {
|
||||
await adapter.cleanVm(vmDir, { merge: true, logInfo: info, logWarn: warn, remove: true })
|
||||
} catch (error) {
|
||||
// consider the clean successful if the VM dir is missing
|
||||
if (error.code !== 'ENOENT') {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
handler.unlink(taskFile).catch(error => warn('deleting task failure', { error }))
|
||||
} catch (error) {
|
||||
warn('failure handling task', { error })
|
||||
}
|
||||
},
|
||||
{ concurrency }
|
||||
)
|
||||
handler.unlink(taskFile).catch(error => warn('deleting task failure', { error }))
|
||||
} catch (error) {
|
||||
warn('failure handling task', { error })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
concurrency = 1
|
||||
@@ -8,7 +8,7 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||
},
|
||||
"version": "0.43.0",
|
||||
"version": "0.40.0",
|
||||
"engines": {
|
||||
"node": ">=14.18"
|
||||
},
|
||||
@@ -17,7 +17,6 @@
|
||||
"test-integration": "node--test *.integ.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@kldzj/stream-throttle": "^1.1.1",
|
||||
"@vates/async-each": "^1.0.0",
|
||||
"@vates/cached-dns.lookup": "^1.0.0",
|
||||
@@ -28,10 +27,9 @@
|
||||
"@vates/nbd-client": "^2.0.0",
|
||||
"@vates/parse-duration": "^0.1.1",
|
||||
"@xen-orchestra/async-map": "^0.1.2",
|
||||
"@xen-orchestra/fs": "^4.1.0",
|
||||
"@xen-orchestra/fs": "^4.0.1",
|
||||
"@xen-orchestra/log": "^0.6.0",
|
||||
"@xen-orchestra/template": "^0.1.0",
|
||||
"app-conf": "^2.3.0",
|
||||
"compare-versions": "^6.0.0",
|
||||
"d3-time-format": "^4.1.0",
|
||||
"decorator-synchronized": "^0.6.0",
|
||||
@@ -44,19 +42,19 @@
|
||||
"proper-lockfile": "^4.1.2",
|
||||
"tar": "^6.1.15",
|
||||
"uuid": "^9.0.0",
|
||||
"vhd-lib": "^4.6.1",
|
||||
"xen-api": "^1.3.6",
|
||||
"vhd-lib": "^4.5.0",
|
||||
"xen-api": "^1.3.4",
|
||||
"yazl": "^2.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"fs-extra": "^11.1.0",
|
||||
"rimraf": "^5.0.1",
|
||||
"sinon": "^16.0.0",
|
||||
"sinon": "^15.0.1",
|
||||
"test": "^3.2.1",
|
||||
"tmp": "^0.2.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@xen-orchestra/xapi": "^3.2.0"
|
||||
"@xen-orchestra/xapi": "^3.0.0"
|
||||
},
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"author": {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"preferGlobal": true,
|
||||
"dependencies": {
|
||||
"golike-defer": "^0.5.1",
|
||||
"xen-api": "^1.3.6"
|
||||
"xen-api": "^1.3.4"
|
||||
},
|
||||
"scripts": {
|
||||
"postversion": "npm publish"
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
"test": "node--test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"sinon": "^16.0.0",
|
||||
"sinon": "^15.0.1",
|
||||
"test": "^3.2.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "@xen-orchestra/fs",
|
||||
"version": "4.1.0",
|
||||
"version": "4.0.1",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"description": "The File System for Xen Orchestra backups.",
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/fs",
|
||||
@@ -53,7 +53,7 @@
|
||||
"cross-env": "^7.0.2",
|
||||
"dotenv": "^16.0.0",
|
||||
"rimraf": "^5.0.1",
|
||||
"sinon": "^16.0.0",
|
||||
"sinon": "^15.0.4",
|
||||
"test": "^3.3.0",
|
||||
"tmp": "^0.2.1"
|
||||
},
|
||||
|
||||
@@ -227,11 +227,19 @@ export default class RemoteHandlerAbstract {
|
||||
|
||||
// when using encryption, the file size is aligned with the encryption block size ( 16 bytes )
|
||||
// that means that the size will be 1 to 16 bytes more than the content size + the initialized vector length (16 bytes)
|
||||
async getSize(file) {
|
||||
assert.strictEqual(this.isEncrypted, false, `Can't compute size of an encrypted file ${file}`)
|
||||
async getSize(file, { exact = true } = {}) {
|
||||
exact && assert.strictEqual(this.isEncrypted, false, `Can't compute size of an encrypted file ${file}`)
|
||||
|
||||
const size = await timeout.call(this._getSize(typeof file === 'string' ? normalizePath(file) : file), this._timeout)
|
||||
return size - this.#encryptor.ivLength
|
||||
return size
|
||||
}
|
||||
|
||||
getSizeApproximationMargin() {
|
||||
if (this.isEncrypted) {
|
||||
// on block for initialization vector + at most 1 bloc - 1 byte for aligment padding
|
||||
return this.#encryptor.ivLength * 2 - 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
async __list(dir, { filter, ignoreMissing = false, prependDir = false } = {}) {
|
||||
@@ -624,18 +632,14 @@ export default class RemoteHandlerAbstract {
|
||||
|
||||
const files = await this._list(dir)
|
||||
await asyncEach(files, file =>
|
||||
this._unlink(`${dir}/${file}`).catch(
|
||||
error => {
|
||||
// Unlink dir behavior is not consistent across platforms
|
||||
// https://github.com/nodejs/node-v0.x-archive/issues/5791
|
||||
if (error.code === 'EISDIR' || error.code === 'EPERM') {
|
||||
return this._rmtree(`${dir}/${file}`)
|
||||
}
|
||||
throw error
|
||||
},
|
||||
// real unlink concurrency will be 2**max directory depth
|
||||
{ concurrency: 2 }
|
||||
)
|
||||
this._unlink(`${dir}/${file}`).catch(error => {
|
||||
// Unlink dir behavior is not consistent across platforms
|
||||
// https://github.com/nodejs/node-v0.x-archive/issues/5791
|
||||
if (error.code === 'EISDIR' || error.code === 'EPERM') {
|
||||
return this._rmtree(`${dir}/${file}`)
|
||||
}
|
||||
throw error
|
||||
})
|
||||
)
|
||||
return this._rmtree(dir)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,9 @@ import { NodeHttpHandler } from '@aws-sdk/node-http-handler'
|
||||
import { getApplyMd5BodyChecksumPlugin } from '@aws-sdk/middleware-apply-body-checksum'
|
||||
import { Agent as HttpAgent } from 'http'
|
||||
import { Agent as HttpsAgent } from 'https'
|
||||
import pRetry from 'promise-toolbox/retry'
|
||||
import { createLogger } from '@xen-orchestra/log'
|
||||
import { decorateWith } from '@vates/decorate-with'
|
||||
import { PassThrough, pipeline } from 'stream'
|
||||
import { parse } from 'xo-remote-parser'
|
||||
import copyStreamToBuffer from './_copyStreamToBuffer.js'
|
||||
@@ -24,7 +26,6 @@ import guessAwsRegion from './_guessAwsRegion.js'
|
||||
import RemoteHandlerAbstract from './abstract'
|
||||
import { basename, join, split } from './path'
|
||||
import { asyncEach } from '@vates/async-each'
|
||||
import { pRetry } from 'promise-toolbox'
|
||||
|
||||
// endpoints https://docs.aws.amazon.com/general/latest/gr/s3.html
|
||||
|
||||
@@ -77,44 +78,6 @@ export default class S3Handler extends RemoteHandlerAbstract {
|
||||
const parts = split(path)
|
||||
this.#bucket = parts.shift()
|
||||
this.#dir = join(...parts)
|
||||
const WITH_RETRY = [
|
||||
'_closeFile',
|
||||
'_copy',
|
||||
'_getInfo',
|
||||
'_getSize',
|
||||
'_list',
|
||||
'_mkdir',
|
||||
'_openFile',
|
||||
'_outputFile',
|
||||
'_read',
|
||||
'_readFile',
|
||||
'_rename',
|
||||
'_rmdir',
|
||||
'_truncate',
|
||||
'_unlink',
|
||||
'_write',
|
||||
'_writeFile',
|
||||
]
|
||||
WITH_RETRY.forEach(functionName => {
|
||||
if (this[functionName] !== undefined) {
|
||||
// adding the retry on the top level mtehod won't
|
||||
// cover when _functionName are called internally
|
||||
this[functionName] = pRetry.wrap(this[functionName], {
|
||||
delays: [100, 200, 500, 1000, 2000],
|
||||
// these errors should not change on retry
|
||||
when: err => !['EEXIST', 'EISDIR', 'ENOTEMPTY', 'ENOENT', 'ENOTDIR', 'EISDIR'].includes(err?.code),
|
||||
onRetry(error) {
|
||||
warn('retrying method on fs ', {
|
||||
method: functionName,
|
||||
attemptNumber: this.attemptNumber,
|
||||
delay: this.delay,
|
||||
error,
|
||||
file: this.arguments?.[0],
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
get type() {
|
||||
@@ -249,6 +212,21 @@ export default class S3Handler extends RemoteHandlerAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
// some objectstorage provider like backblaze, can answer a 500/503 routinely
|
||||
// in this case we should retry, and let their load balancing do its magic
|
||||
// https://www.backblaze.com/b2/docs/calling.html#error_handling
|
||||
@decorateWith(pRetry.wrap, {
|
||||
delays: [100, 200, 500, 1000, 2000],
|
||||
when: e => e.$metadata?.httpStatusCode === 500,
|
||||
onRetry(error) {
|
||||
warn('retrying writing file', {
|
||||
attemptNumber: this.attemptNumber,
|
||||
delay: this.delay,
|
||||
error,
|
||||
file: this.arguments[0],
|
||||
})
|
||||
},
|
||||
})
|
||||
async _writeFile(file, data, options) {
|
||||
return this.#s3.send(
|
||||
new PutObjectCommand({
|
||||
|
||||
@@ -2,17 +2,6 @@
|
||||
|
||||
## **next**
|
||||
|
||||
## **0.1.4** (2023-10-03)
|
||||
|
||||
- Ability to migrate selected VMs to another host (PR [#7040](https://github.com/vatesfr/xen-orchestra/pull/7040))
|
||||
- Ability to snapshot selected VMs (PR [#7021](https://github.com/vatesfr/xen-orchestra/pull/7021))
|
||||
- Add Patches to Pool Dashboard (PR [#6709](https://github.com/vatesfr/xen-orchestra/pull/6709))
|
||||
- Add remember me checkbox on the login page (PR [#7030](https://github.com/vatesfr/xen-orchestra/pull/7030))
|
||||
|
||||
## **0.1.3** (2023-09-01)
|
||||
|
||||
- Add Alarms to Pool Dashboard (PR [#6976](https://github.com/vatesfr/xen-orchestra/pull/6976))
|
||||
|
||||
## **0.1.2** (2023-07-28)
|
||||
|
||||
- Ability to export selected VMs as CSV file (PR [#6915](https://github.com/vatesfr/xen-orchestra/pull/6915))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@xen-orchestra/lite",
|
||||
"version": "0.1.4",
|
||||
"version": "0.1.2",
|
||||
"scripts": {
|
||||
"dev": "GIT_HEAD=$(git rev-parse HEAD) vite",
|
||||
"build": "run-p type-check build-only",
|
||||
|
||||
@@ -23,8 +23,8 @@ import AppNavigation from "@/components/AppNavigation.vue";
|
||||
import AppTooltips from "@/components/AppTooltips.vue";
|
||||
import UnreachableHostsModal from "@/components/UnreachableHostsModal.vue";
|
||||
import { useChartTheme } from "@/composables/chart-theme.composable";
|
||||
import { usePoolCollection } from "@/composables/xen-api-collection/pool-collection.composable";
|
||||
import { useUiStore } from "@/stores/ui.store";
|
||||
import { usePoolCollection } from "@/stores/xen-api/pool.store";
|
||||
import { useXenApiStore } from "@/stores/xen-api.store";
|
||||
import { useActiveElement, useMagicKeys, whenever } from "@vueuse/core";
|
||||
import { logicAnd } from "@vueuse/math";
|
||||
@@ -74,8 +74,10 @@ if (import.meta.env.DEV) {
|
||||
|
||||
whenever(
|
||||
() => pool.value?.$ref,
|
||||
(poolRef) => {
|
||||
xenApiStore.getXapi().startWatching(poolRef);
|
||||
async (poolRef) => {
|
||||
const xenApi = xenApiStore.getXapi();
|
||||
await xenApi.injectWatchEvent(poolRef);
|
||||
await xenApi.startWatch();
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
@@ -46,51 +46,3 @@ code * {
|
||||
.link.router-link-active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.context-color-success {
|
||||
color: var(--color-green-infra-base);
|
||||
}
|
||||
|
||||
.context-color-error {
|
||||
color: var(--color-red-vates-base);
|
||||
}
|
||||
|
||||
.context-color-warning {
|
||||
color: var(--color-orange-world-base);
|
||||
}
|
||||
|
||||
.context-color-info {
|
||||
color: var(--color-extra-blue-base);
|
||||
}
|
||||
|
||||
.context-background-color-success {
|
||||
background-color: var(--background-color-green-infra);
|
||||
}
|
||||
|
||||
.context-background-color-error {
|
||||
background-color: var(--background-color-red-vates);
|
||||
}
|
||||
|
||||
.context-background-color-warning {
|
||||
background-color: var(--background-color-orange-world);
|
||||
}
|
||||
|
||||
.context-background-color-info {
|
||||
background-color: var(--background-color-extra-blue);
|
||||
}
|
||||
|
||||
.context-border-color-success {
|
||||
border-color: var(--color-green-infra-base);
|
||||
}
|
||||
|
||||
.context-border-color-error {
|
||||
border-color: var(--color-red-vates-base);
|
||||
}
|
||||
|
||||
.context-border-color-warning {
|
||||
border-color: var(--color-orange-world-base);
|
||||
}
|
||||
|
||||
.context-border-color-info {
|
||||
border-color: var(--color-extra-blue-base);
|
||||
}
|
||||
|
||||
@@ -1,346 +0,0 @@
|
||||
<svg width="345" height="254" viewBox="0 0 345 254" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_2254_118640)">
|
||||
<path opacity="0.1"
|
||||
d="M112.532 161.16C123.943 156.653 141.854 155.734 158.158 156.095C209.369 157.221 255.596 168.175 295.58 180.566C310.056 185.053 324.307 189.888 333.704 196.065C352.831 208.64 347.157 226.06 320.561 236.419C311.511 239.945 300.541 242.656 289.278 245.039C269.249 249.3 247.65 252.717 224.921 253.701C208.715 254.394 192.307 253.829 176.197 252.861C131.179 250.146 86.9952 244.182 49.9595 233.931C33.7148 229.444 18.405 223.864 11.7176 216.6C5.03021 209.336 9.30546 200.139 25.8933 195.839C32.7572 194.063 40.9546 193.259 48.9854 192.507C60.8044 191.398 72.813 190.301 83.4783 188.046C94.4965 185.716 107.541 181.209 105.361 176.052C103.011 170.518 101.135 165.66 112.532 161.16Z"
|
||||
fill="#2CA878"/>
|
||||
<path opacity="0.1"
|
||||
d="M164.531 9.89007L163.286 82.4315C163.286 82.4315 208.365 82.4315 246.718 94.9375L247.467 21.6346C247.467 21.6346 185.95 6.88994 164.531 9.89007Z"
|
||||
fill="black"/>
|
||||
<path
|
||||
d="M163.878 8.90472L162.632 81.4461C162.632 81.4461 207.712 81.4461 246.065 93.9521L246.813 20.6492C246.813 20.6492 185.296 5.90459 163.878 8.90472Z"
|
||||
fill="#F5F5FB"/>
|
||||
<path opacity="0.1"
|
||||
d="M145.332 9.83393L146.577 82.3753C146.577 82.3753 101.498 82.3754 63.1447 94.8814L62.3962 21.5981C62.3962 21.5981 123.913 6.8338 145.332 9.83393Z"
|
||||
fill="black"/>
|
||||
<path
|
||||
d="M145.986 8.84956L147.231 81.391C147.231 81.391 102.151 81.391 63.7985 93.897L63.05 20.6137C63.05 20.6137 124.567 5.84943 145.986 8.84956Z"
|
||||
fill="#F5F5FB"/>
|
||||
<path opacity="0.1"
|
||||
d="M258.348 31.9775V103.761C258.348 103.761 273.056 102.011 303.924 140.278L306.667 68.4944C306.667 68.4944 274.537 33.7303 258.348 31.9775Z"
|
||||
fill="black"/>
|
||||
<path
|
||||
d="M257.694 31.3213V103.104C257.694 103.104 272.403 101.355 303.271 139.621L306.013 67.8382C306.013 67.8382 273.883 33.0741 257.694 31.3213Z"
|
||||
fill="#F5F5FB"/>
|
||||
<path opacity="0.1"
|
||||
d="M52.3162 34.1074V105.894C52.3162 105.894 37.6078 104.144 6.73976 142.411L4.00073 70.6276C4.00073 70.6276 36.1271 35.8635 52.3162 34.1074Z"
|
||||
fill="black"/>
|
||||
<path
|
||||
d="M52.9697 33.123V104.91C52.9697 104.91 38.2613 103.16 7.39333 141.426L4.6543 69.6432C4.6543 69.6432 36.7807 34.8791 52.9697 33.123Z"
|
||||
fill="#F5F5FB"/>
|
||||
<path
|
||||
d="M218.358 154.169C218.358 156.614 202.894 170.745 167.695 170.745C132.497 170.745 113.438 157.107 113.438 154.661C113.438 152.216 133.15 158.272 168.349 158.272C203.548 158.272 218.358 151.72 218.358 154.169Z"
|
||||
fill="#F7F7FD"/>
|
||||
<path opacity="0.1"
|
||||
d="M103.322 120.488C105.39 120.488 107.067 120.257 107.067 119.972C107.067 119.688 105.39 119.457 103.322 119.457C101.253 119.457 99.5759 119.688 99.5759 119.972C99.5759 120.257 101.253 120.488 103.322 120.488Z"
|
||||
fill="#2CA878"/>
|
||||
<path opacity="0.1"
|
||||
d="M88.976 121.949C91.0447 121.949 92.7217 121.718 92.7217 121.433C92.7217 121.149 91.0447 120.918 88.976 120.918C86.9072 120.918 85.2302 121.149 85.2302 121.433C85.2302 121.718 86.9072 121.949 88.976 121.949Z"
|
||||
fill="#2CA878"/>
|
||||
<path opacity="0.1"
|
||||
d="M72.0841 119.972C74.1528 119.972 75.8299 119.741 75.8299 119.457C75.8299 119.172 74.1528 118.941 72.0841 118.941C70.0154 118.941 68.3384 119.172 68.3384 119.457C68.3384 119.741 70.0154 119.972 72.0841 119.972Z"
|
||||
fill="#2CA878"/>
|
||||
<path opacity="0.1"
|
||||
d="M94.7187 125.96C96.1881 125.96 97.3793 125.795 97.3793 125.592C97.3793 125.389 96.1881 125.225 94.7187 125.225C93.2493 125.225 92.0581 125.389 92.0581 125.592C92.0581 125.795 93.2493 125.96 94.7187 125.96Z"
|
||||
fill="#2CA878"/>
|
||||
<path opacity="0.1"
|
||||
d="M79.8305 125.592C81.2999 125.592 82.4911 125.427 82.4911 125.224C82.4911 125.021 81.2999 124.856 79.8305 124.856C78.3611 124.856 77.1699 125.021 77.1699 125.224C77.1699 125.427 78.3611 125.592 79.8305 125.592Z"
|
||||
fill="#2CA878"/>
|
||||
<path
|
||||
d="M88.8974 121.318C88.8974 121.318 94.1467 104.03 88.0737 97.1072C83.5305 91.9276 78.3662 92.5348 76.0782 93.1913C75.4806 93.351 74.9247 93.6392 74.449 94.0359C73.9732 94.4327 73.589 94.9284 73.3229 95.489C72.5155 97.2385 72.6691 100.084 78.3629 103.393C87.9038 108.94 88.4954 116.552 88.4954 116.552L88.8974 121.318Z"
|
||||
fill="#2CA878"/>
|
||||
<path d="M76.8889 95.6396C76.8889 95.6396 92.3687 100.501 88.8975 121.318" stroke="#565987" stroke-miterlimit="10"/>
|
||||
<path d="M85.9297 97.3828C85.9297 97.3828 83.7986 98.8238 84.4752 100.993" stroke="#565987" stroke-miterlimit="10"/>
|
||||
<path d="M78.4414 98.6663C78.4414 98.6663 79.7488 97.3337 81.3275 98.0689" stroke="#565987" stroke-miterlimit="10"/>
|
||||
<path d="M84.5764 105.359C84.5764 105.359 86.8644 104.194 87.456 105.655" stroke="#565987" stroke-miterlimit="10"/>
|
||||
<path d="M90.2213 107.791C90.2213 107.791 88.5021 107.653 88.4727 108.477" stroke="#565987" stroke-miterlimit="10"/>
|
||||
<path
|
||||
d="M89.11 121.456C89.11 121.456 82.9587 108.891 81.2656 109.167C80.4648 109.298 80.0954 110.089 79.9255 110.887C79.7152 111.916 79.8295 112.985 80.2523 113.946C81.1381 115.955 83.4816 119.779 89.11 121.456Z"
|
||||
fill="#2CA878"/>
|
||||
<path d="M81.3079 111.55C81.3079 111.55 87.7632 120.951 89.0118 121.364" stroke="#565987" stroke-miterlimit="10"/>
|
||||
<path d="M80.7229 113.017H82.2297" stroke="#565987" stroke-miterlimit="10"/>
|
||||
<path d="M82.5271 116.457L84.887 116.559" stroke="#565987" stroke-miterlimit="10"/>
|
||||
<path d="M83.9652 113.706L83.707 114.94" stroke="#565987" stroke-miterlimit="10"/>
|
||||
<path d="M86.4754 117.465L86.4297 118.587" stroke="#565987" stroke-miterlimit="10"/>
|
||||
<path
|
||||
d="M88.927 121.456C88.927 121.456 95.0751 108.891 96.7715 109.167C97.5755 109.298 97.9449 110.089 98.1116 110.887C98.3203 111.916 98.2061 112.985 97.7847 113.946C96.899 115.955 94.5554 119.779 88.927 121.456Z"
|
||||
fill="#2CA878"/>
|
||||
<path d="M96.719 111.55C96.719 111.55 90.2636 120.951 89.0183 121.364" stroke="#565987" stroke-miterlimit="10"/>
|
||||
<path d="M97.3139 113.017H95.8071" stroke="#565987" stroke-miterlimit="10"/>
|
||||
<path d="M95.5096 116.457L93.1465 116.559" stroke="#565987" stroke-miterlimit="10"/>
|
||||
<path d="M94.0715 113.706L94.3298 114.94" stroke="#565987" stroke-miterlimit="10"/>
|
||||
<path d="M91.5613 117.465L91.607 118.587" stroke="#565987" stroke-miterlimit="10"/>
|
||||
<path
|
||||
d="M78.3662 124.39C78.1986 124.253 78.0766 124.067 78.0165 123.858C77.9889 123.754 77.9983 123.643 78.043 123.545C78.0877 123.447 78.165 123.367 78.2616 123.32C78.4839 123.231 78.7257 123.389 78.9153 123.546C78.993 123.638 79.089 123.713 79.197 123.766C79.3051 123.818 79.4229 123.847 79.5429 123.852C79.4205 123.74 79.3288 123.599 79.2766 123.441C79.2245 123.284 79.2138 123.115 79.2454 122.952C79.2581 122.885 79.2874 122.822 79.3304 122.768C79.4546 122.634 79.6802 122.693 79.8305 122.798C80.3044 123.126 80.4352 123.783 80.4385 124.364C80.464 124.146 80.464 123.925 80.4385 123.707C80.4287 123.599 80.4457 123.491 80.4879 123.391C80.5301 123.292 80.5962 123.204 80.6803 123.136C80.7927 123.075 80.9188 123.045 81.0464 123.047C81.1524 123.032 81.2605 123.04 81.3635 123.069C81.4666 123.098 81.5623 123.149 81.6445 123.218C81.7227 123.326 81.7628 123.458 81.7587 123.591C81.7545 123.725 81.7063 123.854 81.6217 123.957C81.4453 124.156 81.2364 124.324 81.0039 124.452C80.8261 124.557 80.6751 124.702 80.5627 124.876C80.5485 124.899 80.5375 124.925 80.53 124.951H79.1899C78.8925 124.8 78.6157 124.612 78.3662 124.39Z"
|
||||
fill="#2CA878"/>
|
||||
<path
|
||||
d="M101.802 119.329C101.634 119.193 101.512 119.009 101.452 118.801C101.424 118.696 101.433 118.585 101.478 118.486C101.523 118.387 101.6 118.307 101.697 118.259C101.817 118.236 101.94 118.245 102.055 118.285C102.17 118.326 102.272 118.396 102.351 118.489C102.428 118.581 102.524 118.656 102.632 118.708C102.74 118.76 102.858 118.788 102.978 118.791C102.856 118.68 102.764 118.538 102.712 118.381C102.66 118.223 102.649 118.055 102.681 117.892C102.692 117.825 102.722 117.762 102.766 117.711C102.89 117.577 103.116 117.632 103.266 117.737C103.74 118.066 103.871 118.722 103.874 119.306C103.899 119.088 103.899 118.868 103.874 118.65C103.864 118.542 103.881 118.433 103.923 118.334C103.966 118.234 104.032 118.147 104.116 118.079C104.228 118.017 104.354 117.988 104.482 117.993C104.588 117.977 104.696 117.984 104.799 118.014C104.903 118.043 104.998 118.094 105.08 118.164C105.158 118.272 105.199 118.402 105.195 118.536C105.19 118.669 105.142 118.797 105.057 118.899C104.881 119.099 104.672 119.266 104.439 119.395C104.262 119.501 104.111 119.646 103.998 119.818C103.984 119.842 103.973 119.867 103.965 119.894H102.625C102.327 119.743 102.05 119.553 101.802 119.329Z"
|
||||
fill="#2CA878"/>
|
||||
<path
|
||||
d="M93.9899 124.39C93.8222 124.253 93.7003 124.067 93.6402 123.858C93.6133 123.754 93.6229 123.643 93.6676 123.545C93.7122 123.447 93.7891 123.368 93.8853 123.32C94.1108 123.231 94.3494 123.389 94.539 123.546C94.6168 123.638 94.7128 123.713 94.8208 123.765C94.9289 123.818 95.0466 123.847 95.1666 123.852C95.0445 123.74 94.9532 123.598 94.9017 123.441C94.8501 123.283 94.84 123.115 94.8724 122.952C94.8824 122.885 94.9107 122.821 94.9541 122.768C95.0783 122.634 95.3038 122.693 95.4542 122.798C95.9281 123.126 96.0621 123.783 96.0654 124.364C96.0892 124.146 96.0892 123.925 96.0654 123.707C96.0557 123.599 96.0727 123.491 96.1149 123.391C96.1571 123.292 96.2232 123.204 96.3073 123.136C96.4613 123.065 96.6312 123.035 96.8002 123.049C96.9692 123.064 97.1316 123.122 97.2715 123.218C97.3507 123.326 97.3915 123.457 97.3874 123.591C97.3832 123.725 97.3344 123.854 97.2486 123.957C97.0729 124.157 96.8639 124.324 96.6309 124.452C96.4544 124.559 96.3037 124.704 96.1896 124.876C96.1768 124.9 96.1658 124.925 96.1569 124.951H94.8201C94.5201 124.802 94.241 124.613 93.9899 124.39Z"
|
||||
fill="#2CA878"/>
|
||||
<path
|
||||
d="M71.8292 114.162C71.8292 114.162 71.3291 114.819 72.0612 115.803C72.7934 116.788 73.4013 117.638 73.1562 118.259C73.1562 118.259 72.0482 116.411 71.1493 116.384C70.2505 116.358 70.8486 115.272 71.8292 114.162Z"
|
||||
fill="#2CA878"/>
|
||||
<path opacity="0.1"
|
||||
d="M71.8292 114.162C71.7854 114.226 71.7512 114.295 71.7279 114.369C70.8487 115.403 70.3813 116.371 71.2245 116.394C72.0123 116.417 72.9569 117.832 73.1857 118.186C73.1789 118.214 73.1702 118.242 73.1595 118.268C73.1595 118.268 72.0515 116.42 71.1526 116.394C70.2538 116.368 70.8487 115.272 71.8292 114.162Z"
|
||||
fill="black"/>
|
||||
<path
|
||||
d="M70.9075 115C70.9075 115.233 70.9336 115.42 70.9663 115.42C70.999 115.42 71.0219 115.233 71.0219 115C71.0219 114.767 70.9925 114.878 70.9598 114.878C70.9271 114.878 70.9075 114.77 70.9075 115Z"
|
||||
fill="#FFD037"/>
|
||||
<path
|
||||
d="M70.5873 115.278C70.7899 115.39 70.9664 115.455 70.9828 115.426C70.9991 115.396 70.8455 115.285 70.6559 115.176C70.4663 115.068 70.5317 115.144 70.5186 115.176C70.5056 115.209 70.3846 115.183 70.5873 115.278Z"
|
||||
fill="#FFD037"/>
|
||||
<path
|
||||
d="M74.4897 114.162C74.4897 114.162 74.9931 114.819 74.2577 115.803C73.5222 116.788 72.9208 117.638 73.166 118.259C73.166 118.259 74.2707 116.411 75.1729 116.384C76.075 116.358 75.4801 115.272 74.4897 114.162Z"
|
||||
fill="#2CA878"/>
|
||||
<path opacity="0.1"
|
||||
d="M74.4897 114.162C74.5336 114.226 74.5678 114.295 74.5911 114.369C75.4703 115.403 75.9377 116.371 75.0944 116.394C74.3067 116.417 73.3654 117.832 73.1333 118.186C73.14 118.214 73.1488 118.242 73.1595 118.268C73.1595 118.268 74.2642 116.42 75.1663 116.394C76.0684 116.368 75.4801 115.272 74.4897 114.162Z"
|
||||
fill="black"/>
|
||||
<path
|
||||
d="M75.4247 115C75.4247 115.233 75.4018 115.42 75.3691 115.42C75.3364 115.42 75.3103 115.233 75.3103 115C75.3103 114.767 75.343 114.878 75.3757 114.878C75.4084 114.878 75.4247 114.77 75.4247 115Z"
|
||||
fill="#FFD037"/>
|
||||
<path
|
||||
d="M75.7514 115.278C75.5488 115.39 75.3723 115.455 75.3592 115.426C75.3461 115.396 75.4932 115.285 75.6861 115.176C75.8789 115.068 75.8103 115.144 75.8266 115.176C75.843 115.209 75.9443 115.183 75.7514 115.278Z"
|
||||
fill="#FFD037"/>
|
||||
<path
|
||||
d="M75.2646 118.183C75.2646 118.183 73.8591 118.141 73.4374 117.839C73.0158 117.537 71.2802 117.182 71.1756 117.658C71.071 118.134 69.0674 120.071 70.6494 120.084C72.2314 120.097 74.3298 119.834 74.7514 119.578C75.173 119.322 75.2646 118.183 75.2646 118.183Z"
|
||||
fill="#A8A8A8"/>
|
||||
<path opacity="0.2"
|
||||
d="M70.6231 119.913C72.2051 119.913 74.3035 119.667 74.7251 119.407C75.052 119.21 75.1729 118.505 75.2154 118.18H75.2645C75.2645 118.18 75.1729 119.315 74.7513 119.575C74.3297 119.834 72.2345 120.093 70.6493 120.08C70.195 120.08 70.0348 119.913 70.0446 119.67C70.1067 119.821 70.2832 119.91 70.6231 119.913Z"
|
||||
fill="black"/>
|
||||
<path opacity="0.1"
|
||||
d="M63.7919 164.62C70.0595 164.62 75.1403 163.866 75.1403 162.936C75.1403 162.006 70.0595 161.252 63.7919 161.252C57.5244 161.252 52.4436 162.006 52.4436 162.936C52.4436 163.866 57.5244 164.62 63.7919 164.62Z"
|
||||
fill="#2CA878"/>
|
||||
<path opacity="0.1"
|
||||
d="M95.4902 178.563C101.758 178.563 106.839 177.809 106.839 176.879C106.839 175.949 101.758 175.195 95.4902 175.195C89.2227 175.195 84.1418 175.949 84.1418 176.879C84.1418 177.809 89.2227 178.563 95.4902 178.563Z"
|
||||
fill="#2CA878"/>
|
||||
<path opacity="0.1"
|
||||
d="M48.8613 194.427C55.1288 194.427 60.2096 193.673 60.2096 192.743C60.2096 191.813 55.1288 191.06 48.8613 191.06C42.5938 191.06 37.5129 191.813 37.5129 192.743C37.5129 193.673 42.5938 194.427 48.8613 194.427Z"
|
||||
fill="#2CA878"/>
|
||||
<path opacity="0.1"
|
||||
d="M289.311 229.274C295.578 229.274 300.659 228.52 300.659 227.59C300.659 226.66 295.578 225.906 289.311 225.906C283.043 225.906 277.962 226.66 277.962 227.59C277.962 228.52 283.043 229.274 289.311 229.274Z"
|
||||
fill="#2CA878"/>
|
||||
<path opacity="0.1"
|
||||
d="M160.514 189.687C164.664 189.687 168.029 189.309 168.029 188.844C168.029 188.378 164.664 188 160.514 188C156.364 188 153 188.378 153 188.844C153 189.309 156.364 189.687 160.514 189.687Z"
|
||||
fill="#2CA878"/>
|
||||
<path opacity="0.1"
|
||||
d="M268.049 146.36C272.199 146.36 275.563 145.982 275.563 145.516C275.563 145.051 272.199 144.673 268.049 144.673C263.899 144.673 260.535 145.051 260.535 145.516C260.535 145.982 263.899 146.36 268.049 146.36Z"
|
||||
fill="#2CA878"/>
|
||||
<path opacity="0.1"
|
||||
d="M209.539 210.367C213.689 210.367 217.053 209.989 217.053 209.523C217.053 209.057 213.689 208.68 209.539 208.68C205.389 208.68 202.025 209.057 202.025 209.523C202.025 209.989 205.389 210.367 209.539 210.367Z"
|
||||
fill="#2CA878"/>
|
||||
<path opacity="0.1"
|
||||
d="M144.678 204.799C148.828 204.799 152.193 204.422 152.193 203.956C152.193 203.49 148.828 203.112 144.678 203.112C140.528 203.112 137.164 203.49 137.164 203.956C137.164 204.422 140.528 204.799 144.678 204.799Z"
|
||||
fill="#2CA878"/>
|
||||
<path opacity="0.1"
|
||||
d="M83.8935 202.61C88.0436 202.61 91.4078 202.232 91.4078 201.766C91.4078 201.301 88.0436 200.923 83.8935 200.923C79.7434 200.923 76.3792 201.301 76.3792 201.766C76.3792 202.232 79.7434 202.61 83.8935 202.61Z"
|
||||
fill="#2CA878"/>
|
||||
<path opacity="0.1"
|
||||
d="M68.0542 217.722C72.2042 217.722 75.5685 217.345 75.5685 216.879C75.5685 216.413 72.2042 216.035 68.0542 216.035C63.9041 216.035 60.5398 216.413 60.5398 216.879C60.5398 217.345 63.9041 217.722 68.0542 217.722Z"
|
||||
fill="#2CA878"/>
|
||||
<g opacity="0.1">
|
||||
<path opacity="0.1"
|
||||
d="M166.303 100.898C193.763 100.898 216.024 90.9207 216.024 78.6134C216.024 66.3061 193.763 56.3291 166.303 56.3291C138.843 56.3291 116.582 66.3061 116.582 78.6134C116.582 90.9207 138.843 100.898 166.303 100.898Z"
|
||||
fill="black"/>
|
||||
<path opacity="0.1"
|
||||
d="M216.887 86.5803C214.687 98.2001 192.951 107.322 166.457 107.322C139.73 107.322 117.844 98.0392 115.968 86.2783H115.781V105.215C124.521 114.386 143.871 120.763 166.332 120.763C188.794 120.763 208.143 114.386 216.887 105.215V86.5803Z"
|
||||
fill="black"/>
|
||||
<path opacity="0.1"
|
||||
d="M216.887 109.623C214.687 121.239 192.951 130.364 166.457 130.364C139.73 130.364 117.844 121.082 115.968 109.317H115.781V128.257C124.521 137.428 143.871 143.802 166.332 143.802C188.794 143.802 208.143 137.428 216.887 128.257V109.623Z"
|
||||
fill="black"/>
|
||||
<path opacity="0.1"
|
||||
d="M216.887 134.966C214.687 146.586 192.951 155.708 166.457 155.708C139.73 155.708 117.844 146.428 115.968 134.664H115.781V155.087C115.78 155.522 115.869 155.953 116.043 156.352C116.216 156.751 116.47 157.109 116.788 157.405C125.94 165.844 144.698 169.149 166.332 169.149C187.967 169.149 206.715 165.844 215.88 157.405C216.198 157.109 216.452 156.751 216.625 156.352C216.799 155.953 216.888 155.522 216.887 155.087V134.966Z"
|
||||
fill="black"/>
|
||||
</g>
|
||||
<path
|
||||
d="M166.303 99.5853C193.763 99.5853 216.024 89.6082 216.024 77.3009C216.024 64.9936 193.763 55.0166 166.303 55.0166C138.843 55.0166 116.582 64.9936 116.582 77.3009C116.582 89.6082 138.843 99.5853 166.303 99.5853Z"
|
||||
fill="#2CA878"/>
|
||||
<path
|
||||
d="M216.887 85.2678C214.687 96.8876 192.951 106.009 166.457 106.009C139.73 106.009 117.844 96.7267 115.968 84.9658H115.781V103.902C124.521 113.073 143.871 119.451 166.332 119.451C188.794 119.451 208.143 113.073 216.887 103.902V85.2678Z"
|
||||
fill="#2CA878"/>
|
||||
<path
|
||||
d="M216.887 108.31C214.687 119.927 192.951 129.052 166.457 129.052C139.73 129.052 117.844 119.769 115.968 108.005H115.781V126.944C124.521 136.115 143.871 142.49 166.332 142.49C188.794 142.49 208.143 136.115 216.887 126.944V108.31Z"
|
||||
fill="#2CA878"/>
|
||||
<path
|
||||
d="M216.887 133.654C214.687 145.273 192.951 154.395 166.457 154.395C139.73 154.395 117.844 145.116 115.968 133.352H115.781V153.775C115.78 154.21 115.869 154.641 116.043 155.039C116.216 155.438 116.47 155.797 116.788 156.092C125.94 164.531 144.698 167.837 166.332 167.837C187.967 167.837 206.715 164.531 215.88 156.092C216.198 155.797 216.452 155.438 216.625 155.039C216.799 154.641 216.888 154.21 216.887 153.775V133.654Z"
|
||||
fill="#2CA878"/>
|
||||
<path opacity="0.1"
|
||||
d="M239.593 190.876C249.882 190.876 258.224 189.368 258.224 187.508C258.224 185.648 249.882 184.141 239.593 184.141C229.304 184.141 220.962 185.648 220.962 187.508C220.962 189.368 229.304 190.876 239.593 190.876Z"
|
||||
fill="#2CA878"/>
|
||||
<path
|
||||
d="M229.134 144.597C230.075 145.155 231.553 145.52 233.412 144.318L233.396 146.98C233.527 148.805 235.776 155.459 235.776 155.459L237.361 160.931C238.499 164.798 239.085 168.807 239.103 172.839C239.103 174.172 239.227 175.551 239.609 176.588C240.456 178.886 239.283 181.84 238.769 182.93C237.911 183.221 237.038 183.467 236.155 183.668C236.076 183.687 235.998 183.711 235.923 183.74V183.724C235.923 183.724 233.543 184.672 232.484 184.541C231.696 184.446 231.203 185.434 231.83 186.159C231.318 186.316 230.784 186.387 230.248 186.369C229.189 186.238 228.66 188.063 230.644 188.585C232.628 189.107 243.868 188.585 243.868 188.585C244.184 188.426 244.438 188.167 244.591 187.847C244.743 187.527 244.786 187.166 244.712 186.819L246.117 186.76C246.117 186.76 248.101 185.848 246.117 183.369L246.009 183.448L245.588 182.194V180.504C245.588 180.504 248.101 168.359 247.307 166.938C246.513 165.516 246.091 158.183 246.091 158.183L246.369 150.24C247.085 148.756 247.451 146.426 247.637 144.604C247.908 143.736 248.078 142.839 248.144 141.932C248.176 141.502 248.202 141.056 248.219 140.59C248.23 139.567 248.066 138.55 247.735 137.583C248.632 136.246 249.635 134.984 250.732 133.808C251.189 133.382 251.536 132.85 251.744 132.26C251.951 131.669 252.014 131.037 251.925 130.417L251.272 125.983L249.683 118.433C249.882 116.336 249.448 115.128 248.964 114.445C249.078 113.766 249.186 113.09 249.268 112.407C249.618 109.448 249.461 106.45 248.804 103.544C248.575 102.533 248.261 101.493 247.552 100.725C246.751 99.8515 245.45 99.323 245.15 98.184C245.107 97.8937 245.046 97.6064 244.967 97.324C244.676 96.5789 243.761 96.3065 242.956 96.208C241.525 96.0341 239.456 95.9028 238.08 96.4673C236.872 97.0036 235.878 97.9303 235.256 99.0998C234.946 99.5804 234.829 100.161 234.929 100.725C234.978 100.92 235.054 101.108 235.154 101.283C234.696 102.53 234.746 103.908 235.293 105.118C235.84 106.329 236.84 107.274 238.077 107.749C238.109 107.851 238.139 107.949 238.162 108.044C238.222 108.278 238.266 108.516 238.292 108.757C238.266 109.216 238.237 109.679 238.217 110.142L238.168 110.329C237.03 110.835 235.921 111.402 234.844 112.029C235.635 113.204 234.71 118.161 234.71 118.161C232.726 119.333 230.745 122.333 232.86 125.464C234.975 128.596 233.785 131.983 233.785 131.983L233.458 137.084L233.393 138.105H233.416L233.393 138.492H233.448V138.909C232.52 139.254 231.16 139.943 230.353 141.223C229.97 141.851 229.505 142.424 228.97 142.927C228.846 143.034 228.749 143.17 228.687 143.323C228.626 143.475 228.603 143.641 228.619 143.805C228.635 143.969 228.69 144.127 228.78 144.264C228.869 144.402 228.991 144.517 229.134 144.597ZM245.101 131.048C245.021 130.165 245.184 129.277 245.571 128.481H246.506C246.534 128.688 246.572 128.894 246.62 129.098C246.889 130.263 245.816 130.811 245.101 131.048Z"
|
||||
fill="url(#paint0_linear_2254_118640)"/>
|
||||
<path
|
||||
d="M245.362 181.597L245.872 183.129L244.218 184.79L238.747 186.084L234.802 185.7L235.056 184.295C235.132 183.992 235.288 183.717 235.509 183.498C235.73 183.279 236.006 183.125 236.308 183.054C237.943 182.673 241.022 181.8 241.672 180.589L245.362 181.597Z"
|
||||
fill="#DB8B8B"/>
|
||||
<path opacity="0.1"
|
||||
d="M245.362 181.597L245.872 183.129L244.218 184.79L238.747 186.084L234.802 185.7L235.056 184.295C235.132 183.992 235.288 183.717 235.509 183.498C235.73 183.279 236.006 183.125 236.308 183.054C237.943 182.673 241.022 181.8 241.672 180.589L245.362 181.597Z"
|
||||
fill="black"/>
|
||||
<path
|
||||
d="M236.073 185.302L236.838 184.281L236.073 183.096C236.073 183.096 233.785 184.025 232.765 183.897C231.745 183.769 231.239 185.558 233.148 186.07C235.056 186.582 245.872 186.07 245.872 186.07C245.872 186.07 247.781 185.174 245.872 182.745L244.218 184.025C244.218 184.025 241.417 185.558 240.018 185.302H236.073Z"
|
||||
fill="#C17174"/>
|
||||
<path
|
||||
d="M232.641 187.475L236.586 187.859L242.058 186.579L243.711 184.918L243.577 184.514L243.388 183.943L243.201 183.385L239.505 182.364C239.317 182.67 239.061 182.928 238.756 183.116C237.557 183.94 235.406 184.534 234.141 184.829C234.088 184.842 234.035 184.859 233.984 184.879C233.738 184.957 233.514 185.093 233.331 185.276C233.11 185.493 232.953 185.768 232.879 186.07L232.641 187.475Z"
|
||||
fill="#DB8B8B"/>
|
||||
<path opacity="0.1"
|
||||
d="M232.641 187.475L236.586 187.859L242.058 186.579L243.711 184.918L243.577 184.515L242.058 185.686C242.058 185.686 239.256 187.219 237.857 186.963H233.909L234.674 185.943L233.988 184.879C233.741 184.958 233.517 185.094 233.334 185.276C233.113 185.494 232.957 185.769 232.883 186.071L232.641 187.475Z"
|
||||
fill="black"/>
|
||||
<path
|
||||
d="M233.909 187.091L234.674 186.07L233.909 184.885C233.909 184.885 231.621 185.814 230.601 185.686C229.582 185.558 229.075 187.347 230.984 187.859C232.893 188.371 243.708 187.859 243.708 187.859C243.708 187.859 245.617 186.963 243.708 184.537L242.054 185.814C242.054 185.814 239.253 187.347 237.854 187.091H233.909Z"
|
||||
fill="#C17174"/>
|
||||
<path
|
||||
d="M237.089 112.974C237.089 112.974 243.198 113.63 242.319 111.057C242.068 110.332 242.003 109.555 242.129 108.799C242.287 107.862 242.656 106.973 243.208 106.202L237.482 107.095C237.826 107.671 238.088 108.293 238.259 108.943C238.93 111.654 237.089 112.974 237.089 112.974Z"
|
||||
fill="#DB8B8B"/>
|
||||
<path
|
||||
d="M247.526 142.1C247.526 142.1 247.398 147.594 246.127 150.279L245.872 158.075C245.872 158.075 246.251 165.231 247.016 166.636C247.781 168.041 245.362 179.926 245.362 179.926V181.971C245.362 181.971 241.289 183.504 240.132 181.715L241.276 166.508L240.766 145.425L247.526 142.1Z"
|
||||
fill="#474463"/>
|
||||
<path opacity="0.1"
|
||||
d="M247.526 142.1C247.526 142.1 247.398 147.594 246.127 150.279L245.872 158.075C245.872 158.075 246.251 165.231 247.016 166.636C247.781 168.041 245.362 179.926 245.362 179.926V181.971C245.362 181.971 241.289 183.504 240.132 181.715L241.276 166.508L240.766 145.425L247.526 142.1Z"
|
||||
fill="black"/>
|
||||
<path
|
||||
d="M250.069 124.476L250.834 126.521L251.487 130.863C251.572 131.469 251.513 132.087 251.313 132.665C251.114 133.243 250.78 133.765 250.34 134.188C248.814 135.721 246.777 138.915 246.777 138.915L244.362 131.631C244.362 131.631 246.777 131.247 246.395 129.586C246.012 127.925 246.143 124.86 246.143 124.86L250.069 124.476Z"
|
||||
fill="#FF748E"/>
|
||||
<path opacity="0.05"
|
||||
d="M250.098 124.446L250.86 126.491L251.514 130.837C251.6 131.442 251.541 132.059 251.342 132.637C251.143 133.215 250.81 133.737 250.37 134.159C248.84 135.692 246.807 138.889 246.807 138.889L244.372 131.595C244.372 131.595 246.791 131.211 246.408 129.55C246.026 127.89 246.153 124.82 246.153 124.82L250.098 124.446Z"
|
||||
fill="black"/>
|
||||
<path opacity="0.1"
|
||||
d="M238.76 183.129C240.394 185.709 242.934 184.245 243.385 183.956L243.198 183.398L239.505 182.364C239.32 182.675 239.065 182.937 238.76 183.129Z"
|
||||
fill="black"/>
|
||||
<path
|
||||
d="M233.654 147.095C233.782 148.884 235.942 155.4 235.942 155.4L237.469 160.763C238.561 164.556 239.125 168.482 239.145 172.429C239.107 173.672 239.272 174.913 239.632 176.102C240.652 178.915 238.616 182.746 238.616 182.746C240.397 185.943 243.45 183.77 243.45 183.77V181.725C243.45 181.725 245.869 170.607 245.231 168.434C244.594 166.261 244.342 160.386 244.342 160.386V149.777C246.38 147.738 247.619 145.03 247.833 142.149C247.863 141.729 247.889 141.289 247.905 140.836C248.006 137.954 246.042 134.573 245.29 133.368C245.107 133.076 244.99 132.912 244.99 132.912L233.756 137.389H233.729V137.832V138.797L233.654 147.095Z"
|
||||
fill="#474463"/>
|
||||
<path opacity="0.1"
|
||||
d="M237.472 107.095C237.816 107.671 238.078 108.293 238.25 108.943C238.872 109.18 239.535 109.289 240.199 109.264C240.864 109.24 241.517 109.081 242.12 108.799C242.277 107.862 242.646 106.973 243.198 106.202L237.472 107.095Z"
|
||||
fill="black"/>
|
||||
<path
|
||||
d="M240.018 109.012C242.758 109.012 244.98 106.781 244.98 104.03C244.98 101.278 242.758 99.0469 240.018 99.0469C237.278 99.0469 235.056 101.278 235.056 104.03C235.056 106.781 237.278 109.012 240.018 109.012Z"
|
||||
fill="#DB8B8B"/>
|
||||
<path opacity="0.1"
|
||||
d="M233.654 138.787H233.71C239.044 139.89 245.745 134.307 245.745 134.307C245.565 134.002 245.41 133.684 245.28 133.355C245.097 133.063 244.98 132.898 244.98 132.898L233.746 137.376L233.716 137.822L233.654 138.787Z"
|
||||
fill="black"/>
|
||||
<path
|
||||
d="M235.057 112.846C235.057 112.846 242.182 108.5 244.47 110.289L247.653 114.379C247.653 114.379 249.69 115.019 249.311 119.122L250.837 126.534L246.261 127.683C246.261 127.683 243.591 130.24 245.754 133.946C245.754 133.946 239.008 139.569 233.661 138.416L234.043 132.413C234.043 132.413 235.187 129.088 233.154 126.022C231.121 122.956 233.027 120.015 234.936 118.866C234.929 118.853 235.818 113.995 235.057 112.846Z"
|
||||
fill="#FF748E"/>
|
||||
<path
|
||||
d="M234.674 138.915C234.674 138.915 232 139.427 230.729 141.472C230.361 142.087 229.913 142.649 229.395 143.143C229.279 143.249 229.189 143.381 229.133 143.528C229.077 143.675 229.056 143.834 229.071 143.991C229.087 144.148 229.139 144.299 229.223 144.432C229.307 144.565 229.421 144.677 229.555 144.758C230.565 145.368 232.209 145.742 234.292 144.026C237.871 141.088 234.674 138.915 234.674 138.915Z"
|
||||
fill="#DB8B8B"/>
|
||||
<path opacity="0.1"
|
||||
d="M238.619 115.275C238.619 115.275 241.162 115.019 241.799 121.662C242.437 128.306 243.453 129.842 243.453 129.842C243.453 129.842 244.091 133.292 242.561 135.337C241.031 137.382 238.109 141.728 238.109 141.728C238.109 141.728 234.929 142.621 234.292 139.43L238.237 132.019C238.237 132.019 238.871 129.461 237.344 127.673C235.818 125.884 234.674 113.995 238.619 115.275Z"
|
||||
fill="black"/>
|
||||
<path
|
||||
d="M238.364 114.892C238.364 114.892 240.907 114.636 241.544 121.283C242.182 127.929 243.198 129.459 243.198 129.459C243.198 129.459 243.852 132.909 242.309 134.954C240.767 136.999 237.871 141.345 237.871 141.345C237.871 141.345 234.69 142.237 234.053 139.047L237.998 131.635C237.998 131.635 238.635 129.078 237.106 127.289C235.576 125.5 234.419 113.615 238.364 114.892Z"
|
||||
fill="#FF748E"/>
|
||||
<path opacity="0.1"
|
||||
d="M236.263 119.684C236.263 119.684 236.011 123.645 237.155 125.818C238.299 127.991 238.299 130.801 237.155 132.334"
|
||||
fill="black"/>
|
||||
<path opacity="0.1"
|
||||
d="M235.628 114.826C235.628 114.826 236.645 113.805 239.574 113.677C242.502 113.549 243.77 112.656 243.77 112.656"
|
||||
fill="black"/>
|
||||
<path opacity="0.1"
|
||||
d="M242.705 97.5896C243.479 97.6848 244.362 97.9507 244.64 98.6794C244.716 98.9567 244.776 99.2384 244.82 99.5229C245.107 100.639 246.359 101.164 247.13 102.011C247.81 102.766 248.111 103.784 248.333 104.778C248.967 107.626 249.118 110.561 248.781 113.46C248.533 115.577 248.023 117.665 247.882 119.792C247.742 121.919 248 124.161 249.157 125.95C249.657 126.718 250.314 127.394 250.667 128.247C249.938 128.841 248.931 128.927 247.99 128.96C246.901 128.997 245.806 128.997 244.705 128.96C244.129 128.983 243.556 128.862 243.038 128.608C242.672 128.374 242.37 128.05 242.162 127.666C241.535 126.6 241.378 125.316 241.404 124.079C241.43 122.841 241.626 121.61 241.649 120.37C241.714 119.693 241.564 119.013 241.221 118.426C240.905 118.058 240.547 117.727 240.155 117.442C238.792 116.227 238.325 114.284 238.279 112.456C238.233 110.627 238.534 108.799 238.381 106.977C238.322 106.265 238.054 105.408 237.358 105.27C237.138 105.261 236.919 105.233 236.704 105.185C236.145 104.968 236.2 104.177 236.05 103.596C235.87 102.94 235.315 102.438 235.158 101.781C234.792 100.235 236.753 98.3872 238.037 97.8423C239.322 97.2975 241.329 97.4189 242.705 97.5896Z"
|
||||
fill="black"/>
|
||||
<path
|
||||
d="M242.832 97.3331C243.607 97.4283 244.489 97.6942 244.767 98.4261C244.844 98.7023 244.904 98.9829 244.947 99.2664C245.235 100.382 246.487 100.908 247.258 101.755C247.938 102.509 248.238 103.527 248.461 104.522C249.094 107.373 249.245 110.312 248.905 113.213C248.66 115.331 248.15 117.418 248.01 119.548C247.869 121.679 248.127 123.914 249.284 125.703C249.784 126.474 250.441 127.147 250.794 128.001C250.066 128.598 249.059 128.68 248.118 128.713C247.028 128.748 245.933 128.748 244.833 128.713C244.257 128.737 243.684 128.618 243.166 128.365C242.799 128.129 242.498 127.804 242.29 127.42C241.662 126.353 241.505 125.07 241.531 123.832C241.558 122.595 241.754 121.364 241.777 120.126C241.777 119.45 241.737 118.734 241.348 118.183C241.033 117.814 240.676 117.483 240.283 117.198C238.92 115.984 238.452 114.041 238.407 112.212C238.361 110.384 238.662 108.556 238.508 106.734C238.449 106.022 238.181 105.165 237.485 105.027C237.265 105.018 237.046 104.99 236.831 104.942C236.272 104.725 236.328 103.934 236.178 103.353C235.998 102.697 235.442 102.198 235.285 101.538C234.919 99.9919 236.88 98.1439 238.165 97.599C239.449 97.0541 241.466 97.1624 242.832 97.3331Z"
|
||||
fill="#464353"/>
|
||||
<path
|
||||
d="M41.8503 188.007C41.8503 188.007 45.3542 187.899 46.4067 187.144C47.4591 186.389 51.7932 185.486 52.0547 186.697C52.3162 187.909 57.317 192.717 53.3621 192.75C49.4072 192.783 44.1775 192.13 43.1218 191.486C42.0661 190.843 41.8503 188.007 41.8503 188.007Z"
|
||||
fill="#A8A8A8"/>
|
||||
<path opacity="0.2"
|
||||
d="M53.434 192.32C49.4824 192.353 44.2494 191.703 43.197 191.06C42.3962 190.568 42.0759 188.808 41.968 187.994H41.8503C41.8503 187.994 42.0726 190.83 43.1251 191.474C44.1775 192.117 49.4105 192.767 53.3654 192.737C54.5061 192.737 54.8983 192.32 54.8787 191.716C54.7186 192.094 54.2838 192.32 53.434 192.32Z"
|
||||
fill="black"/>
|
||||
<path
|
||||
d="M264.401 141.84C264.401 141.84 266.804 141.764 267.529 141.246C268.255 140.727 271.226 140.11 271.406 140.941C271.586 141.771 275.021 145.073 272.305 145.096C269.589 145.119 266 144.669 265.277 144.229C264.555 143.79 264.401 141.84 264.401 141.84Z"
|
||||
fill="#A8A8A8"/>
|
||||
<path opacity="0.2"
|
||||
d="M272.354 144.808C269.641 144.827 266.049 144.381 265.326 143.941C264.774 143.613 264.555 142.395 264.483 141.837H264.401C264.401 141.837 264.555 143.783 265.277 144.227C266 144.67 269.592 145.113 272.305 145.093C273.089 145.093 273.36 144.808 273.344 144.391C273.236 144.647 272.939 144.801 272.354 144.808Z"
|
||||
fill="black"/>
|
||||
<path opacity="0.1"
|
||||
d="M10.2273 14.1997C14.1319 14.1997 17.2971 11.021 17.2971 7.09987C17.2971 3.17872 14.1319 0 10.2273 0C6.32274 0 3.15747 3.17872 3.15747 7.09987C3.15747 11.021 6.32274 14.1997 10.2273 14.1997Z"
|
||||
fill="#2CA878"/>
|
||||
<path opacity="0.1"
|
||||
d="M314.201 34.5503C318.105 34.5503 321.271 31.3716 321.271 27.4505C321.271 23.5293 318.105 20.3506 314.201 20.3506C310.296 20.3506 307.131 23.5293 307.131 27.4505C307.131 31.3716 310.296 34.5503 314.201 34.5503Z"
|
||||
fill="#2CA878"/>
|
||||
<path opacity="0.1"
|
||||
d="M5.22965 40.1043C8.11791 40.1043 10.4593 37.753 10.4593 34.8525C10.4593 31.9519 8.11791 29.6006 5.22965 29.6006C2.34139 29.6006 0 31.9519 0 34.8525C0 37.753 2.34139 40.1043 5.22965 40.1043Z"
|
||||
fill="#2CA878"/>
|
||||
<path opacity="0.1"
|
||||
d="M58.1798 10.5633C61.0681 10.5633 63.4095 8.21196 63.4095 5.31144C63.4095 2.41091 61.0681 0.0595703 58.1798 0.0595703C55.2916 0.0595703 52.9502 2.41091 52.9502 5.31144C52.9502 8.21196 55.2916 10.5633 58.1798 10.5633Z"
|
||||
fill="#2CA878"/>
|
||||
<path opacity="0.1"
|
||||
d="M272.595 11.2196C275.484 11.2196 277.825 8.86821 277.825 5.96769C277.825 3.06716 275.484 0.71582 272.595 0.71582C269.707 0.71582 267.366 3.06716 267.366 5.96769C267.366 8.86821 269.707 11.2196 272.595 11.2196Z"
|
||||
fill="#2CA878"/>
|
||||
<path
|
||||
d="M290.775 212.176C290.775 212.176 292.083 213.896 290.17 216.492C288.258 219.089 286.68 221.278 287.317 222.896C287.317 222.896 290.203 218.078 292.547 218.009C294.89 217.94 293.357 215.077 290.775 212.176Z"
|
||||
fill="#FC6681"/>
|
||||
<path opacity="0.1"
|
||||
d="M290.775 212.176C290.889 212.343 290.978 212.525 291.04 212.717C293.328 215.422 294.557 217.946 292.347 218.009C290.295 218.068 287.833 221.754 287.242 222.686C287.261 222.758 287.285 222.828 287.314 222.896C287.314 222.896 290.2 218.078 292.543 218.009C294.887 217.94 293.357 215.077 290.775 212.176Z"
|
||||
fill="black"/>
|
||||
<path
|
||||
d="M293.207 214.368C293.207 214.972 293.138 215.464 293.056 215.464C292.975 215.464 292.906 214.972 292.906 214.368C292.906 213.764 292.991 214.04 293.073 214.04C293.154 214.04 293.207 213.761 293.207 214.368Z"
|
||||
fill="#FFD037"/>
|
||||
<path
|
||||
d="M294.044 215.09C293.514 215.379 293.063 215.553 293.011 215.481C292.959 215.409 293.37 215.113 293.897 214.824C294.423 214.536 294.224 214.746 294.259 214.824C294.295 214.903 294.573 214.802 294.044 215.09Z"
|
||||
fill="#FFD037"/>
|
||||
<path
|
||||
d="M283.859 212.176C283.859 212.176 282.551 213.896 284.463 216.492C286.376 219.089 287.954 221.278 287.317 222.896C287.317 222.896 284.427 218.078 282.087 218.009C279.747 217.94 281.273 215.077 283.859 212.176Z"
|
||||
fill="#FC6681"/>
|
||||
<path opacity="0.1"
|
||||
d="M283.859 212.176C283.745 212.343 283.655 212.525 283.591 212.717C281.303 215.422 280.077 217.946 282.283 218.009C284.336 218.068 286.801 221.754 287.389 222.686C287.369 222.755 287.346 222.827 287.32 222.896C287.32 222.896 284.431 218.078 282.091 218.009C279.75 217.94 281.273 215.077 283.859 212.176Z"
|
||||
fill="black"/>
|
||||
<path
|
||||
d="M281.424 214.368C281.424 214.972 281.492 215.464 281.577 215.464C281.662 215.464 281.728 214.972 281.728 214.368C281.728 213.764 281.643 214.04 281.558 214.04C281.473 214.04 281.424 213.761 281.424 214.368Z"
|
||||
fill="#FFD037"/>
|
||||
<path
|
||||
d="M280.59 215.09C281.12 215.379 281.571 215.553 281.62 215.481C281.669 215.409 281.264 215.113 280.734 214.824C280.205 214.536 280.407 214.746 280.375 214.824C280.342 214.903 280.061 214.802 280.59 215.09Z"
|
||||
fill="#FFD037"/>
|
||||
<path
|
||||
d="M281.842 222.673C281.842 222.673 285.503 222.558 286.605 221.77C287.706 220.982 292.236 220.04 292.511 221.304C292.785 222.568 298.008 227.596 293.877 227.629C289.746 227.662 284.274 226.973 283.176 226.316C282.078 225.66 281.842 222.673 281.842 222.673Z"
|
||||
fill="#A8A8A8"/>
|
||||
<path opacity="0.2"
|
||||
d="M293.952 227.19C289.818 227.222 284.349 226.533 283.248 225.877C282.411 225.364 282.074 223.523 281.963 222.673H281.842C281.842 222.673 282.074 225.627 283.176 226.31C284.277 226.993 289.746 227.662 293.877 227.623C295.07 227.623 295.482 227.186 295.459 226.556C295.296 226.943 294.838 227.183 293.952 227.19Z"
|
||||
fill="black"/>
|
||||
<g opacity="0.7">
|
||||
<path
|
||||
d="M103.433 41.3486C102.075 41.3486 100.748 41.7529 99.6194 42.5103C98.4906 43.2677 97.6109 44.3443 97.0913 45.6038C96.5718 46.8634 96.4359 48.2494 96.7007 49.5865C96.9656 50.9236 97.6193 52.1518 98.5792 53.1159C99.5392 54.0799 100.762 54.7364 102.094 55.0023C103.425 55.2683 104.805 55.1318 106.059 54.6101C107.314 54.0884 108.386 53.2049 109.14 52.0713C109.894 50.9377 110.297 49.605 110.297 48.2417C110.297 46.4136 109.574 44.6603 108.286 43.3676C106.999 42.0749 105.253 41.3486 103.433 41.3486ZM103.433 42.6846C104.532 42.6846 105.606 43.0118 106.52 43.625C107.434 44.2381 108.146 45.1096 108.566 46.1293C108.987 47.1489 109.097 48.2709 108.882 49.3533C108.668 50.4358 108.139 51.43 107.362 52.2104C106.585 52.9908 105.595 53.5223 104.517 53.7376C103.439 53.9529 102.322 53.8424 101.306 53.42C100.291 52.9977 99.4233 52.2825 98.8127 51.3648C98.2021 50.4472 97.8763 49.3683 97.8763 48.2647C97.8763 46.7848 98.4617 45.3654 99.5037 44.319C100.546 43.2725 101.959 42.6846 103.433 42.6846ZM107.319 46.3116L106.695 45.6781C106.664 45.6464 106.628 45.6211 106.587 45.6038C106.547 45.5866 106.503 45.5777 106.459 45.5777C106.415 45.5777 106.372 45.5866 106.332 45.6038C106.291 45.6211 106.255 45.6464 106.224 45.6781L102.302 49.5809L100.648 47.8938C100.617 47.862 100.581 47.8367 100.54 47.8195C100.5 47.8022 100.457 47.7933 100.413 47.7933C100.369 47.7933 100.325 47.8022 100.285 47.8195C100.244 47.8367 100.208 47.862 100.177 47.8938L99.5497 48.5207C99.5181 48.5513 99.493 48.588 99.4758 48.6287C99.4586 48.6693 99.4497 48.7129 99.4497 48.7571C99.4497 48.8012 99.4586 48.8448 99.4758 48.8854C99.493 48.9261 99.5181 48.9628 99.5497 48.9934L102.067 51.5405C102.097 51.5723 102.134 51.5976 102.174 51.6148C102.214 51.6321 102.258 51.641 102.302 51.641C102.346 51.641 102.389 51.6321 102.43 51.6148C102.47 51.5976 102.507 51.5723 102.537 51.5405L107.319 46.7745C107.351 46.7439 107.376 46.7071 107.393 46.6665C107.41 46.6259 107.419 46.5823 107.419 46.5381C107.419 46.494 107.41 46.4503 107.393 46.4097C107.376 46.3691 107.351 46.3324 107.319 46.3018V46.3116Z"
|
||||
fill="#2CA878"/>
|
||||
<path
|
||||
d="M103.433 41.3486C102.075 41.3486 100.748 41.7529 99.6194 42.5103C98.4906 43.2677 97.6109 44.3443 97.0913 45.6038C96.5718 46.8634 96.4359 48.2494 96.7007 49.5865C96.9656 50.9236 97.6193 52.1518 98.5792 53.1159C99.5392 54.0799 100.762 54.7364 102.094 55.0023C103.425 55.2683 104.805 55.1318 106.059 54.6101C107.314 54.0884 108.386 53.2049 109.14 52.0713C109.894 50.9377 110.297 49.605 110.297 48.2417C110.297 46.4136 109.574 44.6603 108.286 43.3676C106.999 42.0749 105.253 41.3486 103.433 41.3486ZM103.433 42.6846C104.532 42.6846 105.606 43.0118 106.52 43.625C107.434 44.2381 108.146 45.1096 108.566 46.1293C108.987 47.1489 109.097 48.2709 108.882 49.3533C108.668 50.4358 108.139 51.43 107.362 52.2104C106.585 52.9908 105.595 53.5223 104.517 53.7376C103.439 53.9529 102.322 53.8424 101.306 53.42C100.291 52.9977 99.4233 52.2825 98.8127 51.3648C98.2021 50.4472 97.8763 49.3683 97.8763 48.2647C97.8763 46.7848 98.4617 45.3654 99.5037 44.319C100.546 43.2725 101.959 42.6846 103.433 42.6846ZM107.319 46.3116L106.695 45.6781C106.664 45.6464 106.628 45.6211 106.587 45.6038C106.547 45.5866 106.503 45.5777 106.459 45.5777C106.415 45.5777 106.372 45.5866 106.332 45.6038C106.291 45.6211 106.255 45.6464 106.224 45.6781L102.302 49.5809L100.648 47.8938C100.617 47.862 100.581 47.8367 100.54 47.8195C100.5 47.8022 100.457 47.7933 100.413 47.7933C100.369 47.7933 100.325 47.8022 100.285 47.8195C100.244 47.8367 100.208 47.862 100.177 47.8938L99.5497 48.5207C99.5181 48.5513 99.493 48.588 99.4758 48.6287C99.4586 48.6693 99.4497 48.7129 99.4497 48.7571C99.4497 48.8012 99.4586 48.8448 99.4758 48.8854C99.493 48.9261 99.5181 48.9628 99.5497 48.9934L102.067 51.5405C102.097 51.5723 102.134 51.5976 102.174 51.6148C102.214 51.6321 102.258 51.641 102.302 51.641C102.346 51.641 102.389 51.6321 102.43 51.6148C102.47 51.5976 102.507 51.5723 102.537 51.5405L107.319 46.7745C107.351 46.7439 107.376 46.7071 107.393 46.6665C107.41 46.6259 107.419 46.5823 107.419 46.5381C107.419 46.494 107.41 46.4503 107.393 46.4097C107.376 46.3691 107.351 46.3324 107.319 46.3018V46.3116Z"
|
||||
fill="white" fill-opacity="0.2"/>
|
||||
</g>
|
||||
<g opacity="0.7">
|
||||
<path
|
||||
d="M27.4458 76.2471C26.1243 76.5554 24.9233 77.2501 23.9947 78.2435C23.0661 79.2368 22.4514 80.4843 22.2284 81.8283C22.0054 83.1722 22.1839 84.5525 22.7416 85.7946C23.2992 87.0367 24.2109 88.085 25.3615 88.807C26.512 89.5291 27.8499 89.8925 29.206 89.8514C30.5622 89.8103 31.8757 89.3665 32.9809 88.576C34.086 87.7856 34.933 86.684 35.4151 85.4104C35.8971 84.1368 35.9924 82.7482 35.6891 81.4202C35.4872 80.5364 35.1136 79.7012 34.5898 78.9626C34.0659 78.224 33.4021 77.5964 32.6364 77.1159C31.8706 76.6353 31.018 76.3112 30.1273 76.1621C29.2366 76.0131 28.3254 76.042 27.4458 76.2471ZM27.7465 77.5601C28.8179 77.309 29.9393 77.383 30.9687 77.7726C31.9981 78.1621 32.8892 78.8498 33.5291 79.7485C34.169 80.6471 34.5289 81.7163 34.5631 82.8205C34.5973 83.9248 34.3044 85.0143 33.7215 85.9512C33.1385 86.8881 32.2917 87.6302 31.2884 88.0833C30.2851 88.5364 29.1705 88.6803 28.0857 88.4967C27.0009 88.313 25.9948 87.8102 25.1949 87.0518C24.3949 86.2934 23.8372 85.3136 23.5922 84.2365C23.2652 82.7982 23.5186 81.2883 24.2971 80.0372C25.0756 78.786 26.3159 77.8954 27.7465 77.5601ZM32.3454 80.2189L31.5936 79.7331C31.5569 79.7091 31.5158 79.6928 31.4727 79.6851C31.4296 79.6774 31.3853 79.6785 31.3427 79.6883C31.3 79.6981 31.2597 79.7164 31.2242 79.7421C31.1887 79.7679 31.1587 79.8005 31.136 79.8381L28.1943 84.5254L26.2038 83.2617C26.1671 83.2377 26.126 83.2214 26.0829 83.2137C26.0397 83.206 25.9955 83.2071 25.9529 83.2169C25.9102 83.2267 25.8699 83.245 25.8344 83.2707C25.7989 83.2965 25.7689 83.3291 25.7462 83.3667L25.2723 84.1216C25.2484 84.1585 25.2322 84.1998 25.2245 84.2431C25.2169 84.2864 25.2179 84.3308 25.2277 84.3737C25.2374 84.4165 25.2557 84.457 25.2813 84.4926C25.3069 84.5283 25.3394 84.5584 25.3769 84.5812L28.397 86.4948C28.4338 86.5193 28.4752 86.5361 28.5186 86.544C28.5621 86.552 28.6067 86.5511 28.6498 86.5413C28.6928 86.5314 28.7335 86.513 28.7693 86.4869C28.805 86.4609 28.8352 86.4278 28.8578 86.3898L32.4532 80.6685C32.4772 80.6319 32.4937 80.5907 32.5016 80.5476C32.5095 80.5044 32.5087 80.4601 32.4992 80.4173C32.4898 80.3744 32.4719 80.3339 32.4466 80.2982C32.4213 80.2624 32.3891 80.2321 32.3519 80.209L32.3454 80.2189Z"
|
||||
fill="#2CA878"/>
|
||||
<path
|
||||
d="M27.4458 76.2471C26.1243 76.5554 24.9233 77.2501 23.9947 78.2435C23.0661 79.2368 22.4514 80.4843 22.2284 81.8283C22.0054 83.1722 22.1839 84.5525 22.7416 85.7946C23.2992 87.0367 24.2109 88.085 25.3615 88.807C26.512 89.5291 27.8499 89.8925 29.206 89.8514C30.5622 89.8103 31.8757 89.3665 32.9809 88.576C34.086 87.7856 34.933 86.684 35.4151 85.4104C35.8971 84.1368 35.9924 82.7482 35.6891 81.4202C35.4872 80.5364 35.1136 79.7012 34.5898 78.9626C34.0659 78.224 33.4021 77.5964 32.6364 77.1159C31.8706 76.6353 31.018 76.3112 30.1273 76.1621C29.2366 76.0131 28.3254 76.042 27.4458 76.2471ZM27.7465 77.5601C28.8179 77.309 29.9393 77.383 30.9687 77.7726C31.9981 78.1621 32.8892 78.8498 33.5291 79.7485C34.169 80.6471 34.5289 81.7163 34.5631 82.8205C34.5973 83.9248 34.3044 85.0143 33.7215 85.9512C33.1385 86.8881 32.2917 87.6302 31.2884 88.0833C30.2851 88.5364 29.1705 88.6803 28.0857 88.4967C27.0009 88.313 25.9948 87.8102 25.1949 87.0518C24.3949 86.2934 23.8372 85.3136 23.5922 84.2365C23.2652 82.7982 23.5186 81.2883 24.2971 80.0372C25.0756 78.786 26.3159 77.8954 27.7465 77.5601ZM32.3454 80.2189L31.5936 79.7331C31.5569 79.7091 31.5158 79.6928 31.4727 79.6851C31.4296 79.6774 31.3853 79.6785 31.3427 79.6883C31.3 79.6981 31.2597 79.7164 31.2242 79.7421C31.1887 79.7679 31.1587 79.8005 31.136 79.8381L28.1943 84.5254L26.2038 83.2617C26.1671 83.2377 26.126 83.2214 26.0829 83.2137C26.0397 83.206 25.9955 83.2071 25.9529 83.2169C25.9102 83.2267 25.8699 83.245 25.8344 83.2707C25.7989 83.2965 25.7689 83.3291 25.7462 83.3667L25.2723 84.1216C25.2484 84.1585 25.2322 84.1998 25.2245 84.2431C25.2169 84.2864 25.2179 84.3308 25.2277 84.3737C25.2374 84.4165 25.2557 84.457 25.2813 84.4926C25.3069 84.5283 25.3394 84.5584 25.3769 84.5812L28.397 86.4948C28.4338 86.5193 28.4752 86.5361 28.5186 86.544C28.5621 86.552 28.6067 86.5511 28.6498 86.5413C28.6928 86.5314 28.7335 86.513 28.7693 86.4869C28.805 86.4609 28.8352 86.4278 28.8578 86.3898L32.4532 80.6685C32.4772 80.6319 32.4937 80.5907 32.5016 80.5476C32.5095 80.5044 32.5087 80.4601 32.4992 80.4173C32.4898 80.3744 32.4719 80.3339 32.4466 80.2982C32.4213 80.2624 32.3891 80.2321 32.3519 80.209L32.3454 80.2189Z"
|
||||
fill="white" fill-opacity="0.2"/>
|
||||
</g>
|
||||
<g opacity="0.7">
|
||||
<path
|
||||
d="M203.473 42.3467C202.115 42.3467 200.788 42.751 199.659 43.5084C198.53 44.2658 197.651 45.3423 197.131 46.6019C196.612 47.8614 196.476 49.2474 196.741 50.5845C197.005 51.9217 197.659 53.1499 198.619 54.1139C199.579 55.0779 200.802 55.7344 202.133 56.0004C203.465 56.2664 204.845 56.1298 206.099 55.6081C207.353 55.0864 208.425 54.2029 209.18 53.0693C209.934 51.9358 210.336 50.6031 210.336 49.2398C210.336 47.4116 209.613 45.6583 208.326 44.3656C207.039 43.0729 205.293 42.3467 203.473 42.3467ZM203.473 43.6826C204.572 43.6826 205.646 44.0099 206.56 44.623C207.473 45.2362 208.186 46.1077 208.606 47.1273C209.027 48.1469 209.137 49.2689 208.922 50.3514C208.708 51.4338 208.179 52.4281 207.402 53.2085C206.625 53.9889 205.634 54.5203 204.557 54.7356C203.479 54.9509 202.361 54.8404 201.346 54.4181C200.331 53.9957 199.463 53.2805 198.852 52.3629C198.242 51.4452 197.916 50.3664 197.916 49.2627C197.916 47.7828 198.501 46.3635 199.544 45.317C200.586 44.2705 201.999 43.6826 203.473 43.6826ZM207.359 47.3097L206.735 46.6762C206.704 46.6444 206.668 46.6192 206.627 46.6019C206.587 46.5846 206.543 46.5757 206.499 46.5757C206.455 46.5757 206.412 46.5846 206.371 46.6019C206.331 46.6192 206.294 46.6444 206.264 46.6762L202.342 50.579L200.684 48.9017C200.654 48.8699 200.617 48.8446 200.577 48.8274C200.537 48.8101 200.493 48.8012 200.449 48.8012C200.405 48.8012 200.362 48.8101 200.321 48.8274C200.281 48.8446 200.244 48.8699 200.214 48.9017L199.59 49.5352C199.558 49.5658 199.533 49.6025 199.516 49.6431C199.498 49.6837 199.49 49.7274 199.49 49.7715C199.49 49.8156 199.498 49.8593 199.516 49.8999C199.533 49.9405 199.558 49.9772 199.59 50.0078L202.106 52.555C202.137 52.5868 202.173 52.612 202.214 52.6293C202.254 52.6466 202.298 52.6554 202.342 52.6554C202.386 52.6554 202.429 52.6466 202.469 52.6293C202.51 52.612 202.546 52.5868 202.577 52.555L207.359 47.7889C207.39 47.7583 207.416 47.7216 207.433 47.681C207.45 47.6404 207.459 47.5967 207.459 47.5526C207.459 47.5085 207.45 47.4648 207.433 47.4242C207.416 47.3836 207.39 47.3469 207.359 47.3163V47.3097Z"
|
||||
fill="#2CA878"/>
|
||||
<path
|
||||
d="M203.473 42.3467C202.115 42.3467 200.788 42.751 199.659 43.5084C198.53 44.2658 197.651 45.3423 197.131 46.6019C196.612 47.8614 196.476 49.2474 196.741 50.5845C197.005 51.9217 197.659 53.1499 198.619 54.1139C199.579 55.0779 200.802 55.7344 202.133 56.0004C203.465 56.2664 204.845 56.1298 206.099 55.6081C207.353 55.0864 208.425 54.2029 209.18 53.0693C209.934 51.9358 210.336 50.6031 210.336 49.2398C210.336 47.4116 209.613 45.6583 208.326 44.3656C207.039 43.0729 205.293 42.3467 203.473 42.3467ZM203.473 43.6826C204.572 43.6826 205.646 44.0099 206.56 44.623C207.473 45.2362 208.186 46.1077 208.606 47.1273C209.027 48.1469 209.137 49.2689 208.922 50.3514C208.708 51.4338 208.179 52.4281 207.402 53.2085C206.625 53.9889 205.634 54.5203 204.557 54.7356C203.479 54.9509 202.361 54.8404 201.346 54.4181C200.331 53.9957 199.463 53.2805 198.852 52.3629C198.242 51.4452 197.916 50.3664 197.916 49.2627C197.916 47.7828 198.501 46.3635 199.544 45.317C200.586 44.2705 201.999 43.6826 203.473 43.6826ZM207.359 47.3097L206.735 46.6762C206.704 46.6444 206.668 46.6192 206.627 46.6019C206.587 46.5846 206.543 46.5757 206.499 46.5757C206.455 46.5757 206.412 46.5846 206.371 46.6019C206.331 46.6192 206.294 46.6444 206.264 46.6762L202.342 50.579L200.684 48.9017C200.654 48.8699 200.617 48.8446 200.577 48.8274C200.537 48.8101 200.493 48.8012 200.449 48.8012C200.405 48.8012 200.362 48.8101 200.321 48.8274C200.281 48.8446 200.244 48.8699 200.214 48.9017L199.59 49.5352C199.558 49.5658 199.533 49.6025 199.516 49.6431C199.498 49.6837 199.49 49.7274 199.49 49.7715C199.49 49.8156 199.498 49.8593 199.516 49.8999C199.533 49.9405 199.558 49.9772 199.59 50.0078L202.106 52.555C202.137 52.5868 202.173 52.612 202.214 52.6293C202.254 52.6466 202.298 52.6554 202.342 52.6554C202.386 52.6554 202.429 52.6466 202.469 52.6293C202.51 52.612 202.546 52.5868 202.577 52.555L207.359 47.7889C207.39 47.7583 207.416 47.7216 207.433 47.681C207.45 47.6404 207.459 47.5967 207.459 47.5526C207.459 47.5085 207.45 47.4648 207.433 47.4242C207.416 47.3836 207.39 47.3469 207.359 47.3163V47.3097Z"
|
||||
fill="white" fill-opacity="0.2"/>
|
||||
</g>
|
||||
<g opacity="0.7">
|
||||
<path
|
||||
d="M282.221 76.3888C280.868 76.2834 279.514 76.5831 278.331 77.2502C277.147 77.9172 276.187 78.9216 275.572 80.1365C274.957 81.3513 274.714 82.7221 274.874 84.0754C275.035 85.4288 275.591 86.7041 276.473 87.7401C277.354 88.7761 278.522 89.5263 279.829 89.896C281.135 90.2656 282.521 90.238 283.812 89.8168C285.102 89.3955 286.24 88.5994 287.08 87.5292C287.92 86.4589 288.426 85.1625 288.533 83.8038C288.604 82.9006 288.497 81.9922 288.218 81.1306C287.939 80.269 287.494 79.471 286.908 78.7824C286.321 78.0937 285.606 77.5278 284.802 77.1171C283.997 76.7064 283.121 76.4589 282.221 76.3888ZM282.117 77.7182C283.214 77.801 284.261 78.2089 285.127 78.8903C285.993 79.5716 286.638 80.4958 286.981 81.5457C287.323 82.5955 287.348 83.7238 287.051 84.7876C286.754 85.8514 286.15 86.8028 285.314 87.5213C284.478 88.2398 283.449 88.6931 282.357 88.8236C281.265 88.9542 280.158 88.7562 279.178 88.2547C278.198 87.7532 277.388 86.9709 276.851 86.0067C276.314 85.0426 276.074 83.94 276.161 82.8388C276.217 82.1097 276.415 81.3988 276.745 80.7468C277.075 80.0947 277.529 79.5143 278.082 79.0388C278.635 78.5632 279.276 78.2018 279.968 77.9752C280.661 77.7486 281.391 77.6613 282.117 77.7182ZM285.712 81.6374L285.137 80.958C285.109 80.924 285.075 80.896 285.036 80.8755C284.997 80.8551 284.955 80.8427 284.911 80.839C284.868 80.8353 284.824 80.8405 284.782 80.8541C284.741 80.8678 284.702 80.8897 284.669 80.9186L280.463 84.5062L278.94 82.7042C278.912 82.6697 278.877 82.6413 278.838 82.6206C278.799 82.5998 278.757 82.5872 278.713 82.5835C278.669 82.5799 278.624 82.5852 278.582 82.5991C278.541 82.6131 278.502 82.6354 278.469 82.6648L277.792 83.2425C277.759 83.2706 277.731 83.3052 277.711 83.3442C277.691 83.3832 277.679 83.4258 277.676 83.4696C277.672 83.5133 277.678 83.5573 277.692 83.599C277.705 83.6406 277.727 83.679 277.756 83.7119L280.067 86.4462C280.095 86.4801 280.129 86.5081 280.168 86.5286C280.207 86.549 280.249 86.5614 280.293 86.5651C280.336 86.5688 280.38 86.5636 280.422 86.55C280.463 86.5363 280.502 86.5144 280.535 86.4855L285.673 82.1068C285.707 82.0789 285.734 82.0446 285.755 82.0057C285.775 81.9668 285.788 81.9242 285.791 81.8804C285.795 81.8367 285.79 81.7926 285.776 81.7509C285.763 81.7091 285.741 81.6705 285.712 81.6374Z"
|
||||
fill="#2CA878"/>
|
||||
<path
|
||||
d="M282.221 76.3888C280.868 76.2834 279.514 76.5831 278.331 77.2502C277.147 77.9172 276.187 78.9216 275.572 80.1365C274.957 81.3513 274.714 82.7221 274.874 84.0754C275.035 85.4288 275.591 86.7041 276.473 87.7401C277.354 88.7761 278.522 89.5263 279.829 89.896C281.135 90.2656 282.521 90.238 283.812 89.8168C285.102 89.3955 286.24 88.5994 287.08 87.5292C287.92 86.4589 288.426 85.1625 288.533 83.8038C288.604 82.9006 288.497 81.9922 288.218 81.1306C287.939 80.269 287.494 79.471 286.908 78.7824C286.321 78.0937 285.606 77.5278 284.802 77.1171C283.997 76.7064 283.121 76.4589 282.221 76.3888ZM282.117 77.7182C283.214 77.801 284.261 78.2089 285.127 78.8903C285.993 79.5716 286.638 80.4958 286.981 81.5457C287.323 82.5955 287.348 83.7238 287.051 84.7876C286.754 85.8514 286.15 86.8028 285.314 87.5213C284.478 88.2398 283.449 88.6931 282.357 88.8236C281.265 88.9542 280.158 88.7562 279.178 88.2547C278.198 87.7532 277.388 86.9709 276.851 86.0067C276.314 85.0426 276.074 83.94 276.161 82.8388C276.217 82.1097 276.415 81.3988 276.745 80.7468C277.075 80.0947 277.529 79.5143 278.082 79.0388C278.635 78.5632 279.276 78.2018 279.968 77.9752C280.661 77.7486 281.391 77.6613 282.117 77.7182ZM285.712 81.6374L285.137 80.958C285.109 80.924 285.075 80.896 285.036 80.8755C284.997 80.8551 284.955 80.8427 284.911 80.839C284.868 80.8353 284.824 80.8405 284.782 80.8541C284.741 80.8678 284.702 80.8897 284.669 80.9186L280.463 84.5062L278.94 82.7042C278.912 82.6697 278.877 82.6413 278.838 82.6206C278.799 82.5998 278.757 82.5872 278.713 82.5835C278.669 82.5799 278.624 82.5852 278.582 82.5991C278.541 82.6131 278.502 82.6354 278.469 82.6648L277.792 83.2425C277.759 83.2706 277.731 83.3052 277.711 83.3442C277.691 83.3832 277.679 83.4258 277.676 83.4696C277.672 83.5133 277.678 83.5573 277.692 83.599C277.705 83.6406 277.727 83.679 277.756 83.7119L280.067 86.4462C280.095 86.4801 280.129 86.5081 280.168 86.5286C280.207 86.549 280.249 86.5614 280.293 86.5651C280.336 86.5688 280.38 86.5636 280.422 86.55C280.463 86.5363 280.502 86.5144 280.535 86.4855L285.673 82.1068C285.707 82.0789 285.734 82.0446 285.755 82.0057C285.775 81.9668 285.788 81.9242 285.791 81.8804C285.795 81.8367 285.79 81.7926 285.776 81.7509C285.763 81.7091 285.741 81.6705 285.712 81.6374Z"
|
||||
fill="white" fill-opacity="0.2"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_2254_118640" x1="240.289" y1="188.818" x2="240.289" y2="96.0439"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#808080" stop-opacity="0.25"/>
|
||||
<stop offset="0.54" stop-color="#808080" stop-opacity="0.12"/>
|
||||
<stop offset="1" stop-color="#808080" stop-opacity="0.1"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_2254_118640">
|
||||
<rect width="345" height="254" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 54 KiB |
@@ -16,10 +16,6 @@
|
||||
required
|
||||
/>
|
||||
</FormInputWrapper>
|
||||
<label class="remember-me-label">
|
||||
<FormCheckbox v-model="rememberMe" />
|
||||
<p>{{ $t("keep-me-logged") }}</p>
|
||||
</label>
|
||||
<UiButton type="submit" :busy="isConnecting">
|
||||
{{ $t("login") }}
|
||||
</UiButton>
|
||||
@@ -32,9 +28,6 @@ import { usePageTitleStore } from "@/stores/page-title.store";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useLocalStorage } from "@vueuse/core";
|
||||
|
||||
import FormCheckbox from "@/components/form/FormCheckbox.vue";
|
||||
import FormInput from "@/components/form/FormInput.vue";
|
||||
import FormInputWrapper from "@/components/form/FormInputWrapper.vue";
|
||||
import UiButton from "@/components/ui/UiButton.vue";
|
||||
@@ -49,16 +42,12 @@ const password = ref("");
|
||||
const error = ref<string>();
|
||||
const passwordRef = ref<InstanceType<typeof FormInput>>();
|
||||
const isInvalidPassword = ref(false);
|
||||
const rememberMe = useLocalStorage("rememberMe", false);
|
||||
|
||||
const focusPasswordInput = () => passwordRef.value?.focus();
|
||||
|
||||
onMounted(() => {
|
||||
if (rememberMe.value) {
|
||||
xenApiStore.reconnect();
|
||||
} else {
|
||||
focusPasswordInput();
|
||||
}
|
||||
xenApiStore.reconnect();
|
||||
focusPasswordInput();
|
||||
});
|
||||
|
||||
watch(password, () => {
|
||||
@@ -83,19 +72,6 @@ async function handleSubmit() {
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.remember-me-label {
|
||||
cursor: pointer;
|
||||
width: fit-content;
|
||||
& .form-checkbox {
|
||||
margin: 1rem 1rem 1rem 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
& p {
|
||||
display: inline;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.form-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -111,6 +87,7 @@ form {
|
||||
font-size: 2rem;
|
||||
min-width: 30em;
|
||||
max-width: 100%;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
margin: 0 auto;
|
||||
@@ -127,7 +104,7 @@ h1 {
|
||||
|
||||
img {
|
||||
width: 40rem;
|
||||
margin: auto auto 5rem auto;
|
||||
margin-bottom: 5rem;
|
||||
}
|
||||
|
||||
input {
|
||||
@@ -141,6 +118,6 @@ input {
|
||||
}
|
||||
|
||||
button {
|
||||
margin: 2rem auto;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -14,66 +14,66 @@
|
||||
</UiActionButton>
|
||||
</UiFilterGroup>
|
||||
|
||||
<UiModal v-model="isOpen">
|
||||
<ConfirmModalLayout @submit.prevent="handleSubmit">
|
||||
<template #default>
|
||||
<div class="rows">
|
||||
<CollectionFilterRow
|
||||
v-for="(newFilter, index) in newFilters"
|
||||
:key="newFilter.id"
|
||||
v-model="newFilters[index]"
|
||||
:available-filters="availableFilters"
|
||||
@remove="removeNewFilter"
|
||||
/>
|
||||
</div>
|
||||
<UiModal
|
||||
v-if="isOpen"
|
||||
:icon="faFilter"
|
||||
@submit.prevent="handleSubmit"
|
||||
@close="handleCancel"
|
||||
>
|
||||
<div class="rows">
|
||||
<CollectionFilterRow
|
||||
v-for="(newFilter, index) in newFilters"
|
||||
:key="newFilter.id"
|
||||
v-model="newFilters[index]"
|
||||
:available-filters="availableFilters"
|
||||
@remove="removeNewFilter"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="newFilters.some((filter) => filter.isAdvanced)"
|
||||
class="available-properties"
|
||||
<div
|
||||
v-if="newFilters.some((filter) => filter.isAdvanced)"
|
||||
class="available-properties"
|
||||
>
|
||||
{{ $t("available-properties-for-advanced-filter") }}
|
||||
<div class="properties">
|
||||
<UiBadge
|
||||
v-for="(filter, property) in availableFilters"
|
||||
:key="property"
|
||||
:icon="getFilterIcon(filter)"
|
||||
>
|
||||
{{ $t("available-properties-for-advanced-filter") }}
|
||||
<div class="properties">
|
||||
<UiBadge
|
||||
v-for="(filter, property) in availableFilters"
|
||||
:key="property"
|
||||
:icon="getFilterIcon(filter)"
|
||||
>
|
||||
{{ property }}
|
||||
</UiBadge>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
{{ property }}
|
||||
</UiBadge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #buttons>
|
||||
<UiButton transparent @click="addNewFilter">
|
||||
{{ $t("add-or") }}
|
||||
</UiButton>
|
||||
<UiButton :disabled="!isFilterValid" type="submit">
|
||||
{{ $t(editedFilter ? "update" : "add") }}
|
||||
</UiButton>
|
||||
<UiButton outlined @click="close">
|
||||
{{ $t("cancel") }}
|
||||
</UiButton>
|
||||
</template>
|
||||
</ConfirmModalLayout>
|
||||
<template #buttons>
|
||||
<UiButton transparent @click="addNewFilter">
|
||||
{{ $t("add-or") }}
|
||||
</UiButton>
|
||||
<UiButton :disabled="!isFilterValid" type="submit">
|
||||
{{ $t(editedFilter ? "update" : "add") }}
|
||||
</UiButton>
|
||||
<UiButton outlined @click="handleCancel">
|
||||
{{ $t("cancel") }}
|
||||
</UiButton>
|
||||
</template>
|
||||
</UiModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Or, parse } from "complex-matcher";
|
||||
import { computed, ref } from "vue";
|
||||
import type { Filters, NewFilter } from "@/types/filter";
|
||||
import { faFilter, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import CollectionFilterRow from "@/components/CollectionFilterRow.vue";
|
||||
import ConfirmModalLayout from "@/components/ui/modals/layouts/ConfirmModalLayout.vue";
|
||||
import UiModal from "@/components/ui/modals/UiModal.vue";
|
||||
import UiActionButton from "@/components/ui/UiActionButton.vue";
|
||||
import UiBadge from "@/components/ui/UiBadge.vue";
|
||||
import UiButton from "@/components/ui/UiButton.vue";
|
||||
import UiFilter from "@/components/ui/UiFilter.vue";
|
||||
import UiFilterGroup from "@/components/ui/UiFilterGroup.vue";
|
||||
import UiModal from "@/components/ui/UiModal.vue";
|
||||
import useModal from "@/composables/modal.composable";
|
||||
import { getFilterIcon } from "@/libs/utils";
|
||||
import type { Filters, NewFilter } from "@/types/filter";
|
||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { Or, parse } from "complex-matcher";
|
||||
import { computed, ref } from "vue";
|
||||
|
||||
defineProps<{
|
||||
activeFilters: string[];
|
||||
@@ -85,7 +85,7 @@ const emit = defineEmits<{
|
||||
(event: "removeFilter", filter: string): void;
|
||||
}>();
|
||||
|
||||
const { isOpen, open, close } = useModal({ onClose: () => reset() });
|
||||
const { isOpen, open, close } = useModal();
|
||||
const newFilters = ref<NewFilter[]>([]);
|
||||
let newFilterId = 0;
|
||||
|
||||
@@ -156,6 +156,11 @@ const handleSubmit = () => {
|
||||
reset();
|
||||
close();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
reset();
|
||||
close();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
@@ -185,10 +190,4 @@ const handleSubmit = () => {
|
||||
margin-top: 0.6rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.rows {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -219,6 +219,7 @@ const valueInputAfter = computed(() =>
|
||||
.collection-filter-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 1rem 0;
|
||||
border-bottom: 1px solid var(--background-color-secondary);
|
||||
gap: 1rem;
|
||||
|
||||
@@ -241,8 +242,4 @@ const valueInputAfter = computed(() =>
|
||||
.form-widget-advanced {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.ui-action-button:first-of-type {
|
||||
margin-left: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -17,56 +17,56 @@
|
||||
</UiActionButton>
|
||||
</UiFilterGroup>
|
||||
|
||||
<UiModal v-model="isOpen">
|
||||
<ConfirmModalLayout @submit.prevent="handleSubmit">
|
||||
<template #default>
|
||||
<div class="form-widgets">
|
||||
<FormWidget :label="$t('sort-by')">
|
||||
<select v-model="newSortProperty">
|
||||
<option v-if="!newSortProperty"></option>
|
||||
<option
|
||||
v-for="(sort, property) in availableSorts"
|
||||
:key="property"
|
||||
:value="property"
|
||||
>
|
||||
{{ sort.label ?? property }}
|
||||
</option>
|
||||
</select>
|
||||
</FormWidget>
|
||||
<FormWidget>
|
||||
<select v-model="newSortIsAscending">
|
||||
<option :value="true">{{ $t("ascending") }}</option>
|
||||
<option :value="false">{{ $t("descending") }}</option>
|
||||
</select>
|
||||
</FormWidget>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #buttons>
|
||||
<UiButton type="submit">{{ $t("add") }}</UiButton>
|
||||
<UiButton outlined @click="close">
|
||||
{{ $t("cancel") }}
|
||||
</UiButton>
|
||||
</template>
|
||||
</ConfirmModalLayout>
|
||||
<UiModal
|
||||
v-if="isOpen"
|
||||
:icon="faSort"
|
||||
@submit.prevent="handleSubmit"
|
||||
@close="handleCancel"
|
||||
>
|
||||
<div class="form-widgets">
|
||||
<FormWidget :label="$t('sort-by')">
|
||||
<select v-model="newSortProperty">
|
||||
<option v-if="!newSortProperty"></option>
|
||||
<option
|
||||
v-for="(sort, property) in availableSorts"
|
||||
:key="property"
|
||||
:value="property"
|
||||
>
|
||||
{{ sort.label ?? property }}
|
||||
</option>
|
||||
</select>
|
||||
</FormWidget>
|
||||
<FormWidget>
|
||||
<select v-model="newSortIsAscending">
|
||||
<option :value="true">{{ $t("ascending") }}</option>
|
||||
<option :value="false">{{ $t("descending") }}</option>
|
||||
</select>
|
||||
</FormWidget>
|
||||
</div>
|
||||
<template #buttons>
|
||||
<UiButton type="submit">{{ $t("add") }}</UiButton>
|
||||
<UiButton outlined @click="handleCancel">
|
||||
{{ $t("cancel") }}
|
||||
</UiButton>
|
||||
</template>
|
||||
</UiModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import FormWidget from "@/components/FormWidget.vue";
|
||||
import UiIcon from "@/components/ui/icon/UiIcon.vue";
|
||||
import ConfirmModalLayout from "@/components/ui/modals/layouts/ConfirmModalLayout.vue";
|
||||
import UiModal from "@/components/ui/modals/UiModal.vue";
|
||||
import UiActionButton from "@/components/ui/UiActionButton.vue";
|
||||
import UiButton from "@/components/ui/UiButton.vue";
|
||||
import UiFilter from "@/components/ui/UiFilter.vue";
|
||||
import UiFilterGroup from "@/components/ui/UiFilterGroup.vue";
|
||||
import UiIcon from "@/components/ui/icon/UiIcon.vue";
|
||||
import UiModal from "@/components/ui/UiModal.vue";
|
||||
import useModal from "@/composables/modal.composable";
|
||||
import type { ActiveSorts, Sorts } from "@/types/sort";
|
||||
import {
|
||||
faCaretDown,
|
||||
faCaretUp,
|
||||
faPlus,
|
||||
faSort,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { ref } from "vue";
|
||||
|
||||
@@ -81,7 +81,7 @@ const emit = defineEmits<{
|
||||
(event: "removeSort", property: string): void;
|
||||
}>();
|
||||
|
||||
const { isOpen, open, close } = useModal({ onClose: () => reset() });
|
||||
const { isOpen, open, close } = useModal();
|
||||
|
||||
const newSortProperty = ref();
|
||||
const newSortIsAscending = ref<boolean>(true);
|
||||
@@ -96,6 +96,11 @@ const handleSubmit = () => {
|
||||
reset();
|
||||
close();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
reset();
|
||||
close();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
|
||||
@@ -28,9 +28,13 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="item in filteredAndSortedCollection" :key="item.$ref">
|
||||
<tr v-for="item in filteredAndSortedCollection" :key="item[idProperty]">
|
||||
<td v-if="isSelectable">
|
||||
<input v-model="selected" :value="item.$ref" type="checkbox" />
|
||||
<input
|
||||
v-model="selected"
|
||||
:value="item[props.idProperty]"
|
||||
type="checkbox"
|
||||
/>
|
||||
</td>
|
||||
<slot :item="item" name="body-row" />
|
||||
</tr>
|
||||
@@ -38,7 +42,10 @@
|
||||
</UiTable>
|
||||
</template>
|
||||
|
||||
<script generic="T extends XenApiRecord<any>" lang="ts" setup>
|
||||
<script lang="ts" setup>
|
||||
import { computed, toRef, watch } from "vue";
|
||||
import type { Filters } from "@/types/filter";
|
||||
import type { Sorts } from "@/types/sort";
|
||||
import CollectionFilter from "@/components/CollectionFilter.vue";
|
||||
import CollectionSorter from "@/components/CollectionSorter.vue";
|
||||
import UiTable from "@/components/ui/UiTable.vue";
|
||||
@@ -47,20 +54,17 @@ import useCollectionSorter from "@/composables/collection-sorter.composable";
|
||||
import useFilteredCollection from "@/composables/filtered-collection.composable";
|
||||
import useMultiSelect from "@/composables/multi-select.composable";
|
||||
import useSortedCollection from "@/composables/sorted-collection.composable";
|
||||
import type { XenApiRecord } from "@/libs/xen-api/xen-api.types";
|
||||
import type { Filters } from "@/types/filter";
|
||||
import type { Sorts } from "@/types/sort";
|
||||
import { computed, toRef, watch } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue?: T["$ref"][];
|
||||
modelValue?: string[];
|
||||
availableFilters?: Filters;
|
||||
availableSorts?: Sorts;
|
||||
collection: T[];
|
||||
collection: Record<string, any>[];
|
||||
idProperty: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "update:modelValue", selectedRefs: T["$ref"][]): void;
|
||||
(event: "update:modelValue", selectedRefs: string[]): void;
|
||||
}>();
|
||||
|
||||
const isSelectable = computed(() => props.modelValue !== undefined);
|
||||
@@ -81,10 +85,12 @@ const filteredAndSortedCollection = useSortedCollection(
|
||||
compareFn
|
||||
);
|
||||
|
||||
const usableRefs = computed(() => props.collection.map((item) => item["$ref"]));
|
||||
const usableRefs = computed(() =>
|
||||
props.collection.map((item) => item[props.idProperty])
|
||||
);
|
||||
|
||||
const selectableRefs = computed(() =>
|
||||
filteredAndSortedCollection.value.map((item) => item["$ref"])
|
||||
filteredAndSortedCollection.value.map((item) => item[props.idProperty])
|
||||
);
|
||||
|
||||
const { selected, areAllSelected } = useMultiSelect(usableRefs, selectableRefs);
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
<template>
|
||||
<UiCardSpinner v-if="!areSomeLoaded" />
|
||||
<UiTable v-else class="hosts-patches-table" :class="{ desktop: isDesktop }">
|
||||
<tr v-for="patch in sortedPatches" :key="patch.$id">
|
||||
<th>{{ patch.name }}</th>
|
||||
<td>
|
||||
<div class="version">
|
||||
{{ patch.version }}
|
||||
<template v-if="hasMultipleHosts">
|
||||
<UiSpinner v-if="!areAllLoaded" />
|
||||
<UiCounter
|
||||
v-else
|
||||
v-tooltip="{
|
||||
placement: 'left',
|
||||
content: $t('n-hosts-awaiting-patch', {
|
||||
n: patch.$hostRefs.size,
|
||||
}),
|
||||
}"
|
||||
:value="patch.$hostRefs.size"
|
||||
class="counter"
|
||||
color="error"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</UiTable>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import UiCardSpinner from "@/components/ui/UiCardSpinner.vue";
|
||||
import UiCounter from "@/components/ui/UiCounter.vue";
|
||||
import UiSpinner from "@/components/ui/UiSpinner.vue";
|
||||
import UiTable from "@/components/ui/UiTable.vue";
|
||||
import type { XenApiPatchWithHostRefs } from "@/composables/host-patches.composable";
|
||||
import { vTooltip } from "@/directives/tooltip.directive";
|
||||
import { useUiStore } from "@/stores/ui.store";
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
patches: XenApiPatchWithHostRefs[];
|
||||
hasMultipleHosts: boolean;
|
||||
areAllLoaded: boolean;
|
||||
areSomeLoaded: boolean;
|
||||
}>();
|
||||
|
||||
const sortedPatches = computed(() =>
|
||||
[...props.patches].sort(
|
||||
(patch1, patch2) => patch1.changelog.date - patch2.changelog.date
|
||||
)
|
||||
);
|
||||
|
||||
const { isDesktop } = useUiStore();
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.hosts-patches-table.desktop {
|
||||
max-width: 45rem;
|
||||
}
|
||||
|
||||
.version {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.counter {
|
||||
font-size: 1rem;
|
||||
}
|
||||
</style>
|
||||
@@ -1,96 +0,0 @@
|
||||
<template>
|
||||
<div class="object-link">
|
||||
<UiSpinner v-if="!isReady" />
|
||||
<template v-else-if="record !== undefined">
|
||||
<RouterLink v-if="objectRoute" :to="objectRoute">
|
||||
{{ record.name_label }}
|
||||
</RouterLink>
|
||||
<span v-else>{{ record.name_label }}</span>
|
||||
</template>
|
||||
<span v-else class="unknown">{{ uuid }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script generic="T extends ObjectType" lang="ts" setup>
|
||||
import UiSpinner from "@/components/ui/UiSpinner.vue";
|
||||
import type {
|
||||
ObjectType,
|
||||
ObjectTypeToRecord,
|
||||
} from "@/libs/xen-api/xen-api.types";
|
||||
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 { StoreDefinition } from "pinia";
|
||||
import { computed, onUnmounted, watch } from "vue";
|
||||
import type { RouteRecordName } from "vue-router";
|
||||
|
||||
type HandledTypes = "host" | "vm" | "sr" | "pool";
|
||||
type XRecord = ObjectTypeToRecord<T>;
|
||||
type Config = Partial<
|
||||
Record<
|
||||
ObjectType,
|
||||
{
|
||||
useStore: StoreDefinition<any, any, any, any>;
|
||||
routeName: RouteRecordName | undefined;
|
||||
}
|
||||
>
|
||||
>;
|
||||
|
||||
const props = defineProps<{
|
||||
type: T;
|
||||
uuid: XRecord["uuid"];
|
||||
}>();
|
||||
|
||||
const config: Config = {
|
||||
host: { useStore: useHostStore, routeName: "host.dashboard" },
|
||||
vm: { useStore: useVmStore, routeName: "vm.console" },
|
||||
sr: { useStore: useSrStore, routeName: undefined },
|
||||
pool: { useStore: usePoolStore, routeName: "pool.dashboard" },
|
||||
} satisfies Record<HandledTypes, any>;
|
||||
|
||||
const store = computed(() => config[props.type]?.useStore());
|
||||
|
||||
const subscriptionId = Symbol();
|
||||
|
||||
watch(
|
||||
store,
|
||||
(nextStore, previousStore) => {
|
||||
previousStore?.unsubscribe(subscriptionId);
|
||||
nextStore?.subscribe(subscriptionId);
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
onUnmounted(() => {
|
||||
store.value?.unsubscribe(subscriptionId);
|
||||
});
|
||||
|
||||
const record = computed<ObjectTypeToRecord<HandledTypes> | undefined>(
|
||||
() => store.value?.getByUuid(props.uuid as any)
|
||||
);
|
||||
|
||||
const isReady = computed(() => {
|
||||
return store.value?.isReady ?? true;
|
||||
});
|
||||
|
||||
const objectRoute = computed(() => {
|
||||
const { routeName } = config[props.type] ?? {};
|
||||
|
||||
if (routeName === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
name: routeName,
|
||||
params: { uuid: props.uuid },
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.unknown {
|
||||
color: var(--color-blue-scale-300);
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
@@ -7,12 +7,12 @@
|
||||
</template>
|
||||
|
||||
<script
|
||||
generic="T extends XenApiRecord<ObjectType>, I extends T['uuid']"
|
||||
generic="T extends XenApiRecord<RawObjectType>, I extends T['uuid']"
|
||||
lang="ts"
|
||||
setup
|
||||
>
|
||||
import UiSpinner from "@/components/ui/UiSpinner.vue";
|
||||
import type { ObjectType, XenApiRecord } from "@/libs/xen-api/xen-api.types";
|
||||
import type { RawObjectType, XenApiRecord } from "@/libs/xen-api";
|
||||
import ObjectNotFoundView from "@/views/ObjectNotFoundView.vue";
|
||||
import { computed } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import UiIcon from "@/components/ui/icon/UiIcon.vue";
|
||||
import { VM_POWER_STATE } from "@/libs/xen-api/xen-api.enums";
|
||||
import { POWER_STATE } from "@/libs/xen-api";
|
||||
import {
|
||||
faMoon,
|
||||
faPause,
|
||||
@@ -15,14 +15,14 @@ import {
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
state: VM_POWER_STATE;
|
||||
state: POWER_STATE;
|
||||
}>();
|
||||
|
||||
const icons = {
|
||||
[VM_POWER_STATE.RUNNING]: faPlay,
|
||||
[VM_POWER_STATE.PAUSED]: faPause,
|
||||
[VM_POWER_STATE.SUSPENDED]: faMoon,
|
||||
[VM_POWER_STATE.HALTED]: faStop,
|
||||
[POWER_STATE.RUNNING]: faPlay,
|
||||
[POWER_STATE.PAUSED]: faPause,
|
||||
[POWER_STATE.SUSPENDED]: faMoon,
|
||||
[POWER_STATE.HALTED]: faStop,
|
||||
};
|
||||
|
||||
const icon = computed(() => icons[props.state] ?? faQuestion);
|
||||
|
||||
@@ -4,16 +4,19 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import useRelativeTime from "@/composables/relative-time.composable";
|
||||
import { parseDateTime } from "@/libs/utils";
|
||||
import { useNow } from "@vueuse/core";
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
date: Date | number | string;
|
||||
}>();
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
date: Date | number | string;
|
||||
interval?: number;
|
||||
}>(),
|
||||
{ interval: 1000 }
|
||||
);
|
||||
|
||||
const date = computed(() => new Date(parseDateTime(props.date)));
|
||||
const now = useNow({ interval: 1000 });
|
||||
const date = computed(() => new Date(props.date));
|
||||
const now = useNow({ interval: props.interval });
|
||||
const relativeTime = useRelativeTime(date, now);
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<RouterLink v-slot="{ isActive, href }" :to="isDisabled ? '' : to" custom>
|
||||
<RouterLink
|
||||
v-slot="{ isActive, href }"
|
||||
:to="disabled || isTabBarDisabled ? '' : to"
|
||||
custom
|
||||
>
|
||||
<UiTab :active="isActive" :disabled="disabled" :href="href" tag="a">
|
||||
<slot />
|
||||
</UiTab>
|
||||
@@ -7,20 +11,20 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useContext } from "@/composables/context.composable";
|
||||
import { DisabledContext } from "@/context";
|
||||
import { IK_TAB_BAR_DISABLED } from "@/types/injection-keys";
|
||||
import { computed, inject } from "vue";
|
||||
import type { RouteLocationRaw } from "vue-router";
|
||||
import UiTab from "@/components/ui/UiTab.vue";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
to: RouteLocationRaw;
|
||||
disabled?: boolean;
|
||||
}>(),
|
||||
{ disabled: undefined }
|
||||
);
|
||||
defineProps<{
|
||||
to: RouteLocationRaw;
|
||||
disabled?: boolean;
|
||||
}>();
|
||||
|
||||
const isDisabled = useContext(DisabledContext, () => props.disabled);
|
||||
const isTabBarDisabled = inject(
|
||||
IK_TAB_BAR_DISABLED,
|
||||
computed(() => false)
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped></style>
|
||||
|
||||
@@ -1,58 +1,45 @@
|
||||
<template>
|
||||
<UiModal v-model="isSslModalOpen" color="error">
|
||||
<ConfirmModalLayout :icon="faServer">
|
||||
<template #title>{{ $t("unreachable-hosts") }}</template>
|
||||
|
||||
<template #default>
|
||||
<div class="description">
|
||||
<p>{{ $t("following-hosts-unreachable") }}</p>
|
||||
<p>{{ $t("allow-self-signed-ssl") }}</p>
|
||||
<ul>
|
||||
<li v-for="url in unreachableHostsUrls" :key="url">
|
||||
<a :href="url" class="link" rel="noopener" target="_blank">{{
|
||||
url
|
||||
}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #buttons>
|
||||
<UiButton color="success" @click="reload">
|
||||
{{ $t("unreachable-hosts-reload-page") }}
|
||||
</UiButton>
|
||||
<UiButton @click="closeSslModal">{{ $t("cancel") }}</UiButton>
|
||||
</template>
|
||||
</ConfirmModalLayout>
|
||||
<UiModal
|
||||
v-if="isSslModalOpen"
|
||||
:icon="faServer"
|
||||
color="error"
|
||||
@close="clearUnreachableHostsUrls"
|
||||
>
|
||||
<template #title>{{ $t("unreachable-hosts") }}</template>
|
||||
<div class="description">
|
||||
<p>{{ $t("following-hosts-unreachable") }}</p>
|
||||
<p>{{ $t("allow-self-signed-ssl") }}</p>
|
||||
<ul>
|
||||
<li v-for="url in unreachableHostsUrls" :key="url">
|
||||
<a :href="url" class="link" rel="noopener" target="_blank">{{
|
||||
url
|
||||
}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<template #buttons>
|
||||
<UiButton color="success" @click="reload">
|
||||
{{ $t("unreachable-hosts-reload-page") }}
|
||||
</UiButton>
|
||||
<UiButton @click="clearUnreachableHostsUrls">{{ $t("cancel") }}</UiButton>
|
||||
</template>
|
||||
</UiModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ConfirmModalLayout from "@/components/ui/modals/layouts/ConfirmModalLayout.vue";
|
||||
import UiModal from "@/components/ui/modals/UiModal.vue";
|
||||
import UiButton from "@/components/ui/UiButton.vue";
|
||||
import useModal from "@/composables/modal.composable";
|
||||
import { useHostCollection } from "@/stores/xen-api/host.store";
|
||||
import { useHostCollection } from "@/composables/xen-api-collection/host-collection.composable";
|
||||
import { faServer } from "@fortawesome/free-solid-svg-icons";
|
||||
import UiModal from "@/components/ui/UiModal.vue";
|
||||
import UiButton from "@/components/ui/UiButton.vue";
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { difference } from "lodash-es";
|
||||
import { ref, watch } from "vue";
|
||||
|
||||
const { records: hosts } = useHostCollection();
|
||||
const unreachableHostsUrls = ref<Set<string>>(new Set());
|
||||
const clearUnreachableHostsUrls = () => unreachableHostsUrls.value.clear();
|
||||
const isSslModalOpen = computed(() => unreachableHostsUrls.value.size > 0);
|
||||
const reload = () => window.location.reload();
|
||||
|
||||
const { isOpen: isSslModalOpen, close: closeSslModal } = useModal({
|
||||
onClose: () => unreachableHostsUrls.value.clear(),
|
||||
});
|
||||
|
||||
watch(
|
||||
() => unreachableHostsUrls.value.size,
|
||||
(size) => {
|
||||
isSslModalOpen.value = size > 0;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
watch(hosts, (nextHosts, previousHosts) => {
|
||||
difference(nextHosts, previousHosts).forEach((host) => {
|
||||
const url = new URL("http://localhost");
|
||||
@@ -66,11 +53,7 @@ watch(hosts, (nextHosts, previousHosts) => {
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.description {
|
||||
text-align: center;
|
||||
|
||||
p {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
.description p {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,7 +2,12 @@
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<LinearChart :data="data" :value-formatter="customValueFormatter" />
|
||||
<LinearChart
|
||||
title="Chart title"
|
||||
subtitle="Chart subtitle"
|
||||
:data="data"
|
||||
:value-formatter="customValueFormatter"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
<template>
|
||||
<VueCharts :option="option" autoresize class="chart" />
|
||||
<UiCard class="linear-chart">
|
||||
<VueCharts :option="option" autoresize class="chart" />
|
||||
<slot name="summary" />
|
||||
</UiCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import UiCard from "@/components/ui/UiCard.vue";
|
||||
import type { LinearChartData, ValueFormatter } from "@/types/chart";
|
||||
import { IK_CHART_VALUE_FORMATTER } from "@/types/injection-keys";
|
||||
import { utcFormat } from "d3-time-format";
|
||||
@@ -11,6 +15,7 @@ import { LineChart } from "echarts/charts";
|
||||
import {
|
||||
GridComponent,
|
||||
LegendComponent,
|
||||
TitleComponent,
|
||||
TooltipComponent,
|
||||
} from "echarts/components";
|
||||
import { use } from "echarts/core";
|
||||
@@ -21,6 +26,8 @@ import VueCharts from "vue-echarts";
|
||||
const Y_AXIS_MAX_VALUE = 200;
|
||||
|
||||
const props = defineProps<{
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
data: LinearChartData;
|
||||
valueFormatter?: ValueFormatter;
|
||||
maxValue?: number;
|
||||
@@ -45,10 +52,15 @@ use([
|
||||
LineChart,
|
||||
GridComponent,
|
||||
TooltipComponent,
|
||||
TitleComponent,
|
||||
LegendComponent,
|
||||
]);
|
||||
|
||||
const option = computed<EChartsOption>(() => ({
|
||||
title: {
|
||||
text: props.title,
|
||||
subtext: props.subtitle,
|
||||
},
|
||||
legend: {
|
||||
data: props.data.map((series) => series.label),
|
||||
},
|
||||
|
||||
@@ -58,7 +58,7 @@ const getDefaultOpenedDirectories = (): Set<string> => {
|
||||
}
|
||||
|
||||
const openedDirectories = new Set<string>();
|
||||
const parts = currentRoute.path.split("/").slice(2);
|
||||
const parts = currentRoute.path.split("/");
|
||||
let currentPath = "";
|
||||
|
||||
for (const part of parts) {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<template>
|
||||
<UiModal v-model="isRawValueModalOpen">
|
||||
<BasicModalLayout>
|
||||
<CodeHighlight :code="rawValueModalPayload" />
|
||||
</BasicModalLayout>
|
||||
<UiModal v-if="isRawValueModalOpen" @close="closeRawValueModal">
|
||||
<CodeHighlight :code="rawValueModalPayload" />
|
||||
</UiModal>
|
||||
<StoryParamsTable>
|
||||
<thead>
|
||||
@@ -38,15 +36,6 @@
|
||||
>
|
||||
<UiIcon :icon="faRepeat" />
|
||||
</sup>
|
||||
<sup
|
||||
v-if="param.isUsingContext()"
|
||||
v-tooltip="
|
||||
`If this prop is not provided, value will be read from context. Otherwise, context will be updated with this prop value.`
|
||||
"
|
||||
class="context-indicator"
|
||||
>
|
||||
Ctx
|
||||
</sup>
|
||||
</th>
|
||||
<td>
|
||||
<CodeHighlight :code="param.getTypeLabel()" />
|
||||
@@ -101,8 +90,7 @@ import CodeHighlight from "@/components/CodeHighlight.vue";
|
||||
import StoryParamsTable from "@/components/component-story/StoryParamsTable.vue";
|
||||
import StoryWidget from "@/components/component-story/StoryWidget.vue";
|
||||
import UiIcon from "@/components/ui/icon/UiIcon.vue";
|
||||
import BasicModalLayout from "@/components/ui/modals/layouts/BasicModalLayout.vue";
|
||||
import UiModal from "@/components/ui/modals/UiModal.vue";
|
||||
import UiModal from "@/components/ui/UiModal.vue";
|
||||
import useModal from "@/composables/modal.composable";
|
||||
import useSortedCollection from "@/composables/sorted-collection.composable";
|
||||
import { vTooltip } from "@/directives/tooltip.directive";
|
||||
@@ -133,6 +121,7 @@ const model = useVModel(props, "modelValue", emit);
|
||||
|
||||
const {
|
||||
open: openRawValueModal,
|
||||
close: closeRawValueModal,
|
||||
isOpen: isRawValueModalOpen,
|
||||
payload: rawValueModalPayload,
|
||||
} = useModal<string>();
|
||||
@@ -193,8 +182,7 @@ const {
|
||||
}
|
||||
}
|
||||
|
||||
.v-model-indicator,
|
||||
.context-indicator {
|
||||
.v-model-indicator {
|
||||
color: var(--color-green-infra-base);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<input
|
||||
v-model="value"
|
||||
:class="{ indeterminate: type === 'checkbox' && value === undefined }"
|
||||
:disabled="isDisabled"
|
||||
:disabled="isLabelDisabled || disabled"
|
||||
:type="type === 'radio' ? 'radio' : 'checkbox'"
|
||||
class="input"
|
||||
v-bind="$attrs"
|
||||
@@ -19,24 +19,23 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import UiIcon from "@/components/ui/icon/UiIcon.vue";
|
||||
import { useContext } from "@/composables/context.composable";
|
||||
import { DisabledContext } from "@/context";
|
||||
import { IK_CHECKBOX_TYPE, IK_FORM_HAS_LABEL } from "@/types/injection-keys";
|
||||
import {
|
||||
IK_FORM_HAS_LABEL,
|
||||
IK_FORM_LABEL_DISABLED,
|
||||
IK_CHECKBOX_TYPE,
|
||||
} from "@/types/injection-keys";
|
||||
import { type HTMLAttributes, computed, inject } from "vue";
|
||||
import { faCheck, faCircle, faMinus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { useVModel } from "@vueuse/core";
|
||||
import { computed, type HTMLAttributes, inject } from "vue";
|
||||
import UiIcon from "@/components/ui/icon/UiIcon.vue";
|
||||
|
||||
defineOptions({ inheritAttrs: false });
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue?: unknown;
|
||||
disabled?: boolean;
|
||||
wrapperAttrs?: HTMLAttributes;
|
||||
}>(),
|
||||
{ disabled: undefined }
|
||||
);
|
||||
const props = defineProps<{
|
||||
modelValue?: unknown;
|
||||
disabled?: boolean;
|
||||
wrapperAttrs?: HTMLAttributes;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "update:modelValue", value: boolean): void;
|
||||
@@ -48,7 +47,10 @@ const hasLabel = inject(
|
||||
IK_FORM_HAS_LABEL,
|
||||
computed(() => false)
|
||||
);
|
||||
const isDisabled = useContext(DisabledContext, () => props.disabled);
|
||||
const isLabelDisabled = inject(
|
||||
IK_FORM_LABEL_DISABLED,
|
||||
computed(() => false)
|
||||
);
|
||||
const icon = computed(() => {
|
||||
if (type !== "checkbox") {
|
||||
return faCircle;
|
||||
@@ -151,8 +153,7 @@ const icon = computed(() => {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 1.25em;
|
||||
transition:
|
||||
background-color 0.125s ease-in-out,
|
||||
transition: background-color 0.125s ease-in-out,
|
||||
border-color 0.125s ease-in-out;
|
||||
border: var(--checkbox-border-width) solid var(--border-color);
|
||||
border-radius: var(--checkbox-border-radius);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
ref="inputElement"
|
||||
v-model="value"
|
||||
:class="inputClass"
|
||||
:disabled="isDisabled"
|
||||
:disabled="disabled || isLabelDisabled"
|
||||
:required="required"
|
||||
class="select"
|
||||
v-bind="$attrs"
|
||||
@@ -23,7 +23,7 @@
|
||||
ref="textarea"
|
||||
v-model="value"
|
||||
:class="inputClass"
|
||||
:disabled="isDisabled"
|
||||
:disabled="disabled || isLabelDisabled"
|
||||
:required="required"
|
||||
class="textarea"
|
||||
v-bind="$attrs"
|
||||
@@ -34,7 +34,7 @@
|
||||
ref="inputElement"
|
||||
v-model="value"
|
||||
:class="inputClass"
|
||||
:disabled="isDisabled"
|
||||
:disabled="disabled || isLabelDisabled"
|
||||
:required="required"
|
||||
class="input"
|
||||
v-bind="$attrs"
|
||||
@@ -52,10 +52,13 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import UiIcon from "@/components/ui/icon/UiIcon.vue";
|
||||
import { useContext } from "@/composables/context.composable";
|
||||
import { ColorContext, DisabledContext } from "@/context";
|
||||
import type { Color } from "@/types";
|
||||
import { IK_INPUT_ID, IK_INPUT_TYPE } from "@/types/injection-keys";
|
||||
import {
|
||||
IK_FORM_INPUT_COLOR,
|
||||
IK_FORM_LABEL_DISABLED,
|
||||
IK_INPUT_ID,
|
||||
IK_INPUT_TYPE,
|
||||
} from "@/types/injection-keys";
|
||||
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
|
||||
import { faAngleDown } from "@fortawesome/free-solid-svg-icons";
|
||||
import { useTextareaAutosize, useVModel } from "@vueuse/core";
|
||||
@@ -84,11 +87,9 @@ const props = withDefaults(
|
||||
right?: boolean;
|
||||
wrapperAttrs?: HTMLAttributes;
|
||||
}>(),
|
||||
{ disabled: undefined }
|
||||
{ color: "info" }
|
||||
);
|
||||
|
||||
const { name: contextColor } = useContext(ColorContext, () => props.color);
|
||||
|
||||
const inputElement = ref();
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -100,19 +101,25 @@ const isEmpty = computed(
|
||||
() => props.modelValue == null || String(props.modelValue).trim() === ""
|
||||
);
|
||||
const inputType = inject(IK_INPUT_TYPE, "input");
|
||||
|
||||
const isDisabled = useContext(DisabledContext, () => props.disabled);
|
||||
const isLabelDisabled = inject(
|
||||
IK_FORM_LABEL_DISABLED,
|
||||
computed(() => false)
|
||||
);
|
||||
const parentColor = inject(
|
||||
IK_FORM_INPUT_COLOR,
|
||||
computed(() => undefined)
|
||||
);
|
||||
|
||||
const wrapperClass = computed(() => [
|
||||
`form-${inputType}`,
|
||||
{
|
||||
disabled: isDisabled.value,
|
||||
disabled: props.disabled === true || isLabelDisabled.value,
|
||||
empty: isEmpty.value,
|
||||
},
|
||||
]);
|
||||
|
||||
const inputClass = computed(() => [
|
||||
contextColor.value,
|
||||
parentColor.value ?? props.color,
|
||||
{
|
||||
right: props.right,
|
||||
"has-before": props.before !== undefined,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
v-if="label !== undefined || learnMoreUrl !== undefined"
|
||||
class="label-container"
|
||||
>
|
||||
<label :class="{ light }" :for="id" class="label">
|
||||
<label :for="id" class="label">
|
||||
<UiIcon :icon="icon" />
|
||||
{{ label }}
|
||||
</label>
|
||||
@@ -37,10 +37,13 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import UiIcon from "@/components/ui/icon/UiIcon.vue";
|
||||
import { useContext } from "@/composables/context.composable";
|
||||
import { ColorContext, DisabledContext } from "@/context";
|
||||
import type { Color } from "@/types";
|
||||
import { IK_FORM_HAS_LABEL, IK_INPUT_ID } from "@/types/injection-keys";
|
||||
import {
|
||||
IK_FORM_HAS_LABEL,
|
||||
IK_FORM_INPUT_COLOR,
|
||||
IK_FORM_LABEL_DISABLED,
|
||||
IK_INPUT_ID,
|
||||
} from "@/types/injection-keys";
|
||||
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
|
||||
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { uniqueId } from "lodash-es";
|
||||
@@ -48,20 +51,16 @@ import { computed, provide, useSlots } from "vue";
|
||||
|
||||
const slots = useSlots();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
label?: string;
|
||||
id?: string;
|
||||
icon?: IconDefinition;
|
||||
learnMoreUrl?: string;
|
||||
warning?: string;
|
||||
error?: string;
|
||||
help?: string;
|
||||
disabled?: boolean;
|
||||
light?: boolean;
|
||||
}>(),
|
||||
{ disabled: undefined }
|
||||
);
|
||||
const props = defineProps<{
|
||||
label?: string;
|
||||
id?: string;
|
||||
icon?: IconDefinition;
|
||||
learnMoreUrl?: string;
|
||||
warning?: string;
|
||||
error?: string;
|
||||
help?: string;
|
||||
disabled?: boolean;
|
||||
}>();
|
||||
|
||||
const id = computed(() => props.id ?? uniqueId("form-input-"));
|
||||
provide(IK_INPUT_ID, id);
|
||||
@@ -78,13 +77,17 @@ const color = computed<Color | undefined>(() => {
|
||||
return undefined;
|
||||
});
|
||||
|
||||
provide(IK_FORM_INPUT_COLOR, color);
|
||||
|
||||
provide(
|
||||
IK_FORM_HAS_LABEL,
|
||||
computed(() => slots.label !== undefined)
|
||||
);
|
||||
|
||||
useContext(ColorContext, color);
|
||||
useContext(DisabledContext, () => props.disabled);
|
||||
provide(
|
||||
IK_FORM_LABEL_DISABLED,
|
||||
computed(() => props.disabled ?? false)
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
@@ -96,24 +99,14 @@ useContext(DisabledContext, () => props.disabled);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.label {
|
||||
text-transform: uppercase;
|
||||
font-weight: 700;
|
||||
color: var(--color-blue-scale-100);
|
||||
font-size: 1.4rem;
|
||||
padding: 1rem 0;
|
||||
|
||||
&.light {
|
||||
font-size: 1.6rem;
|
||||
color: var(--color-blue-scale-300);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
&:not(.light) {
|
||||
font-size: 1.4rem;
|
||||
text-transform: uppercase;
|
||||
font-weight: 700;
|
||||
color: var(--color-blue-scale-100);
|
||||
}
|
||||
}
|
||||
|
||||
.messages-container {
|
||||
|
||||
@@ -1,28 +1,20 @@
|
||||
<template>
|
||||
<UiModal
|
||||
v-model="isCodeModalOpen"
|
||||
@submit.prevent="saveJson"
|
||||
:color="isJsonValid ? 'success' : 'error'"
|
||||
closable
|
||||
v-if="isCodeModalOpen"
|
||||
:icon="faCode"
|
||||
@close="closeCodeModal"
|
||||
>
|
||||
<FormModalLayout @submit.prevent="saveJson" :icon="faCode">
|
||||
<template #default>
|
||||
<FormTextarea class="modal-textarea" v-model="editedJson" />
|
||||
</template>
|
||||
|
||||
<template #buttons>
|
||||
<UiButton transparent @click="formatJson">
|
||||
{{ $t("reformat") }}
|
||||
</UiButton>
|
||||
<UiButton outlined @click="closeCodeModal">
|
||||
{{ $t("cancel") }}
|
||||
</UiButton>
|
||||
<UiButton :disabled="!isJsonValid" type="submit">
|
||||
{{ $t("save") }}
|
||||
</UiButton>
|
||||
</template>
|
||||
</FormModalLayout>
|
||||
<FormTextarea class="modal-textarea" v-model="editedJson" />
|
||||
<template #buttons>
|
||||
<UiButton transparent @click="formatJson">{{ $t("reformat") }}</UiButton>
|
||||
<UiButton outlined @click="closeCodeModal">{{ $t("cancel") }}</UiButton>
|
||||
<UiButton :disabled="!isJsonValid" type="submit"
|
||||
>{{ $t("save") }}
|
||||
</UiButton>
|
||||
</template>
|
||||
</UiModal>
|
||||
|
||||
<FormInput
|
||||
@click="openCodeModal"
|
||||
:model-value="jsonValue"
|
||||
@@ -34,9 +26,8 @@
|
||||
<script lang="ts" setup>
|
||||
import FormInput from "@/components/form/FormInput.vue";
|
||||
import FormTextarea from "@/components/form/FormTextarea.vue";
|
||||
import FormModalLayout from "@/components/ui/modals/layouts/FormModalLayout.vue";
|
||||
import UiModal from "@/components/ui/modals/UiModal.vue";
|
||||
import UiButton from "@/components/ui/UiButton.vue";
|
||||
import UiModal from "@/components/ui/UiModal.vue";
|
||||
import useModal from "@/composables/modal.composable";
|
||||
import { faCode } from "@fortawesome/free-solid-svg-icons";
|
||||
import { useVModel, whenever } from "@vueuse/core";
|
||||
|
||||
@@ -28,10 +28,10 @@
|
||||
import InfraAction from "@/components/infra/InfraAction.vue";
|
||||
import InfraItemLabel from "@/components/infra/InfraItemLabel.vue";
|
||||
import InfraVmList from "@/components/infra/InfraVmList.vue";
|
||||
import { useHostCollection } from "@/stores/xen-api/host.store";
|
||||
import { usePoolCollection } from "@/stores/xen-api/pool.store";
|
||||
import { useHostCollection } from "@/composables/xen-api-collection/host-collection.composable";
|
||||
import { usePoolCollection } from "@/composables/xen-api-collection/pool-collection.composable";
|
||||
import { vTooltip } from "@/directives/tooltip.directive";
|
||||
import type { XenApiHost } from "@/libs/xen-api/xen-api.types";
|
||||
import type { XenApiHost } from "@/libs/xen-api";
|
||||
import { useUiStore } from "@/stores/ui.store";
|
||||
import {
|
||||
faAngleDown,
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import InfraHostItem from "@/components/infra/InfraHostItem.vue";
|
||||
import { useHostCollection } from "@/stores/xen-api/host.store";
|
||||
import { useHostCollection } from "@/composables/xen-api-collection/host-collection.composable";
|
||||
|
||||
const { records: hosts, isReady, hasError } = useHostCollection();
|
||||
</script>
|
||||
|
||||
@@ -28,7 +28,7 @@ import InfraHostList from "@/components/infra/InfraHostList.vue";
|
||||
import InfraItemLabel from "@/components/infra/InfraItemLabel.vue";
|
||||
import InfraLoadingItem from "@/components/infra/InfraLoadingItem.vue";
|
||||
import InfraVmList from "@/components/infra/InfraVmList.vue";
|
||||
import { usePoolCollection } from "@/stores/xen-api/pool.store";
|
||||
import { usePoolCollection } from "@/composables/xen-api-collection/pool-collection.composable";
|
||||
import { faBuilding } from "@fortawesome/free-regular-svg-icons";
|
||||
|
||||
const { isReady, hasError, pool } = usePoolCollection();
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
import InfraAction from "@/components/infra/InfraAction.vue";
|
||||
import InfraItemLabel from "@/components/infra/InfraItemLabel.vue";
|
||||
import PowerStateIcon from "@/components/PowerStateIcon.vue";
|
||||
import { useVmCollection } from "@/stores/xen-api/vm.store";
|
||||
import type { XenApiVm } from "@/libs/xen-api/xen-api.types";
|
||||
import { useVmCollection } from "@/composables/xen-api-collection/vm-collection.composable";
|
||||
import type { XenApiVm } from "@/libs/xen-api";
|
||||
import { faDisplay } from "@fortawesome/free-solid-svg-icons";
|
||||
import { useIntersectionObserver } from "@vueuse/core";
|
||||
import { computed, ref } from "vue";
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
<script lang="ts" setup>
|
||||
import InfraLoadingItem from "@/components/infra/InfraLoadingItem.vue";
|
||||
import InfraVmItem from "@/components/infra/InfraVmItem.vue";
|
||||
import { useVmCollection } from "@/stores/xen-api/vm.store";
|
||||
import type { XenApiHost } from "@/libs/xen-api/xen-api.types";
|
||||
import { useVmCollection } from "@/composables/xen-api-collection/vm-collection.composable";
|
||||
import type { XenApiHost } from "@/libs/xen-api";
|
||||
import { faDisplay } from "@fortawesome/free-solid-svg-icons";
|
||||
import { computed } from "vue";
|
||||
|
||||
|
||||
@@ -14,10 +14,9 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useContext } from "@/composables/context.composable";
|
||||
import { DisabledContext } from "@/context";
|
||||
import {
|
||||
IK_CLOSE_MENU,
|
||||
IK_MENU_DISABLED,
|
||||
IK_MENU_HORIZONTAL,
|
||||
IK_MENU_TELEPORTED,
|
||||
} from "@/types/injection-keys";
|
||||
@@ -25,15 +24,12 @@ import placementJs, { type Options } from "placement.js";
|
||||
import { computed, inject, nextTick, provide, ref, useSlots } from "vue";
|
||||
import { onClickOutside, unrefElement, whenever } from "@vueuse/core";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
horizontal?: boolean;
|
||||
shadow?: boolean;
|
||||
disabled?: boolean;
|
||||
placement?: Options["placement"];
|
||||
}>(),
|
||||
{ disabled: undefined }
|
||||
);
|
||||
const props = defineProps<{
|
||||
horizontal?: boolean;
|
||||
shadow?: boolean;
|
||||
disabled?: boolean;
|
||||
placement?: Options["placement"];
|
||||
}>();
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
@@ -50,9 +46,10 @@ provide(
|
||||
IK_MENU_HORIZONTAL,
|
||||
computed(() => props.horizontal ?? false)
|
||||
);
|
||||
|
||||
useContext(DisabledContext, () => props.disabled);
|
||||
|
||||
provide(
|
||||
IK_MENU_DISABLED,
|
||||
computed(() => props.disabled ?? false)
|
||||
);
|
||||
let clearClickOutsideEvent: (() => void) | undefined;
|
||||
|
||||
const hasTrigger = useSlots().trigger !== undefined;
|
||||
|
||||
@@ -36,28 +36,33 @@
|
||||
import AppMenu from "@/components/menu/AppMenu.vue";
|
||||
import MenuTrigger from "@/components/menu/MenuTrigger.vue";
|
||||
import UiIcon from "@/components/ui/icon/UiIcon.vue";
|
||||
import { useContext } from "@/composables/context.composable";
|
||||
import { DisabledContext } from "@/context";
|
||||
import { IK_CLOSE_MENU, IK_MENU_HORIZONTAL } from "@/types/injection-keys";
|
||||
import {
|
||||
IK_CLOSE_MENU,
|
||||
IK_MENU_DISABLED,
|
||||
IK_MENU_HORIZONTAL,
|
||||
} from "@/types/injection-keys";
|
||||
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
|
||||
import { faAngleDown, faAngleRight } from "@fortawesome/free-solid-svg-icons";
|
||||
import { computed, inject, ref } from "vue";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
icon?: IconDefinition;
|
||||
onClick?: () => any;
|
||||
disabled?: boolean;
|
||||
busy?: boolean;
|
||||
}>(),
|
||||
{ disabled: undefined }
|
||||
);
|
||||
const props = defineProps<{
|
||||
icon?: IconDefinition;
|
||||
onClick?: () => any;
|
||||
disabled?: boolean;
|
||||
busy?: boolean;
|
||||
}>();
|
||||
|
||||
const isParentHorizontal = inject(
|
||||
IK_MENU_HORIZONTAL,
|
||||
computed(() => false)
|
||||
);
|
||||
const isDisabled = useContext(DisabledContext, () => props.disabled);
|
||||
const isMenuDisabled = inject(
|
||||
IK_MENU_DISABLED,
|
||||
computed(() => false)
|
||||
);
|
||||
const isDisabled = computed(
|
||||
() => props.disabled === true || isMenuDisabled.value
|
||||
);
|
||||
|
||||
const submenuIcon = computed(() =>
|
||||
isParentHorizontal.value ? faAngleDown : faAngleRight
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { usePoolCollection } from "@/stores/xen-api/pool.store";
|
||||
import { usePoolCollection } from "@/composables/xen-api-collection/pool-collection.composable";
|
||||
import { computed } from "vue";
|
||||
import { faBuilding } from "@fortawesome/free-regular-svg-icons";
|
||||
import TitleBar from "@/components/TitleBar.vue";
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<script lang="ts" setup>
|
||||
import RouterTab from "@/components/RouterTab.vue";
|
||||
import UiTabBar from "@/components/ui/UiTabBar.vue";
|
||||
import { usePoolCollection } from "@/stores/xen-api/pool.store";
|
||||
import { usePoolCollection } from "@/composables/xen-api-collection/pool-collection.composable";
|
||||
|
||||
const { pool, isReady } = usePoolCollection();
|
||||
</script>
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
<template>
|
||||
<UiCard class="pool-dashboard-alarms">
|
||||
<UiCardTitle>
|
||||
{{ $t("alarms") }}
|
||||
<template v-if="isReady && alarms.length > 0" #right>
|
||||
<UiCounter :value="alarms.length" color="error" />
|
||||
</template>
|
||||
</UiCardTitle>
|
||||
<div v-if="!isStarted" class="pre-start">
|
||||
<div>
|
||||
<p class="text">
|
||||
{{ $t("click-to-display-alarms") }}
|
||||
</p>
|
||||
<UiButton @click="start">{{ $t("load-now") }}</UiButton>
|
||||
</div>
|
||||
<div>
|
||||
<img alt="" src="@/assets/server-status.svg" />
|
||||
</div>
|
||||
</div>
|
||||
<NoDataError v-else-if="hasError" />
|
||||
<div v-else-if="!isReady">
|
||||
<UiCardSpinner />
|
||||
</div>
|
||||
<div v-else-if="alarms.length === 0" class="no-alarm">
|
||||
<div>
|
||||
<img alt="" src="@/assets/server-status.svg" />
|
||||
</div>
|
||||
<p class="text">
|
||||
{{ $t("all-good") }}<br />{{ $t("no-alarm-triggered") }}
|
||||
</p>
|
||||
</div>
|
||||
<div v-else class="table-container">
|
||||
<UiTable>
|
||||
<tbody>
|
||||
<AlarmRow v-for="alarm in alarms" :key="alarm.uuid" :alarm="alarm" />
|
||||
</tbody>
|
||||
</UiTable>
|
||||
</div>
|
||||
</UiCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import NoDataError from "@/components/NoDataError.vue";
|
||||
import AlarmRow from "@/components/pool/dashboard/alarm/AlarmRow.vue";
|
||||
import UiButton from "@/components/ui/UiButton.vue";
|
||||
import UiCard from "@/components/ui/UiCard.vue";
|
||||
import UiCardSpinner from "@/components/ui/UiCardSpinner.vue";
|
||||
import UiCardTitle from "@/components/ui/UiCardTitle.vue";
|
||||
import UiCounter from "@/components/ui/UiCounter.vue";
|
||||
import UiTable from "@/components/ui/UiTable.vue";
|
||||
import { useAlarmCollection } from "@/stores/xen-api/alarm.store";
|
||||
|
||||
const {
|
||||
records: alarms,
|
||||
start,
|
||||
isStarted,
|
||||
isReady,
|
||||
hasError,
|
||||
} = useAlarmCollection({ defer: true });
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.pool-dashboard-alarms {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.pre-start,
|
||||
.no-alarm {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 3rem;
|
||||
}
|
||||
.text {
|
||||
font-size: 2rem;
|
||||
font-weight: 500;
|
||||
.pre-start & {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.no-alarm & {
|
||||
color: var(--color-green-infra-base);
|
||||
}
|
||||
}
|
||||
.table-container {
|
||||
max-height: 24rem;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -37,15 +37,15 @@ import UiCard from "@/components/ui/UiCard.vue";
|
||||
import UiCardFooter from "@/components/ui/UiCardFooter.vue";
|
||||
import UiCardSpinner from "@/components/ui/UiCardSpinner.vue";
|
||||
import UiCardTitle from "@/components/ui/UiCardTitle.vue";
|
||||
import { useHostCollection } from "@/stores/xen-api/host.store";
|
||||
import { useVmCollection } from "@/stores/xen-api/vm.store";
|
||||
import { useVmMetricsCollection } from "@/stores/xen-api/vm-metrics.store";
|
||||
import { useHostCollection } from "@/composables/xen-api-collection/host-collection.composable";
|
||||
import { useVmCollection } from "@/composables/xen-api-collection/vm-collection.composable";
|
||||
import { useVmMetricsCollection } from "@/composables/xen-api-collection/vm-metrics-collection.composable";
|
||||
import { percent } from "@/libs/utils";
|
||||
import { VM_POWER_STATE } from "@/libs/xen-api/xen-api.enums";
|
||||
import { POWER_STATE } from "@/libs/xen-api";
|
||||
import { logicAnd } from "@vueuse/math";
|
||||
import { computed } from "vue";
|
||||
|
||||
const ACTIVE_STATES = new Set([VM_POWER_STATE.RUNNING, VM_POWER_STATE.PAUSED]);
|
||||
const ACTIVE_STATES = new Set([POWER_STATE.RUNNING, POWER_STATE.PAUSED]);
|
||||
|
||||
const {
|
||||
hasError: hostStoreHasError,
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
</UiCard>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useHostCollection } from "@/stores/xen-api/host.store";
|
||||
import { useVmCollection } from "@/stores/xen-api/vm.store";
|
||||
import { useHostCollection } from "@/composables/xen-api-collection/host-collection.composable";
|
||||
import { useVmCollection } from "@/composables/xen-api-collection/vm-collection.composable";
|
||||
import { vTooltip } from "@/directives/tooltip.directive";
|
||||
import HostsCpuUsage from "@/components/pool/dashboard/cpuUsage/HostsCpuUsage.vue";
|
||||
import VmsCpuUsage from "@/components/pool/dashboard/cpuUsage/VmsCpuUsage.vue";
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
<template>
|
||||
<UiCard>
|
||||
<UiCardTitle class="patches-title">
|
||||
{{ $t("patches") }}
|
||||
<template v-if="areAllLoaded" #right>
|
||||
{{ $t("n-missing", { n: count }) }}
|
||||
</template>
|
||||
</UiCardTitle>
|
||||
<div class="table-container">
|
||||
<HostPatches
|
||||
:are-all-loaded="areAllLoaded"
|
||||
:are-some-loaded="areSomeLoaded"
|
||||
:has-multiple-hosts="hosts.length > 1"
|
||||
:patches="patches"
|
||||
/>
|
||||
</div>
|
||||
</UiCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import HostPatches from "@/components/HostPatchesTable.vue";
|
||||
import UiCard from "@/components/ui/UiCard.vue";
|
||||
import UiCardTitle from "@/components/ui/UiCardTitle.vue";
|
||||
import { useHostPatches } from "@/composables/host-patches.composable";
|
||||
import { useHostCollection } from "@/stores/xen-api/host.store";
|
||||
|
||||
const { records: hosts } = useHostCollection();
|
||||
|
||||
const { count, patches, areSomeLoaded, areAllLoaded } = useHostPatches(hosts);
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.patches-title {
|
||||
--section-title-right-color: var(--color-red-vates-base);
|
||||
}
|
||||
|
||||
.table-container {
|
||||
max-height: 40rem;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -1,44 +1,33 @@
|
||||
<template>
|
||||
<UiCard class="linear-chart" :color="hasError ? 'error' : undefined">
|
||||
<UiCardTitle>{{ $t("network-throughput") }}</UiCardTitle>
|
||||
<UiCardTitle :level="UiCardTitleLevel.Subtitle">
|
||||
{{ $t("last-week") }}
|
||||
</UiCardTitle>
|
||||
<NoDataError v-if="hasError" />
|
||||
<UiCardSpinner v-else-if="isLoading" />
|
||||
<LinearChart
|
||||
v-else
|
||||
:data="data"
|
||||
:max-value="customMaxValue"
|
||||
:value-formatter="customValueFormatter"
|
||||
/>
|
||||
</UiCard>
|
||||
<!-- TODO: add a loader when data is not fully loaded or undefined -->
|
||||
<!-- TODO: add small loader with tooltips when stats can be expired -->
|
||||
<!-- TODO: display the NoData component in case of a data recovery error -->
|
||||
<LinearChart
|
||||
:data="data"
|
||||
:max-value="customMaxValue"
|
||||
:subtitle="$t('last-week')"
|
||||
:title="$t('network-throughput')"
|
||||
:value-formatter="customValueFormatter"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { IK_HOST_LAST_WEEK_STATS } from "@/types/injection-keys";
|
||||
import { computed, defineAsyncComponent, inject } from "vue";
|
||||
import { map } from "lodash-es";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { formatSize } from "@/libs/utils";
|
||||
import type { HostStats } from "@/libs/xapi-stats";
|
||||
import { IK_HOST_LAST_WEEK_STATS } from "@/types/injection-keys";
|
||||
import type { LinearChartData } from "@/types/chart";
|
||||
import { map } from "lodash-es";
|
||||
import NoDataError from "@/components/NoDataError.vue";
|
||||
import { RRD_STEP_FROM_STRING } from "@/libs/xapi-stats";
|
||||
import UiCard from "@/components/ui/UiCard.vue";
|
||||
import UiCardTitle from "@/components/ui/UiCardTitle.vue";
|
||||
import UiCardSpinner from "@/components/ui/UiCardSpinner.vue";
|
||||
import { UiCardTitleLevel } from "@/types/enums";
|
||||
import { useHostCollection } from "@/stores/xen-api/host.store";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const LinearChart = defineAsyncComponent(
|
||||
() => import("@/components/charts/LinearChart.vue")
|
||||
);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const hostLastWeekStats = inject(IK_HOST_LAST_WEEK_STATS);
|
||||
const { hasError, isFetching } = useHostCollection();
|
||||
|
||||
const data = computed<LinearChartData>(() => {
|
||||
const stats = hostLastWeekStats?.stats?.value;
|
||||
@@ -93,25 +82,6 @@ const data = computed<LinearChartData>(() => {
|
||||
];
|
||||
});
|
||||
|
||||
const isStatFetched = computed(() => {
|
||||
const stats = hostLastWeekStats?.stats?.value;
|
||||
if (stats === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return stats.every((host) => {
|
||||
const hostStats = host.stats;
|
||||
return (
|
||||
hostStats != null &&
|
||||
Object.values(hostStats.pifs["rx"])[0].length +
|
||||
Object.values(hostStats.pifs["tx"])[0].length ===
|
||||
data.value[0].data.length + data.value[1].data.length
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const isLoading = computed(() => isFetching.value || !isStatFetched.value);
|
||||
|
||||
// TODO: improve the way to get the max value of graph
|
||||
// See: https://github.com/vatesfr/xen-orchestra/pull/6610/files#r1072237279
|
||||
const customMaxValue = computed(
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useHostCollection } from "@/stores/xen-api/host.store";
|
||||
import { useVmCollection } from "@/stores/xen-api/vm.store";
|
||||
import { useHostCollection } from "@/composables/xen-api-collection/host-collection.composable";
|
||||
import { useVmCollection } from "@/composables/xen-api-collection/vm-collection.composable";
|
||||
import { vTooltip } from "@/directives/tooltip.directive";
|
||||
import HostsRamUsage from "@/components/pool/dashboard/ramUsage/HostsRamUsage.vue";
|
||||
import VmsRamUsage from "@/components/pool/dashboard/ramUsage/VmsRamUsage.vue";
|
||||
|
||||
@@ -26,8 +26,8 @@ import UiCard from "@/components/ui/UiCard.vue";
|
||||
import UiCardSpinner from "@/components/ui/UiCardSpinner.vue";
|
||||
import UiCardTitle from "@/components/ui/UiCardTitle.vue";
|
||||
import UiSeparator from "@/components/ui/UiSeparator.vue";
|
||||
import { useHostMetricsCollection } from "@/stores/xen-api/host-metrics.store";
|
||||
import { useVmCollection } from "@/stores/xen-api/vm.store";
|
||||
import { useHostMetricsCollection } from "@/composables/xen-api-collection/host-metrics-collection.composable";
|
||||
import { useVmCollection } from "@/composables/xen-api-collection/vm-collection.composable";
|
||||
import { computed } from "vue";
|
||||
|
||||
const {
|
||||
|
||||
@@ -23,7 +23,7 @@ import SizeStatsSummary from "@/components/ui/SizeStatsSummary.vue";
|
||||
import UiCard from "@/components/ui/UiCard.vue";
|
||||
import UiCardTitle from "@/components/ui/UiCardTitle.vue";
|
||||
import UsageBar from "@/components/UsageBar.vue";
|
||||
import { useSrCollection } from "@/stores/xen-api/sr.store";
|
||||
import { useSrCollection } from "@/composables/xen-api-collection/sr-collection.composable";
|
||||
import { N_ITEMS } from "@/views/pool/PoolDashboardView.vue";
|
||||
import { computed } from "vue";
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import TasksTable from "@/components/tasks/TasksTable.vue";
|
||||
import UiCard from "@/components/ui/UiCard.vue";
|
||||
import UiCardTitle from "@/components/ui/UiCardTitle.vue";
|
||||
import { useTaskCollection } from "@/stores/xen-api/task.store";
|
||||
import { useTaskCollection } from "@/composables/xen-api-collection/task-collection.composable";
|
||||
|
||||
const { pendingTasks } = useTaskCollection();
|
||||
</script>
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
<template>
|
||||
<tr>
|
||||
<th>
|
||||
<div
|
||||
v-tooltip="new Date(parseDateTime(alarm.timestamp)).toLocaleString()"
|
||||
class="ellipsis time"
|
||||
>
|
||||
<RelativeTime :date="alarm.timestamp" />
|
||||
</div>
|
||||
</th>
|
||||
<td>
|
||||
<div
|
||||
ref="descriptionElement"
|
||||
v-tooltip="hasTooltip"
|
||||
class="ellipsis description"
|
||||
>
|
||||
{{ $t(`alarm-type.${alarm.type}`, { n: alarm.triggerLevel * 100 }) }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="level">{{ alarm.level * 100 }}%</td>
|
||||
<td class="on">{{ $t("on-object", { object: alarm.cls }) }}</td>
|
||||
<td class="object">
|
||||
<ObjectLink :type="rawTypeToType(alarm.cls)" :uuid="alarm.obj_uuid" />
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup generic="T extends RawObjectType">
|
||||
import ObjectLink from "@/components/ObjectLink.vue";
|
||||
import RelativeTime from "@/components/RelativeTime.vue";
|
||||
import { vTooltip } from "@/directives/tooltip.directive";
|
||||
import { hasEllipsis, parseDateTime } from "@/libs/utils";
|
||||
import type { RawObjectType } from "@/libs/xen-api/xen-api.types";
|
||||
import { rawTypeToType } from "@/libs/xen-api/xen-api.utils";
|
||||
import type { XenApiAlarm } from "@/types/xen-api";
|
||||
import { computed, ref } from "vue";
|
||||
|
||||
defineProps<{
|
||||
alarm: XenApiAlarm<T>;
|
||||
}>();
|
||||
|
||||
const descriptionElement = ref<HTMLElement>();
|
||||
const hasTooltip = computed(() => hasEllipsis(descriptionElement.value));
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.ellipsis {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.level {
|
||||
color: var(--color-red-vates-base);
|
||||
font-size: 1.4rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.on {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.object-link {
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
@@ -1,23 +1,22 @@
|
||||
<template>
|
||||
<UiCardTitle
|
||||
:level="UiCardTitleLevel.SubtitleWithUnderline"
|
||||
:left="$t('hosts')"
|
||||
:right="$t('top-#', { n: N_ITEMS })"
|
||||
subtitle
|
||||
/>
|
||||
<NoDataError v-if="hasError" />
|
||||
<UsageBar v-else :data="statFetched ? data : undefined" :n-items="N_ITEMS" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, inject, type ComputedRef } from "vue";
|
||||
import NoDataError from "@/components/NoDataError.vue";
|
||||
import UiCardTitle from "@/components/ui/UiCardTitle.vue";
|
||||
import UsageBar from "@/components/UsageBar.vue";
|
||||
import { useHostCollection } from "@/composables/xen-api-collection/host-collection.composable";
|
||||
import { getAvgCpuUsage } from "@/libs/utils";
|
||||
import { IK_HOST_STATS } from "@/types/injection-keys";
|
||||
import { N_ITEMS } from "@/views/pool/PoolDashboardView.vue";
|
||||
import NoDataError from "@/components/NoDataError.vue";
|
||||
import UiCardTitle from "@/components/ui/UiCardTitle.vue";
|
||||
import { UiCardTitleLevel } from "@/types/enums";
|
||||
import UsageBar from "@/components/UsageBar.vue";
|
||||
import { useHostCollection } from "@/stores/xen-api/host.store";
|
||||
import { computed, type ComputedRef, inject } from "vue";
|
||||
|
||||
const { hasError } = useHostCollection();
|
||||
|
||||
|
||||
@@ -1,33 +1,24 @@
|
||||
<template>
|
||||
<UiCard class="linear-chart" :color="hasError ? 'error' : undefined">
|
||||
<UiCardTitle>{{ $t("pool-cpu-usage") }}</UiCardTitle>
|
||||
<UiCardTitle :level="UiCardTitleLevel.Subtitle">
|
||||
{{ $t("last-week") }}
|
||||
</UiCardTitle>
|
||||
<NoDataError v-if="hasError" />
|
||||
<UiCardSpinner v-else-if="isLoading" />
|
||||
<LinearChart
|
||||
v-else
|
||||
:data="data"
|
||||
:max-value="customMaxValue"
|
||||
:value-formatter="customValueFormatter"
|
||||
/>
|
||||
</UiCard>
|
||||
<!-- TODO: add a loader when data is not fully loaded or undefined -->
|
||||
<!-- TODO: add small loader with tooltips when stats can be expired -->
|
||||
<!-- TODO: Display the NoDataError component in case of a data recovery error -->
|
||||
<LinearChart
|
||||
:data="data"
|
||||
:max-value="customMaxValue"
|
||||
:subtitle="$t('last-week')"
|
||||
:title="$t('pool-cpu-usage')"
|
||||
:value-formatter="customValueFormatter"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, defineAsyncComponent, inject } from "vue";
|
||||
import { useHostCollection } from "@/composables/xen-api-collection/host-collection.composable";
|
||||
import type { HostStats } from "@/libs/xapi-stats";
|
||||
import { IK_HOST_LAST_WEEK_STATS } from "@/types/injection-keys";
|
||||
import type { LinearChartData, ValueFormatter } from "@/types/chart";
|
||||
import NoDataError from "@/components/NoDataError.vue";
|
||||
import { RRD_STEP_FROM_STRING } from "@/libs/xapi-stats";
|
||||
import type { LinearChartData, ValueFormatter } from "@/types/chart";
|
||||
import { IK_HOST_LAST_WEEK_STATS } from "@/types/injection-keys";
|
||||
import { sumBy } from "lodash-es";
|
||||
import UiCard from "@/components/ui/UiCard.vue";
|
||||
import UiCardTitle from "@/components/ui/UiCardTitle.vue";
|
||||
import UiCardSpinner from "@/components/ui/UiCardSpinner.vue";
|
||||
import { UiCardTitleLevel } from "@/types/enums";
|
||||
import { useHostCollection } from "@/stores/xen-api/host.store";
|
||||
import { computed, defineAsyncComponent, inject } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const LinearChart = defineAsyncComponent(
|
||||
@@ -38,7 +29,8 @@ const { t } = useI18n();
|
||||
|
||||
const hostLastWeekStats = inject(IK_HOST_LAST_WEEK_STATS);
|
||||
|
||||
const { records: hosts, isFetching, hasError } = useHostCollection();
|
||||
const { records: hosts } = useHostCollection();
|
||||
|
||||
const customMaxValue = computed(
|
||||
() => 100 * sumBy(hosts.value, (host) => +host.cpu_info.cpu_count)
|
||||
);
|
||||
@@ -87,22 +79,6 @@ const data = computed<LinearChartData>(() => {
|
||||
},
|
||||
];
|
||||
});
|
||||
const isStatFetched = computed(() => {
|
||||
const stats = hostLastWeekStats?.stats?.value;
|
||||
if (stats === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return stats.every((host) => {
|
||||
const hostStats = host.stats;
|
||||
return (
|
||||
hostStats != null &&
|
||||
Object.values(hostStats.cpus)[0].length === data.value[0].data.length
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const isLoading = computed(() => isFetching.value || !isStatFetched.value);
|
||||
|
||||
const customValueFormatter: ValueFormatter = (value) => `${value}%`;
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<UiCardTitle
|
||||
:level="UiCardTitleLevel.SubtitleWithUnderline"
|
||||
subtitle
|
||||
:left="$t('vms')"
|
||||
:right="$t('top-#', { n: N_ITEMS })"
|
||||
/>
|
||||
@@ -9,15 +9,14 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { type ComputedRef, computed, inject } from "vue";
|
||||
import NoDataError from "@/components/NoDataError.vue";
|
||||
import UiCardTitle from "@/components/ui/UiCardTitle.vue";
|
||||
import UsageBar from "@/components/UsageBar.vue";
|
||||
import { useVmCollection } from "@/stores/xen-api/vm.store";
|
||||
import { useVmCollection } from "@/composables/xen-api-collection/vm-collection.composable";
|
||||
import { getAvgCpuUsage } from "@/libs/utils";
|
||||
import { IK_VM_STATS } from "@/types/injection-keys";
|
||||
import { N_ITEMS } from "@/views/pool/PoolDashboardView.vue";
|
||||
import { UiCardTitleLevel } from "@/types/enums";
|
||||
import { computed, type ComputedRef, inject } from "vue";
|
||||
|
||||
const { hasError } = useVmCollection();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<UiCardTitle
|
||||
:level="UiCardTitleLevel.SubtitleWithUnderline"
|
||||
subtitle
|
||||
:left="$t('hosts')"
|
||||
:right="$t('top-#', { n: N_ITEMS })"
|
||||
/>
|
||||
@@ -10,10 +10,9 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import UiCardTitle from "@/components/ui/UiCardTitle.vue";
|
||||
import { useHostCollection } from "@/stores/xen-api/host.store";
|
||||
import { useHostCollection } from "@/composables/xen-api-collection/host-collection.composable";
|
||||
import { IK_HOST_STATS } from "@/types/injection-keys";
|
||||
import { type ComputedRef, computed, inject } from "vue";
|
||||
import { UiCardTitleLevel } from "@/types/enums";
|
||||
import UsageBar from "@/components/UsageBar.vue";
|
||||
import { formatSize, parseRamUsage } from "@/libs/utils";
|
||||
import { N_ITEMS } from "@/views/pool/PoolDashboardView.vue";
|
||||
|
||||
@@ -1,43 +1,37 @@
|
||||
<template>
|
||||
<UiCard class="linear-chart" :color="hasError ? 'error' : undefined">
|
||||
<UiCardTitle>{{ $t("pool-ram-usage") }}</UiCardTitle>
|
||||
<UiCardTitle :level="UiCardTitleLevel.Subtitle">
|
||||
{{ $t("last-week") }}
|
||||
</UiCardTitle>
|
||||
<NoDataError v-if="hasError" />
|
||||
<UiCardSpinner v-else-if="isLoading" />
|
||||
<LinearChart
|
||||
v-else
|
||||
:data="data"
|
||||
:max-value="customMaxValue"
|
||||
:value-formatter="customValueFormatter"
|
||||
/>
|
||||
<SizeStatsSummary :size="currentData.size" :usage="currentData.usage" />
|
||||
</UiCard>
|
||||
<!-- TODO: add a loader when data is not fully loaded or undefined -->
|
||||
<!-- TODO: add small loader with tooltips when stats can be expired -->
|
||||
<!-- TODO: display the NoDataError component in case of a data recovery error -->
|
||||
<LinearChart
|
||||
:data="data"
|
||||
:max-value="customMaxValue"
|
||||
:subtitle="$t('last-week')"
|
||||
:title="$t('pool-ram-usage')"
|
||||
:value-formatter="customValueFormatter"
|
||||
>
|
||||
<template #summary>
|
||||
<SizeStatsSummary :size="currentData.size" :usage="currentData.usage" />
|
||||
</template>
|
||||
</LinearChart>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, defineAsyncComponent, inject } from "vue";
|
||||
import { formatSize } from "@/libs/utils";
|
||||
import { IK_HOST_LAST_WEEK_STATS } from "@/types/injection-keys";
|
||||
import type { LinearChartData } from "@/types/chart";
|
||||
import NoDataError from "@/components/NoDataError.vue";
|
||||
import { RRD_STEP_FROM_STRING } from "@/libs/xapi-stats";
|
||||
import SizeStatsSummary from "@/components/ui/SizeStatsSummary.vue";
|
||||
import { useHostCollection } from "@/composables/xen-api-collection/host-collection.composable";
|
||||
import { useHostMetricsCollection } from "@/composables/xen-api-collection/host-metrics-collection.composable";
|
||||
import { formatSize } from "@/libs/utils";
|
||||
import { RRD_STEP_FROM_STRING } from "@/libs/xapi-stats";
|
||||
import type { LinearChartData, ValueFormatter } from "@/types/chart";
|
||||
import { IK_HOST_LAST_WEEK_STATS } from "@/types/injection-keys";
|
||||
import { sumBy } from "lodash-es";
|
||||
import UiCard from "@/components/ui/UiCard.vue";
|
||||
import UiCardTitle from "@/components/ui/UiCardTitle.vue";
|
||||
import UiCardSpinner from "@/components/ui/UiCardSpinner.vue";
|
||||
import { UiCardTitleLevel } from "@/types/enums";
|
||||
import { useHostCollection } from "@/stores/xen-api/host.store";
|
||||
import { useHostMetricsCollection } from "@/stores/xen-api/host-metrics.store";
|
||||
import { computed, defineAsyncComponent, inject } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const LinearChart = defineAsyncComponent(
|
||||
() => import("@/components/charts/LinearChart.vue")
|
||||
);
|
||||
|
||||
const { runningHosts, isFetching, hasError } = useHostCollection();
|
||||
const { runningHosts } = useHostCollection();
|
||||
const { getHostMemory } = useHostMetricsCollection();
|
||||
|
||||
const { t } = useI18n();
|
||||
@@ -98,23 +92,6 @@ const data = computed<LinearChartData>(() => {
|
||||
];
|
||||
});
|
||||
|
||||
const isStatFetched = computed(() => {
|
||||
const stats = hostLastWeekStats?.stats?.value;
|
||||
if (stats === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return stats.every((host) => {
|
||||
const hostStats = host.stats;
|
||||
return (
|
||||
hostStats != null && hostStats.memory.length === data.value[0].data.length
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const isLoading = computed(
|
||||
() => (isFetching.value && !hasError.value) || !isStatFetched.value
|
||||
);
|
||||
|
||||
const customValueFormatter = (value: number) => String(formatSize(value));
|
||||
const customValueFormatter: ValueFormatter = (value) =>
|
||||
String(formatSize(value));
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<UiCardTitle
|
||||
:level="UiCardTitleLevel.SubtitleWithUnderline"
|
||||
subtitle
|
||||
:left="$t('vms')"
|
||||
:right="$t('top-#', { n: N_ITEMS })"
|
||||
/>
|
||||
@@ -9,15 +9,14 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, inject, type ComputedRef } from "vue";
|
||||
import NoDataError from "@/components/NoDataError.vue";
|
||||
import UiCardTitle from "@/components/ui/UiCardTitle.vue";
|
||||
import UsageBar from "@/components/UsageBar.vue";
|
||||
import { useVmCollection } from "@/composables/xen-api-collection/vm-collection.composable";
|
||||
import { formatSize, parseRamUsage } from "@/libs/utils";
|
||||
import { IK_VM_STATS } from "@/types/injection-keys";
|
||||
import { N_ITEMS } from "@/views/pool/PoolDashboardView.vue";
|
||||
import NoDataError from "@/components/NoDataError.vue";
|
||||
import UiCardTitle from "@/components/ui/UiCardTitle.vue";
|
||||
import { UiCardTitleLevel } from "@/types/enums";
|
||||
import UsageBar from "@/components/UsageBar.vue";
|
||||
import { useVmCollection } from "@/stores/xen-api/vm.store";
|
||||
import { computed, type ComputedRef, inject } from "vue";
|
||||
|
||||
const { hasError } = useVmCollection();
|
||||
|
||||
|
||||
@@ -34,9 +34,9 @@
|
||||
<script lang="ts" setup>
|
||||
import RelativeTime from "@/components/RelativeTime.vue";
|
||||
import UiProgressBar from "@/components/ui/progress/UiProgressBar.vue";
|
||||
import { useHostCollection } from "@/stores/xen-api/host.store";
|
||||
import { useHostCollection } from "@/composables/xen-api-collection/host-collection.composable";
|
||||
import { parseDateTime } from "@/libs/utils";
|
||||
import type { XenApiTask } from "@/libs/xen-api/xen-api.types";
|
||||
import type { XenApiTask } from "@/libs/xen-api";
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
|
||||
@@ -40,8 +40,8 @@
|
||||
import TaskRow from "@/components/tasks/TaskRow.vue";
|
||||
import UiSpinner from "@/components/ui/UiSpinner.vue";
|
||||
import UiTable from "@/components/ui/UiTable.vue";
|
||||
import { useTaskCollection } from "@/stores/xen-api/task.store";
|
||||
import type { XenApiTask } from "@/libs/xen-api/xen-api.types";
|
||||
import { useTaskCollection } from "@/composables/xen-api-collection/task-collection.composable";
|
||||
import type { XenApiTask } from "@/libs/xen-api";
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
<template>
|
||||
<button
|
||||
:class="{
|
||||
busy: busy,
|
||||
busy: isBusy,
|
||||
disabled: isDisabled,
|
||||
active,
|
||||
'has-icon': icon !== undefined,
|
||||
}"
|
||||
:disabled="busy || isDisabled"
|
||||
class="ui-action-button"
|
||||
:disabled="isBusy || isDisabled"
|
||||
type="button"
|
||||
class="ui-action-button"
|
||||
>
|
||||
<UiIcon :busy="busy" :icon="icon" />
|
||||
<UiIcon :busy="isBusy" :icon="icon" />
|
||||
<slot />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import UiIcon from "@/components/ui/icon/UiIcon.vue";
|
||||
import { useContext } from "@/composables/context.composable";
|
||||
import { DisabledContext } from "@/context";
|
||||
import {
|
||||
IK_BUTTON_GROUP_BUSY,
|
||||
IK_BUTTON_GROUP_DISABLED,
|
||||
} from "@/types/injection-keys";
|
||||
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
|
||||
import { computed, inject } from "vue";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -28,10 +31,20 @@ const props = withDefaults(
|
||||
icon?: IconDefinition;
|
||||
active?: boolean;
|
||||
}>(),
|
||||
{ disabled: undefined }
|
||||
{ busy: undefined, disabled: undefined }
|
||||
);
|
||||
|
||||
const isDisabled = useContext(DisabledContext, () => props.disabled);
|
||||
const isGroupBusy = inject(
|
||||
IK_BUTTON_GROUP_BUSY,
|
||||
computed(() => false)
|
||||
);
|
||||
const isBusy = computed(() => props.busy ?? isGroupBusy.value);
|
||||
|
||||
const isGroupDisabled = inject(
|
||||
IK_BUTTON_GROUP_DISABLED,
|
||||
computed(() => false)
|
||||
);
|
||||
const isDisabled = computed(() => props.disabled ?? isGroupDisabled.value);
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<button
|
||||
:class="className"
|
||||
:disabled="busy || isDisabled"
|
||||
:disabled="isBusy || isDisabled"
|
||||
:type="type || 'button'"
|
||||
class="ui-button"
|
||||
>
|
||||
<UiSpinner v-if="busy" />
|
||||
<UiSpinner v-if="isBusy" />
|
||||
<template v-else>
|
||||
<UiIcon :icon="icon" class="icon" />
|
||||
<slot />
|
||||
@@ -15,9 +15,10 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import UiSpinner from "@/components/ui/UiSpinner.vue";
|
||||
import { useContext } from "@/composables/context.composable";
|
||||
import { ColorContext, DisabledContext } from "@/context";
|
||||
import {
|
||||
IK_BUTTON_GROUP_BUSY,
|
||||
IK_BUTTON_GROUP_COLOR,
|
||||
IK_BUTTON_GROUP_DISABLED,
|
||||
IK_BUTTON_GROUP_OUTLINED,
|
||||
IK_BUTTON_GROUP_TRANSPARENT,
|
||||
} from "@/types/injection-keys";
|
||||
@@ -38,31 +39,45 @@ const props = withDefaults(
|
||||
active?: boolean;
|
||||
}>(),
|
||||
{
|
||||
busy: undefined,
|
||||
disabled: undefined,
|
||||
outlined: undefined,
|
||||
transparent: undefined,
|
||||
}
|
||||
);
|
||||
|
||||
const isDisabled = useContext(DisabledContext, () => props.disabled);
|
||||
const isGroupBusy = inject(
|
||||
IK_BUTTON_GROUP_BUSY,
|
||||
computed(() => false)
|
||||
);
|
||||
const isBusy = computed(() => props.busy ?? isGroupBusy.value);
|
||||
|
||||
const isGroupDisabled = inject(
|
||||
IK_BUTTON_GROUP_DISABLED,
|
||||
computed(() => false)
|
||||
);
|
||||
const isDisabled = computed(() => props.disabled ?? isGroupDisabled.value);
|
||||
|
||||
const isGroupOutlined = inject(
|
||||
IK_BUTTON_GROUP_OUTLINED,
|
||||
computed(() => false)
|
||||
);
|
||||
|
||||
const isGroupTransparent = inject(
|
||||
IK_BUTTON_GROUP_TRANSPARENT,
|
||||
computed(() => false)
|
||||
);
|
||||
|
||||
const { name: contextColor } = useContext(ColorContext, () => props.color);
|
||||
const buttonGroupColor = inject(
|
||||
IK_BUTTON_GROUP_COLOR,
|
||||
computed(() => "info")
|
||||
);
|
||||
const buttonColor = computed(() => props.color ?? buttonGroupColor.value);
|
||||
|
||||
const className = computed(() => {
|
||||
return [
|
||||
`color-${contextColor.value}`,
|
||||
`color-${buttonColor.value}`,
|
||||
{
|
||||
busy: props.busy,
|
||||
busy: isBusy.value,
|
||||
active: props.active,
|
||||
disabled: isDisabled.value,
|
||||
outlined: props.outlined ?? isGroupOutlined.value,
|
||||
|
||||
@@ -5,25 +5,35 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useContext } from "@/composables/context.composable";
|
||||
import { ColorContext, DisabledContext } from "@/context";
|
||||
import type { Color } from "@/types";
|
||||
import {
|
||||
IK_BUTTON_GROUP_BUSY,
|
||||
IK_BUTTON_GROUP_COLOR,
|
||||
IK_BUTTON_GROUP_DISABLED,
|
||||
IK_BUTTON_GROUP_OUTLINED,
|
||||
IK_BUTTON_GROUP_TRANSPARENT,
|
||||
} from "@/types/injection-keys";
|
||||
import { computed, provide } from "vue";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
busy?: boolean;
|
||||
disabled?: boolean;
|
||||
color?: Color;
|
||||
outlined?: boolean;
|
||||
transparent?: boolean;
|
||||
merge?: boolean;
|
||||
}>(),
|
||||
{ disabled: undefined }
|
||||
const props = defineProps<{
|
||||
busy?: boolean;
|
||||
disabled?: boolean;
|
||||
color?: Color;
|
||||
outlined?: boolean;
|
||||
transparent?: boolean;
|
||||
merge?: boolean;
|
||||
}>();
|
||||
provide(
|
||||
IK_BUTTON_GROUP_BUSY,
|
||||
computed(() => props.busy ?? false)
|
||||
);
|
||||
provide(
|
||||
IK_BUTTON_GROUP_DISABLED,
|
||||
computed(() => props.disabled ?? false)
|
||||
);
|
||||
provide(
|
||||
IK_BUTTON_GROUP_COLOR,
|
||||
computed(() => props.color ?? "info")
|
||||
);
|
||||
provide(
|
||||
IK_BUTTON_GROUP_OUTLINED,
|
||||
@@ -33,16 +43,13 @@ provide(
|
||||
IK_BUTTON_GROUP_TRANSPARENT,
|
||||
computed(() => props.transparent ?? false)
|
||||
);
|
||||
|
||||
useContext(ColorContext, () => props.color);
|
||||
useContext(DisabledContext, () => props.disabled);
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.ui-button-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-content: left;
|
||||
gap: 1rem;
|
||||
|
||||
&.merge {
|
||||
|
||||
@@ -1,42 +1,24 @@
|
||||
<template>
|
||||
<div :class="classProp" class="ui-card">
|
||||
<div class="ui-card" :class="{ error: color === 'error' }">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ColorContext } from "@/context";
|
||||
import { useContext } from "@/composables/context.composable";
|
||||
import type { Color } from "@/types";
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
color?: Color;
|
||||
defineProps<{
|
||||
color?: "error";
|
||||
}>();
|
||||
|
||||
const { name: contextColor, backgroundClass } = useContext(
|
||||
ColorContext,
|
||||
() => props.color
|
||||
);
|
||||
|
||||
// We don't want to inherit "info" color
|
||||
const classProp = computed(() => {
|
||||
if (props.color === undefined && contextColor.value === "info") {
|
||||
return "bg-primary";
|
||||
}
|
||||
|
||||
return backgroundClass.value;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.ui-card {
|
||||
padding: 2.1rem;
|
||||
border-radius: 0.8rem;
|
||||
background-color: var(--background-color-primary);
|
||||
box-shadow: var(--shadow-200);
|
||||
}
|
||||
|
||||
.bg-primary {
|
||||
background-color: var(--background-color-primary);
|
||||
.error {
|
||||
background-color: var(--background-color-red-vates);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,40 +1,35 @@
|
||||
<template>
|
||||
<div :class="['ui-section-title', tags.left]">
|
||||
<component :is="tags.left" v-if="$slots.default || left" class="left">
|
||||
<div :class="{ subtitle }" class="ui-section-title">
|
||||
<component
|
||||
:is="subtitle ? 'h5' : 'h4'"
|
||||
v-if="$slots.default || left"
|
||||
class="left"
|
||||
>
|
||||
<slot>{{ left }}</slot>
|
||||
<UiCounter class="count" v-if="count > 0" :value="count" color="info" />
|
||||
</component>
|
||||
<component :is="tags.right" v-if="$slots.right || right" class="right">
|
||||
<component
|
||||
:is="subtitle ? 'h6' : 'h5'"
|
||||
v-if="$slots.right || right"
|
||||
class="right"
|
||||
>
|
||||
<slot name="right">{{ right }}</slot>
|
||||
</component>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
import UiCounter from "@/components/ui/UiCounter.vue";
|
||||
import { UiCardTitleLevel } from "@/types/enums";
|
||||
|
||||
const props = withDefaults(
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
count?: number;
|
||||
level?: UiCardTitleLevel;
|
||||
subtitle?: boolean;
|
||||
left?: string;
|
||||
right?: string;
|
||||
count?: number;
|
||||
}>(),
|
||||
{ count: 0, level: UiCardTitleLevel.Title }
|
||||
{ count: 0 }
|
||||
);
|
||||
|
||||
const tags = computed(() => {
|
||||
switch (props.level) {
|
||||
case UiCardTitleLevel.Subtitle:
|
||||
return { left: "h6", right: "h6" };
|
||||
case UiCardTitleLevel.SubtitleWithUnderline:
|
||||
return { left: "h5", right: "h6" };
|
||||
default:
|
||||
return { left: "h4", right: "h5" };
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
@@ -42,6 +37,7 @@ const tags = computed(() => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
--section-title-left-size: 2rem;
|
||||
--section-title-left-color: var(--color-blue-scale-100);
|
||||
@@ -50,17 +46,9 @@ const tags = computed(() => {
|
||||
--section-title-right-color: var(--color-extra-blue-base);
|
||||
--section-title-right-weight: 700;
|
||||
|
||||
&.h6 {
|
||||
margin-bottom: 1rem;
|
||||
--section-title-left-size: 1.5rem;
|
||||
--section-title-left-color: var(--color-blue-scale-300);
|
||||
--section-title-left-weight: 400;
|
||||
}
|
||||
|
||||
&.h5 {
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
&.subtitle {
|
||||
border-bottom: 1px solid var(--color-extra-blue-base);
|
||||
|
||||
--section-title-left-size: 1.6rem;
|
||||
--section-title-left-color: var(--color-extra-blue-base);
|
||||
--section-title-left-weight: 700;
|
||||
|
||||
157
@xen-orchestra/lite/src/components/ui/UiModal.vue
Normal file
157
@xen-orchestra/lite/src/components/ui/UiModal.vue
Normal file
@@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<form
|
||||
:class="className"
|
||||
class="ui-modal"
|
||||
v-bind="$attrs"
|
||||
@click.self="emit('close')"
|
||||
>
|
||||
<div class="container">
|
||||
<span v-if="onClose" class="close-icon" @click="emit('close')">
|
||||
<UiIcon :icon="faXmark" />
|
||||
</span>
|
||||
<div v-if="icon || $slots.icon" class="modal-icon">
|
||||
<slot name="icon">
|
||||
<UiIcon :icon="icon" />
|
||||
</slot>
|
||||
</div>
|
||||
<UiTitle v-if="$slots.title" type="h4">
|
||||
<slot name="title" />
|
||||
</UiTitle>
|
||||
<div v-if="$slots.subtitle" class="subtitle">
|
||||
<slot name="subtitle" />
|
||||
</div>
|
||||
<div v-if="$slots.default" class="content">
|
||||
<slot />
|
||||
</div>
|
||||
<UiButtonGroup :color="color">
|
||||
<slot name="buttons" />
|
||||
</UiButtonGroup>
|
||||
</div>
|
||||
</form>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import UiButtonGroup from "@/components/ui/UiButtonGroup.vue";
|
||||
import UiIcon from "@/components/ui/icon/UiIcon.vue";
|
||||
import UiTitle from "@/components/ui/UiTitle.vue";
|
||||
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
|
||||
import { faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { useMagicKeys, whenever } from "@vueuse/core";
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
icon?: IconDefinition;
|
||||
color?: "info" | "warning" | "error" | "success";
|
||||
onClose?: () => void;
|
||||
}>(),
|
||||
{ color: "info" }
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "close"): void;
|
||||
}>();
|
||||
|
||||
const { escape } = useMagicKeys();
|
||||
whenever(escape, () => emit("close"));
|
||||
|
||||
const className = computed(() => {
|
||||
return [`color-${props.color}`, { "has-icon": props.icon !== undefined }];
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.ui-modal {
|
||||
position: fixed;
|
||||
z-index: 2;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #00000080;
|
||||
}
|
||||
|
||||
.color-success {
|
||||
--modal-color: var(--color-green-infra-base);
|
||||
--modal-background-color: var(--background-color-green-infra);
|
||||
}
|
||||
|
||||
.color-info {
|
||||
--modal-color: var(--color-extra-blue-base);
|
||||
--modal-background-color: var(--background-color-extra-blue);
|
||||
}
|
||||
|
||||
.color-warning {
|
||||
--modal-color: var(--color-orange-world-base);
|
||||
--modal-background-color: var(--background-color-orange-world);
|
||||
}
|
||||
|
||||
.color-error {
|
||||
--modal-color: var(--color-red-vates-base);
|
||||
--modal-background-color: var(--background-color-red-vates);
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
min-width: 40rem;
|
||||
padding: 4.2rem;
|
||||
text-align: center;
|
||||
border-radius: 1rem;
|
||||
background-color: var(--modal-background-color);
|
||||
box-shadow: var(--shadow-400);
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
font-size: 2rem;
|
||||
position: absolute;
|
||||
top: 1.5rem;
|
||||
right: 2rem;
|
||||
padding: 0.2rem 0.5rem;
|
||||
cursor: pointer;
|
||||
color: var(--modal-color);
|
||||
}
|
||||
|
||||
.container :deep(.accent) {
|
||||
color: var(--modal-color);
|
||||
}
|
||||
|
||||
.modal-icon {
|
||||
font-size: 4.8rem;
|
||||
margin: 2rem 0;
|
||||
color: var(--modal-color);
|
||||
}
|
||||
|
||||
.ui-title {
|
||||
margin-top: 4rem;
|
||||
|
||||
.has-icon & {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.6rem;
|
||||
font-weight: 400;
|
||||
color: var(--color-blue-scale-200);
|
||||
}
|
||||
|
||||
.content {
|
||||
overflow: auto;
|
||||
font-size: 1.6rem;
|
||||
max-height: calc(100vh - 40rem);
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.ui-button-group {
|
||||
margin-top: 4rem;
|
||||
}
|
||||
</style>
|
||||
@@ -9,19 +9,22 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useContext } from "@/composables/context.composable";
|
||||
import { DisabledContext } from "@/context";
|
||||
import { IK_TAB_BAR_DISABLED } from "@/types/injection-keys";
|
||||
import { computed, inject } from "vue";
|
||||
|
||||
const props = withDefaults(
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
disabled?: boolean;
|
||||
active?: boolean;
|
||||
tag?: string;
|
||||
}>(),
|
||||
{ tag: "span", disabled: undefined }
|
||||
{ tag: "span" }
|
||||
);
|
||||
|
||||
const isTabBarDisabled = useContext(DisabledContext, () => props.disabled);
|
||||
const isTabBarDisabled = inject(
|
||||
IK_TAB_BAR_DISABLED,
|
||||
computed(() => false)
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
|
||||
@@ -5,17 +5,17 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useContext } from "@/composables/context.composable";
|
||||
import { DisabledContext } from "@/context";
|
||||
import { IK_TAB_BAR_DISABLED } from "@/types/injection-keys";
|
||||
import { computed, provide } from "vue";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
disabled?: boolean;
|
||||
}>(),
|
||||
{ disabled: undefined }
|
||||
const props = defineProps<{
|
||||
disabled?: boolean;
|
||||
}>();
|
||||
|
||||
provide(
|
||||
IK_TAB_BAR_DISABLED,
|
||||
computed(() => props.disabled ?? false)
|
||||
);
|
||||
|
||||
useContext(DisabledContext, () => props.disabled);
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
<template>
|
||||
<UiIcon
|
||||
:class="textClass"
|
||||
:icon="faXmark"
|
||||
class="modal-close-icon"
|
||||
@click="close"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import UiIcon from "@/components/ui/icon/UiIcon.vue";
|
||||
import { useContext } from "@/composables/context.composable";
|
||||
import { ColorContext } from "@/context";
|
||||
import { IK_MODAL_CLOSE } from "@/types/injection-keys";
|
||||
import { faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { inject } from "vue";
|
||||
|
||||
const { textClass } = useContext(ColorContext);
|
||||
|
||||
const close = inject(IK_MODAL_CLOSE, undefined);
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.modal-close-icon {
|
||||
font-size: 2rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@@ -1,83 +0,0 @@
|
||||
<template>
|
||||
<component
|
||||
:is="tag"
|
||||
:class="[backgroundClass, { nested: isNested }]"
|
||||
class="modal-container"
|
||||
>
|
||||
<header v-if="$slots.header" class="modal-header">
|
||||
<slot name="header" />
|
||||
</header>
|
||||
<main v-if="$slots.default" class="modal-content">
|
||||
<slot name="default" />
|
||||
</main>
|
||||
<footer v-if="$slots.footer" class="modal-footer">
|
||||
<slot name="footer" />
|
||||
</footer>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useContext } from "@/composables/context.composable";
|
||||
import { ColorContext } from "@/context";
|
||||
import type { Color } from "@/types";
|
||||
import { IK_MODAL_NESTED } from "@/types/injection-keys";
|
||||
import { inject, provide } from "vue";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
tag?: string;
|
||||
color?: Color;
|
||||
}>(),
|
||||
{ tag: "div" }
|
||||
);
|
||||
|
||||
defineSlots<{
|
||||
header: () => any;
|
||||
default: () => any;
|
||||
footer: () => any;
|
||||
}>();
|
||||
|
||||
const { backgroundClass } = useContext(ColorContext, () => props.color);
|
||||
|
||||
const isNested = inject(IK_MODAL_NESTED, false);
|
||||
provide(IK_MODAL_NESTED, true);
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.modal-container {
|
||||
display: grid;
|
||||
grid-template-rows: 1fr auto 1fr;
|
||||
max-width: calc(100vw - 2rem);
|
||||
max-height: calc(100vh - 20rem);
|
||||
padding: 2rem;
|
||||
gap: 1rem;
|
||||
border-radius: 1rem;
|
||||
font-size: 1.6rem;
|
||||
|
||||
&:not(.nested) {
|
||||
min-width: 40rem;
|
||||
box-shadow: var(--shadow-400);
|
||||
}
|
||||
|
||||
&.nested {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
text-align: center;
|
||||
grid-row: 2;
|
||||
padding: 2rem;
|
||||
max-height: 75vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
grid-row: 3;
|
||||
align-self: end;
|
||||
}
|
||||
</style>
|
||||
@@ -1,57 +0,0 @@
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div v-if="isOpen" class="ui-modal" @click.self="close">
|
||||
<slot />
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useContext } from "@/composables/context.composable";
|
||||
import { ColorContext } from "@/context";
|
||||
import type { Color } from "@/types";
|
||||
import { IK_MODAL_CLOSE } from "@/types/injection-keys";
|
||||
import { useMagicKeys, useVModel, whenever } from "@vueuse/core/index";
|
||||
import { provide } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean;
|
||||
color?: Color;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "update:modelValue", value: boolean): void;
|
||||
}>();
|
||||
|
||||
const isOpen = useVModel(props, "modelValue", emit);
|
||||
|
||||
const close = () => (isOpen.value = false);
|
||||
|
||||
provide(IK_MODAL_CLOSE, close);
|
||||
|
||||
useContext(ColorContext, () => props.color);
|
||||
|
||||
const { escape } = useMagicKeys();
|
||||
|
||||
whenever(escape, () => close());
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.ui-modal {
|
||||
position: fixed;
|
||||
z-index: 2;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(26, 27, 56, 0.25);
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
font-size: 1.6rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
</style>
|
||||
@@ -1,26 +0,0 @@
|
||||
<template>
|
||||
<ModalContainer>
|
||||
<template #header>
|
||||
<ModalCloseIcon class="close-icon" />
|
||||
</template>
|
||||
|
||||
<template #default>
|
||||
<slot />
|
||||
</template>
|
||||
</ModalContainer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ModalCloseIcon from "@/components/ui/modals/ModalCloseIcon.vue";
|
||||
import ModalContainer from "@/components/ui/modals/ModalContainer.vue";
|
||||
|
||||
defineSlots<{
|
||||
default: () => void;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.close-icon {
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
@@ -1,77 +0,0 @@
|
||||
<template>
|
||||
<ModalContainer tag="form">
|
||||
<template #header>
|
||||
<div class="close-bar">
|
||||
<ModalCloseIcon />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #default>
|
||||
<UiIcon :class="textClass" :icon="icon" class="main-icon" />
|
||||
<div v-if="$slots.title || $slots.subtitle" class="titles">
|
||||
<UiTitle v-if="$slots.title" type="h4">
|
||||
<slot name="title" />
|
||||
</UiTitle>
|
||||
<div v-if="$slots.subtitle" class="subtitle">
|
||||
<slot name="subtitle" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="$slots.default">
|
||||
<slot name="default" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<UiButtonGroup>
|
||||
<slot name="buttons" />
|
||||
</UiButtonGroup>
|
||||
</template>
|
||||
</ModalContainer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import UiIcon from "@/components/ui/icon/UiIcon.vue";
|
||||
import ModalCloseIcon from "@/components/ui/modals/ModalCloseIcon.vue";
|
||||
import ModalContainer from "@/components/ui/modals/ModalContainer.vue";
|
||||
import UiButtonGroup from "@/components/ui/UiButtonGroup.vue";
|
||||
import UiTitle from "@/components/ui/UiTitle.vue";
|
||||
import { useContext } from "@/composables/context.composable";
|
||||
import { ColorContext } from "@/context";
|
||||
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
|
||||
|
||||
defineProps<{
|
||||
icon?: IconDefinition;
|
||||
}>();
|
||||
|
||||
const { textClass } = useContext(ColorContext);
|
||||
|
||||
defineSlots<{
|
||||
title: () => void;
|
||||
subtitle: () => void;
|
||||
default: () => void;
|
||||
buttons: () => void;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.close-bar {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.main-icon {
|
||||
font-size: 4.8rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.titles {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.6rem;
|
||||
font-weight: 400;
|
||||
color: var(--color-blue-scale-200);
|
||||
}
|
||||
</style>
|
||||
@@ -1,71 +0,0 @@
|
||||
<template>
|
||||
<ModalContainer tag="form">
|
||||
<template #header>
|
||||
<div :class="borderClass" class="title-bar">
|
||||
<UiIcon :class="textClass" :icon="icon" />
|
||||
<slot name="title" />
|
||||
<ModalCloseIcon class="close-icon" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #default>
|
||||
<slot />
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<UiButtonGroup class="footer-buttons">
|
||||
<slot name="buttons" />
|
||||
</UiButtonGroup>
|
||||
</template>
|
||||
</ModalContainer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import UiIcon from "@/components/ui/icon/UiIcon.vue";
|
||||
import ModalCloseIcon from "@/components/ui/modals/ModalCloseIcon.vue";
|
||||
import ModalContainer from "@/components/ui/modals/ModalContainer.vue";
|
||||
import UiButtonGroup from "@/components/ui/UiButtonGroup.vue";
|
||||
import { useContext } from "@/composables/context.composable";
|
||||
import { ColorContext, DisabledContext } from "@/context";
|
||||
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
icon?: IconDefinition;
|
||||
disabled?: boolean;
|
||||
}>(),
|
||||
{ disabled: undefined }
|
||||
);
|
||||
|
||||
defineSlots<{
|
||||
title: () => void;
|
||||
default: () => void;
|
||||
buttons: () => void;
|
||||
}>();
|
||||
|
||||
const { textClass, borderClass } = useContext(ColorContext);
|
||||
|
||||
useContext(DisabledContext, () => props.disabled);
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.title-bar {
|
||||
display: flex;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
font-size: 2.4rem;
|
||||
gap: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
font-weight: 500;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
margin-left: auto;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.footer-buttons {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
@@ -2,7 +2,7 @@
|
||||
<MenuItem
|
||||
v-tooltip="!areAllSelectedVmsHalted && $t('selected-vms-in-execution')"
|
||||
:busy="areSomeSelectedVmsCloning"
|
||||
:disabled="isDisabled"
|
||||
:disabled="!areAllSelectedVmsHalted"
|
||||
:icon="faCopy"
|
||||
@click="handleCopy"
|
||||
>
|
||||
@@ -12,10 +12,9 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import MenuItem from "@/components/menu/MenuItem.vue";
|
||||
import { useVmCollection } from "@/stores/xen-api/vm.store";
|
||||
import { useVmCollection } from "@/composables/xen-api-collection/vm-collection.composable";
|
||||
import { vTooltip } from "@/directives/tooltip.directive";
|
||||
import { VM_POWER_STATE, VM_OPERATION } from "@/libs/xen-api/xen-api.enums";
|
||||
import type { XenApiVm } from "@/libs/xen-api/xen-api.types";
|
||||
import { POWER_STATE, VM_OPERATION, type XenApiVm } from "@/libs/xen-api";
|
||||
import { useXenApiStore } from "@/stores/xen-api.store";
|
||||
import { faCopy } from "@fortawesome/free-solid-svg-icons";
|
||||
import { computed } from "vue";
|
||||
@@ -32,22 +31,16 @@ const selectedVms = computed(() =>
|
||||
.filter((vm): vm is XenApiVm => vm !== undefined)
|
||||
);
|
||||
|
||||
const areAllSelectedVmsHalted = computed(
|
||||
() =>
|
||||
selectedVms.value.length > 0 &&
|
||||
selectedVms.value.every(
|
||||
(selectedVm) => selectedVm.power_state === VM_POWER_STATE.HALTED
|
||||
)
|
||||
const areAllSelectedVmsHalted = computed(() =>
|
||||
selectedVms.value.every(
|
||||
(selectedVm) => selectedVm.power_state === POWER_STATE.HALTED
|
||||
)
|
||||
);
|
||||
|
||||
const areSomeSelectedVmsCloning = computed(() =>
|
||||
selectedVms.value.some((vm) => isOperationPending(vm, VM_OPERATION.CLONE))
|
||||
);
|
||||
|
||||
const isDisabled = computed(() => {
|
||||
return selectedVms.value.length === 0 || !areAllSelectedVmsHalted.value;
|
||||
});
|
||||
|
||||
const handleCopy = async () => {
|
||||
const xapiStore = useXenApiStore();
|
||||
|
||||
|
||||
@@ -1,53 +1,50 @@
|
||||
<template>
|
||||
<MenuItem
|
||||
v-tooltip="areSomeVmsInExecution && $t('selected-vms-in-execution')"
|
||||
:disabled="isDisabled"
|
||||
:disabled="areSomeVmsInExecution"
|
||||
:icon="faTrashCan"
|
||||
v-tooltip="areSomeVmsInExecution && $t('selected-vms-in-execution')"
|
||||
@click="openDeleteModal"
|
||||
>
|
||||
{{ $t("delete") }}
|
||||
</MenuItem>
|
||||
<UiModal v-model="isDeleteModalOpen">
|
||||
<ConfirmModalLayout :icon="faSatellite">
|
||||
<template #title>
|
||||
<i18n-t keypath="confirm-delete" scope="global" tag="div">
|
||||
<span :class="textClass">
|
||||
{{ $t("n-vms", { n: vmRefs.length }) }}
|
||||
</span>
|
||||
</i18n-t>
|
||||
</template>
|
||||
|
||||
<template #subtitle>
|
||||
{{ $t("please-confirm") }}
|
||||
</template>
|
||||
|
||||
<template #buttons>
|
||||
<UiButton outlined @click="closeDeleteModal">
|
||||
{{ $t("go-back") }}
|
||||
</UiButton>
|
||||
<UiButton @click="deleteVms">
|
||||
{{ $t("delete-vms", { n: vmRefs.length }) }}
|
||||
</UiButton>
|
||||
</template>
|
||||
</ConfirmModalLayout>
|
||||
<UiModal
|
||||
v-if="isDeleteModalOpen"
|
||||
:icon="faSatellite"
|
||||
@close="closeDeleteModal"
|
||||
>
|
||||
<template #title>
|
||||
<i18n-t keypath="confirm-delete" scope="global" tag="div">
|
||||
<span class="accent">
|
||||
{{ $t("n-vms", { n: vmRefs.length }) }}
|
||||
</span>
|
||||
</i18n-t>
|
||||
</template>
|
||||
<template #subtitle>
|
||||
{{ $t("please-confirm") }}
|
||||
</template>
|
||||
<template #buttons>
|
||||
<UiButton outlined @click="closeDeleteModal">
|
||||
{{ $t("go-back") }}
|
||||
</UiButton>
|
||||
<UiButton @click="deleteVms">
|
||||
{{ $t("delete-vms", { n: vmRefs.length }) }}
|
||||
</UiButton>
|
||||
</template>
|
||||
</UiModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import MenuItem from "@/components/menu/MenuItem.vue";
|
||||
import ConfirmModalLayout from "@/components/ui/modals/layouts/ConfirmModalLayout.vue";
|
||||
import UiModal from "@/components/ui/modals/UiModal.vue";
|
||||
import { useVmCollection } from "@/composables/xen-api-collection/vm-collection.composable";
|
||||
import { POWER_STATE } from "@/libs/xen-api";
|
||||
import UiButton from "@/components/ui/UiButton.vue";
|
||||
import { useContext } from "@/composables/context.composable";
|
||||
import UiModal from "@/components/ui/UiModal.vue";
|
||||
import useModal from "@/composables/modal.composable";
|
||||
import { ColorContext } from "@/context";
|
||||
import { vTooltip } from "@/directives/tooltip.directive";
|
||||
import type { XenApiVm } from "@/libs/xen-api/xen-api.types";
|
||||
import { VM_POWER_STATE } from "@/libs/xen-api/xen-api.enums";
|
||||
import { useXenApiStore } from "@/stores/xen-api.store";
|
||||
import { useVmCollection } from "@/stores/xen-api/vm.store";
|
||||
import { faSatellite, faTrashCan } from "@fortawesome/free-solid-svg-icons";
|
||||
import { computed } from "vue";
|
||||
import { vTooltip } from "@/directives/tooltip.directive";
|
||||
import type { XenApiVm } from "@/libs/xen-api";
|
||||
|
||||
const props = defineProps<{
|
||||
vmRefs: XenApiVm["$ref"][];
|
||||
@@ -66,17 +63,11 @@ const vms = computed<XenApiVm[]>(() =>
|
||||
);
|
||||
|
||||
const areSomeVmsInExecution = computed(() =>
|
||||
vms.value.some((vm) => vm.power_state !== VM_POWER_STATE.HALTED)
|
||||
);
|
||||
|
||||
const isDisabled = computed(
|
||||
() => vms.value.length === 0 || areSomeVmsInExecution.value
|
||||
vms.value.some((vm) => vm.power_state !== POWER_STATE.HALTED)
|
||||
);
|
||||
|
||||
const deleteVms = async () => {
|
||||
await xenApi.vm.delete(props.vmRefs);
|
||||
closeDeleteModal();
|
||||
};
|
||||
|
||||
const { textClass } = useContext(ColorContext);
|
||||
</script>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useVmCollection } from "@/stores/xen-api/vm.store";
|
||||
import { useVmCollection } from "@/composables/xen-api-collection/vm-collection.composable";
|
||||
import { computed } from "vue";
|
||||
import { exportVmsAsCsvFile, exportVmsAsJsonFile } from "@/libs/vm";
|
||||
import MenuItem from "@/components/menu/MenuItem.vue";
|
||||
@@ -38,7 +38,7 @@ import {
|
||||
faFileExport,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { vTooltip } from "@/directives/tooltip.directive";
|
||||
import type { XenApiVm } from "@/libs/xen-api/xen-api.types";
|
||||
import type { XenApiVm } from "@/libs/xen-api";
|
||||
|
||||
const props = defineProps<{
|
||||
vmRefs: XenApiVm["$ref"][];
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
<template>
|
||||
<MenuItem
|
||||
v-tooltip="
|
||||
!areAllVmsMigratable && $t('some-selected-vms-can-not-be-migrated')
|
||||
"
|
||||
:busy="isMigrating"
|
||||
:disabled="isParentDisabled || !areAllVmsMigratable"
|
||||
:icon="faRoute"
|
||||
@click="openModal"
|
||||
>
|
||||
{{ $t("migrate") }}
|
||||
</MenuItem>
|
||||
|
||||
<UiModal v-model="isModalOpen">
|
||||
<FormModalLayout :disabled="isMigrating" @submit.prevent="handleMigrate">
|
||||
<template #title>
|
||||
{{ $t("migrate-n-vms", { n: selectedRefs.length }) }}
|
||||
</template>
|
||||
|
||||
<div>
|
||||
<FormInputWrapper :label="$t('select-destination-host')" light>
|
||||
<FormSelect v-model="selectedHost">
|
||||
<option :value="undefined">
|
||||
{{ $t("select-destination-host") }}
|
||||
</option>
|
||||
<option
|
||||
v-for="host in availableHosts"
|
||||
:key="host.$ref"
|
||||
:value="host"
|
||||
>
|
||||
{{ host.name_label }}
|
||||
</option>
|
||||
</FormSelect>
|
||||
</FormInputWrapper>
|
||||
</div>
|
||||
|
||||
<template #buttons>
|
||||
<UiButton outlined @click="closeModal">
|
||||
{{ isMigrating ? $t("close") : $t("cancel") }}
|
||||
</UiButton>
|
||||
<UiButton :busy="isMigrating" :disabled="!isValid" type="submit">
|
||||
{{ $t("migrate-n-vms", { n: selectedRefs.length }) }}
|
||||
</UiButton>
|
||||
</template>
|
||||
</FormModalLayout>
|
||||
</UiModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import FormInputWrapper from "@/components/form/FormInputWrapper.vue";
|
||||
import FormSelect from "@/components/form/FormSelect.vue";
|
||||
import MenuItem from "@/components/menu/MenuItem.vue";
|
||||
import FormModalLayout from "@/components/ui/modals/layouts/FormModalLayout.vue";
|
||||
import UiModal from "@/components/ui/modals/UiModal.vue";
|
||||
import UiButton from "@/components/ui/UiButton.vue";
|
||||
import { useContext } from "@/composables/context.composable";
|
||||
import useModal from "@/composables/modal.composable";
|
||||
import { useVmMigration } from "@/composables/vm-migration.composable";
|
||||
import { DisabledContext } from "@/context";
|
||||
import { vTooltip } from "@/directives/tooltip.directive";
|
||||
import type { XenApiVm } from "@/libs/xen-api/xen-api.types";
|
||||
import { faRoute } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
const props = defineProps<{
|
||||
selectedRefs: XenApiVm["$ref"][];
|
||||
}>();
|
||||
|
||||
const isParentDisabled = useContext(DisabledContext);
|
||||
|
||||
const {
|
||||
open: openModal,
|
||||
isOpen: isModalOpen,
|
||||
close: closeModal,
|
||||
} = useModal({
|
||||
onClose: () => (selectedHost.value = undefined),
|
||||
});
|
||||
|
||||
const {
|
||||
selectedHost,
|
||||
availableHosts,
|
||||
isValid,
|
||||
migrate,
|
||||
isMigrating,
|
||||
areAllVmsMigratable,
|
||||
} = useVmMigration(() => props.selectedRefs);
|
||||
|
||||
const handleMigrate = async () => {
|
||||
try {
|
||||
await migrate();
|
||||
closeModal();
|
||||
} catch (e) {
|
||||
console.error("Error while migrating", e);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -95,12 +95,12 @@
|
||||
import MenuItem from "@/components/menu/MenuItem.vue";
|
||||
import PowerStateIcon from "@/components/PowerStateIcon.vue";
|
||||
import UiIcon from "@/components/ui/icon/UiIcon.vue";
|
||||
import { useHostCollection } from "@/stores/xen-api/host.store";
|
||||
import { useHostMetricsCollection } from "@/stores/xen-api/host-metrics.store";
|
||||
import { usePoolCollection } from "@/stores/xen-api/pool.store";
|
||||
import { useVmCollection } from "@/stores/xen-api/vm.store";
|
||||
import type { XenApiHost, XenApiVm } from "@/libs/xen-api/xen-api.types";
|
||||
import { VM_POWER_STATE, VM_OPERATION } from "@/libs/xen-api/xen-api.enums";
|
||||
import { useHostCollection } from "@/composables/xen-api-collection/host-collection.composable";
|
||||
import { useHostMetricsCollection } from "@/composables/xen-api-collection/host-metrics-collection.composable";
|
||||
import { usePoolCollection } from "@/composables/xen-api-collection/pool-collection.composable";
|
||||
import { useVmCollection } from "@/composables/xen-api-collection/vm-collection.composable";
|
||||
import type { XenApiHost, XenApiVm } from "@/libs/xen-api";
|
||||
import { POWER_STATE, VM_OPERATION } from "@/libs/xen-api";
|
||||
import { useXenApiStore } from "@/stores/xen-api.store";
|
||||
import {
|
||||
faCirclePlay,
|
||||
@@ -136,16 +136,16 @@ const vmRefsWithPowerState = computed(() =>
|
||||
const xenApi = useXenApiStore().getXapi();
|
||||
|
||||
const areVmsRunning = computed(() =>
|
||||
vms.value.every((vm) => vm.power_state === VM_POWER_STATE.RUNNING)
|
||||
vms.value.every((vm) => vm.power_state === POWER_STATE.RUNNING)
|
||||
);
|
||||
const areVmsHalted = computed(() =>
|
||||
vms.value.every((vm) => vm.power_state === VM_POWER_STATE.HALTED)
|
||||
vms.value.every((vm) => vm.power_state === POWER_STATE.HALTED)
|
||||
);
|
||||
const areVmsSuspended = computed(() =>
|
||||
vms.value.every((vm) => vm.power_state === VM_POWER_STATE.SUSPENDED)
|
||||
vms.value.every((vm) => vm.power_state === POWER_STATE.SUSPENDED)
|
||||
);
|
||||
const areVmsPaused = computed(() =>
|
||||
vms.value.every((vm) => vm.power_state === VM_POWER_STATE.PAUSED)
|
||||
vms.value.every((vm) => vm.power_state === POWER_STATE.PAUSED)
|
||||
);
|
||||
|
||||
const areOperationsPending = (operation: VM_OPERATION | VM_OPERATION[]) =>
|
||||
@@ -179,7 +179,7 @@ const areVmsBusyToForceShutdown = computed(() =>
|
||||
areOperationsPending(VM_OPERATION.HARD_SHUTDOWN)
|
||||
);
|
||||
const getHostState = (host: XenApiHost) =>
|
||||
isHostRunning(host) ? VM_POWER_STATE.RUNNING : VM_POWER_STATE.HALTED;
|
||||
isHostRunning(host) ? POWER_STATE.RUNNING : POWER_STATE.HALTED;
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
<template>
|
||||
<MenuItem
|
||||
:busy="areSomeVmsSnapshoting"
|
||||
:disabled="isDisabled"
|
||||
:icon="faCamera"
|
||||
@click="handleSnapshot"
|
||||
>
|
||||
{{ $t("snapshot") }}
|
||||
</MenuItem>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import MenuItem from "@/components/menu/MenuItem.vue";
|
||||
import { useVmCollection } from "@/stores/xen-api/vm.store";
|
||||
import { VM_OPERATION } from "@/libs/xen-api/xen-api.enums";
|
||||
import type { XenApiVm } from "@/libs/xen-api/xen-api.types";
|
||||
import { useXenApiStore } from "@/stores/xen-api.store";
|
||||
import { faCamera } from "@fortawesome/free-solid-svg-icons";
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
vmRefs: XenApiVm["$ref"][];
|
||||
}>();
|
||||
|
||||
const { getByOpaqueRef, isOperationPending } = useVmCollection();
|
||||
|
||||
const vms = computed(() =>
|
||||
props.vmRefs
|
||||
.map((vmRef) => getByOpaqueRef(vmRef))
|
||||
.filter((vm): vm is XenApiVm => vm !== undefined)
|
||||
);
|
||||
|
||||
const areSomeVmsSnapshoting = computed(() =>
|
||||
vms.value.some((vm) => isOperationPending(vm, VM_OPERATION.SNAPSHOT))
|
||||
);
|
||||
|
||||
const isDisabled = computed(
|
||||
() => vms.value.length === 0 || areSomeVmsSnapshoting.value
|
||||
);
|
||||
|
||||
const handleSnapshot = () => {
|
||||
const vmRefsToSnapshot = Object.fromEntries(
|
||||
vms.value.map((vm) => [
|
||||
vm.$ref,
|
||||
`${vm.name_label}_${new Date().toISOString()}`,
|
||||
])
|
||||
);
|
||||
return useXenApiStore().getXapi().vm.snapshot(vmRefsToSnapshot);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped></style>
|
||||
@@ -21,8 +21,8 @@ import TitleBar from "@/components/TitleBar.vue";
|
||||
import UiIcon from "@/components/ui/icon/UiIcon.vue";
|
||||
import UiButton from "@/components/ui/UiButton.vue";
|
||||
import VmActionPowerStateItems from "@/components/vm/VmActionItems/VmActionPowerStateItems.vue";
|
||||
import { useVmCollection } from "@/stores/xen-api/vm.store";
|
||||
import type { XenApiVm } from "@/libs/xen-api/xen-api.types";
|
||||
import { useVmCollection } from "@/composables/xen-api-collection/vm-collection.composable";
|
||||
import type { XenApiVm } from "@/libs/xen-api";
|
||||
import {
|
||||
faAngleDown,
|
||||
faDisplay,
|
||||
|
||||
@@ -15,12 +15,16 @@
|
||||
<VmActionPowerStateItems :vm-refs="selectedRefs" />
|
||||
</template>
|
||||
</MenuItem>
|
||||
<VmActionMigrateItem :selected-refs="selectedRefs" />
|
||||
<MenuItem v-tooltip="$t('coming-soon')" :icon="faRoute">
|
||||
{{ $t("migrate") }}
|
||||
</MenuItem>
|
||||
<VmActionCopyItem :selected-refs="selectedRefs" />
|
||||
<MenuItem v-tooltip="$t('coming-soon')" :icon="faEdit">
|
||||
{{ $t("edit-config") }}
|
||||
</MenuItem>
|
||||
<VmActionSnapshotItem :vm-refs="selectedRefs" />
|
||||
<MenuItem v-tooltip="$t('coming-soon')" :icon="faCamera">
|
||||
{{ $t("snapshot") }}
|
||||
</MenuItem>
|
||||
<VmActionExportItem :vm-refs="selectedRefs" />
|
||||
<VmActionDeleteItem :vm-refs="selectedRefs" />
|
||||
</AppMenu>
|
||||
@@ -31,18 +35,18 @@ import AppMenu from "@/components/menu/AppMenu.vue";
|
||||
import MenuItem from "@/components/menu/MenuItem.vue";
|
||||
import UiButton from "@/components/ui/UiButton.vue";
|
||||
import VmActionCopyItem from "@/components/vm/VmActionItems/VmActionCopyItem.vue";
|
||||
import VmActionDeleteItem from "@/components/vm/VmActionItems/VmActionDeleteItem.vue";
|
||||
import VmActionExportItem from "@/components/vm/VmActionItems/VmActionExportItem.vue";
|
||||
import VmActionMigrateItem from "@/components/vm/VmActionItems/VmActionMigrateItem.vue";
|
||||
import VmActionDeleteItem from "@/components/vm/VmActionItems/VmActionDeleteItem.vue";
|
||||
import VmActionPowerStateItems from "@/components/vm/VmActionItems/VmActionPowerStateItems.vue";
|
||||
import VmActionSnapshotItem from "@/components/vm/VmActionItems/VmActionSnapshotItem.vue";
|
||||
import { vTooltip } from "@/directives/tooltip.directive";
|
||||
import type { XenApiVm } from "@/libs/xen-api/xen-api.types";
|
||||
import type { XenApiVm } from "@/libs/xen-api";
|
||||
import { useUiStore } from "@/stores/ui.store";
|
||||
import {
|
||||
faCamera,
|
||||
faEdit,
|
||||
faEllipsis,
|
||||
faPowerOff,
|
||||
faRoute,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { storeToRefs } from "pinia";
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ export const useChartTheme = () => {
|
||||
|
||||
const getColors = () => ({
|
||||
background: style.getPropertyValue("--background-color-primary"),
|
||||
text: style.getPropertyValue("--color-blue-scale-300"),
|
||||
title: style.getPropertyValue("--color-blue-scale-100"),
|
||||
subtitle: style.getPropertyValue("--color-blue-scale-300"),
|
||||
splitLine: style.getPropertyValue("--color-blue-scale-400"),
|
||||
primary: style.getPropertyValue("--color-extra-blue-base"),
|
||||
secondary: style.getPropertyValue("--color-orange-world-base"),
|
||||
@@ -27,10 +28,24 @@ export const useChartTheme = () => {
|
||||
backgroundColor: colors.value.background,
|
||||
textStyle: {},
|
||||
grid: {
|
||||
top: 40,
|
||||
top: 80,
|
||||
left: 80,
|
||||
right: 20,
|
||||
},
|
||||
title: {
|
||||
textStyle: {
|
||||
color: colors.value.title,
|
||||
fontFamily: "Poppins, sans-serif",
|
||||
fontWeight: 500,
|
||||
fontSize: 20,
|
||||
},
|
||||
subtextStyle: {
|
||||
color: colors.value.subtitle,
|
||||
fontFamily: "Poppins, sans-serif",
|
||||
fontWeight: 400,
|
||||
fontSize: 14,
|
||||
},
|
||||
},
|
||||
line: {
|
||||
itemStyle: {
|
||||
borderWidth: 2,
|
||||
@@ -220,7 +235,7 @@ export const useChartTheme = () => {
|
||||
},
|
||||
axisLabel: {
|
||||
show: true,
|
||||
color: colors.value.text,
|
||||
color: colors.value.subtitle,
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
@@ -280,7 +295,7 @@ export const useChartTheme = () => {
|
||||
},
|
||||
axisLabel: {
|
||||
show: true,
|
||||
color: colors.value.text,
|
||||
color: colors.value.subtitle,
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
@@ -310,7 +325,7 @@ export const useChartTheme = () => {
|
||||
left: "right",
|
||||
top: "bottom",
|
||||
textStyle: {
|
||||
color: colors.value.text,
|
||||
color: colors.value.subtitle,
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
<!-- TOC -->
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Simple Context](#simple-context)
|
||||
- [1. Create the context](#1-create-the-context)
|
||||
- [2. Use the context](#2-use-the-context)
|
||||
- [2.1. Read](#21-read)
|
||||
- [2.2. Update](#22-update)
|
||||
- [Advanced Context](#advanced-context)
|
||||
- [1. Create the context](#1-create-the-context-1)
|
||||
- [2. Use the context](#2-use-the-context-1)
|
||||
- [2.1. Read](#21-read-1)
|
||||
- [2.2. Update](#22-update-1)
|
||||
- [Caveats (boolean props)](#caveats-boolean-props)
|
||||
<!-- TOC -->
|
||||
|
||||
# Overview
|
||||
|
||||
`createContext` lets you create a context that is both readable and writable, and is accessible by a component and all
|
||||
its descendants at any depth.
|
||||
|
||||
Each descendant has the ability to change the context value, affecting itself and all of its descendants at any level.
|
||||
|
||||
## Simple Context
|
||||
|
||||
### 1. Create the context
|
||||
|
||||
`createContext` takes the initial context value as first argument.
|
||||
|
||||
```ts
|
||||
// context.ts
|
||||
|
||||
const CounterContext = createContext(0);
|
||||
```
|
||||
|
||||
### 2. Use the context
|
||||
|
||||
#### 2.1. Read
|
||||
|
||||
You can get the current Context value by using `useContext(CounterContext)`.
|
||||
|
||||
```ts
|
||||
const counter = useContext(CounterContext);
|
||||
|
||||
console.log(counter.value); // 0
|
||||
```
|
||||
|
||||
#### 2.2. Update
|
||||
|
||||
You can pass a `MaybeRefOrGetter` as second argument to update the context value.
|
||||
|
||||
```ts
|
||||
// MyComponent.vue
|
||||
|
||||
const props = defineProps<{
|
||||
counter?: number;
|
||||
}>();
|
||||
|
||||
const counter = useContext(CounterContext, () => props.counter);
|
||||
|
||||
// When calling <MyComponent />
|
||||
console.log(counter.value); // 0
|
||||
|
||||
// When calling <MyComponent :counter="20" />
|
||||
console.log(counter.value); // 20
|
||||
```
|
||||
|
||||
## Advanced Context
|
||||
|
||||
To customize the context output, you can pass a custom context builder as the second argument of `createContext`.
|
||||
|
||||
### 1. Create the context
|
||||
|
||||
```ts
|
||||
// context.ts
|
||||
|
||||
// Example 1. Return a object
|
||||
const CounterContext = createContext(10, (counter) => ({
|
||||
counter,
|
||||
isEven: computed(() => counter.value % 2 === 0),
|
||||
}));
|
||||
|
||||
// Example 2. Return a computed value
|
||||
const DoubleContext = createContext(10, (num) => computed(() => num.value * 2));
|
||||
```
|
||||
|
||||
### 2. Use the context
|
||||
|
||||
#### 2.1. Read
|
||||
|
||||
When using the context, it will return your custom value.
|
||||
|
||||
```ts
|
||||
const { counter, isEven } = useContext(CounterContext);
|
||||
const double = useContext(DoubleContext);
|
||||
|
||||
console.log(counter.value); // 10
|
||||
console.log(isEven.value); // true
|
||||
console.log(double.value); // 20
|
||||
```
|
||||
|
||||
#### 2.2. Update
|
||||
|
||||
Same as with a simple context, you can pass a `MaybeRefOrGetter` as second argument.
|
||||
|
||||
```ts
|
||||
// Parent.vue
|
||||
useContext(CounterContext, 99);
|
||||
useContext(DoubleContext, 99);
|
||||
|
||||
// Child.vue
|
||||
const { isEven } = useContext(CounterContext);
|
||||
const double = useContext(DoubleContext);
|
||||
|
||||
console.log(isEven.value); // false
|
||||
console.log(double.value); // 198
|
||||
```
|
||||
|
||||
## Caveats (boolean props)
|
||||
|
||||
When working with `boolean` props, there's an important caveat to be aware of.
|
||||
|
||||
If the `MaybeRefOrGetter` returns any other value than `undefined`, the context will be updated according to this value.
|
||||
|
||||
This could be problematic if the value comes from a `boolean` prop.
|
||||
|
||||
```ts
|
||||
const props = defineProps<{
|
||||
disabled?: boolean;
|
||||
}>();
|
||||
|
||||
useContext(MyBooleanContext, () => props.disabled); // Update to `false` if `undefined`
|
||||
```
|
||||
|
||||
In that case, Vue will automatically set the default value for `disabled` prop to `false`.
|
||||
|
||||
Even if the `disabled` prop in not provided at all, the current context will not be used and will be replaced
|
||||
by `false`.
|
||||
|
||||
To circumvent this issue, you need to use `withDefaults` and specifically set the default value for `boolean` props
|
||||
to `undefined`:
|
||||
|
||||
```ts
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
disabled?: boolean;
|
||||
}>(),
|
||||
{ disabled: undefined }
|
||||
);
|
||||
|
||||
useContext(MyBoolean, () => props.disabled); // Keep parent value if `undefined`
|
||||
```
|
||||
@@ -1,43 +0,0 @@
|
||||
import type { ComputedRef, InjectionKey, MaybeRefOrGetter } from "vue";
|
||||
import { computed, inject, provide, toValue } from "vue";
|
||||
|
||||
type Context<T = any, Output = any> = ReturnType<
|
||||
typeof createContext<T, Output>
|
||||
>;
|
||||
|
||||
type ContextOutput<Ctx extends Context> = Ctx extends Context<any, infer Output>
|
||||
? Output
|
||||
: never;
|
||||
|
||||
type ContextValue<Ctx extends Context> = Ctx extends Context<infer T>
|
||||
? T
|
||||
: never;
|
||||
|
||||
export const createContext = <T, Output = ComputedRef<T>>(
|
||||
initialValue: MaybeRefOrGetter<T>,
|
||||
customBuilder?: (value: ComputedRef<T>) => Output
|
||||
) => {
|
||||
return {
|
||||
id: Symbol() as InjectionKey<MaybeRefOrGetter<T>>,
|
||||
initialValue,
|
||||
builder: customBuilder ?? ((value) => value as Output),
|
||||
};
|
||||
};
|
||||
|
||||
export const useContext = <Ctx extends Context, T extends ContextValue<Ctx>>(
|
||||
context: Ctx,
|
||||
newValue?: MaybeRefOrGetter<T | undefined>
|
||||
): ContextOutput<Ctx> => {
|
||||
const currentValue = inject(context.id, context.initialValue);
|
||||
|
||||
const build = (value: MaybeRefOrGetter<T>) =>
|
||||
context.builder(computed(() => toValue(value)));
|
||||
|
||||
if (newValue !== undefined) {
|
||||
const updatedValue = () => toValue(newValue) ?? toValue(currentValue);
|
||||
provide(context.id, updatedValue);
|
||||
return build(updatedValue);
|
||||
}
|
||||
|
||||
return build(currentValue);
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user