Skip to content

Commit

Permalink
consoles: Import VNC dialogs from cockpit-project#1795
Browse files Browse the repository at this point in the history
  • Loading branch information
mvollmer committed Jan 13, 2025
1 parent 6c1f318 commit eaf58a2
Show file tree
Hide file tree
Showing 4 changed files with 338 additions and 0 deletions.
122 changes: 122 additions & 0 deletions src/components/vm/consoles/vncAdd.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* This file is part of Cockpit.
*
* Copyright 2024 Fsas Technologies Inc.
*
* Cockpit is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Cockpit is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
*/
import React from 'react';
import cockpit from 'cockpit';
import PropTypes from 'prop-types';
import { Button, Form, Modal, ModalVariant } from "@patternfly/react-core";
import { DialogsContext } from 'dialogs.jsx';

import { ModalError } from 'cockpit-components-inline-notification.jsx';
import { VncRow } from './vncBody.jsx';
import { domainAttachVnc, domainGet } from '../../../libvirtApi/domain.js';

const _ = cockpit.gettext;

export class AddVNC extends React.Component {
static contextType = DialogsContext;

constructor(props) {
super(props);

this.state = {
dialogError: undefined,
vncAddress: "",
vncPort: "",
vncPassword: "",
addVncInProgress: false,
};
this.add = this.add.bind(this);
this.onValueChanged = this.onValueChanged.bind(this);
this.dialogErrorSet = this.dialogErrorSet.bind(this);
}

onValueChanged(key, value) {
const stateDelta = { [key]: value };

this.setState(stateDelta);
}

dialogErrorSet(text, detail) {
this.setState({ dialogError: text, dialogErrorDetail: detail });
}

add() {
const Dialogs = this.context;
const { vm } = this.props;

this.setState({ addVncInProgress: true });
const vncParams = {
connectionName: vm.connectionName,
vmName: vm.name,
vncAddress: this.state.vncAddress || "",
vncPort: this.state.vncPort || "",
vncPassword: this.state.vncPassword || "",
};

domainAttachVnc(vncParams)
.then(() => {
domainGet({ connectionName: vm.connectionName, id: vm.id });
Dialogs.close();
})
.catch(exc => this.dialogErrorSet(_("VNC device settings could not be saved"), exc.message))
.finally(() => this.setState({ addVncInProgress: false }));
}

render() {
const Dialogs = this.context;
const { idPrefix } = this.props;

const defaultBody = (
<Form onSubmit={e => e.preventDefault()} isHorizontal>
<VncRow idPrefix={idPrefix}
dialogValues={this.state}
onValueChanged={this.onValueChanged} />
</Form>
);

return (
<Modal position="top" variant={ModalVariant.medium} id={`${idPrefix}-dialog`} isOpen onClose={Dialogs.close} className='vnc-add'
title={_("Add VNC")}
footer={
<>
<Button isLoading={this.state.addVncInProgress}
isDisabled={false}
id={`${idPrefix}-add`}
variant='primary'
onClick={this.add}>
{_("Add")}
</Button>
<Button id={`${idPrefix}-cancel`} variant='link' onClick={Dialogs.close}>
{_("Cancel")}
</Button>
</>
}>
{this.state.dialogError && <ModalError dialogError={this.state.dialogError} dialogErrorDetail={this.state.dialogErrorDetail} />}
{defaultBody}
</Modal>
);
}
}

AddVNC.propTypes = {
idPrefix: PropTypes.string.isRequired,
vm: PropTypes.object.isRequired,
};

export default AddVNC;
63 changes: 63 additions & 0 deletions src/components/vm/consoles/vncBody.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* This file is part of Cockpit.
*
* Copyright 2024 Fsas Technologies Inc.
*
* Cockpit is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Cockpit is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
*/

import React from 'react';
import PropTypes from 'prop-types';
import { FormGroup, Grid, GridItem, TextInput } from "@patternfly/react-core";

import cockpit from 'cockpit';

const _ = cockpit.gettext;

export const VncRow = ({ idPrefix, onValueChanged, dialogValues }) => {
return (
<Grid hasGutter md={6}>
<GridItem span={6}>
<FormGroup fieldId={`${idPrefix}-address`} label={_("VNC address")}>
<TextInput id={`${idPrefix}-address`}
value={dialogValues.vncAddress}
type="text"
onChange={(event) => onValueChanged('vncAddress', event.target.value)} />
</FormGroup>
</GridItem>
<GridItem span={6}>
<FormGroup fieldId={`${idPrefix}-port`} label={_("VNC port")}>
<TextInput id={`${idPrefix}-port`}
value={dialogValues.vncPort}
type="number"
onChange={(event) => onValueChanged('vncPort', event.target.value)} />
</FormGroup>
</GridItem>
<GridItem span={6}>
<FormGroup fieldId={`${idPrefix}-password`} label={_("VNC password")}>
<TextInput id={`${idPrefix}-password`}
value={dialogValues.vncPassword}
type="password"
onChange={(event) => onValueChanged('vncPassword', event.target.value)} />
</FormGroup>
</GridItem>
</Grid>
);
};

