feat(xo5/hub): open Hub to all plans and gate recipes individually (#10117)

This commit is contained in:
MlssFrncJrg
2026-07-24 17:30:04 +02:00
committed by GitHub
parent 235ff3c455
commit 83b43a44f6
11 changed files with 1197 additions and 42 deletions

View File

@@ -24,6 +24,9 @@
- [Pool] XO now reconnects to a surviving pool member when the master becomes unreachable (e.g. HA promoted a new master after the old one died), instead of staying stuck on the dead master, including after an XO restart (PR [#10016](https://github.com/vatesfr/xen-orchestra/pull/10016))
- [XO6/VIF] Add possibility to create VIF from network tab in VM page (PR [#9677](https://github.com/vatesfr/xen-orchestra/pull/9677))
- [vm stats] Reduce the memory consumption of the rrd stats (PR [#10039](https://github.com/vatesfr/xen-orchestra/pull/10039))
- [XO6/Site] Add "New VM" and "Connect pool" actions to the site infrastructure tree (PR [#10047](https://github.com/vatesfr/xen-orchestra/pull/10047))
- [XO5/Hub] Recipes are now available to all plans, not just Premium (PR [#10117](https://github.com/vatesfr/xen-orchestra/pull/10117))
- [Hub] Kubernetes cluster recipe no longer requires the XOA plugin to work (PR [#10127](https://github.com/vatesfr/xen-orchestra/pull/10127))
- [XO6/Host] Add possibilty to enable or disable an host (PR [#10074](https://github.com/vatesfr/xen-orchestra/pull/10074))
- [XO6/Site] Add "Connect pool" action to the site infrastructure tree (PR [#10047](https://github.com/vatesfr/xen-orchestra/pull/10047))
- [VM]: Add possibility to duplicate a VM (PR [#9580](https://github.com/vatesfr/xen-orchestra/pull/9580))
@@ -97,5 +100,6 @@
- xo-server minor
- xo-server-openmetrics minor
- xo-server-perf-alert patch
- xo-web minor
<!--packages-end-->

View File

@@ -151,7 +151,8 @@
"xo-collection": "^0.6.1",
"xo-common": "^0.10.0",
"xo-remote-parser": "^0.10.1",
"xo-vmdk-to-vhd": "^2.5.10"
"xo-vmdk-to-vhd": "^2.5.10",
"yaml": "^2.9.0"
},
"devDependencies": {
"@babel/cli": "^7.29.0",

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,62 @@
import { createCluster } from '../_hubKubernetesCluster.mjs'
createCluster.description = 'Creates a Kubernetes cluster'
createCluster.params = {
clusterName: { type: 'string' },
controlPlaneIpAddress: { type: 'string', optional: true },
controlPlaneIpAddresses: {
type: 'array',
items: { type: 'string' },
optional: true,
uniqueItems: true,
},
gatewayIpAddress: { type: 'string', optional: true },
controlPlanePoolSize: {
type: 'integer',
default: 1,
enum: [1, 3, 5, 7],
},
k8sVersion: {
type: 'string',
pattern: '[0-9a-zA-Z.]+',
optional: true,
},
nameservers: {
type: 'array',
items: { type: 'string' },
optional: true,
},
nbNodes: { type: 'integer' },
network: { type: 'string' },
searches: {
type: 'array',
items: { type: 'string' },
optional: true,
},
sr: { type: 'string' },
sshKey: { type: 'string' },
vipAddress: { type: 'string', optional: true },
workerNodeIpAddresses: {
type: 'array',
items: { type: 'string' },
optional: true,
},
xoUrl: { type: 'string' },
useInsecureXoConnection: { type: 'boolean', default: false },
customClusterPodCIDR: {
type: 'string',
optional: true,
default: '10.1.0.0/16',
},
customClusterServiceCIDR: {
type: 'string',
optional: true,
default: '10.152.183.0/24',
},
}
createCluster.resolve = { sr: ['sr', 'SR', 'administrate'] }
createCluster.permission = 'admin'
export const recipe = {
createKubernetesCluster: createCluster,
}

View File

@@ -1847,7 +1847,7 @@ export const editVmNotes = async vm => {
await _call('vm.set', { id: resolveId(vm), notes: notes || null })
}
export const createKubernetesCluster = params => _call('xoa.recipe.createKubernetesCluster', params)
export const createKubernetesCluster = params => _call('hub.recipe.createKubernetesCluster', params)
export const createEasyVirtVm = params => _call('xoa.recipe.createEasyVirtVm', params)

View File

@@ -3,7 +3,7 @@ import Icon from 'icon'
import React from 'react'
import { Container, Col, Row } from 'grid'
import { getXoaPlan, routes, TryXoa } from 'utils'
import { routes } from 'utils'
import { NavLink, NavTabs } from 'nav'
import Page from '../page'
@@ -39,16 +39,7 @@ const Hub = routes('hub', {
recipes: Recipes,
})(({ children }) => (
<Page header={Header} title='hubPage' formatTitle>
{getXoaPlan() === 'Community' ? (
<Container>
<h2 className='text-info'>{_('hubCommunity')}</h2>
<p>
<TryXoa page='hub' />
</p>
</Container>
) : (
children
)}
{children}
</Page>
))

View File

@@ -3,8 +3,7 @@ import React from 'react'
import { adminOnly } from 'utils'
import { Container, Col, Row } from 'grid'
import RecipeKub from './recipe-kub'
import RecipeEasyVirt from './recipe-ev'
import RECIPES from './recipes'
// ==================================================================
@@ -13,12 +12,11 @@ export default decorate([
() => (
<Container>
<Row>
<Col mediumSize={4}>
<RecipeKub />
</Col>
<Col mediumSize={4}>
<RecipeEasyVirt />
</Col>
{RECIPES.filter(({ isAvailable }) => isAvailable()).map(({ id, component: Recipe }) => (
<Col key={id} mediumSize={4}>
<Recipe />
</Col>
))}
</Row>
</Container>
),

View File

@@ -70,11 +70,16 @@ export default decorate([
},
onChangeFaultTolerance(__, faultTolerance) {
const { onChange, value } = this.props
const willUseHa = faultTolerance.value > 0
onChange({
...value,
faultTolerance: faultTolerance.value,
// n * 2 + 1 is the formula to meet the quorum of RAFT consensus algorithm
controlPlanePoolSize: faultTolerance.value * 2 + 1,
// clear the fields of the mode we're leaving so both aren't sent to the API at submit time
controlPlaneIpAddress: willUseHa ? undefined : value.controlPlaneIpAddress,
controlPlaneIpAddresses: willUseHa ? value.controlPlaneIpAddresses : undefined,
vipAddress: willUseHa ? value.vipAddress : undefined,
})
},
onChangeK8sVersion(__, k8sVersion) {

View File

@@ -0,0 +1,18 @@
import { getXoaPlan } from 'utils'
import RecipeEasyVirt from './recipe-ev'
import RecipeKub from './recipe-kub'
const RECIPES = [
{
id: 'kubernetes',
component: RecipeKub,
isAvailable: () => true,
},
{
id: 'easyvirt',
component: RecipeEasyVirt,
isAvailable: () => getXoaPlan() !== 'Community',
},
]
export default RECIPES

View File

@@ -2,7 +2,7 @@ import _ from 'intl'
import decorate from 'apply-decorators'
import Icon from 'icon'
import React from 'react'
import { addSubscriptions, adminOnly } from 'utils'
import { addSubscriptions, adminOnly, getXoaPlan, TryXoa } from 'utils'
import { Container, Col, Row } from 'grid'
import { injectState, provideState } from 'reaclette'
import { isEmpty, map, omit, orderBy } from 'lodash'
@@ -31,24 +31,32 @@ export default decorate([
},
}),
injectState,
({ state: { resources } }) => (
<Container>
<Row>
{isEmpty(resources) ? (
<Col>
<h2 className='text-muted'>
&nbsp; {_('vmNoAvailable')}
<Icon icon='alarm' color='yellow' />
</h2>
</Col>
) : (
resources.map(data => (
<Col key={data.namespace} mediumSize={6} largeSize={4}>
<Resource {...data} />
({ state: { resources } }) =>
getXoaPlan() === 'Community' ? (
<Container>
<h2 className='text-info'>{_('hubCommunity')}</h2>
<p>
<TryXoa page='hub' />
</p>
</Container>
) : (
<Container>
<Row>
{isEmpty(resources) ? (
<Col>
<h2 className='text-muted'>
&nbsp; {_('vmNoAvailable')}
<Icon icon='alarm' color='yellow' />
</h2>
</Col>
))
)}
</Row>
</Container>
),
) : (
resources.map(data => (
<Col key={data.namespace} mediumSize={6} largeSize={4}>
<Resource {...data} />
</Col>
))
)}
</Row>
</Container>
),
])

View File

@@ -20274,7 +20274,7 @@ yaml-types@^0.3.0:
resolved "https://registry.yarnpkg.com/yaml-types/-/yaml-types-0.3.0.tgz#b077f34496e12dc709d97f2c3bad3fff11089680"
integrity sha512-i9RxAO/LZBiE0NJUy9pbN5jFz5EasYDImzRkj8Y81kkInTi1laia3P3K/wlMKzOxFQutZip8TejvQP/DwgbU7A==
yaml@^2.0.0, yaml@^2.4.1, yaml@^2.6.1, yaml@^2.7.0, yaml@^2.8.0, yaml@^2.8.2:
yaml@^2.0.0, yaml@^2.4.1, yaml@^2.6.1, yaml@^2.7.0, yaml@^2.8.0, yaml@^2.8.2, yaml@^2.9.0:
version "2.9.0"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.9.0.tgz#78274afd93598a1dfdd6130df6a566defcbf9aa4"
integrity sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==