Pool configurable in GUI.

This commit is contained in:
Michael Lipp 2024-12-01 13:41:18 +01:00
parent 77cfcff2ed
commit 367aebeee5
6 changed files with 296 additions and 134 deletions

View file

@ -60,6 +60,8 @@ public class VmPool {
} }
/** /**
* All permissions.
*
* @return the permissions * @return the permissions
*/ */
public List<Grant> permissions() { public List<Grant> permissions() {

View file

@ -5,17 +5,33 @@
data-jgwc-on-unload="JGConsole.jgwc.unmountVueApps"> data-jgwc-on-unload="JGConsole.jgwc.unmountVueApps">
<form :id="formId" ref="formDom" onsubmit="return false;"> <form :id="formId" ref="formDom" onsubmit="return false;">
<section> <section>
<span>{{ localize("Select VM") }}</span> <fieldset>
<p> <legend>{{ localize("Select VM or pool") }}</legend>
<label> <ul>
<span>{{ localize("VM") }}</span> <li>
<select v-model="vmNameInput"> <label>
<#list vmNames as name> <input v-model="resource" type="radio" name="resource" value="vm">
<option value="${name}">${name}</option> <span>{{ localize("VM") }}</span>
</#list> <select v-model="vmNameInput" :disabled="resource !== 'vm'">
</select> <#list vmNames as name>
</label> <option value="${name}">${name}</option>
</p> </#list>
</select>
</label>
</li>
<li>
<label>
<input v-model="resource" type="radio" name="resource" value="pool">
<span>{{ localize("Pool") }}</span>
<select v-model="poolNameInput" :disabled="resource !== 'pool'">
<#list poolNames as name>
<option value="${name}">${name}</option>
</#list>
</select>
</label>
</li>
</ul>
</fieldset>
</section> </section>
</form> </form>
</div> </div>

View file

@ -1,7 +1,7 @@
conletName = VM-Zugriff conletName = VM-Zugriff
okayLabel = Anwenden und Schließen okayLabel = Anwenden und Schließen
Select\ VM = VM auswählen Select\ VM\ or\ pool = VM oder Pool auswählen
Start\ VM = VM starten Start\ VM = VM starten
Stop\ VM = VM anhalten Stop\ VM = VM anhalten

View file

@ -38,6 +38,7 @@ import java.time.Duration;
import java.util.Base64; import java.util.Base64;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -49,13 +50,14 @@ import java.util.stream.Collectors;
import org.bouncycastle.util.Objects; import org.bouncycastle.util.Objects;
import org.jdrupes.vmoperator.common.K8sObserver; import org.jdrupes.vmoperator.common.K8sObserver;
import org.jdrupes.vmoperator.common.VmDefinition; import org.jdrupes.vmoperator.common.VmDefinition;
import org.jdrupes.vmoperator.common.VmDefinition.Permission; import org.jdrupes.vmoperator.common.VmPool;
import org.jdrupes.vmoperator.manager.events.ChannelTracker; import org.jdrupes.vmoperator.manager.events.ChannelTracker;
import org.jdrupes.vmoperator.manager.events.GetDisplayPassword; import org.jdrupes.vmoperator.manager.events.GetDisplayPassword;
import org.jdrupes.vmoperator.manager.events.ModifyVm; import org.jdrupes.vmoperator.manager.events.ModifyVm;
import org.jdrupes.vmoperator.manager.events.ResetVm; import org.jdrupes.vmoperator.manager.events.ResetVm;
import org.jdrupes.vmoperator.manager.events.VmChannel; import org.jdrupes.vmoperator.manager.events.VmChannel;
import org.jdrupes.vmoperator.manager.events.VmDefChanged; import org.jdrupes.vmoperator.manager.events.VmDefChanged;
import org.jdrupes.vmoperator.manager.events.VmPoolChanged;
import org.jgrapes.core.Channel; import org.jgrapes.core.Channel;
import org.jgrapes.core.Components; import org.jgrapes.core.Components;
import org.jgrapes.core.Event; import org.jgrapes.core.Event;
@ -107,7 +109,7 @@ import org.jgrapes.webconsole.base.freemarker.FreeMarkerConlet;
*/ */
@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.ExcessiveImports", @SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.ExcessiveImports",
"PMD.CouplingBetweenObjects", "PMD.GodClass", "PMD.TooManyMethods" }) "PMD.CouplingBetweenObjects", "PMD.GodClass", "PMD.TooManyMethods" })
public class VmAccess extends FreeMarkerConlet<VmAccess.ViewerModel> { public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
private static final String VM_NAME_PROPERTY = "vmName"; private static final String VM_NAME_PROPERTY = "vmName";
private static final String RENDERED private static final String RENDERED
@ -126,6 +128,8 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ViewerModel> {
private Set<String> syncUsers = Collections.emptySet(); private Set<String> syncUsers = Collections.emptySet();
private Set<String> syncRoles = Collections.emptySet(); private Set<String> syncRoles = Collections.emptySet();
private boolean deleteConnectionFile = true; private boolean deleteConnectionFile = true;
@SuppressWarnings("PMD.UseConcurrentHashMap")
private final Map<String, VmPool> vmPools = new HashMap<>();
/** /**
* The periodically generated update event. * The periodically generated update event.
@ -247,20 +251,28 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ViewerModel> {
* @throws InterruptedException the interrupted exception * @throws InterruptedException the interrupted exception
*/ */
@Handler @Handler
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
public void onConsoleConfigured(ConsoleConfigured event, public void onConsoleConfigured(ConsoleConfigured event,
ConsoleConnection connection) throws InterruptedException, ConsoleConnection connection) throws InterruptedException,
IOException { IOException {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final var rendered = (Set<String>) connection.session().get(RENDERED); final var rendered
= (Set<ResourceModel>) connection.session().get(RENDERED);
connection.session().remove(RENDERED); connection.session().remove(RENDERED);
if (!syncPreviews(connection.session())) { if (!syncPreviews(connection.session())) {
return; return;
} }
addMissingVms(event, connection, rendered);
}
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
private void addMissingVms(ConsoleConfigured event,
ConsoleConnection connection, final Set<ResourceModel> rendered) {
boolean foundMissing = false; boolean foundMissing = false;
for (var vmName : accessibleVms(connection)) { for (var vmName : accessibleVms(connection)) {
if (rendered.contains(vmName)) { if (rendered.stream()
.anyMatch(r -> r.type() == ResourceModel.Type.VM
&& r.name().equals(vmName))) {
continue; continue;
} }
if (!foundMissing) { if (!foundMissing) {
@ -300,13 +312,12 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ViewerModel> {
} }
@Override @Override
protected Optional<ViewerModel> createNewState(AddConletRequest event, protected Optional<ResourceModel> createNewState(AddConletRequest event,
ConsoleConnection connection, String conletId) throws Exception { ConsoleConnection connection, String conletId) throws Exception {
var model = new ViewerModel(conletId); var model = new ResourceModel(conletId);
model.vmName = (String) event.properties().get(VM_NAME_PROPERTY); model.setType(ResourceModel.Type.VM);
if (model.vmName != null) { model
model.setGenerated(true); .setName((String) event.properties().get(VM_NAME_PROPERTY));
}
String jsonState = objectMapper.writeValueAsString(model); String jsonState = objectMapper.writeValueAsString(model);
connection.respond(new KeyValueStoreUpdate().update( connection.respond(new KeyValueStoreUpdate().update(
storagePath(connection.session(), model.getConletId()), jsonState)); storagePath(connection.session(), model.getConletId()), jsonState));
@ -314,9 +325,9 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ViewerModel> {
} }
@Override @Override
protected Optional<ViewerModel> createStateRepresentation(Event<?> event, protected Optional<ResourceModel> createStateRepresentation(Event<?> event,
ConsoleConnection connection, String conletId) throws Exception { ConsoleConnection connection, String conletId) throws Exception {
var model = new ViewerModel(conletId); var model = new ResourceModel(conletId);
String jsonState = objectMapper.writeValueAsString(model); String jsonState = objectMapper.writeValueAsString(model);
connection.respond(new KeyValueStoreUpdate().update( connection.respond(new KeyValueStoreUpdate().update(
storagePath(connection.session(), model.getConletId()), jsonState)); storagePath(connection.session(), model.getConletId()), jsonState));
@ -325,7 +336,7 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ViewerModel> {
@Override @Override
@SuppressWarnings("PMD.EmptyCatchBlock") @SuppressWarnings("PMD.EmptyCatchBlock")
protected Optional<ViewerModel> recreateState(Event<?> event, protected Optional<ResourceModel> recreateState(Event<?> event,
ConsoleConnection channel, String conletId) throws Exception { ConsoleConnection channel, String conletId) throws Exception {
KeyValueStoreQuery query = new KeyValueStoreQuery( KeyValueStoreQuery query = new KeyValueStoreQuery(
storagePath(channel.session(), conletId), channel); storagePath(channel.session(), conletId), channel);
@ -334,8 +345,8 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ViewerModel> {
if (!query.results().isEmpty()) { if (!query.results().isEmpty()) {
var json = query.results().get(0).values().stream().findFirst() var json = query.results().get(0).values().stream().findFirst()
.get(); .get();
ViewerModel model ResourceModel model
= objectMapper.readValue(json, ViewerModel.class); = objectMapper.readValue(json, ResourceModel.class);
return Optional.of(model); return Optional.of(model);
} }
} catch (InterruptedException e) { } catch (InterruptedException e) {
@ -347,58 +358,23 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ViewerModel> {
} }
@Override @Override
@SuppressWarnings({ "PMD.AvoidInstantiatingObjectsInLoops", "unchecked" }) @SuppressWarnings({ "PMD.AvoidInstantiatingObjectsInLoops" })
protected Set<RenderMode> doRenderConlet(RenderConletRequestBase<?> event, protected Set<RenderMode> doRenderConlet(RenderConletRequestBase<?> event,
ConsoleConnection channel, String conletId, ViewerModel model) ConsoleConnection channel, String conletId, ResourceModel model)
throws Exception { throws Exception {
if (event.renderAs().contains(RenderMode.Preview)) {
return renderPreview(event, channel, conletId, model);
}
// Render edit
ResourceBundle resourceBundle = resourceBundle(channel.locale()); ResourceBundle resourceBundle = resourceBundle(channel.locale());
Set<RenderMode> renderedAs = EnumSet.noneOf(RenderMode.class); Set<RenderMode> renderedAs = EnumSet.noneOf(RenderMode.class);
if (event.renderAs().contains(RenderMode.Preview)) {
channel.associated(PENDING, Event.class)
.ifPresent(e -> {
e.resumeHandling();
channel.setAssociated(PENDING, null);
});
// Remove conlet if definition has been removed
if (model.vmName() != null
&& !channelTracker.associated(model.vmName()).isPresent()) {
channel.respond(
new DeleteConlet(conletId, Collections.emptySet()));
return Collections.emptySet();
}
// Don't render if user has not at least one permission
if (model.vmName() != null
&& channelTracker.associated(model.vmName())
.map(d -> permissions(d, channel.session()).isEmpty())
.orElse(true)) {
return Collections.emptySet();
}
// Render
Template tpl
= freemarkerConfig().getTemplate("VmAccess-preview.ftl.html");
channel.respond(new RenderConlet(type(), conletId,
processTemplate(event, tpl,
fmModel(event, channel, conletId, model)))
.setRenderAs(
RenderMode.Preview.addModifiers(event.renderAs()))
.setSupportedModes(syncPreviews(channel.session())
? MODES_FOR_GENERATED
: MODES));
renderedAs.add(RenderMode.Preview);
if (!Strings.isNullOrEmpty(model.vmName())) {
Optional.ofNullable(channel.session().get(RENDERED))
.ifPresent(s -> ((Set<String>) s).add(model.vmName()));
updateConfig(channel, model);
}
}
if (event.renderAs().contains(RenderMode.Edit)) { if (event.renderAs().contains(RenderMode.Edit)) {
Template tpl = freemarkerConfig() Template tpl = freemarkerConfig()
.getTemplate("VmAccess-edit.ftl.html"); .getTemplate("VmAccess-edit.ftl.html");
var fmModel = fmModel(event, channel, conletId, model); var fmModel = fmModel(event, channel, conletId, model);
fmModel.put("vmNames", accessibleVms(channel)); fmModel.put("vmNames", accessibleVms(channel));
fmModel.put("poolNames", accessiblePools(channel));
channel.respond(new OpenModalDialog(type(), conletId, channel.respond(new OpenModalDialog(type(), conletId,
processTemplate(event, tpl, fmModel)) processTemplate(event, tpl, fmModel))
.addOption("cancelable", true) .addOption("cancelable", true)
@ -408,13 +384,69 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ViewerModel> {
return renderedAs; return renderedAs;
} }
@SuppressWarnings("unchecked")
private Set<RenderMode> renderPreview(RenderConletRequestBase<?> event,
ConsoleConnection channel, String conletId, ResourceModel model)
throws TemplateNotFoundException, MalformedTemplateNameException,
ParseException, IOException {
channel.associated(PENDING, Event.class)
.ifPresent(e -> {
e.resumeHandling();
channel.setAssociated(PENDING, null);
});
if (model.type() == ResourceModel.Type.VM && model.name() != null) {
// Remove conlet if VM definition has been removed
// or user has not at least one permission
Optional<VmDefinition> vmDef
= channelTracker.associated(model.name());
if (vmDef.isEmpty()
|| vmPermissions(vmDef.get(), channel.session()).isEmpty()) {
channel.respond(
new DeleteConlet(conletId, Collections.emptySet()));
return Collections.emptySet();
}
}
if (model.type() == ResourceModel.Type.POOL && model.name() != null) {
// Remove conlet if pool definition has been removed
// or user has not at least one permission
VmPool pool = vmPools.get(model.name());
if (pool == null
|| poolPermissions(pool, channel.session()).isEmpty()) {
channel.respond(
new DeleteConlet(conletId, Collections.emptySet()));
return Collections.emptySet();
}
}
// Render
Template tpl
= freemarkerConfig().getTemplate("VmAccess-preview.ftl.html");
channel.respond(new RenderConlet(type(), conletId,
processTemplate(event, tpl,
fmModel(event, channel, conletId, model)))
.setRenderAs(
RenderMode.Preview.addModifiers(event.renderAs()))
.setSupportedModes(syncPreviews(channel.session())
? MODES_FOR_GENERATED
: MODES));
if (!Strings.isNullOrEmpty(model.name())) {
Optional.ofNullable(channel.session().get(RENDERED))
.ifPresent(s -> ((Set<ResourceModel>) s).add(model));
updateConfig(channel, model);
}
return EnumSet.of(RenderMode.Preview);
}
private List<String> accessibleVms(ConsoleConnection channel) { private List<String> accessibleVms(ConsoleConnection channel) {
return channelTracker.associated().stream() return channelTracker.associated().stream()
.filter(d -> !permissions(d, channel.session()).isEmpty()) .filter(d -> !vmPermissions(d, channel.session()).isEmpty())
.map(d -> d.getMetadata().getName()).sorted().toList(); .map(d -> d.getMetadata().getName()).sorted().toList();
} }
private Set<Permission> permissions(VmDefinition vmDef, Session session) { private Set<VmDefinition.Permission> vmPermissions(VmDefinition vmDef,
Session session) {
var user = WebConsoleUtils.userFromSession(session) var user = WebConsoleUtils.userFromSession(session)
.map(ConsoleUser::getName).orElse(null); .map(ConsoleUser::getName).orElse(null);
var roles = WebConsoleUtils.rolesFromSession(session) var roles = WebConsoleUtils.rolesFromSession(session)
@ -422,17 +454,32 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ViewerModel> {
return vmDef.permissionsFor(user, roles); return vmDef.permissionsFor(user, roles);
} }
private void updateConfig(ConsoleConnection channel, ViewerModel model) { private List<String> accessiblePools(ConsoleConnection channel) {
return vmPools.values().stream()
.filter(d -> !poolPermissions(d, channel.session()).isEmpty())
.map(d -> d.name()).sorted().toList();
}
private Set<VmPool.Permission> poolPermissions(VmPool pool,
Session session) {
var user = WebConsoleUtils.userFromSession(session)
.map(ConsoleUser::getName).orElse(null);
var roles = WebConsoleUtils.rolesFromSession(session)
.stream().map(ConsoleRole::getName).toList();
return pool.permissionsFor(user, roles);
}
private void updateConfig(ConsoleConnection channel, ResourceModel model) {
channel.respond(new NotifyConletView(type(), channel.respond(new NotifyConletView(type(),
model.getConletId(), "updateConfig", model.vmName())); model.getConletId(), "updateConfig", model.type(), model.name()));
updateVmDef(channel, model); updateVmDef(channel, model);
} }
private void updateVmDef(ConsoleConnection channel, ViewerModel model) { private void updateVmDef(ConsoleConnection channel, ResourceModel model) {
if (Strings.isNullOrEmpty(model.vmName())) { if (Strings.isNullOrEmpty(model.name())) {
return; return;
} }
channelTracker.value(model.vmName()).ifPresent(item -> { channelTracker.value(model.name()).ifPresent(item -> {
try { try {
var vmDef = item.associated(); var vmDef = item.associated();
var data = Map.of("metadata", var data = Map.of("metadata",
@ -441,8 +488,8 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ViewerModel> {
"spec", vmDef.spec(), "spec", vmDef.spec(),
"status", vmDef.getStatus(), "status", vmDef.getStatus(),
"userPermissions", "userPermissions",
permissions(vmDef, channel.session()).stream() vmPermissions(vmDef, channel.session()).stream()
.map(Permission::toString).toList()); .map(VmDefinition.Permission::toString).toList());
channel.respond(new NotifyConletView(type(), channel.respond(new NotifyConletView(type(),
model.getConletId(), "updateVmDefinition", data)); model.getConletId(), "updateVmDefinition", data));
} catch (JsonSyntaxException e) { } catch (JsonSyntaxException e) {
@ -454,7 +501,8 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ViewerModel> {
@Override @Override
protected void doConletDeleted(ConletDeleted event, protected void doConletDeleted(ConletDeleted event,
ConsoleConnection channel, String conletId, ViewerModel conletState) ConsoleConnection channel, String conletId,
ResourceModel conletState)
throws Exception { throws Exception {
if (event.renderModes().isEmpty()) { if (event.renderModes().isEmpty()) {
channel.respond(new KeyValueStoreUpdate().delete( channel.respond(new KeyValueStoreUpdate().delete(
@ -487,7 +535,7 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ViewerModel> {
for (var conletId : entry.getValue()) { for (var conletId : entry.getValue()) {
var model = stateFromSession(connection.session(), conletId); var model = stateFromSession(connection.session(), conletId);
if (model.isEmpty() if (model.isEmpty()
|| !Objects.areEqual(model.get().vmName(), vmName)) { || !Objects.areEqual(model.get().name(), vmName)) {
continue; continue;
} }
if (event.type() == K8sObserver.ResponseType.DELETED) { if (event.type() == K8sObserver.ResponseType.DELETED) {
@ -500,21 +548,36 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ViewerModel> {
} }
} }
/**
* On vm pool changed.
*
* @param event the event
* @param channel the channel
*/
@Handler(namedChannels = "manager")
public void onVmPoolChanged(VmPoolChanged event) {
if (event.deleted()) {
vmPools.remove(event.vmPool().name());
return;
}
vmPools.put(event.vmPool().name(), event.vmPool());
}
@Override @Override
@SuppressWarnings({ "PMD.AvoidDecimalLiteralsInBigDecimalConstructor", @SuppressWarnings({ "PMD.AvoidDecimalLiteralsInBigDecimalConstructor",
"PMD.ConfusingArgumentToVarargsMethod", "PMD.NcssCount", "PMD.ConfusingArgumentToVarargsMethod", "PMD.NcssCount",
"PMD.AvoidLiteralsInIfCondition" }) "PMD.AvoidLiteralsInIfCondition" })
protected void doUpdateConletState(NotifyConletModel event, protected void doUpdateConletState(NotifyConletModel event,
ConsoleConnection channel, ViewerModel model) ConsoleConnection channel, ResourceModel model)
throws Exception { throws Exception {
event.stop(); event.stop();
if ("selectedVm".equals(event.method())) { if ("selectedResource".equals(event.method())) {
selectVm(event, channel, model); selectResource(event, channel, model);
return; return;
} }
// Handle command for selected VM // Handle command for selected VM
var both = Optional.ofNullable(model.vmName()) var both = Optional.ofNullable(model.name())
.flatMap(vm -> channelTracker.value(vm)); .flatMap(vm -> channelTracker.value(vm));
if (both.isEmpty()) { if (both.isEmpty()) {
return; return;
@ -522,31 +585,31 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ViewerModel> {
var vmChannel = both.get().channel(); var vmChannel = both.get().channel();
var vmDef = both.get().associated(); var vmDef = both.get().associated();
var vmName = vmDef.metadata().getName(); var vmName = vmDef.metadata().getName();
var perms = permissions(vmDef, channel.session()); var perms = vmPermissions(vmDef, channel.session());
var resourceBundle = resourceBundle(channel.locale()); var resourceBundle = resourceBundle(channel.locale());
switch (event.method()) { switch (event.method()) {
case "start": case "start":
if (perms.contains(Permission.START)) { if (perms.contains(VmDefinition.Permission.START)) {
fire(new ModifyVm(vmName, "state", "Running", vmChannel)); fire(new ModifyVm(vmName, "state", "Running", vmChannel));
} }
break; break;
case "stop": case "stop":
if (perms.contains(Permission.STOP)) { if (perms.contains(VmDefinition.Permission.STOP)) {
fire(new ModifyVm(vmName, "state", "Stopped", vmChannel)); fire(new ModifyVm(vmName, "state", "Stopped", vmChannel));
} }
break; break;
case "reset": case "reset":
if (perms.contains(Permission.RESET)) { if (perms.contains(VmDefinition.Permission.RESET)) {
confirmReset(event, channel, model, resourceBundle); confirmReset(event, channel, model, resourceBundle);
} }
break; break;
case "resetConfirmed": case "resetConfirmed":
if (perms.contains(Permission.RESET)) { if (perms.contains(VmDefinition.Permission.RESET)) {
fire(new ResetVm(vmName), vmChannel); fire(new ResetVm(vmName), vmChannel);
} }
break; break;
case "openConsole": case "openConsole":
if (perms.contains(Permission.ACCESS_CONSOLE)) { if (perms.contains(VmDefinition.Permission.ACCESS_CONSOLE)) {
var user = WebConsoleUtils.userFromSession(channel.session()) var user = WebConsoleUtils.userFromSession(channel.session())
.map(ConsoleUser::getName).orElse(""); .map(ConsoleUser::getName).orElse("");
var pwQuery var pwQuery
@ -561,17 +624,26 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ViewerModel> {
} }
} }
private void selectVm(NotifyConletModel event, ConsoleConnection channel, @SuppressWarnings({ "PMD.AvoidLiteralsInIfCondition",
ViewerModel model) throws JsonProcessingException { "PMD.UseLocaleWithCaseConversions" })
model.setVmName(event.param(0)); private void selectResource(NotifyConletModel event,
String jsonState = objectMapper.writeValueAsString(model); ConsoleConnection channel, ResourceModel model)
channel.respond(new KeyValueStoreUpdate().update(storagePath( throws JsonProcessingException {
channel.session(), model.getConletId()), jsonState)); try {
updateConfig(channel, model); model.setType(ResourceModel.Type
.valueOf(event.<String> param(0).toUpperCase()));
model.setName(event.param(1));
String jsonState = objectMapper.writeValueAsString(model);
channel.respond(new KeyValueStoreUpdate().update(storagePath(
channel.session(), model.getConletId()), jsonState));
updateConfig(channel, model);
} catch (IllegalArgumentException e) {
logger.warning(() -> "Invalid resource type: " + e.getMessage());
}
} }
private void openConsole(String vmName, ConsoleConnection connection, private void openConsole(String vmName, ConsoleConnection connection,
ViewerModel model, String password) { ResourceModel model, String password) {
var vmDef = channelTracker.associated(vmName).orElse(null); var vmDef = channelTracker.associated(vmName).orElse(null);
if (vmDef == null) { if (vmDef == null) {
return; return;
@ -642,7 +714,7 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ViewerModel> {
} }
private void confirmReset(NotifyConletModel event, private void confirmReset(NotifyConletModel event,
ConsoleConnection channel, ViewerModel model, ConsoleConnection channel, ResourceModel model,
ResourceBundle resourceBundle) throws TemplateNotFoundException, ResourceBundle resourceBundle) throws TemplateNotFoundException,
MalformedTemplateNameException, ParseException, IOException { MalformedTemplateNameException, ParseException, IOException {
Template tpl = freemarkerConfig() Template tpl = freemarkerConfig()
@ -662,58 +734,97 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ViewerModel> {
} }
/** /**
* The Class VmsModel. * The Class AccessModel.
*/ */
@SuppressWarnings("PMD.DataClass") @SuppressWarnings("PMD.DataClass")
public static class ViewerModel extends ConletBaseModel { public static class ResourceModel extends ConletBaseModel {
private String vmName;
private boolean generated;
/** /**
* Instantiates a new vms model. * The Enum ResourceType.
*/
@SuppressWarnings("PMD.ShortVariable")
public enum Type {
VM, POOL
}
private Type type;
private String name;
/**
* Instantiates a new resource model.
* *
* @param conletId the conlet id * @param conletId the conlet id
*/ */
public ViewerModel(@JsonProperty("conletId") String conletId) { public ResourceModel(@JsonProperty("conletId") String conletId) {
super(conletId); super(conletId);
} }
/** /**
* Gets the vm name. * Gets the resource name.
* *
* @return the vmName * @return the string
*/ */
@JsonGetter("vmName") @JsonGetter("name")
public String vmName() { public String name() {
return vmName; return name;
} }
/** /**
* Sets the vm name. * Sets the name.
* *
* @param vmName the vmName to set * @param name the resource name to set
*/ */
public void setVmName(String vmName) { public void setName(String name) {
this.vmName = vmName; this.name = name;
} }
/** /**
* Checks if is generated. * @return the resourceType
*
* @return the generated
*/ */
public boolean isGenerated() { @JsonGetter("type")
return generated; public Type type() {
return type;
} }
/** /**
* Sets the generated. * Sets the type.
* *
* @param generated the generated to set * @param type the resource type to set
*/ */
public void setGenerated(boolean generated) { public void setType(Type type) {
this.generated = generated; this.type = type;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + java.util.Objects.hash(name, type);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ResourceModel other = (ResourceModel) obj;
return java.util.Objects.equals(name, other.name)
&& type == other.type;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(50);
builder.append("AccessModel [resourceType=").append(type)
.append(", resourceName=").append(name).append(']');
return builder.toString();
} }
} }

View file

@ -44,6 +44,7 @@ interface Api {
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
vmName: string; vmName: string;
vmDefinition: any; vmDefinition: any;
poolName: string;
} }
const localize = (key: string) => { const localize = (key: string) => {
@ -62,7 +63,8 @@ window.orgJDrupesVmOperatorVmAccess.initPreview = (previewDom: HTMLElement,
const previewApi: Api = reactive({ const previewApi: Api = reactive({
vmName: "", vmName: "",
vmDefinition: {} vmDefinition: {},
poolName: ""
}); });
const configured = computed(() => previewApi.vmDefinition.spec); const configured = computed(() => previewApi.vmDefinition.spec);
const startable = computed(() => previewApi.vmDefinition.spec && const startable = computed(() => previewApi.vmDefinition.spec &&
@ -76,7 +78,8 @@ window.orgJDrupesVmOperatorVmAccess.initPreview = (previewDom: HTMLElement,
const permissions = computed(() => previewApi.vmDefinition.spec const permissions = computed(() => previewApi.vmDefinition.spec
? previewApi.vmDefinition.userPermissions : []); ? previewApi.vmDefinition.userPermissions : []);
watch(() => previewApi.vmName, (name: string) => { watch(previewApi, (api: Api) => {
const name = api.vmName || api.poolName;
if (name !== "") { if (name !== "") {
JGConsole.instance.updateConletTitle(conletId, name); JGConsole.instance.updateConletTitle(conletId, name);
} }
@ -139,14 +142,21 @@ window.orgJDrupesVmOperatorVmAccess.initPreview = (previewDom: HTMLElement,
}; };
JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmaccess.VmAccess", JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmaccess.VmAccess",
"updateConfig", function(conletId: string, vmName: string) { "updateConfig",
function(conletId: string, type: string, resource: string) {
const conlet = JGConsole.findConletPreview(conletId); const conlet = JGConsole.findConletPreview(conletId);
if (!conlet) { if (!conlet) {
return; return;
} }
const api = getApi<Api>(conlet.element().querySelector( const api = getApi<Api>(conlet.element().querySelector(
":scope .jdrupes-vmoperator-vmaccess-preview"))!; ":scope .jdrupes-vmoperator-vmaccess-preview"))!;
api.vmName = vmName; if (type === "VM") {
api.vmName = resource;
api.poolName = "";
} else {
api.poolName = resource;
api.vmName = "";
}
}); });
JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmaccess.VmAccess", JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmaccess.VmAccess",
@ -203,19 +213,36 @@ window.orgJDrupesVmOperatorVmAccess.initEdit = (dialogDom: HTMLElement,
l10nBundles, JGWC.lang()!, key); l10nBundles, JGWC.lang()!, key);
}; };
const resource = ref<string>("vm");
const vmNameInput = ref<string>(""); const vmNameInput = ref<string>("");
const poolNameInput = ref<string>("");
watch(resource, (resource: string) => {
if (resource === "vm") {
poolNameInput.value = "";
}
if (resource === "pool")
vmNameInput.value = "";
});
const conletId = (<HTMLElement>dialogDom.closest( const conletId = (<HTMLElement>dialogDom.closest(
"[data-conlet-id]")!).dataset["conletId"]!; "[data-conlet-id]")!).dataset["conletId"]!;
const conlet = JGConsole.findConletPreview(conletId); const conlet = JGConsole.findConletPreview(conletId);
if (conlet) { if (conlet) {
const api = getApi<Api>(conlet.element().querySelector( const api = getApi<Api>(conlet.element().querySelector(
":scope .jdrupes-vmoperator-vmaccess-preview"))!; ":scope .jdrupes-vmoperator-vmaccess-preview"))!;
if (api.poolName) {
resource.value = "pool";
}
vmNameInput.value = api.vmName; vmNameInput.value = api.vmName;
poolNameInput.value = api.poolName;
} }
provideApi(dialogDom, vmNameInput); provideApi(dialogDom, { resource: () => resource.value,
name: () => resource.value === "vm"
? vmNameInput.value : poolNameInput.value });
return { formId, localize, vmNameInput }; return { formId, localize, resource, vmNameInput, poolNameInput };
} }
}); });
app.use(JgwcPlugin); app.use(JgwcPlugin);
@ -229,8 +256,9 @@ window.orgJDrupesVmOperatorVmAccess.applyEdit =
} }
const conletId = (<HTMLElement>dialogDom.closest("[data-conlet-id]")!) const conletId = (<HTMLElement>dialogDom.closest("[data-conlet-id]")!)
.dataset["conletId"]!; .dataset["conletId"]!;
const vmName = getApi<ref<string>>(dialogDom!)!.value; const editApi = getApi<ref<string>>(dialogDom!)!;
JGConsole.notifyConletModel(conletId, "selectedVm", vmName); JGConsole.notifyConletModel(conletId, "selectedResource", editApi.resource(),
editApi.name());
} }
window.orgJDrupesVmOperatorVmAccess.confirmReset = window.orgJDrupesVmOperatorVmAccess.confirmReset =

View file

@ -77,6 +77,11 @@
} }
.jdrupes-vmoperator-vmaccess.jdrupes-vmoperator-vmaccess-edit { .jdrupes-vmoperator-vmaccess.jdrupes-vmoperator-vmaccess-edit {
fieldset ul li {
margin-top: 0.5em;
}
select { select {
width: 15em; width: 15em;
} }