From 39d3e81196db404c8151bb1a26ad1ac8c1809dec Mon Sep 17 00:00:00 2001 From: crschnick Date: Mon, 30 Dec 2024 02:33:11 +0000 Subject: [PATCH] Port incus features to lxc [stage] --- .../ext/system/incus/IncusCommandView.java | 8 +- .../ext/system/incus/IncusContainerStore.java | 14 ++-- .../xpipe/ext/system/lxd/LxdCommandView.java | 57 +++++++++++--- .../system/lxd/LxdContainerActionMenu.java | 53 +++++++++++++ .../system/lxd/LxdContainerConsoleAction.java | 57 ++++++++++++++ .../lxd/LxdContainerEditConfigAction.java | 58 ++++++++++++++ .../lxd/LxdContainerEditRunConfigAction.java | 69 +++++++++++++++++ .../ext/system/lxd/LxdContainerStore.java | 77 +++++++++++++++++-- .../system/lxd/LxdContainerStoreProvider.java | 23 ++++-- ext/system/src/main/java/module-info.java | 2 + version | 2 +- 11 files changed, 388 insertions(+), 32 deletions(-) create mode 100644 ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerActionMenu.java create mode 100644 ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerConsoleAction.java create mode 100644 ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerEditConfigAction.java create mode 100644 ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerEditRunConfigAction.java diff --git a/ext/system/src/main/java/io/xpipe/ext/system/incus/IncusCommandView.java b/ext/system/src/main/java/io/xpipe/ext/system/incus/IncusCommandView.java index 1423df621..0c4186930 100644 --- a/ext/system/src/main/java/io/xpipe/ext/system/incus/IncusCommandView.java +++ b/ext/system/src/main/java/io/xpipe/ext/system/incus/IncusCommandView.java @@ -139,20 +139,20 @@ public class IncusCommandView extends CommandViewBase { } } - public ShellControl exec(String container, Integer uid, FilePath dir) { + public ShellControl exec(String container, Integer uid, FilePath dir, ShellDialect shell) { return shellControl - .subShell(createOpenFunction(container, uid, dir, false), createOpenFunction(container, uid, dir, true)) + .subShell(createOpenFunction(container, uid, dir, false, shell), createOpenFunction(container, uid, dir, true, shell)) .withErrorFormatter(IncusCommandView::formatErrorMessage) .withExceptionConverter(IncusCommandView::convertException) .elevated(requiresElevation()); } - private ShellOpenFunction createOpenFunction(String containerName, Integer uid, FilePath dir, boolean terminal) { + private ShellOpenFunction createOpenFunction(String containerName, Integer uid, FilePath dir, boolean terminal, ShellDialect shell) { return new ShellOpenFunction() { @Override public CommandBuilder prepareWithoutInitCommand() { return execCommand(containerName, uid, dir, terminal) - .add(ShellDialects.SH.getLaunchCommand().loginCommand()); + .add(shell.getLaunchCommand().loginCommand()); } @Override diff --git a/ext/system/src/main/java/io/xpipe/ext/system/incus/IncusContainerStore.java b/ext/system/src/main/java/io/xpipe/ext/system/incus/IncusContainerStore.java index 99a7ad156..2f18d1f46 100644 --- a/ext/system/src/main/java/io/xpipe/ext/system/incus/IncusContainerStore.java +++ b/ext/system/src/main/java/io/xpipe/ext/system/incus/IncusContainerStore.java @@ -7,6 +7,8 @@ import io.xpipe.app.ext.ShellStore; import io.xpipe.app.storage.DataStoreEntryRef; import io.xpipe.app.util.*; import io.xpipe.core.process.ShellControl; +import io.xpipe.core.process.ShellDialect; +import io.xpipe.core.process.ShellDialects; import io.xpipe.core.store.FilePath; import io.xpipe.core.store.FixedChildStore; import io.xpipe.core.store.StatefulDataStore; @@ -77,10 +79,11 @@ public class IncusContainerStore public ShellControl control(ShellControl parent) throws Exception { Integer uid = null; FilePath homeDir = null; - if (identity != null && identity.unwrap().getUsername() != null) { - try (var temp = new IncusCommandView(parent) - .exec(containerName, null, null) - .start()) { + ShellDialect shell; + try (var temp = new IncusCommandView(parent) + .exec(containerName, null, null, ShellDialects.SH) + .start()) { + if (identity != null && identity.unwrap().getUsername() != null) { var passwd = PasswdFile.parse(temp); var username = identity.unwrap().getUsername(); uid = passwd.getUidForUserIfPresent(username) @@ -89,9 +92,10 @@ public class IncusContainerStore homeDir = temp.command("eval echo ~" +username).readStdoutIfPossible().filter(s -> !s.isBlank()) .map(FilePath::new).orElse(null); } + shell = CommandSupport.isInPath(temp, "bash") ? ShellDialects.BASH : ShellDialects.SH; } - var sc = new IncusCommandView(parent).exec(containerName, uid, homeDir); + var sc = new IncusCommandView(parent).exec(containerName, uid, homeDir, shell); sc.withSourceStore(IncusContainerStore.this); if (identity != null && identity.unwrap().getPassword() != null) { sc.setElevationHandler(new BaseElevationHandler( diff --git a/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdCommandView.java b/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdCommandView.java index bef624bcd..9f8fc2681 100644 --- a/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdCommandView.java +++ b/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdCommandView.java @@ -7,6 +7,7 @@ import io.xpipe.app.storage.DataStoreEntryRef; import io.xpipe.app.util.CommandViewBase; import io.xpipe.core.process.*; +import io.xpipe.core.store.FilePath; import lombok.NonNull; import java.util.*; @@ -86,12 +87,40 @@ public class LxdCommandView extends CommandViewBase { .readStdoutOrThrow(); } + public String queryContainerState(String containerName) throws Exception { + var states = listContainersAndStates(); + return states.getOrDefault(containerName, "?"); + } + + public void start(String containerName) throws Exception { + build(commandBuilder -> commandBuilder.add("start").addQuoted(containerName)) + .execute(); + } + + public void stop(String containerName) throws Exception { + build(commandBuilder -> commandBuilder.add("stop").addQuoted(containerName)) + .execute(); + } + + public void pause(String containerName) throws Exception { + build(commandBuilder -> commandBuilder.add("pause").addQuoted(containerName)) + .execute(); + } + + public CommandControl console(String containerName) throws Exception { + return build(commandBuilder -> commandBuilder.add("console").addQuoted(containerName)); + } + + public CommandControl configEdit(String containerName) throws Exception { + return build(commandBuilder -> commandBuilder.add("config", "edit").addQuoted(containerName)); + } + public List> listContainers(DataStoreEntryRef store) throws Exception { return listContainersAndStates().entrySet().stream() .map(s -> { boolean running = s.getValue().toLowerCase(Locale.ROOT).equals("running"); - var c = new LxdContainerStore(store, s.getKey()); + var c = LxdContainerStore.builder().cmd(store).containerName(s.getKey()).build(); var entry = DataStoreEntry.createNew(c.getContainerName(), c); entry.setStorePersistentState(ContainerStoreState.builder() .containerState(s.getValue()) @@ -119,33 +148,37 @@ public class LxdCommandView extends CommandViewBase { } } - public ShellControl exec(String container) { + public ShellControl exec(String container, Integer uid, FilePath dir, ShellDialect shell) { return shellControl - .subShell(createOpenFunction(container, false), createOpenFunction(container, true)) + .subShell(createOpenFunction(container, uid, dir, shell, false), createOpenFunction(container, uid,dir, shell, true)) .withErrorFormatter(LxdCommandView::formatErrorMessage) .withExceptionConverter(LxdCommandView::convertException) .elevated(requiresElevation()); } - private ShellOpenFunction createOpenFunction(String containerName, boolean terminal) { + private ShellOpenFunction createOpenFunction(String containerName, Integer uid, FilePath dir, ShellDialect shell, boolean terminal) { return new ShellOpenFunction() { @Override public CommandBuilder prepareWithoutInitCommand() { - return execCommand(containerName, terminal) - .add(ShellDialects.SH.getLaunchCommand().loginCommand()); + return execCommand(containerName, uid, dir, terminal) + .add(shell.getLaunchCommand().loginCommand()); } @Override public CommandBuilder prepareWithInitCommand(@NonNull String command) { - return execCommand(containerName, terminal).add(command); + return execCommand(containerName, uid, dir , terminal).add(command); } }; } - public CommandBuilder execCommand(String containerName, boolean terminal) { - return CommandBuilder.of() - .add("lxc", "exec", terminal ? "-t" : "-T") - .addQuoted(containerName) - .add("--"); + public CommandBuilder execCommand(String containerName, Integer uid, FilePath dir, boolean terminal) { + var c = CommandBuilder.of().add("lxc", "exec", terminal ? "-t" : "-T"); + if (uid != null) { + c.add("--user").add(uid.toString()); + } + if (dir != null) { + c.add("--cwd").addFile(dir); + } + return c.addQuoted(containerName).add("--"); } } diff --git a/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerActionMenu.java b/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerActionMenu.java new file mode 100644 index 000000000..0199564e0 --- /dev/null +++ b/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerActionMenu.java @@ -0,0 +1,53 @@ +package io.xpipe.ext.system.lxd; + +import io.xpipe.app.core.AppI18n; +import io.xpipe.app.ext.ActionProvider; +import io.xpipe.app.storage.DataStoreEntryRef; +import io.xpipe.ext.base.store.StorePauseAction; +import io.xpipe.ext.base.store.StoreRestartAction; +import io.xpipe.ext.base.store.StoreStartAction; +import io.xpipe.ext.base.store.StoreStopAction; +import javafx.beans.value.ObservableValue; + +import java.util.List; + +public class LxdContainerActionMenu implements ActionProvider { + + @Override + public BranchDataStoreCallSite getBranchDataStoreCallSite() { + return new BranchDataStoreCallSite() { + + @Override + public Class getApplicableClass() { + return LxdContainerStore.class; + } + + @Override + public boolean isMajor(DataStoreEntryRef o) { + return true; + } + + @Override + public ObservableValue getName(DataStoreEntryRef store) { + return AppI18n.observable("containerActions"); + } + + @Override + public String getIcon(DataStoreEntryRef store) { + return "mdi2p-package-variant-closed"; + } + + @Override + public List getChildren(DataStoreEntryRef store) { + return List.of( + new StoreStartAction(), + new StoreStopAction(), + new StorePauseAction(), + new StoreRestartAction(), + new LxdContainerConsoleAction(), + new LxdContainerEditConfigAction(), + new LxdContainerEditRunConfigAction()); + } + }; + } +} diff --git a/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerConsoleAction.java b/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerConsoleAction.java new file mode 100644 index 000000000..006b84d21 --- /dev/null +++ b/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerConsoleAction.java @@ -0,0 +1,57 @@ +package io.xpipe.ext.system.lxd; + +import io.xpipe.app.core.AppI18n; +import io.xpipe.app.ext.ActionProvider; +import io.xpipe.app.storage.DataStoreEntry; +import io.xpipe.app.storage.DataStoreEntryRef; +import io.xpipe.app.terminal.TerminalLauncher; +import io.xpipe.ext.system.incus.IncusCommandView; +import javafx.beans.value.ObservableValue; +import lombok.Value; + +public class LxdContainerConsoleAction implements ActionProvider { + + @Override + public LeafDataStoreCallSite getLeafDataStoreCallSite() { + return new LeafDataStoreCallSite() { + + @Override + public ActionProvider.Action createAction(DataStoreEntryRef store) { + return new Action(store.get()); + } + + @Override + public Class getApplicableClass() { + return LxdContainerStore.class; + } + + @Override + public ObservableValue getName(DataStoreEntryRef store) { + return AppI18n.observable("serialConsole"); + } + + @Override + public String getIcon(DataStoreEntryRef store) { + return "mdi2c-console"; + } + + @Override + public boolean requiresValidStore() { + return false; + } + }; + } + + @Value + static class Action implements ActionProvider.Action { + + DataStoreEntry store; + + @Override + public void execute() throws Exception { + var d = (LxdContainerStore) store.getStore(); + var view = new IncusCommandView(d.getCmd().getStore().getHost().getStore().getOrStartSession()); + TerminalLauncher.open(store.getName(), view.console(d.getContainerName())); + } + } +} diff --git a/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerEditConfigAction.java b/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerEditConfigAction.java new file mode 100644 index 000000000..462becd61 --- /dev/null +++ b/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerEditConfigAction.java @@ -0,0 +1,58 @@ +package io.xpipe.ext.system.lxd; + +import io.xpipe.app.core.AppI18n; +import io.xpipe.app.ext.ActionProvider; +import io.xpipe.app.storage.DataStoreEntry; +import io.xpipe.app.storage.DataStoreEntryRef; +import io.xpipe.app.terminal.TerminalLauncher; +import io.xpipe.ext.system.incus.IncusCommandView; +import javafx.beans.value.ObservableValue; +import lombok.Value; + +public class LxdContainerEditConfigAction implements ActionProvider { + + @Override + public LeafDataStoreCallSite getLeafDataStoreCallSite() { + return new LeafDataStoreCallSite() { + + @Override + public ActionProvider.Action createAction(DataStoreEntryRef store) { + return new Action(store.get()); + } + + @Override + public Class getApplicableClass() { + return LxdContainerStore.class; + } + + @Override + public ObservableValue getName(DataStoreEntryRef store) { + return AppI18n.observable("editConfiguration"); + } + + @Override + public String getIcon(DataStoreEntryRef store) { + return "mdi2f-file-document-edit"; + } + + @Override + public boolean requiresValidStore() { + return false; + } + }; + } + + @Value + static class Action implements ActionProvider.Action { + + DataStoreEntry store; + + @Override + public void execute() throws Exception { + var d = (LxdContainerStore) store.getStore(); + var view = new IncusCommandView( + d.getCmd().getStore().getHost().getStore().getOrStartSession()); + TerminalLauncher.open(store.getName(), view.configEdit(d.getContainerName())); + } + } +} diff --git a/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerEditRunConfigAction.java b/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerEditRunConfigAction.java new file mode 100644 index 000000000..022bd675b --- /dev/null +++ b/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerEditRunConfigAction.java @@ -0,0 +1,69 @@ +package io.xpipe.ext.system.lxd; + +import io.xpipe.app.browser.BrowserFullSessionModel; +import io.xpipe.app.browser.file.BrowserFileOpener; +import io.xpipe.app.core.AppI18n; +import io.xpipe.app.core.AppLayoutModel; +import io.xpipe.app.ext.ActionProvider; +import io.xpipe.app.ext.ProcessControlProvider; +import io.xpipe.app.storage.DataStoreEntry; +import io.xpipe.app.storage.DataStoreEntryRef; +import io.xpipe.core.store.FilePath; +import javafx.beans.value.ObservableValue; +import lombok.Value; + +public class LxdContainerEditRunConfigAction implements ActionProvider { + + @Override + public LeafDataStoreCallSite getLeafDataStoreCallSite() { + return new LeafDataStoreCallSite() { + + @Override + public ActionProvider.Action createAction(DataStoreEntryRef store) { + return new Action(store.get()); + } + + @Override + public Class getApplicableClass() { + return LxdContainerStore.class; + } + + @Override + public ObservableValue getName(DataStoreEntryRef store) { + return AppI18n.observable("editRunConfiguration"); + } + + @Override + public String getIcon(DataStoreEntryRef store) { + return "mdi2m-movie-edit"; + } + + @Override + public boolean requiresValidStore() { + return false; + } + }; + } + + @Value + static class Action implements ActionProvider.Action { + + DataStoreEntry store; + + @Override + public void execute() throws Exception { + var d = (LxdContainerStore) store.getStore(); + var elevatedRef = ProcessControlProvider.get() + .elevated(d.getCmd().getStore().getHost().get().ref()); + var file = new FilePath("/run/lxd/" + d.getContainerName() + "/lxc.conf"); + var model = BrowserFullSessionModel.DEFAULT.openFileSystemSync( + elevatedRef, m -> file.getParent().toString(), null, true); + var found = model.findFile(file.toString()); + if (found.isEmpty()) { + return; + } + AppLayoutModel.get().selectBrowser(); + BrowserFileOpener.openInTextEditor(model, found.get()); + } + } +} diff --git a/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerStore.java b/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerStore.java index de80b41ba..30ef15c6f 100644 --- a/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerStore.java +++ b/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerStore.java @@ -5,13 +5,19 @@ import io.xpipe.app.ext.ShellControlFunction; import io.xpipe.app.ext.ShellControlParentStoreFunction; import io.xpipe.app.ext.ShellStore; import io.xpipe.app.storage.DataStoreEntryRef; -import io.xpipe.app.util.LicenseRequiredException; -import io.xpipe.app.util.Validators; +import io.xpipe.app.util.*; import io.xpipe.core.process.ShellControl; +import io.xpipe.core.process.ShellDialect; +import io.xpipe.core.process.ShellDialects; +import io.xpipe.core.store.FilePath; import io.xpipe.core.store.FixedChildStore; import io.xpipe.core.store.StatefulDataStore; import com.fasterxml.jackson.annotation.JsonTypeName; +import io.xpipe.ext.base.identity.IdentityValue; +import io.xpipe.ext.base.store.PauseableStore; +import io.xpipe.ext.base.store.StartableStore; +import io.xpipe.ext.base.store.StoppableStore; import lombok.AllArgsConstructor; import lombok.Value; import lombok.experimental.SuperBuilder; @@ -25,10 +31,11 @@ import java.util.OptionalInt; @Jacksonized @Value @AllArgsConstructor -public class LxdContainerStore implements ShellStore, FixedChildStore, StatefulDataStore { +public class LxdContainerStore implements ShellStore, FixedChildStore, StatefulDataStore, StartableStore, StoppableStore, PauseableStore { private final DataStoreEntryRef cmd; private final String containerName; + IdentityValue identity; @Override public Class getStateClass() { @@ -41,6 +48,9 @@ public class LxdContainerStore implements ShellStore, FixedChildStore, StatefulD Validators.isType(cmd, LxdCmdStore.class); cmd.checkComplete(); Validators.nonNull(containerName); + if (identity != null) { + identity.checkComplete(false, false, false); + } } @Override @@ -58,8 +68,30 @@ public class LxdContainerStore implements ShellStore, FixedChildStore, StatefulD } @Override - public ShellControl control(ShellControl parent) { - var base = new LxdCommandView(parent).exec(containerName); + public ShellControl control(ShellControl parent) throws Exception { + Integer uid = null; + FilePath homeDir = null; + ShellDialect shell; + try (var temp = new LxdCommandView(parent) + .exec(containerName, null, null, ShellDialects.SH) + .start()) { + if (identity != null && identity.unwrap().getUsername() != null) { + var passwd = PasswdFile.parse(temp); + var username = identity.unwrap().getUsername(); + uid = passwd.getUidForUserIfPresent(username) + .orElseThrow(() -> new IllegalArgumentException("User " + username + " not found")); + homeDir = temp.command("eval echo ~" +username).readStdoutIfPossible().filter(s -> !s.isBlank()) + .map(FilePath::new).orElse(null); + } + shell = CommandSupport.isInPath(temp, "bash") ? ShellDialects.BASH : ShellDialects.SH; + } + + var base = new LxdCommandView(parent).exec(containerName, uid, homeDir, shell); + if (identity != null && identity.unwrap().getPassword() != null) { + base.setElevationHandler(new BaseElevationHandler( + LxdContainerStore.this, identity.unwrap().getPassword()) + .orElse(base.getElevationHandler())); + } return base.withSourceStore(LxdContainerStore.this) .onInit(shellControl -> { var s = getState().toBuilder() @@ -85,4 +117,39 @@ public class LxdContainerStore implements ShellStore, FixedChildStore, StatefulD } }; } + + + private void refreshContainerState(ShellControl sc) throws Exception { + var state = getState(); + var view = new LxdCommandView(sc); + var displayState = view.queryContainerState(containerName); + var running = "RUNNING".equals(displayState); + var newState = + state.toBuilder().containerState(displayState).running(running).build(); + setState(newState); + } + + @Override + public void start() throws Exception { + var sc = getCmd().getStore().getHost().getStore().getOrStartSession(); + var view = new LxdCommandView(sc); + view.start(containerName); + refreshContainerState(sc); + } + + @Override + public void stop() throws Exception { + var sc = getCmd().getStore().getHost().getStore().getOrStartSession(); + var view = new LxdCommandView(sc); + view.stop(containerName); + refreshContainerState(sc); + } + + @Override + public void pause() throws Exception { + var sc = getCmd().getStore().getHost().getStore().getOrStartSession(); + var view = new LxdCommandView(sc); + view.pause(containerName); + refreshContainerState(sc); + } } diff --git a/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerStoreProvider.java b/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerStoreProvider.java index 1ee0dfcec..7cda75c54 100644 --- a/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerStoreProvider.java +++ b/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerStoreProvider.java @@ -12,8 +12,10 @@ import io.xpipe.app.util.OptionsBuilder; import io.xpipe.app.util.ShellStoreFormat; import io.xpipe.app.util.SimpleValidator; import io.xpipe.core.store.DataStore; +import io.xpipe.ext.base.identity.IdentityChoice; import io.xpipe.ext.base.store.ShellStoreProvider; +import io.xpipe.ext.system.incus.IncusContainerStore; import javafx.beans.property.Property; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; @@ -46,7 +48,7 @@ public class LxdContainerStoreProvider implements ShellStoreProvider { in a host shell of `%s` to open a shell into the container. """, new LxdCommandView(null) - .execCommand(lxd.getContainerName(), true) + .execCommand(lxd.getContainerName(), null, null,true) .buildSimple(), lxd.getCmd().getStore().getHost().get().getName()); } @@ -59,8 +61,8 @@ public class LxdContainerStoreProvider implements ShellStoreProvider { @Override public GuiDialog guiDialog(DataStoreEntry entry, Property store) { - var val = new SimpleValidator(); LxdContainerStore st = (LxdContainerStore) store.getValue(); + var identity = new SimpleObjectProperty<>(st.getIdentity()); var q = new OptionsBuilder() .name("host") @@ -73,8 +75,18 @@ public class LxdContainerStoreProvider implements ShellStoreProvider { .description("lxdContainerDescription") .addString(new SimpleObjectProperty<>(st.getContainerName()), false) .disable() - .buildComp(); - return new GuiDialog(q, val); + .sub(IdentityChoice.container(identity), identity) + .bind( + () -> { + return LxdContainerStore.builder() + .containerName(st.getContainerName()) + .cmd(st.getCmd()) + .identity(identity.getValue()) + .build(); + }, + store) + .buildDialog(); + return q; } @Override @@ -86,7 +98,8 @@ public class LxdContainerStoreProvider implements ShellStoreProvider { @Override public ObservableValue informationString(StoreSection section) { - return ShellStoreFormat.shellStore(section, (ContainerStoreState s) -> s.getContainerState()); + return ShellStoreFormat.shellStore( + section, (ContainerStoreState s) -> DataStoreFormatter.capitalize(s.getContainerState())); } @Override diff --git a/ext/system/src/main/java/module-info.java b/ext/system/src/main/java/module-info.java index 8f7309c7d..3da6e08d7 100644 --- a/ext/system/src/main/java/module-info.java +++ b/ext/system/src/main/java/module-info.java @@ -6,6 +6,7 @@ import io.xpipe.ext.system.incus.IncusContainerStoreProvider; import io.xpipe.ext.system.incus.IncusInstallStoreProvider; import io.xpipe.ext.system.incus.IncusScanProvider; import io.xpipe.ext.system.lxd.LxdCmdStoreProvider; +import io.xpipe.ext.system.lxd.LxdContainerActionMenu; import io.xpipe.ext.system.lxd.LxdContainerStoreProvider; import io.xpipe.ext.system.lxd.LxdScanProvider; import io.xpipe.ext.system.podman.PodmanCmdStoreProvider; @@ -36,5 +37,6 @@ open module io.xpipe.ext.system { PodmanCmdStoreProvider; provides ActionProvider with IncusContainerActionMenu, + LxdContainerActionMenu, PodmanContainerActionMenu; } diff --git a/version b/version index 23c30db68..3e9788cdc 100644 --- a/version +++ b/version @@ -1 +1 @@ -14.0-31 +14.0-32