Feature/web gui (#15)
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
Add node display.
This commit is contained in:
parent
b8e1074150
commit
8567a2f052
10 changed files with 90 additions and 28 deletions
|
|
@ -23,6 +23,9 @@ package org.jdrupes.vmoperator.common;
|
||||||
*/
|
*/
|
||||||
public class Constants {
|
public class Constants {
|
||||||
|
|
||||||
|
/** The Constant APP_NAME. */
|
||||||
|
public static final String APP_NAME = "vm-runner";
|
||||||
|
|
||||||
/** The Constant VM_OP_NAME. */
|
/** The Constant VM_OP_NAME. */
|
||||||
public static final String VM_OP_NAME = "vm-operator";
|
public static final String VM_OP_NAME = "vm-operator";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ dependencies {
|
||||||
implementation 'org.jgrapes:org.jgrapes.util:[1.31.0,2)'
|
implementation 'org.jgrapes:org.jgrapes.util:[1.31.0,2)'
|
||||||
|
|
||||||
implementation 'org.jgrapes:org.jgrapes.webconsole.base:[1.2.0,2)'
|
implementation 'org.jgrapes:org.jgrapes.webconsole.base:[1.2.0,2)'
|
||||||
implementation 'org.jgrapes:org.jgrapes.webconsole.vuejs:[1.3.0,2)'
|
implementation 'org.jgrapes:org.jgrapes.webconsole.vuejs:[1.5.0,2)'
|
||||||
implementation 'org.jgrapes:org.jgrapes.webconsole.rbac:[1.0.0,2)'
|
implementation 'org.jgrapes:org.jgrapes.webconsole.rbac:[1.0.0,2)'
|
||||||
implementation 'org.jgrapes:org.jgrapes.webconlet.locallogin:[1.0.0,2)'
|
implementation 'org.jgrapes:org.jgrapes.webconlet.locallogin:[1.0.0,2)'
|
||||||
|
|
||||||
|
|
@ -39,7 +39,7 @@ dependencies {
|
||||||
|
|
||||||
application {
|
application {
|
||||||
applicationName = 'vm-manager'
|
applicationName = 'vm-manager'
|
||||||
applicationDefaultJvmArgs = ['-Xmx50m', '-XX:+UseParallelGC',
|
applicationDefaultJvmArgs = ['-Xmx128m', '-XX:+UseParallelGC',
|
||||||
'-Djava.util.logging.manager=org.jdrupes.vmoperator.util.LongLoggingManager'
|
'-Djava.util.logging.manager=org.jdrupes.vmoperator.util.LongLoggingManager'
|
||||||
]
|
]
|
||||||
// Define the main class for the application.
|
// Define the main class for the application.
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,6 @@ package org.jdrupes.vmoperator.manager;
|
||||||
*/
|
*/
|
||||||
public class Constants extends org.jdrupes.vmoperator.common.Constants {
|
public class Constants extends org.jdrupes.vmoperator.common.Constants {
|
||||||
|
|
||||||
/** The Constant APP_NAME. */
|
|
||||||
public static final String APP_NAME = "vm-runner";
|
|
||||||
|
|
||||||
/** The Constant STATE_RUNNING. */
|
/** The Constant STATE_RUNNING. */
|
||||||
public static final String STATE_RUNNING = "Running";
|
public static final String STATE_RUNNING = "Running";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@
|
||||||
|
|
||||||
package org.jdrupes.vmoperator.manager;
|
package org.jdrupes.vmoperator.manager;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import io.kubernetes.client.openapi.ApiClient;
|
import io.kubernetes.client.openapi.ApiClient;
|
||||||
import io.kubernetes.client.openapi.ApiException;
|
import io.kubernetes.client.openapi.ApiException;
|
||||||
|
|
@ -31,6 +33,7 @@ import io.kubernetes.client.openapi.models.V1ObjectMeta;
|
||||||
import io.kubernetes.client.util.Config;
|
import io.kubernetes.client.util.Config;
|
||||||
import io.kubernetes.client.util.Watch;
|
import io.kubernetes.client.util.Watch;
|
||||||
import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi;
|
import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi;
|
||||||
|
import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject;
|
||||||
import io.kubernetes.client.util.generic.options.ListOptions;
|
import io.kubernetes.client.util.generic.options.ListOptions;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
|
@ -52,6 +55,7 @@ import static org.jdrupes.vmoperator.manager.Constants.VM_OP_NAME;
|
||||||
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.VmDefChanged.Type;
|
import org.jdrupes.vmoperator.manager.events.VmDefChanged.Type;
|
||||||
|
import org.jdrupes.vmoperator.util.GsonPtr;
|
||||||
import org.jgrapes.core.Channel;
|
import org.jgrapes.core.Channel;
|
||||||
import org.jgrapes.core.Component;
|
import org.jgrapes.core.Component;
|
||||||
import org.jgrapes.core.Components;
|
import org.jgrapes.core.Components;
|
||||||
|
|
@ -251,8 +255,8 @@ public class VmWatcher extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleVmDefinitionChange(V1APIResource vmsCrd,
|
private void handleVmDefinitionChange(V1APIResource vmsCrd,
|
||||||
Watch.Response<V1Namespace> item) {
|
Watch.Response<V1Namespace> vmDefStub) {
|
||||||
V1ObjectMeta metadata = item.object.getMetadata();
|
V1ObjectMeta metadata = vmDefStub.object.getMetadata();
|
||||||
VmChannel channel = channels.computeIfAbsent(metadata.getName(),
|
VmChannel channel = channels.computeIfAbsent(metadata.getName(),
|
||||||
k -> {
|
k -> {
|
||||||
try {
|
try {
|
||||||
|
|
@ -268,20 +272,52 @@ public class VmWatcher extends Component {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (event.type() == Type.DELETED) {
|
|
||||||
|
|
||||||
// Get full definition and associate with channel as backup
|
// Get full definition and associate with channel as backup
|
||||||
var apiVersion = K8s.version(item.object.getApiVersion());
|
var apiVersion = K8s.version(vmDefStub.object.getApiVersion());
|
||||||
DynamicKubernetesApi vmCrApi = new DynamicKubernetesApi(VM_OP_GROUP,
|
DynamicKubernetesApi vmCrApi = new DynamicKubernetesApi(VM_OP_GROUP,
|
||||||
apiVersion, vmsCrd.getName(), channel.client());
|
apiVersion, vmsCrd.getName(), channel.client());
|
||||||
var vmDef = K8s.get(vmCrApi, metadata);
|
var curVmDef = K8s.get(vmCrApi, metadata);
|
||||||
vmDef.ifPresent(def -> channel.setVmDefinition(def));
|
curVmDef.ifPresent(def -> channel.setVmDefinition(def));
|
||||||
|
|
||||||
|
// Get eventual definition and augment with "dynamic" data.
|
||||||
|
var vmDef = curVmDef.orElse(channel.vmDefinition());
|
||||||
|
addDynamicData(channel, vmDef);
|
||||||
|
|
||||||
// Create and fire event
|
// Create and fire event
|
||||||
channel.pipeline().fire(new VmDefChanged(VmDefChanged.Type
|
channel.pipeline().fire(new VmDefChanged(VmDefChanged.Type
|
||||||
.valueOf(item.type),
|
.valueOf(vmDefStub.type),
|
||||||
channel.setGeneration(item.object.getMetadata().getGeneration()),
|
channel
|
||||||
vmsCrd, vmDef.orElse(channel.vmDefinition())), channel);
|
.setGeneration(vmDefStub.object.getMetadata().getGeneration()),
|
||||||
|
vmsCrd, vmDef), channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addDynamicData(VmChannel channel,
|
||||||
|
DynamicKubernetesObject vmDef) {
|
||||||
|
var rootNode = GsonPtr.to(vmDef.getRaw()).get(JsonObject.class);
|
||||||
|
rootNode.addProperty("nodeName", "");
|
||||||
|
|
||||||
|
// VM definition status changes before the pod terminates.
|
||||||
|
// This results in pod information being shown for a stopped
|
||||||
|
// VM which is irritating. So check condition first.
|
||||||
|
var isRunning = GsonPtr.to(rootNode).to("status", "conditions")
|
||||||
|
.get(JsonArray.class)
|
||||||
|
.asList().stream().filter(el -> "Running"
|
||||||
|
.equals(((JsonObject) el).get("type").getAsString()))
|
||||||
|
.findFirst().map(el -> "True"
|
||||||
|
.equals(((JsonObject) el).get("status").getAsString()))
|
||||||
|
.orElse(false);
|
||||||
|
if (!isRunning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var podSearch = new ListOptions();
|
||||||
|
podSearch.setLabelSelector("app.kubernetes.io/name=" + APP_NAME
|
||||||
|
+ ",app.kubernetes.io/component=" + APP_NAME
|
||||||
|
+ ",app.kubernetes.io/instance=" + vmDef.getMetadata().getName());
|
||||||
|
var podList = K8s.podApi(channel.client()).list(namespaceToWatch,
|
||||||
|
podSearch);
|
||||||
|
podList.getObject().getItems().stream().forEach(pod -> {
|
||||||
|
rootNode.addProperty("nodeName", pod.getSpec().getNodeName());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -33,26 +33,33 @@
|
||||||
<tr :class="[(rowIndex % 2) ? 'odd' : 'even']"
|
<tr :class="[(rowIndex % 2) ? 'odd' : 'even']"
|
||||||
:aria-expanded="(entry.name in detailsByName) ? 'true' : 'false'">
|
:aria-expanded="(entry.name in detailsByName) ? 'true' : 'false'">
|
||||||
<td v-for="key in controller.keys"
|
<td v-for="key in controller.keys"
|
||||||
|
v-bind:class="'column-' + key"
|
||||||
v-bind:title="key == 'name' ? entry['name']: false"
|
v-bind:title="key == 'name' ? entry['name']: false"
|
||||||
v-bind:rowspan="(key == 'name') && $aash.isDisclosed(scopedId(rowIndex)) ? 2 : false">
|
v-bind:rowspan="(key == 'name') && $aash.isDisclosed(scopedId(rowIndex)) ? 2 : false">
|
||||||
<aash-disclosure-button v-if="key === 'name'" :type="'div'"
|
<aash-disclosure-button v-if="key === 'name'" :type="'div'"
|
||||||
:id-ref="scopedId(rowIndex)">
|
:id-ref="scopedId(rowIndex)">
|
||||||
<span v-html="controller.breakBeforeDots(entry[key])"></span>
|
<span v-html="controller.breakBeforeDots(entry[key])"></span>
|
||||||
</aash-disclosure-button>
|
</aash-disclosure-button>
|
||||||
<span v-else-if="key === 'running'"
|
<span v-else-if="key === 'running' && entry[key]"
|
||||||
v-html="localize(entry[key] ? 'Yes' : 'No')"></span>
|
class="fa fa-check" :title="localize('Yes')"></span>
|
||||||
|
<span v-else-if="key === 'running' && !entry[key]"
|
||||||
|
class="fa fa-close" :title="localize('No')"></span>
|
||||||
<span v-else-if="key === 'currentRam'"
|
<span v-else-if="key === 'currentRam'"
|
||||||
v-html="formatMemory(BigInt(entry[key]))"></span>
|
v-html="formatMemory(entry[key])"></span>
|
||||||
<span v-else
|
<span v-else
|
||||||
v-html="controller.breakBeforeDots(entry[key])"></span>
|
v-html="controller.breakBeforeDots(entry[key])"></span>
|
||||||
</td>
|
</td>
|
||||||
<td class="jdrupes-vmoperator-vmconlet-view-action-list">
|
<td class="jdrupes-vmoperator-vmconlet-view-action-list">
|
||||||
<span role="button" v-if="!entry['running']"
|
<span role="button" v-if="entry.spec.vm.state != 'Running'"
|
||||||
tabindex="0" class="fa fa-play" :title="localize('Start VM')"
|
tabindex="0" class="fa fa-play" :title="localize('Start VM')"
|
||||||
v-on:click="vmAction(entry.name, 'start')"></span>
|
v-on:click="vmAction(entry.name, 'start')"></span>
|
||||||
<span role="button" v-if="entry['running']"
|
<span role="button" v-else class="fa fa-play"
|
||||||
|
aria-disabled="true" :title="localize('Start VM')"></span>
|
||||||
|
<span role="button" v-if="entry.spec.vm.state != 'Stopped'"
|
||||||
tabindex="0" class="fa fa-stop" :title="localize('Stop VM')"
|
tabindex="0" class="fa fa-stop" :title="localize('Stop VM')"
|
||||||
v-on:click="vmAction(entry.name, 'stop')"></span>
|
v-on:click="vmAction(entry.name, 'stop')"></span>
|
||||||
|
<span role="button" v-else class="fa fa-stop"
|
||||||
|
aria-disabled="true" :title="localize('Stop VM')"></span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr :id="scopedId(rowIndex)" v-if="$aash.isDisclosed(scopedId(rowIndex))"
|
<tr :id="scopedId(rowIndex)" v-if="$aash.isDisclosed(scopedId(rowIndex))"
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ currentCpus = Current CPUs
|
||||||
currentRam = Current RAM
|
currentRam = Current RAM
|
||||||
maximumCpus = Maximum CPUs
|
maximumCpus = Maximum CPUs
|
||||||
maximumRam = Maximum RAM
|
maximumRam = Maximum RAM
|
||||||
|
nodeName = Node
|
||||||
requestedCpus = Requested CPUs
|
requestedCpus = Requested CPUs
|
||||||
requestedRam = Requested RAM
|
requestedRam = Requested RAM
|
||||||
running = Running
|
running = Running
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ currentCpus = Aktuelle CPUs
|
||||||
currentRam = Akuelles RAM
|
currentRam = Akuelles RAM
|
||||||
maximumCpus = Maximale CPUs
|
maximumCpus = Maximale CPUs
|
||||||
maximumRam = Maximales RAM
|
maximumRam = Maximales RAM
|
||||||
|
nodeName = Knoten
|
||||||
requestedCpus = Angeforderte CPUs
|
requestedCpus = Angeforderte CPUs
|
||||||
requestedRam = Angefordertes RAM
|
requestedRam = Angefordertes RAM
|
||||||
vmActions = Aktionen
|
vmActions = Aktionen
|
||||||
|
|
|
||||||
|
|
@ -166,11 +166,12 @@ public class VmConlet extends FreeMarkerConlet<VmConlet.VmsModel> {
|
||||||
public void onVmDefChanged(VmDefChanged event, VmChannel channel)
|
public void onVmDefChanged(VmDefChanged event, VmChannel channel)
|
||||||
throws JsonDecodeException {
|
throws JsonDecodeException {
|
||||||
if (event.type() == Type.DELETED) {
|
if (event.type() == Type.DELETED) {
|
||||||
vmInfos.remove(event.vmDefinition().getMetadata().getName());
|
var vmName = event.vmDefinition().getMetadata().getName();
|
||||||
|
vmInfos.remove(vmName);
|
||||||
for (var entry : conletIdsByConsoleConnection().entrySet()) {
|
for (var entry : conletIdsByConsoleConnection().entrySet()) {
|
||||||
for (String conletId : entry.getValue()) {
|
for (String conletId : entry.getValue()) {
|
||||||
entry.getKey().respond(new NotifyConletView(type(),
|
entry.getKey().respond(new NotifyConletView(type(),
|
||||||
conletId, "removeVm"));
|
conletId, "removeVm", vmName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ window.orgJDrupesVmOperatorVmConlet = {};
|
||||||
let vmInfos = reactive(new Map());
|
let vmInfos = reactive(new Map());
|
||||||
|
|
||||||
window.orgJDrupesVmOperatorVmConlet.initPreview
|
window.orgJDrupesVmOperatorVmConlet.initPreview
|
||||||
= (previewDom: HTMLElement, isUpdate: boolean) => {
|
= (previewDom: HTMLElement, _isUpdate: boolean) => {
|
||||||
const app = createApp({});
|
const app = createApp({});
|
||||||
app.use(JgwcPlugin, []);
|
app.use(JgwcPlugin, []);
|
||||||
app.config.globalProperties.window = window;
|
app.config.globalProperties.window = window;
|
||||||
|
|
@ -81,7 +81,7 @@ window.orgJDrupesVmOperatorVmConlet.initPreview
|
||||||
};
|
};
|
||||||
|
|
||||||
window.orgJDrupesVmOperatorVmConlet.initView = (viewDom: HTMLElement,
|
window.orgJDrupesVmOperatorVmConlet.initView = (viewDom: HTMLElement,
|
||||||
isUpdate: boolean) => {
|
_isUpdate: boolean) => {
|
||||||
const app = createApp({
|
const app = createApp({
|
||||||
setup(_props: any) {
|
setup(_props: any) {
|
||||||
const conletId: string
|
const conletId: string
|
||||||
|
|
@ -96,7 +96,8 @@ window.orgJDrupesVmOperatorVmConlet.initView = (viewDom: HTMLElement,
|
||||||
["name", "vmname"],
|
["name", "vmname"],
|
||||||
["running", "running"],
|
["running", "running"],
|
||||||
["currentCpus", "currentCpus"],
|
["currentCpus", "currentCpus"],
|
||||||
["currentRam", "currentRam"]
|
["currentRam", "currentRam"],
|
||||||
|
["nodeName", "nodeName"]
|
||||||
], {
|
], {
|
||||||
sortKey: "name",
|
sortKey: "name",
|
||||||
sortOrder: "up"
|
sortOrder: "up"
|
||||||
|
|
@ -127,11 +128,11 @@ window.orgJDrupesVmOperatorVmConlet.initView = (viewDom: HTMLElement,
|
||||||
};
|
};
|
||||||
|
|
||||||
JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmconlet.VmConlet",
|
JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmconlet.VmConlet",
|
||||||
"updateVm", function(conletId: String, vmDefinition: any) {
|
"updateVm", function(_conletId: String, vmDefinition: any) {
|
||||||
// Add some short-cuts for table controller
|
// Add some short-cuts for table controller
|
||||||
vmDefinition.name = vmDefinition.metadata.name;
|
vmDefinition.name = vmDefinition.metadata.name;
|
||||||
vmDefinition.currentCpus = vmDefinition.status.cpus;
|
vmDefinition.currentCpus = vmDefinition.status.cpus;
|
||||||
vmDefinition.currentRam = vmDefinition.status.ram;
|
vmDefinition.currentRam = BigInt(vmDefinition.status.ram);
|
||||||
for (let condition of vmDefinition.status.conditions) {
|
for (let condition of vmDefinition.status.conditions) {
|
||||||
if (condition.type === "Running") {
|
if (condition.type === "Running") {
|
||||||
vmDefinition.running = condition.status === "True";
|
vmDefinition.running = condition.status === "True";
|
||||||
|
|
@ -143,6 +144,6 @@ JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmconlet.VmConlet",
|
||||||
});
|
});
|
||||||
|
|
||||||
JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmconlet.VmConlet",
|
JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmconlet.VmConlet",
|
||||||
"removeVm", function(conletId: String, vmName: String) {
|
"removeVm", function(_conletId: String, vmName: String) {
|
||||||
vmInfos.delete(vmName);
|
vmInfos.delete(vmName);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -49,3 +49,18 @@
|
||||||
padding-left: 1em;
|
padding-left: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.jdrupes-vmoperator-vmconlet-view-table {
|
||||||
|
td.column-running {
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
span {
|
||||||
|
&.fa-check {
|
||||||
|
color: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.fa-close {
|
||||||
|
color: var(--danger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue