Skip to content

Commit

Permalink
Support creation of pods
Browse files Browse the repository at this point in the history
This implements the ability to create a podman pod for the user and
admin session. A pod can have a volume and port mapping, note that
volumes are only supported from podman 4.0.
  • Loading branch information
strzinek authored and jelly committed Sep 29, 2022
1 parent 11c7695 commit be478c6
Show file tree
Hide file tree
Showing 14 changed files with 583 additions and 249 deletions.
3 changes: 2 additions & 1 deletion src/ContainerRenameModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import cockpit from 'cockpit';

import * as client from './client.js';
import * as utils from './util.js';
import { ErrorNotification } from './Notification.jsx';
import { useDialogs } from "dialogs.jsx";

Expand All @@ -24,7 +25,7 @@ const ContainerRenameModal = ({ container, version, updateContainerAfterEvent })
setName(value);
if (value === "") {
setNameError(_("Container name is required."));
} else if (/^[a-zA-Z0-9][a-zA-Z0-9_\\.-]*$/.test(value)) {
} else if (utils.is_valid_container_name(value)) {
setNameError(null);
} else {
setNameError(_("Invalid characters. Name can only contain letters, numbers, and certain punctuation (_ . -)."));
Expand Down
20 changes: 19 additions & 1 deletion src/Containers.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import './Containers.scss';
import '@patternfly/patternfly/utilities/Accessibility/accessibility.css';
import { ImageRunModal } from './ImageRunModal.jsx';
import { PodActions } from './PodActions.jsx';
import { PodCreateModal } from './PodCreateModal.jsx';

const _ = cockpit.gettext;

Expand Down Expand Up @@ -668,6 +669,16 @@ class Containers extends React.Component {
onAddNotification={this.props.onAddNotification} />);
};

const createPod = () => {
Dialogs.show(<PodCreateModal
user={this.props.user}
selinuxAvailable={this.props.selinuxAvailable}
systemServiceAvailable={this.props.systemServiceAvailable}
userServiceAvailable={this.props.userServiceAvailable}
onAddNotification={this.props.onAddNotification}
version={this.props.version} />);
};

const filterRunning =
<Toolbar>
<ToolbarContent>
Expand All @@ -681,6 +692,13 @@ class Containers extends React.Component {
</FormSelect>
</ToolbarItem>
<Divider isVertical />
<ToolbarItem>
<Button variant="secondary" key="create-new-pod-action"
id="containers-containers-create-pod-btn"
onClick={() => createPod(null)}>
{_("Create pod")}
</Button>
</ToolbarItem>
<ToolbarItem>
<Button variant="primary" key="get-new-image-action"
id="containers-containers-create-container-btn"
Expand Down Expand Up @@ -720,7 +738,7 @@ class Containers extends React.Component {
const tableProps = {};
const rows = partitionedContainers[section].map(container => {
return this.renderRow(this.props.containersStats, container,
this.props.containersDetails[container.Id + container.isSystem.toString()],
this.props.containersDetails[container.Id + container.isSystem.toString()] || null,
localImages);
});
let caption;
Expand Down
91 changes: 91 additions & 0 deletions src/DynamicListForm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Button,
EmptyState, EmptyStateBody,
FormFieldGroup, FormFieldGroupHeader,
HelperText, HelperTextItem,
} from '@patternfly/react-core';

import './DynamicListForm.scss';

export class DynamicListForm extends React.Component {
constructor(props) {
super(props);
this.state = {
list: [],
};
this.keyCounter = 0;
this.removeItem = this.removeItem.bind(this);
this.addItem = this.addItem.bind(this);
this.onItemChange = this.onItemChange.bind(this);
}

removeItem(idx, field, value) {
this.setState(state => {
const items = state.list.concat();
items.splice(idx, 1);
return { list: items };
}, () => this.props.onChange(this.state.list.concat()));
}

addItem() {
this.setState(state => {
return { list: [...state.list, Object.assign({ key: this.keyCounter++ }, this.props.default)] };
}, () => this.props.onChange(this.state.list.concat()));
}

onItemChange(idx, field, value) {
this.setState(state => {
const items = state.list.concat();
items[idx][field] = value || null;
return { list: items };
}, () => this.props.onChange(this.state.list.concat()));
}

render () {
const { id, label, actionLabel, formclass, emptyStateString, helperText } = this.props;
const dialogValues = this.state;
return (
<FormFieldGroup header={
<FormFieldGroupHeader
titleText={{ text: label }}
actions={<Button variant="secondary" className="btn-add" onClick={this.addItem}>{actionLabel}</Button>}
/>
} className={"dynamic-form-group " + formclass}>
{
dialogValues.list.length
? <>
{dialogValues.list.map((item, idx) => {
return React.cloneElement(this.props.itemcomponent, {
idx: idx, item: item, id: id + "-" + idx,
key: idx,
onChange: this.onItemChange, removeitem: this.removeItem, additem: this.addItem, options: this.props.options,
itemCount: Object.keys(dialogValues.list).length,
});
})
}
{helperText &&
<HelperText>
<HelperTextItem>{helperText}</HelperTextItem>
</HelperText>
}
</>
: <EmptyState>
<EmptyStateBody>
{emptyStateString}
</EmptyStateBody>
</EmptyState>
}
</FormFieldGroup>
);
}
}
DynamicListForm.propTypes = {
emptyStateString: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
id: PropTypes.string.isRequired,
itemcomponent: PropTypes.object.isRequired,
formclass: PropTypes.string,
options: PropTypes.object,
};
39 changes: 39 additions & 0 deletions src/DynamicListForm.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
@import "global-variables";

.dynamic-form-group {
.pf-c-empty-state {
padding: 0;
}

.pf-c-form__label {
// Don't allow labels to wrap
white-space: nowrap;
}

.remove-button-group {
// Move 'Remove' button the the end of the row
grid-column: -1;
// Move 'Remove' button to the bottom of the line so as to align with the other form fields
display: flex;
align-items: flex-end;
}

// Set check to the same height as input widgets and vertically align
.pf-c-form__group-control > .pf-c-check {
// Set height to the same as inputs
// Font height is font size * line height (1rem * 1.5)
// Widgets have 5px padding, 1px border (top & bottom): (5 + 1) * 2 = 12
// This all equals to 36px
height: calc(var(--pf-global--FontSize--md) * var(--pf-global--LineHeight--md) + 12px);
align-content: center;
}

// We use FormFieldGroup PF component for the nested look and for ability to add buttons to the header
// However we want to save space and not add indent to the left so we need to override it
.pf-c-form__field-group-body {
// Stretch content fully
--pf-c-form__field-group-body--GridColumn: 1 / -1;
// Reduce padding at the top
--pf-c-form__field-group-body--PaddingTop: var(--pf-global--spacer--xs);
}
}
Loading

0 comments on commit be478c6

Please sign in to comment.