Compare commits
6 Commits
feat_remov
...
xo6/improv
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d46fa9e3e | ||
|
|
e8eb2fe6a7 | ||
|
|
aefcce45ff | ||
|
|
367fb4d8a6 | ||
|
|
e54a0bfc80 | ||
|
|
9e5541703b |
@@ -2,7 +2,6 @@ import assert from 'node:assert'
|
||||
import groupBy from 'lodash/groupBy.js'
|
||||
import ignoreErrors from 'promise-toolbox/ignoreErrors'
|
||||
import { asyncMap } from '@xen-orchestra/async-map'
|
||||
import { createLogger } from '@xen-orchestra/log'
|
||||
import { decorateMethodsWith } from '@vates/decorate-with'
|
||||
import { defer } from 'golike-defer'
|
||||
import { formatDateTime } from '@xen-orchestra/xapi'
|
||||
@@ -11,8 +10,6 @@ import { getOldEntries } from '../../_getOldEntries.mjs'
|
||||
import { Task } from '../../Task.mjs'
|
||||
import { Abstract } from './_Abstract.mjs'
|
||||
|
||||
const { info, warn } = createLogger('xo:backups:AbstractXapi')
|
||||
|
||||
export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
|
||||
constructor({
|
||||
config,
|
||||
@@ -174,20 +171,6 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
|
||||
}
|
||||
}
|
||||
|
||||
// this will delete current snapshot in case of failure
|
||||
// to ensure any retry will start with a clean state, especially in the case of rolling snapshots
|
||||
#removeCurrentSnapshotOnFailure() {
|
||||
if (this._mustDoSnapshot() && this._exportedVm !== undefined) {
|
||||
info('will delete snapshot on failure', { vm: this._vm, snapshot: this._exportedVm })
|
||||
assert.notStrictEqual(
|
||||
this._vm.$ref,
|
||||
this._exportedVm.$ref,
|
||||
'there should have a snapshot, but vm and snapshot have the same ref'
|
||||
)
|
||||
return this._xapi.VM_destroy(this._exportedVm.$ref)
|
||||
}
|
||||
}
|
||||
|
||||
async _fetchJobSnapshots() {
|
||||
const jobId = this._jobId
|
||||
const vmRef = this._vm.$ref
|
||||
@@ -288,17 +271,11 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
|
||||
await this._exportedVm.update_blocked_operations({ pool_migrate, migrate_send })
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
try {
|
||||
await this.#removeCurrentSnapshotOnFailure()
|
||||
} catch (removeSnapshotError) {
|
||||
warn('fail removing current snapshot', { error: removeSnapshotError })
|
||||
}
|
||||
throw error
|
||||
} finally {
|
||||
if (startAfter) {
|
||||
ignoreErrors.call(vm.$callAsync('start', false, false))
|
||||
}
|
||||
|
||||
await this._fetchJobSnapshots()
|
||||
await this._removeUnusedSnapshots()
|
||||
}
|
||||
|
||||
158
@xen-orchestra/web-core/docs/composables/context.composable.md
Normal file
158
@xen-orchestra/web-core/docs/composables/context.composable.md
Normal file
@@ -0,0 +1,158 @@
|
||||
<!-- 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))
|
||||
|
||||
// Example 3. Use a previous value
|
||||
const ColorContext = createContext('info' as Color, (color, previousColor) => ({
|
||||
name: color,
|
||||
colorContextClass: computed(() => (previousColor.value === color.value ? undefined : `color-context-${color.value}`)),
|
||||
}))
|
||||
```
|
||||
|
||||
### 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`
|
||||
```
|
||||
142
@xen-orchestra/web-core/docs/contexts/color-context.md
Normal file
142
@xen-orchestra/web-core/docs/contexts/color-context.md
Normal file
@@ -0,0 +1,142 @@
|
||||
<!-- TOC -->
|
||||
|
||||
- [Overview](#overview)
|
||||
- [CSS variables](#css-variables)
|
||||
- [Available color contexts](#available-color-contexts)
|
||||
- [Usage](#usage)
|
||||
<!-- TOC -->
|
||||
|
||||
# Overview
|
||||
|
||||
The color context provides a way to apply a set of colors variants to 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.
|
||||
|
||||
The purpose is to colorize a component and its descendants by applying a single CSS class on the parent component (e.g., applying the class on a modal component container will style all children components using the context).
|
||||
|
||||
## CSS variables
|
||||
|
||||
The color context relies on the usage of the following variables:
|
||||
|
||||
```css
|
||||
--color-context-primary;
|
||||
--color-context-primary-hover;
|
||||
--color-context-primary-active;
|
||||
--color-context-primary-disabled;
|
||||
|
||||
--color-context-secondary;
|
||||
--color-context-secondary-hover;
|
||||
--color-context-secondary-active;
|
||||
|
||||
--color-context-tertiary;
|
||||
```
|
||||
|
||||
Any component can use these variables for `color`, `background-color` or any other CSS property, to be usable with the color context.
|
||||
|
||||
When you set a color context, the variables are updated with the help of CSS classes defined in `_context.pcss`:
|
||||
|
||||
```css
|
||||
.color-context-info {
|
||||
--color-context-primary: var(--color-purple-base);
|
||||
--color-context-primary-hover: var(--color-purple-d20);
|
||||
--color-context-primary-active: var(--color-purple-d40);
|
||||
--color-context-primary-disabled: var(--color-grey-400);
|
||||
|
||||
--color-context-secondary: var(--background-color-purple-10);
|
||||
--color-context-secondary-hover: var(--background-color-purple-20);
|
||||
--color-context-secondary-active: var(--background-color-purple-30);
|
||||
}
|
||||
|
||||
.color-context-success {
|
||||
--color-context-primary: var(--color-green-base);
|
||||
--color-context-primary-hover: var(--color-green-d20);
|
||||
/*...*/
|
||||
}
|
||||
```
|
||||
|
||||
You can add any other context by adding a `color-context-<my-context>` class and setting the desired values for the variables.
|
||||
|
||||
**Important note: remember to set a value for all variables to avoid any missing styles.**
|
||||
|
||||
## Available color contexts
|
||||
|
||||
Color contexts rely on the type `Color` defined in `/lib/types/color.type.ts`:
|
||||
|
||||
- `info` (_purple_)
|
||||
- `success` (_green_)
|
||||
- `warning` (_orange_)
|
||||
- `error` (_red_)
|
||||
|
||||
## Usage
|
||||
|
||||
To get and set the color context in a component, you can pass the `ColorContext` to `useContext` and apply the `colorContextClass` to the root component:
|
||||
|
||||
```ts
|
||||
// ParentComponent.vue
|
||||
|
||||
import { useContext } from '@core/composables/context.composable'
|
||||
import { ColorContext } from '@core/context'
|
||||
import type { Color } from '@core/types/color.type'
|
||||
import { defineProps } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
color?: Color
|
||||
}>()
|
||||
|
||||
const { colorContextClass } = useContext(ColorContext, () => props.color)
|
||||
```
|
||||
|
||||
All the components using the CSS variables will inherit the color context applied by the `colorContextClass`.
|
||||
It's possible to change the color of a component on demand, if the component has a `color` prop, and passing it as the second parameter of the composable.
|
||||
|
||||
Then, the only thing to do is to apply the class in the component's `template`:
|
||||
|
||||
```vue
|
||||
<!-- ParentComponent.vue -->
|
||||
|
||||
<template>
|
||||
<div :class="colorContextClass">
|
||||
<!-- Will use the color context defined by the class above-->
|
||||
<MyComponent />
|
||||
|
||||
<!-- Will use the color "info" instead of the context-->
|
||||
<MyComponent color="info" />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
`MyComponent` using the context:
|
||||
|
||||
```vue
|
||||
<!-- MyComponent.vue -->
|
||||
|
||||
<template>
|
||||
<div :class="colorContextClass" class="my-component">
|
||||
<p>Lorem ipsum dolor sit amet.</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useContext } from '@core/composables/context.composable'
|
||||
import { ColorContext } from '@core/context'
|
||||
import type { Color } from '@core/types/color.type'
|
||||
import { defineProps } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
color?: Color
|
||||
}>()
|
||||
|
||||
const { colorContextClass } = useContext(ColorContext, () => props.color)
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.my-component {
|
||||
background-color: var(--color-context-secondary);
|
||||
color: var(--color-context-primary);
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
In the example above, if the `color` prop is not set, the component will use the color context (i.e., if its parent uses a `success` color context, `MyComponent` will be styled with the `success` colors defined in `_context.pcss`).
|
||||
|
||||
If the `color` prop is set, the component will use the prop value to update the context for itself and its descendants.
|
||||
@@ -45,3 +45,55 @@
|
||||
.context-border-color-info {
|
||||
border-color: var(--color-purple-base);
|
||||
}
|
||||
|
||||
.color-context-info {
|
||||
--color-context-primary: var(--color-purple-base);
|
||||
--color-context-primary-hover: var(--color-purple-d20);
|
||||
--color-context-primary-active: var(--color-purple-d40);
|
||||
--color-context-primary-disabled: var(--color-grey-400);
|
||||
|
||||
--color-context-secondary: var(--background-color-purple-10);
|
||||
--color-context-secondary-hover: var(--background-color-purple-20);
|
||||
--color-context-secondary-active: var(--background-color-purple-30);
|
||||
|
||||
--color-context-tertiary: var(--background-color-primary);
|
||||
}
|
||||
|
||||
.color-context-success {
|
||||
--color-context-primary: var(--color-green-base);
|
||||
--color-context-primary-hover: var(--color-green-d20);
|
||||
--color-context-primary-active: var(--color-green-d40);
|
||||
--color-context-primary-disabled: var(--color-green-l60);
|
||||
|
||||
--color-context-secondary: var(--background-color-green-10);
|
||||
--color-context-secondary-hover: var(--background-color-green-20);
|
||||
--color-context-secondary-active: var(--background-color-green-30);
|
||||
|
||||
--color-context-tertiary: var(--background-color-primary);
|
||||
}
|
||||
|
||||
.color-context-warning {
|
||||
--color-context-primary: var(--color-orange-base);
|
||||
--color-context-primary-hover: var(--color-orange-d20);
|
||||
--color-context-primary-active: var(--color-orange-d40);
|
||||
--color-context-primary-disabled: var(--color-orange-l60);
|
||||
|
||||
--color-context-secondary: var(--background-color-orange-10);
|
||||
--color-context-secondary-hover: var(--background-color-orange-20);
|
||||
--color-context-secondary-active: var(--background-color-orange-30);
|
||||
|
||||
--color-context-tertiary: var(--background-color-primary);
|
||||
}
|
||||
|
||||
.color-context-error {
|
||||
--color-context-primary: var(--color-red-base);
|
||||
--color-context-primary-hover: var(--color-red-d20);
|
||||
--color-context-primary-active: var(--color-red-d40);
|
||||
--color-context-primary-disabled: var(--color-red-l60);
|
||||
|
||||
--color-context-secondary: var(--background-color-red-10);
|
||||
--color-context-secondary-hover: var(--background-color-red-20);
|
||||
--color-context-secondary-active: var(--background-color-red-30);
|
||||
|
||||
--color-context-tertiary: var(--background-color-primary);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { ComputedRef, InjectionKey, MaybeRefOrGetter } from 'vue'
|
||||
import { computed, inject, provide, toValue } from 'vue'
|
||||
|
||||
export const createContext = <T, Output = ComputedRef<T>>(
|
||||
initialValue: MaybeRefOrGetter<T>,
|
||||
customBuilder?: (value: ComputedRef<T>, previousValue: ComputedRef<T>) => Output
|
||||
) => {
|
||||
return {
|
||||
id: Symbol('CONTEXT_ID') as InjectionKey<MaybeRefOrGetter<T>>,
|
||||
initialValue,
|
||||
builder: customBuilder ?? (value => value as Output),
|
||||
}
|
||||
}
|
||||
|
||||
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 useContext = <Ctx extends Context, T extends ContextValue<Ctx>>(
|
||||
context: Ctx,
|
||||
newValue?: MaybeRefOrGetter<T | undefined>
|
||||
): ContextOutput<Ctx> => {
|
||||
const currentValue = inject(context.id, undefined)
|
||||
|
||||
const updatedValue = () => toValue(newValue) ?? toValue(currentValue) ?? context.initialValue
|
||||
provide(context.id, updatedValue)
|
||||
|
||||
return context.builder(
|
||||
computed(() => toValue(updatedValue)),
|
||||
computed(() => toValue(currentValue))
|
||||
)
|
||||
}
|
||||
8
@xen-orchestra/web-core/lib/context.ts
Normal file
8
@xen-orchestra/web-core/lib/context.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { createContext } from '@core/composables/context.composable'
|
||||
import type { Color } from '@core/types/color.type'
|
||||
import { computed } from 'vue'
|
||||
|
||||
export const ColorContext = createContext('info' as Color, (color, previousColor) => ({
|
||||
name: color,
|
||||
colorContextClass: computed(() => (previousColor.value === color.value ? undefined : `color-context-${color.value}`)),
|
||||
}))
|
||||
1
@xen-orchestra/web-core/lib/types/color.type.ts
Normal file
1
@xen-orchestra/web-core/lib/types/color.type.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type Color = 'info' | 'error' | 'warning' | 'success'
|
||||
27
packages/xo-server/src/api/pusb.mjs
Normal file
27
packages/xo-server/src/api/pusb.mjs
Normal file
@@ -0,0 +1,27 @@
|
||||
export async function scan({ host }) {
|
||||
await this.getXapi(host).call('PUSB.scan', host._xapiRef)
|
||||
}
|
||||
|
||||
scan.params = {
|
||||
host: { type: 'string' },
|
||||
}
|
||||
scan.resolve = {
|
||||
host: ['host', 'host', 'operate'],
|
||||
}
|
||||
|
||||
export async function set({ pusb, enabled }) {
|
||||
const xapi = this.getXapi(pusb)
|
||||
|
||||
if (enabled !== undefined && enabled !== pusb.passthroughEnabled) {
|
||||
await xapi.call('PUSB.set_passthrough_enabled', pusb._xapiRef, enabled)
|
||||
}
|
||||
}
|
||||
|
||||
set.params = {
|
||||
id: { type: 'string' },
|
||||
enabled: { type: 'boolean', optional: true },
|
||||
}
|
||||
|
||||
set.resolve = {
|
||||
pusb: ['id', 'PUSB', 'administrate'],
|
||||
}
|
||||
@@ -889,6 +889,17 @@ const TRANSFORMS = {
|
||||
vm: link(obj, 'VM'),
|
||||
}
|
||||
},
|
||||
|
||||
pusb(obj) {
|
||||
return {
|
||||
type: 'PUSB',
|
||||
|
||||
description: obj.description,
|
||||
host: link(obj, 'host'),
|
||||
passthroughEnabled: obj.passthrough_enabled,
|
||||
usbGroup: link(obj, 'USB_group'),
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
|
||||
@@ -54,13 +54,9 @@ export default class IsoDevice extends Component {
|
||||
() => this.props.vm.$pool,
|
||||
() => this.props.vm.$container,
|
||||
(vmPool, vmContainer) => sr => {
|
||||
const vmRunning = vmContainer !== vmPool
|
||||
const sameHost = vmContainer === sr.$container
|
||||
const samePool = vmPool === sr.$pool
|
||||
|
||||
return (
|
||||
samePool &&
|
||||
(vmRunning ? sr.shared || sameHost : true) &&
|
||||
vmPool === sr.$pool &&
|
||||
(sr.shared || vmContainer === sr.$container) &&
|
||||
(sr.SR_type === 'iso' || (sr.SR_type === 'udev' && sr.size))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import _ from 'intl'
|
||||
import Copiable from 'copiable'
|
||||
import decorate from 'apply-decorators'
|
||||
import Icon from 'icon'
|
||||
import map from 'lodash/map'
|
||||
import React from 'react'
|
||||
import store from 'store'
|
||||
import HomeTags from 'home-tags'
|
||||
@@ -24,10 +23,21 @@ export default decorate([
|
||||
provideState({
|
||||
computed: {
|
||||
areHostsVersionsEqual: ({ areHostsVersionsEqualByPool }, { host }) => areHostsVersionsEqualByPool[host.$pool],
|
||||
inMemoryVms: (_, { vms }) => {
|
||||
const result = []
|
||||
for (const key of Object.keys(vms)) {
|
||||
const vm = vms[key]
|
||||
const { power_state } = vm
|
||||
if (power_state === 'Running' || power_state === 'Paused') {
|
||||
result.push(vm)
|
||||
}
|
||||
}
|
||||
return result
|
||||
},
|
||||
},
|
||||
}),
|
||||
injectState,
|
||||
({ statsOverview, host, nVms, vmController, vms, state: { areHostsVersionsEqual } }) => {
|
||||
({ statsOverview, host, nVms, vmController, state: { areHostsVersionsEqual, inMemoryVms } }) => {
|
||||
const pool = getObject(store.getState(), host.$pool)
|
||||
const vmsFilter = encodeURIComponent(new CM.Property('$container', new CM.String(host.id)).toString())
|
||||
return (
|
||||
@@ -120,7 +130,7 @@ export default decorate([
|
||||
tooltip={`${host.productBrand} (${formatSize(vmController.memory.size)})`}
|
||||
value={vmController.memory.size}
|
||||
/>
|
||||
{map(vms, vm => (
|
||||
{inMemoryVms.map(vm => (
|
||||
<UsageElement
|
||||
tooltip={`${vm.name_label} (${formatSize(vm.memory.size)})`}
|
||||
key={vm.id}
|
||||
|
||||
Reference in New Issue
Block a user