Compare commits
2 Commits
api-utils
...
feat_scope
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8c3ad9294 | ||
|
|
0d4cf48410 |
@@ -11,6 +11,7 @@
|
||||
|
||||
- [VDI/Export] Expose NBD settings in the XO and REST APIs api (PR [#7251](https://github.com/vatesfr/xen-orchestra/pull/7251))
|
||||
- [Menu/Proxies] Added a warning icon if unable to check proxies upgrade (PR [#7237](https://github.com/vatesfr/xen-orchestra/pull/7237))
|
||||
- [Tags] Implement scoped tags (PR [#7258](https://github.com/vatesfr/xen-orchestra/pull/7258))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
@@ -35,6 +36,7 @@
|
||||
<!--packages-start-->
|
||||
|
||||
- vhd-lib patch
|
||||
- xo-cli minor
|
||||
- xo-server minor
|
||||
- xo-web minor
|
||||
|
||||
|
||||
@@ -157,32 +157,33 @@ function extractFlags(args) {
|
||||
|
||||
const noop = Function.prototype
|
||||
|
||||
function parseValue(value) {
|
||||
if (value.startsWith('json:')) {
|
||||
return JSON.parse(value.slice(5))
|
||||
}
|
||||
if (value === 'true') {
|
||||
return true
|
||||
}
|
||||
if (value === 'false') {
|
||||
return false
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
const PARAM_RE = /^([^=]+)=([^]*)$/
|
||||
function parseParameters(args) {
|
||||
if (args[0] === '--') {
|
||||
return args.slice(1).map(parseValue)
|
||||
}
|
||||
|
||||
const params = {}
|
||||
forEach(args, function (arg) {
|
||||
let matches
|
||||
if (!(matches = arg.match(PARAM_RE))) {
|
||||
throw new Error('invalid arg: ' + arg)
|
||||
}
|
||||
params[matches[1]] = parseValue(matches[2])
|
||||
const name = matches[1]
|
||||
let value = matches[2]
|
||||
|
||||
if (value.startsWith('json:')) {
|
||||
value = JSON.parse(value.slice(5))
|
||||
}
|
||||
|
||||
if (name === '@') {
|
||||
params['@'] = value
|
||||
return
|
||||
}
|
||||
|
||||
if (value === 'true') {
|
||||
value = true
|
||||
} else if (value === 'false') {
|
||||
value = false
|
||||
}
|
||||
|
||||
params[name] = value
|
||||
})
|
||||
|
||||
return params
|
||||
|
||||
@@ -134,6 +134,12 @@ export async function rest(args) {
|
||||
|
||||
const { allowUnauthorized, server, token } = await config.load()
|
||||
|
||||
if (server === undefined) {
|
||||
const errorMessage =
|
||||
'Please use `xo-cli --register` to associate with an XO instance first.\n\nSee `xo-cli --help` for more info.'
|
||||
throw errorMessage
|
||||
}
|
||||
|
||||
const baseUrl = server
|
||||
const baseOpts = {
|
||||
headers: {
|
||||
|
||||
@@ -251,19 +251,9 @@ export default class Api {
|
||||
|
||||
constructor(app) {
|
||||
this._logger = null
|
||||
this._methods = { __proto__: null }
|
||||
this._app = app
|
||||
|
||||
const defer =
|
||||
const seq = async methods => {
|
||||
for (const method of methods) {
|
||||
await this.#callApiMethod(method[0], method[1])
|
||||
}
|
||||
}
|
||||
seq.validate = ajv.compile({ type: 'array', minLength: 1, items: { type: ['array', 'string'] } })
|
||||
const if =
|
||||
|
||||
this._methods = { __proto__: null, seq }
|
||||
|
||||
this.addApiMethods(methods)
|
||||
app.hooks.on('start', async () => {
|
||||
this._logger = await app.getLogger('api')
|
||||
@@ -377,7 +367,8 @@ export default class Api {
|
||||
}
|
||||
|
||||
async callApiMethod(connection, name, params = {}) {
|
||||
if (!Object.hasOwn(this._methods, name)) {
|
||||
const method = this._methods[name]
|
||||
if (!method) {
|
||||
throw new MethodNotFound(name)
|
||||
}
|
||||
|
||||
@@ -392,12 +383,11 @@ export default class Api {
|
||||
apiContext.permission = 'none'
|
||||
}
|
||||
|
||||
return this.#apiContext.run(apiContext, () => this.#callApiMethod(name, params))
|
||||
return this.#apiContext.run(apiContext, () => this.#callApiMethod(name, method, params))
|
||||
}
|
||||
|
||||
async #callApiMethod(name, params) {
|
||||
async #callApiMethod(name, method, params) {
|
||||
const app = this._app
|
||||
const method = this._methods[name]
|
||||
const startTime = Date.now()
|
||||
|
||||
const { connection, user } = this.apiContext
|
||||
|
||||
@@ -17,18 +17,23 @@ const INPUT_STYLE = {
|
||||
maxWidth: '8em',
|
||||
}
|
||||
const TAG_STYLE = {
|
||||
backgroundColor: '#2598d9',
|
||||
borderRadius: '0.5em',
|
||||
border: '0.2em solid #2598d9',
|
||||
backgroundColor: '#2598d9',
|
||||
color: 'white',
|
||||
fontSize: '0.6em',
|
||||
margin: '0.2em',
|
||||
marginTop: '-0.1em',
|
||||
padding: '0.3em',
|
||||
verticalAlign: 'middle',
|
||||
}
|
||||
const LINK_STYLE = {
|
||||
cursor: 'pointer',
|
||||
}
|
||||
const TAG_STYLE_ONCLICK = {
|
||||
...TAG_STYLE,
|
||||
...LINK_STYLE,
|
||||
}
|
||||
|
||||
const ADD_TAG_STYLE = {
|
||||
cursor: 'pointer',
|
||||
fontSize: '0.8em',
|
||||
@@ -38,6 +43,23 @@ const REMOVE_TAG_STYLE = {
|
||||
cursor: 'pointer',
|
||||
}
|
||||
|
||||
const TAG_SCOPE_STYLE = {
|
||||
padding: '0.1em',
|
||||
|
||||
borderRadius: '0.2em',
|
||||
}
|
||||
const TAG_SCOPE_LINK_STYLE = {
|
||||
...TAG_SCOPE_STYLE,
|
||||
...LINK_STYLE,
|
||||
}
|
||||
const TAG_SCOPE_VALUE_STYLE = {
|
||||
padding: '0 0.2em',
|
||||
color: '#2598d9',
|
||||
backgroundColor: 'white',
|
||||
borderTopRightRadius: '0.3em', // tag border radius - tag padding
|
||||
borderBottomRightRadius: '0.3em', // tag border radius - tag padding
|
||||
}
|
||||
|
||||
class SelectExistingTag extends Component {
|
||||
state = { tags: [] }
|
||||
|
||||
@@ -156,20 +178,51 @@ export default class Tags extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export const Tag = ({ type, label, onDelete, onClick }) => (
|
||||
<span style={TAG_STYLE}>
|
||||
<span onClick={onClick && (() => onClick(label))} style={onClick && LINK_STYLE}>
|
||||
{label}
|
||||
</span>{' '}
|
||||
{onDelete ? (
|
||||
<span onClick={onDelete && (() => onDelete(label))} style={REMOVE_TAG_STYLE}>
|
||||
<Icon icon='remove-tag' />
|
||||
const ScopedTag = ({ type, label, scope, value, onDelete, onClick }) => {
|
||||
return (
|
||||
<span style={onClick ? TAG_STYLE_ONCLICK : TAG_STYLE}>
|
||||
<span style={onClick ? TAG_SCOPE_LINK_STYLE : TAG_SCOPE_STYLE} onClick={onClick && (() => onClick(label))}>
|
||||
{scope}
|
||||
</span>
|
||||
) : (
|
||||
[]
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
<span style={TAG_SCOPE_VALUE_STYLE}>
|
||||
<span onClick={onClick && (() => onClick(label))} style={onClick && LINK_STYLE}>
|
||||
{value}
|
||||
</span>{' '}
|
||||
{onDelete && (
|
||||
<span onClick={onDelete && (() => onDelete(label))} style={REMOVE_TAG_STYLE}>
|
||||
<Icon icon='remove-tag' />
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
ScopedTag.propTypes = {
|
||||
scope: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
export const Tag = ({ type, label, onDelete, onClick }) => {
|
||||
const [scope, ...values] = label.split('=')
|
||||
if (scope && values?.length > 0) {
|
||||
return <ScopedTag {...{ type, label, scope, value: values.join('='), onDelete, onClick }} />
|
||||
}
|
||||
return (
|
||||
<span style={TAG_STYLE}>
|
||||
<span onClick={onClick && (() => onClick(label))} style={onClick && LINK_STYLE}>
|
||||
{label}
|
||||
</span>{' '}
|
||||
{onDelete ? (
|
||||
<span onClick={onDelete && (() => onDelete(label))} style={REMOVE_TAG_STYLE}>
|
||||
<Icon icon='remove-tag' />
|
||||
</span>
|
||||
) : (
|
||||
[]
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
Tag.propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user