feat(xo-web/VM): ability to add custom notes (#7322)

Fixes #5792
This commit is contained in:
Pierre Donias 2024-01-26 14:59:32 +01:00 committed by GitHub
parent d6abdb246b
commit c250cd9b89
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 110 additions and 3 deletions

View File

@ -19,6 +19,7 @@
- [Plugin/load-balancer] Limit concurrent VM migrations to 2 (configurable) to avoid long paused VMs [#7084](https://github.com/vatesfr/xen-orchestra/issues/7084) (PR [#7297](https://github.com/vatesfr/xen-orchestra/pull/7297))
- [Tags] Admin can create colored tags (PR [#7262](https://github.com/vatesfr/xen-orchestra/pull/7262))
- [Tags] Add tooltips on `xo:no-bak` and `xo:notify-on-snapshot` tags (PR [#7335](https://github.com/vatesfr/xen-orchestra/pull/7335))
- [VM] Custom notes [#5792](https://github.com/vatesfr/xen-orchestra/issues/5792) (PR [#7322](https://github.com/vatesfr/xen-orchestra/pull/7322))
### Bug fixes

View File

@ -694,6 +694,8 @@ set.params = {
name_description: { type: 'string', minLength: 0, optional: true },
notes: { type: ['string', 'null'], maxLength: 2048, optional: true },
high_availability: {
optional: true,
enum: getHaValues(),

View File

@ -401,6 +401,7 @@ const TRANSFORMS = {
installTime: metrics && toTimestamp(metrics.install_time),
name_description: obj.name_description,
name_label: obj.name_label,
notes: otherConfig['xo:notes'],
other: otherConfig,
os_version: (guestMetrics && guestMetrics.os_version) || null,
parent: link(obj, 'parent'),

View File

@ -382,6 +382,11 @@ const methods = {
nameLabel: true,
notes: {
get: vm => vm.other_config['xo:notes'],
set: (value, vm) => vm.update_other_config('xo:notes', value),
},
PV_args: true,
tags: true,

View File

@ -123,6 +123,7 @@
"relative-luminance": "^2.0.1",
"reselect": "^2.5.4",
"rimraf": "^5.0.1",
"sanitize-html": "^2.11.0",
"sass": "^1.38.1",
"semver": "^6.0.0",
"strip-ansi": "^5.2.0",

View File

@ -1197,6 +1197,9 @@ const messages = {
'Enabling this will allow the VM to automatically install Citrix PV drivers from Windows Update. This only includes drivers, the Citrix management agent must still be separately installed.',
windowsToolsModalWarning:
'If you have previously installed XCP-ng tools instead of Citrix tools, this option will break your VM.',
editVmNotes: 'Edit VM notes',
supportsMarkdown: 'Supports Markdown syntax',
vmNotesTooLong: 'VM notes cannot be longer than 2048 characters',
// ----- VM stat tab -----
statsCpu: 'CPU usage',

View File

@ -0,0 +1,36 @@
import _ from 'intl'
import Icon from 'icon'
import clamp from 'lodash/clamp'
import Component from 'base-component'
import React from 'react'
export default class EditVmNotesModalBody extends Component {
get value() {
return { notes: this.state.notes ?? this.props.vm.notes ?? '' }
}
render() {
return (
<div>
<textarea
autoFocus
rows={clamp(this.value.notes.split('\n').length, 5, 20)}
onChange={this.linkState('notes')}
value={this.value.notes}
className='form-control'
/>
{this.value.notes.length > 2048 && (
<em className='text-warning'>
<Icon icon='alarm' /> {_('vmNotesTooLong')}
</em>
)}
<em>
<Icon icon='info' />{' '}
<a href='https://commonmark.org/help/' target='_blank' rel='noreferrer'>
{_('supportsMarkdown')}
</a>
</em>
</div>
)
}
}

View File

@ -1524,6 +1524,18 @@ export const changeVirtualizationMode = vm =>
})
)
import EditVmNotesModalBody from './edit-vm-notes-modal' // eslint-disable-line import/first
export const editVmNotes = async vm => {
const { notes } = await confirm({
icon: 'edit',
title: _('editVmNotes'),
body: <EditVmNotesModalBody vm={vm} />,
})
// Remove notes if `''` is passed
await _call('vm.set', { id: resolveId(vm), notes: notes || null })
}
export const createKubernetesCluster = params => _call('xoa.recipe.createKubernetesCluster', params)
export const deleteTemplates = templates =>

View File

@ -1,15 +1,18 @@
import _ from 'intl'
import ActionButton from 'action-button'
import Copiable from 'copiable'
import decorate from 'apply-decorators'
import defined, { get } from '@xen-orchestra/defined'
import Icon from 'icon'
import isEmpty from 'lodash/isEmpty'
import map from 'lodash/map'
import marked from 'marked'
import React from 'react'
import HomeTags from 'home-tags'
import renderXoItem, { VmTemplate } from 'render-xo-item'
import sanitizeHtml from 'sanitize-html'
import Tooltip from 'tooltip'
import { addTag, editVm, removeTag, subscribeUsers } from 'xo'
import { addTag, editVm, editVmNotes, removeTag, subscribeUsers } from 'xo'
import { BlockLink } from 'link'
import { FormattedRelative, FormattedDate } from 'react-intl'
import { Container, Row, Col } from 'grid'
@ -31,6 +34,18 @@ const CREATED_VM_STYLES = {
whiteSpace: 'pre-line',
}
const NOTES_STYLE = {
maxWidth: '70%',
margin: 'auto',
border: 'dashed 1px #999',
padding: '1em',
borderRadius: '10px',
}
const SANITIZE_OPTIONS = {
allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img']),
}
const GuestToolsDetection = ({ vm }) => {
if (vm.power_state !== 'Running' || vm.pvDriversDetected === undefined) {
return null
@ -276,6 +291,20 @@ const GeneralTab = decorate([
</Col>
</Row>
)}
<Row className='mt-1'>
<div style={NOTES_STYLE}>
{vm.notes !== undefined && (
<p
dangerouslySetInnerHTML={{
__html: sanitizeHtml(marked(vm.notes), SANITIZE_OPTIONS),
}}
/>
)}
<ActionButton icon='edit' handler={editVmNotes} handlerParam={vm}>
{_('editVmNotes')}
</ActionButton>
</div>
</Row>
</Container>
)
},

View File

@ -11876,7 +11876,7 @@ htmlparser2@^6.1.0:
domutils "^2.5.2"
entities "^2.0.0"
htmlparser2@^8.0.1:
htmlparser2@^8.0.0, htmlparser2@^8.0.1:
version "8.0.2"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21"
integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==
@ -16395,6 +16395,11 @@ parse-passwd@^1.0.0:
resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
integrity sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==
parse-srcset@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/parse-srcset/-/parse-srcset-1.0.2.tgz#f2bd221f6cc970a938d88556abc589caaaa2bde1"
integrity sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==
parse5-htmlparser2-tree-adapter@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz#23c2cc233bcf09bb7beba8b8a69d46b08c62c2f1"
@ -17179,7 +17184,7 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2
picocolors "^0.2.1"
source-map "^0.6.1"
postcss@^8.4.14, postcss@^8.4.32, postcss@^8.4.33:
postcss@^8.3.11, postcss@^8.4.14, postcss@^8.4.32, postcss@^8.4.33:
version "8.4.33"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.33.tgz#1378e859c9f69bf6f638b990a0212f43e2aaa742"
integrity sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==
@ -18951,6 +18956,18 @@ safe-regex@^1.1.0:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sanitize-html@^2.11.0:
version "2.11.0"
resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.11.0.tgz#9a6434ee8fcaeddc740d8ae7cd5dd71d3981f8f6"
integrity sha512-BG68EDHRaGKqlsNjJ2xUB7gpInPA8gVx/mvjO743hZaeMCZ2DwzW7xvsqZ+KNU4QKwj86HJ3uu2liISf2qBBUA==
dependencies:
deepmerge "^4.2.2"
escape-string-regexp "^4.0.0"
htmlparser2 "^8.0.0"
is-plain-object "^5.0.0"
parse-srcset "^1.0.2"
postcss "^8.3.11"
sasl-anonymous@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/sasl-anonymous/-/sasl-anonymous-0.1.0.tgz#f544c7e824df2a40d9ad4733829572cc8d9ed5a5"