Pool configurable in GUI.
This commit is contained in:
parent
77cfcff2ed
commit
367aebeee5
6 changed files with 296 additions and 134 deletions
|
|
@ -60,6 +60,8 @@ public class VmPool {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* All permissions.
|
||||||
|
*
|
||||||
* @return the permissions
|
* @return the permissions
|
||||||
*/
|
*/
|
||||||
public List<Grant> permissions() {
|
public List<Grant> permissions() {
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 =
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue