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)) - [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] 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)) - [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 ### Bug fixes

View File

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

View File

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

View File

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

View File

@ -123,6 +123,7 @@
"relative-luminance": "^2.0.1", "relative-luminance": "^2.0.1",
"reselect": "^2.5.4", "reselect": "^2.5.4",
"rimraf": "^5.0.1", "rimraf": "^5.0.1",
"sanitize-html": "^2.11.0",
"sass": "^1.38.1", "sass": "^1.38.1",
"semver": "^6.0.0", "semver": "^6.0.0",
"strip-ansi": "^5.2.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.', '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: windowsToolsModalWarning:
'If you have previously installed XCP-ng tools instead of Citrix tools, this option will break your VM.', '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 ----- // ----- VM stat tab -----
statsCpu: 'CPU usage', 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 createKubernetesCluster = params => _call('xoa.recipe.createKubernetesCluster', params)
export const deleteTemplates = templates => export const deleteTemplates = templates =>

View File

@ -1,15 +1,18 @@
import _ from 'intl' import _ from 'intl'
import ActionButton from 'action-button'
import Copiable from 'copiable' import Copiable from 'copiable'
import decorate from 'apply-decorators' import decorate from 'apply-decorators'
import defined, { get } from '@xen-orchestra/defined' import defined, { get } from '@xen-orchestra/defined'
import Icon from 'icon' import Icon from 'icon'
import isEmpty from 'lodash/isEmpty' import isEmpty from 'lodash/isEmpty'
import map from 'lodash/map' import map from 'lodash/map'
import marked from 'marked'
import React from 'react' import React from 'react'
import HomeTags from 'home-tags' import HomeTags from 'home-tags'
import renderXoItem, { VmTemplate } from 'render-xo-item' import renderXoItem, { VmTemplate } from 'render-xo-item'
import sanitizeHtml from 'sanitize-html'
import Tooltip from 'tooltip' import Tooltip from 'tooltip'
import { addTag, editVm, removeTag, subscribeUsers } from 'xo' import { addTag, editVm, editVmNotes, removeTag, subscribeUsers } from 'xo'
import { BlockLink } from 'link' import { BlockLink } from 'link'
import { FormattedRelative, FormattedDate } from 'react-intl' import { FormattedRelative, FormattedDate } from 'react-intl'
import { Container, Row, Col } from 'grid' import { Container, Row, Col } from 'grid'
@ -31,6 +34,18 @@ const CREATED_VM_STYLES = {
whiteSpace: 'pre-line', 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 }) => { const GuestToolsDetection = ({ vm }) => {
if (vm.power_state !== 'Running' || vm.pvDriversDetected === undefined) { if (vm.power_state !== 'Running' || vm.pvDriversDetected === undefined) {
return null return null
@ -276,6 +291,20 @@ const GeneralTab = decorate([
</Col> </Col>
</Row> </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> </Container>
) )
}, },

View File

@ -11876,7 +11876,7 @@ htmlparser2@^6.1.0:
domutils "^2.5.2" domutils "^2.5.2"
entities "^2.0.0" entities "^2.0.0"
htmlparser2@^8.0.1: htmlparser2@^8.0.0, htmlparser2@^8.0.1:
version "8.0.2" version "8.0.2"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21"
integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA== 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" resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
integrity sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q== 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: parse5-htmlparser2-tree-adapter@^7.0.0:
version "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" 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" picocolors "^0.2.1"
source-map "^0.6.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" version "8.4.33"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.33.tgz#1378e859c9f69bf6f638b990a0212f43e2aaa742" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.33.tgz#1378e859c9f69bf6f638b990a0212f43e2aaa742"
integrity sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg== 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" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== 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: sasl-anonymous@^0.1.0:
version "0.1.0" version "0.1.0"
resolved "https://registry.yarnpkg.com/sasl-anonymous/-/sasl-anonymous-0.1.0.tgz#f544c7e824df2a40d9ad4733829572cc8d9ed5a5" resolved "https://registry.yarnpkg.com/sasl-anonymous/-/sasl-anonymous-0.1.0.tgz#f544c7e824df2a40d9ad4733829572cc8d9ed5a5"