Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show user owned containers and images #173

Merged
merged 3 commits into from
Oct 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,10 @@ rpm: dist-gzip $(RPM_NAME).spec
# build a VM with locally built rpm installed
$(VM_IMAGE): rpm bots
rm -f $(VM_IMAGE) $(VM_IMAGE).qcow2
# HACK: https://bugzilla.redhat.com/show_bug.cgi?id=1762663
if [ $(TEST_OS) = "fedora-31" ]; then bots/image-customize -v -r 'grub2-editenv - set "$$(grub2-editenv list | grep ^kernelopts) debug"' $(TEST_OS); fi
bots/image-customize -v -i cockpit-ws -i `pwd`/$(RPM_NAME)-*.noarch.rpm -s $(CURDIR)/test/vm.install $(TEST_OS)
if [ $(TEST_OS) = "fedora-31" ]; then bots/image-customize -v -r 'grub2-editenv - set "$$(grub2-editenv list | grep ^kernelopts | sed "s/debug//")"' $(TEST_OS); fi

# convenience target for the above
vm: $(VM_IMAGE)
Expand Down
2 changes: 1 addition & 1 deletion src/ContainerCommitModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ class ContainerCommitModal extends React.Component {

if (reply && 'logs' in reply && Array.isArray(reply.logs) && reply.logs.length > 0)
console.log("Container commit:", message.parameters.reply.logs.join("\n"));
}, this.props.onHide)
}, this.props.onHide, this.props.container.isSystem)
.then(() => this.props.onHide())
.catch(ex => {
this.setState({
Expand Down
6 changes: 3 additions & 3 deletions src/ContainerTerminal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ class ContainerTerminal extends React.Component {
var realWidth = this.state.term._core._renderCoordinator.dimensions.actualCellWidth;
var cols = Math.floor((width - padding) / realWidth);
this.state.term.resize(cols, 24);
cockpit.spawn(["sh", "-c", "echo '1 24 " + cols.toString() + "'>" + this.state.control_channel], { superuser: true });
cockpit.spawn(["sh", "-c", "echo '1 24 " + cols.toString() + "'>" + this.state.control_channel], { superuser: this.props.system ? "require" : null });
this.setState({ cols: cols });
}

Expand All @@ -91,12 +91,12 @@ class ContainerTerminal extends React.Component {
return;
}

utils.podmanCall("GetAttachSockets", { name: this.state.container })
utils.podmanCall("GetAttachSockets", { name: this.state.container }, this.props.system)
.then(out => {
let opts = {
payload: "packet",
unix: out.sockets.io_socket,
superuser: "require",
superuser: this.props.system ? "require" : null,
binary: false
};

Expand Down
30 changes: 19 additions & 11 deletions src/Containers.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,15 @@ class Containers extends React.Component {

if (force)
args.timeout = 0;
utils.podmanCall("StopContainer", args)
utils.podmanCall("StopContainer", args, container.isSystem)
.catch(ex => this.setState({
actionError: cockpit.format(_("Failed to stop container $0"), container.names),
actionErrorDetail: ex.parameters && ex.parameters.reason
}));
}

startContainer(container) {
utils.podmanCall("StartContainer", { name: container.names })
utils.podmanCall("StartContainer", { name: container.names }, container.isSystem)
.catch(ex => this.setState({
actionError: cockpit.format(_("Failed to start container $0"), container.names),
actionErrorDetail: ex.parameters && ex.parameters.reason
Expand All @@ -85,24 +85,31 @@ class Containers extends React.Component {

if (force)
args.timeout = 0;
utils.podmanCall("RestartContainer", args)
utils.podmanCall("RestartContainer", args, container.isSystem)
.catch(ex => this.setState({
actionError: cockpit.format(_("Failed to restart container $0"), container.names),
actionErrorDetail: ex.parameters && ex.parameters.reason
}));
}

renderRow(containersStats, container) {
const containerStats = containersStats[container.id];
const containerStats = containersStats[container.id + container.isSystem.toString()];
const isRunning = container.status == "running";
const image = container.image;

let proc = "";
let mem = "";
if (containerStats) {
proc = containerStats.cpu ? utils.format_cpu_percent(containerStats.cpu * 100) : <abbr title={_("not available")}>{_("n/a")}</abbr>;
mem = containerStats.mem_usage ? utils.format_memory_and_limit(containerStats.mem_usage, containerStats.mem_limit) : <abbr title={_("not available")}>{_("n/a")}</abbr>;
martinpitt marked this conversation as resolved.
Show resolved Hide resolved
}
let columns = [
{ name: container.names, header: true },
image,
utils.quote_cmdline(container.command),
isRunning ? utils.format_cpu_percent(containerStats.cpu * 100) : "",
containerStats ? utils.format_memory_and_limit(containerStats.mem_usage, containerStats.mem_limit) : "",
proc,
mem,
container.isSystem ? _("system") : this.props.user,
container.status /* TODO: i18n */,
];
let tabs = [{
Expand All @@ -112,7 +119,7 @@ class Containers extends React.Component {
}, {
name: _("Console"),
renderer: ContainerTerminal,
data: { containerId: container.id, containerStatus: container.status, width:this.state.width }
data: { containerId: container.id, containerStatus: container.status, width:this.state.width, system:container.isSystem }
}];

var actions = [
Expand Down Expand Up @@ -152,7 +159,8 @@ class Containers extends React.Component {
return (
<ScrollableAnchor id={container.id} key={container.id}>
<Listing.ListingRow
rowId={container.id}
key={container.id + container.isSystem.toString()}
rowId={container.id + container.isSystem.toString()}
columns={columns}
tabRenderers={tabs}
listingActions={actions}
Expand All @@ -172,7 +180,7 @@ class Containers extends React.Component {
this.setState({
selectContainerDeleteModal: false
});
utils.podmanCall("RemoveContainer", { name: id })
utils.podmanCall("RemoveContainer", { name: id }, this.state.containerWillDelete.isSystem)
.catch(ex => console.error("Failed to do RemoveContainer call:", JSON.stringify(ex)));
}

Expand All @@ -185,7 +193,7 @@ class Containers extends React.Component {
// TODO: force
handleForceRemoveContainer() {
const id = this.state.containerWillDelete ? this.state.containerWillDelete.id : "";
utils.podmanCall("RemoveContainer", { name: id, force: true })
utils.podmanCall("RemoveContainer", { name: id, force: true }, this.state.containerWillDelete.isSystem)
.then(reply => {
this.setState({
setContainerRemoveErrorModal: false
Expand All @@ -201,7 +209,7 @@ class Containers extends React.Component {
}

render() {
const columnTitles = [_("Name"), _("Image"), _("Command"), _("CPU"), _("Memory"), _("State")];
const columnTitles = [_("Name"), _("Image"), _("Command"), _("CPU"), _("Memory"), _("Owner"), _("State")];

let emptyCaption = _("No containers");
if (this.props.containers === null)
Expand Down
4 changes: 2 additions & 2 deletions src/ImageRunModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,8 @@ export class ImageRunModal extends React.Component {
onRunClicked() {
const createConfig = this.getCreateConfig();

utils.podmanCall("CreateContainer", { create: createConfig })
.then(reply => utils.podmanCall("StartContainer", { name: reply.container }))
utils.podmanCall("CreateContainer", { create: createConfig }, this.state.image.isSystem)
.then(reply => utils.podmanCall("StartContainer", { name: reply.container }, this.state.image.isSystem))
.then(() => this.props.close())
.catch(ex => {
this.setState({
Expand Down
7 changes: 6 additions & 1 deletion src/ImageSearchModal.css
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,9 @@
padding-left: 0.25rem;
padding-right: 0.25rem;
}
}
}

.ct-form .radio {
margin-top: 0px;
margin-bottom: 0px;
}
21 changes: 19 additions & 2 deletions src/ImageSearchModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ export class ImageSearchModal extends React.Component {
imageList: [],
searchInProgress: false,
searchFinished: false,
isSystem: props.systemServiceAvailable,
};
this.onDownloadClicked = this.onDownloadClicked.bind(this);
this.onItemSelected = this.onItemSelected.bind(this);
this.onSearchTriggered = this.onSearchTriggered.bind(this);
this.onValueChanged = this.onValueChanged.bind(this);
this.onKeyPress = this.onKeyPress.bind(this);
this.onToggleUser = this.onToggleUser.bind(this);
}

componentDidMount() {
Expand All @@ -38,11 +40,15 @@ export class ImageSearchModal extends React.Component {
this.activeConnection.close();
}

onToggleUser(ev) {
this.setState({ isSystem: ev.target.id === "system" });
}

onDownloadClicked() {
let selectedImageName = this.state.imageList[this.state.selected].name;

this.props.close();
this.props.downloadImage(selectedImageName, this.state.imageTag);
this.props.downloadImage(selectedImageName, this.state.imageTag, this.state.isSystem);
}

onItemSelected(key) {
Expand All @@ -63,7 +69,7 @@ export class ImageSearchModal extends React.Component {

this.setState({ searchInProgress: true });

varlink.connect(utils.PODMAN_SYSTEM_ADDRESS)
varlink.connect(utils.getAddress(this.state.isSystem), this.state.isSystem)
.then(connection => {
this.activeConnection = connection;

Expand Down Expand Up @@ -110,6 +116,17 @@ export class ImageSearchModal extends React.Component {
render() {
let defaultBody = (
<React.Fragment>
{ this.props.userServiceAvailable && this.props.systemServiceAvailable &&
<form className="ct-form">
<label className="control-label" htmlFor="as-user">{_("Download as:")}</label>
<fieldset id="as-user">
<input type="radio" value="system" id="system" onChange={this.onToggleUser} checked={this.state.isSystem} />
<label className="radio" htmlFor="system">{_("System")}</label>
<input type="radio" value="user" id="user" onChange={this.onToggleUser} checked={!this.state.isSystem} />
<label className="radio" htmlFor="user">{this.props.user.name}</label>
</fieldset>
</form>
}
<div className="input-group">
<span className="input-group-addon">
<span className="fa fa-search" />
Expand Down
10 changes: 8 additions & 2 deletions src/ImageUsedBy.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@ const _ = cockpit.gettext;

const renderRow = (containerStats, container, showAll) => {
const isRunning = container.status == "running";
let proc = "";
let mem = "";
if (containerStats) {
proc = containerStats.cpu ? utils.format_cpu_percent(containerStats.cpu * 100) : <abbr title={_("not available")}>{_("n/a")}</abbr>;
mem = containerStats.mem_usage ? utils.format_memory_and_limit(containerStats.mem_usage, containerStats.mem_limit) : <abbr title={_("not available")}>{_("n/a")}</abbr>;
}
martinpitt marked this conversation as resolved.
Show resolved Hide resolved

const columns = [
{ name: container.names, header: true },
utils.quote_cmdline(container.command),
isRunning ? utils.format_cpu_percent(containerStats.cpu * 100) : "",
containerStats ? utils.format_memory_and_limit(containerStats.mem_usage, containerStats.mem_limit) : "",
proc,
mem,
container.status /* TODO: i18n */,

];
Expand Down
45 changes: 25 additions & 20 deletions src/Images.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@ class Images extends React.Component {
}));
}

downloadImage(imageName, imageTag) {
downloadImage(imageName, imageTag, system) {
let pullImageId = imageName;
if (imageTag)
pullImageId += ":" + imageTag;

this.setState({ imageDownloadInProgress: imageName });
utils.podmanCall("PullImage", { name: pullImageId })
utils.podmanCall("PullImage", { name: pullImageId }, system)
.then(() => {
this.setState({ imageDownloadInProgress: undefined });
})
Expand Down Expand Up @@ -95,7 +95,7 @@ class Images extends React.Component {
this.setState({
selectImageDeleteModal: false,
});
utils.podmanCall("RemoveImage", { name: image })
utils.podmanCall("RemoveImage", { name: image }, this.state.imageWillDelete.isSystem)
.catch(ex => {
this.imageRemoveErrorMsg = ex.parameters.reason;
this.setState({
Expand All @@ -106,7 +106,7 @@ class Images extends React.Component {

handleForceRemoveImage() {
const id = this.state.imageWillDelete ? this.state.imageWillDelete.id : "";
utils.podmanCall("RemoveImage", { name: id, force: true })
utils.podmanCall("RemoveImage", { name: id, force: true }, this.state.imageWillDelete.isSystem)
.then(reply => {
this.setState({
setImageRemoveErrorModal: false
Expand Down Expand Up @@ -147,6 +147,7 @@ class Images extends React.Component {
vulnerabilityColumn,
moment(image.created, utils.GOLANG_TIME_FORMAT).calendar(),
cockpit.format_bytes(image.size),
image.isSystem ? _("system") : this.props.user,
{
element: runImage,
tight: true
Expand All @@ -172,7 +173,7 @@ class Images extends React.Component {
name: _("Used By"),
renderer: ImageUsedBy,
data: {
containers: this.props.imageContainerList !== null ? this.props.imageContainerList[image.id] : null,
containers: this.props.imageContainerList !== null ? this.props.imageContainerList[image.id + image.isSystem.toString()] : null,
showAll: this.props.showAll,
}
});
Expand All @@ -186,8 +187,8 @@ class Images extends React.Component {
];
return (
<Listing.ListingRow
key={image.id}
rowId={image.id}
key={image.id + image.isSystem.toString()}
rowId={image.id + image.isSystem.toString()}
columns={columns}
tabRenderers={tabs}
listingActions={actions} />
Expand All @@ -201,7 +202,7 @@ class Images extends React.Component {
}

render() {
const columnTitles = [ _("Name"), _(''), _("Created"), _("Size"), _('') ];
const columnTitles = [ _("Name"), '', _("Created"), _("Size"), _("Owner"), '' ];
let emptyCaption = _("No images");
if (this.props.images === null)
emptyCaption = "Loading...";
Expand All @@ -216,17 +217,18 @@ class Images extends React.Component {
</a>
];
let filtered = [];
if (this.props.images !== null)
filtered = Object.keys(this.props.images).filter(id => id === this.props.images[id].id);
if (this.props.textFilter.length > 0)
filtered = filtered.filter(id => {
for (let i = 0; i < this.props.images[id].repoTags.length; i++) {
let tag = this.props.images[id].repoTags[i].toLowerCase();
if (tag.indexOf(this.props.textFilter.toLowerCase()) >= 0)
return true;
}
return false;
});
if (this.props.images !== null) {
filtered = Object.keys(this.props.images);
if (this.props.textFilter.length > 0)
filtered = filtered.filter(id => {
for (let i = 0; i < this.props.images[id].repoTags.length; i++) {
let tag = this.props.images[id].repoTags[i].toLowerCase();
if (tag.indexOf(this.props.textFilter.toLowerCase()) >= 0)
return true;
}
return false;
});
}
let imageRows = filtered.map(id => this.renderRow(this.props.images[id]));
const imageDeleteModal =
<ModalExample
Expand Down Expand Up @@ -263,7 +265,10 @@ class Images extends React.Component {
{this.state.showSearchImageModal &&
<ImageSearchModal
close={() => this.setState({ showSearchImageModal: false })}
downloadImage={this.downloadImage} /> }
downloadImage={this.downloadImage}
user={this.props.user}
userServiceAvailable={this.props.userServiceAvailable}
systemServiceAvailable={this.props.systemServiceAvailable} /> }
{this.state.imageDownloadInProgress && <div className='download-in-progress'> {_("Pulling")} {this.state.imageDownloadInProgress}... </div>}
</div>
);
Expand Down
Loading