VncRow.propTypes = {
idPrefix: PropTypes.string.isRequired,
onValueChanged: PropTypes.func.isRequired,
dialogValues: PropTypes.object.isRequired,
};
127 changes: 127 additions & 0 deletions src/components/vm/consoles/vncEdit.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* This file is part of Cockpit.
*
* Copyright 2024 Fsas Technologies Inc.
*
* Cockpit is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Cockpit is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
*/
import React from 'react';
import cockpit from 'cockpit';
import PropTypes from 'prop-types';
import { Button, Form, Modal, ModalVariant } from "@patternfly/react-core";

import { ModalError } from 'cockpit-components-inline-notification.jsx';
import { DialogsContext } from 'dialogs.jsx';
import { VncRow } from './vncBody.jsx';
import { domainChangeVncSettings, domainGet } from '../../../libvirtApi/domain.js';

const _ = cockpit.gettext;

export class EditVNCModal extends React.Component {
static contextType = DialogsContext;

constructor(props) {
super(props);

this.state = {
dialogError: undefined,
saveDisabled: false,
vmName: props.vmName,
vmId: props.vmId,
connectionName: props.connectionName,
vncAddress: props.consoleDetail.address || "",
vncPort: props.consoleDetail.port || "",
vncPassword: props.consoleDetail.password || "",
};

this.save = this.save.bind(this);
this.onValueChanged = this.onValueChanged.bind(this);
this.dialogErrorSet = this.dialogErrorSet.bind(this);
}

onValueChanged(key, value) {
const stateDelta = { [key]: value };
this.setState(stateDelta);
}

dialogErrorSet(text, detail) {
this.setState({ dialogError: text, dialogErrorDetail: detail });
}

save() {
const Dialogs = this.context;

const vncParams = {
connectionName: this.state.connectionName,
vmName: this.state.vmName,
vncAddress: this.state.vncAddress || "",
vncPort: this.state.vncPort || "",
vncPassword: this.state.vncPassword || "",
};

domainChangeVncSettings(vncParams)
.then(() => {
domainGet({ connectionName: this.state.connectionName, id: this.state.vmId });
Dialogs.close();
})
.catch((exc) => {
this.dialogErrorSet(_("VNC settings could not be saved"), exc.message);
});
}

render() {
const Dialogs = this.context;
const { idPrefix } = this.props;

const defaultBody = (
<Form onSubmit={e => e.preventDefault()} isHorizontal>
<VncRow idPrefix={idPrefix}
dialogValues={this.state}
onValueChanged={this.onValueChanged} />
</Form>
);
const showWarning = () => {
};

return (
<Modal position="top" variant={ModalVariant.medium} id={`${idPrefix}-dialog`} isOpen onClose={Dialogs.close} className='vnc-edit'
title={_("Edit VNC settings")}
footer={
<>
<Button isDisabled={this.state.saveDisabled} id={`${idPrefix}-save`} variant='primary' onClick={this.save}>
{_("Save")}
</Button>
<Button id={`${idPrefix}-cancel`} variant='link' onClick={Dialogs.close}>
{_("Cancel")}
</Button>
</>
}>
<>
{ showWarning() }
{this.state.dialogError && <ModalError dialogError={this.state.dialogError} dialogErrorDetail={this.state.dialogErrorDetail} />}
{defaultBody}
</>
</Modal>
);
}
}
EditVNCModal.propTypes = {
idPrefix: PropTypes.string.isRequired,
vmName: PropTypes.string.isRequired,
vmId: PropTypes.string.isRequired,
connectionName: PropTypes.string.isRequired,
consoleDetail: PropTypes.object.isRequired,
};

export default EditVNCModal;
26 changes: 26 additions & 0 deletions src/libvirtApi/domain.js
Original file line number Diff line number Diff line change
Expand Up @@ -1089,3 +1089,29 @@ export async function domainAddTPM({ connectionName, vmName }) {
const args = ["virt-xml", "-c", `qemu:///${connectionName}`, "--add-device", "--tpm", "default", vmName];
return cockpit.spawn(args, { err: "message", superuser: connectionName === "system" ? "try" : null });
}

export function domainAttachVnc({ connectionName, vmName, vncAddress, vncPort, vncPassword }) {
const args = ['virt-xml', '-c', `qemu:///${connectionName}`, vmName, '--add-device', '--graphics', `vnc,listen=${vncAddress},port=${vncPort},passwd=${vncPassword}`];
const options = { err: "message" };

if (connectionName === "system")
options.superuser = "try";

return cockpit.spawn(args, options);
}

export function domainChangeVncSettings({
connectionName,
vmName,
vncAddress,
vncPort,
vncPassword,
}) {
const options = { err: "message" };
if (connectionName === "system")
options.superuser = "try";

const args = ["virt-xml", "-c", `qemu:///${connectionName}`, vmName, "--edit", "--graphics", `vnc,listen=${vncAddress},port=${vncPort},passwd=${vncPassword}`];

return cockpit.spawn(args, options);
}

0 comments on commit eaf58a2

Please sign in to comment.