Add tracking of client (viewer) connection state and visualize in GUI.
This commit is contained in:
parent
9019907224
commit
355eded86b
11 changed files with 331 additions and 6 deletions
|
|
@ -1457,6 +1457,11 @@ spec:
|
||||||
Amount of memory in use.
|
Amount of memory in use.
|
||||||
type: string
|
type: string
|
||||||
default: "0"
|
default: "0"
|
||||||
|
consoleClient:
|
||||||
|
description: >-
|
||||||
|
The hostname of the currently connected client.
|
||||||
|
type: string
|
||||||
|
default: ""
|
||||||
displayPasswordSerial:
|
displayPasswordSerial:
|
||||||
description: >-
|
description: >-
|
||||||
Counts changes of the display password. Set to -1
|
Counts changes of the display password. Set to -1
|
||||||
|
|
@ -1473,6 +1478,12 @@ spec:
|
||||||
lastTransitionTime: "1970-01-01T00:00:00Z"
|
lastTransitionTime: "1970-01-01T00:00:00Z"
|
||||||
reason: Creation
|
reason: Creation
|
||||||
message: "Creation of CR"
|
message: "Creation of CR"
|
||||||
|
- type: ConsoleConnected
|
||||||
|
status: "False"
|
||||||
|
observedGeneration: 1
|
||||||
|
lastTransitionTime: "1970-01-01T00:00:00Z"
|
||||||
|
reason: Creation
|
||||||
|
message: "Creation of CR"
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: object
|
type: object
|
||||||
|
|
|
||||||
|
|
@ -30,10 +30,13 @@ import java.math.BigDecimal;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import static org.jdrupes.vmoperator.common.Constants.APP_NAME;
|
import static org.jdrupes.vmoperator.common.Constants.APP_NAME;
|
||||||
import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP;
|
import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP;
|
||||||
import static org.jdrupes.vmoperator.common.Constants.VM_OP_KIND_VM;
|
import static org.jdrupes.vmoperator.common.Constants.VM_OP_KIND_VM;
|
||||||
|
|
@ -50,6 +53,8 @@ import org.jdrupes.vmoperator.runner.qemu.events.HotpluggableCpuStatus;
|
||||||
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange;
|
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange;
|
||||||
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.RunState;
|
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.RunState;
|
||||||
import org.jdrupes.vmoperator.runner.qemu.events.ShutdownEvent;
|
import org.jdrupes.vmoperator.runner.qemu.events.ShutdownEvent;
|
||||||
|
import org.jdrupes.vmoperator.runner.qemu.events.SpiceConnectedEvent;
|
||||||
|
import org.jdrupes.vmoperator.runner.qemu.events.SpiceDisconnectedEvent;
|
||||||
import org.jdrupes.vmoperator.util.GsonPtr;
|
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;
|
||||||
|
|
@ -275,6 +280,7 @@ public class StatusUpdater extends Component {
|
||||||
|
|
||||||
private void updateRunningCondition(RunnerStateChange event,
|
private void updateRunningCondition(RunnerStateChange event,
|
||||||
K8sDynamicModel from, JsonObject cond) {
|
K8sDynamicModel from, JsonObject cond) {
|
||||||
|
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
|
||||||
boolean reportedRunning
|
boolean reportedRunning
|
||||||
= "True".equals(cond.get("status").getAsString());
|
= "True".equals(cond.get("status").getAsString());
|
||||||
if (RUNNING_STATES.contains(event.runState())
|
if (RUNNING_STATES.contains(event.runState())
|
||||||
|
|
@ -363,4 +369,91 @@ public class StatusUpdater extends Component {
|
||||||
public void onShutdown(ShutdownEvent event) throws ApiException {
|
public void onShutdown(ShutdownEvent event) throws ApiException {
|
||||||
shutdownByGuest = event.byGuest();
|
shutdownByGuest = event.byGuest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On spice connected.
|
||||||
|
*
|
||||||
|
* @param event the event
|
||||||
|
* @throws ApiException the api exception
|
||||||
|
*/
|
||||||
|
@Handler
|
||||||
|
public void onSpiceConnected(SpiceConnectedEvent event)
|
||||||
|
throws ApiException {
|
||||||
|
if (vmStub == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
vmStub.updateStatus(from -> {
|
||||||
|
JsonObject status = from.status();
|
||||||
|
status.addProperty("consoleClient", event.clientHost());
|
||||||
|
updateConsoleConnectedCondition(from, status, true);
|
||||||
|
return status;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Log event
|
||||||
|
var evt = new EventsV1Event()
|
||||||
|
.reportingController(VM_OP_GROUP + "/" + APP_NAME)
|
||||||
|
.action("ConsoleConnectionUpdate")
|
||||||
|
.reason("Connection from " + event.clientHost());
|
||||||
|
K8s.createEvent(apiClient, vmStub.model().get(), evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On spice disconnected.
|
||||||
|
*
|
||||||
|
* @param event the event
|
||||||
|
* @throws ApiException the api exception
|
||||||
|
*/
|
||||||
|
@Handler
|
||||||
|
public void onSpiceDisconnected(SpiceDisconnectedEvent event)
|
||||||
|
throws ApiException {
|
||||||
|
if (vmStub == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
vmStub.updateStatus(from -> {
|
||||||
|
JsonObject status = from.status();
|
||||||
|
status.addProperty("consoleClient", "");
|
||||||
|
updateConsoleConnectedCondition(from, status, false);
|
||||||
|
return status;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Log event
|
||||||
|
var evt = new EventsV1Event()
|
||||||
|
.reportingController(VM_OP_GROUP + "/" + APP_NAME)
|
||||||
|
.action("ConsoleConnectionUpdate")
|
||||||
|
.reason("Disconnected from " + event.clientHost());
|
||||||
|
K8s.createEvent(apiClient, vmStub.model().get(), evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateConsoleConnectedCondition(VmDefinitionModel from,
|
||||||
|
JsonObject status, boolean connected) {
|
||||||
|
// Optimize, as we can get this several times
|
||||||
|
var current = status.getAsJsonArray("conditions").asList().stream()
|
||||||
|
.map(cond -> (JsonObject) cond)
|
||||||
|
.filter(cond -> "ConsoleConnected"
|
||||||
|
.equals(cond.get("type").getAsString()))
|
||||||
|
.findFirst()
|
||||||
|
.map(cond -> "True".equals(cond.get("status").getAsString()));
|
||||||
|
if (current.isPresent() && current.get() == connected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do update
|
||||||
|
final var condition = Map.of("type", "ConsoleConnected",
|
||||||
|
"status", connected ? "True" : "False",
|
||||||
|
"observedGeneration", from.getMetadata().getGeneration(),
|
||||||
|
"reason", connected ? "Connected" : "Disconnected",
|
||||||
|
"lastTransitionTime", Instant.now().toString());
|
||||||
|
List<Object> toReplace = new ArrayList<>(List.of(condition));
|
||||||
|
List<Object> newConds
|
||||||
|
= status.getAsJsonArray("conditions").asList().stream()
|
||||||
|
.map(cond -> (JsonObject) cond)
|
||||||
|
.map(cond -> "ConsoleConnected"
|
||||||
|
.equals(cond.get("type").getAsString())
|
||||||
|
? toReplace.remove(0)
|
||||||
|
: cond)
|
||||||
|
.collect(Collectors.toCollection(() -> new ArrayList<>()));
|
||||||
|
newConds.addAll(toReplace);
|
||||||
|
status.add("conditions",
|
||||||
|
apiClient.getJSON().getGson().toJsonTree(newConds));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,8 @@ public class MonitorEvent extends Event<Void> {
|
||||||
* The kind of monitor event.
|
* The kind of monitor event.
|
||||||
*/
|
*/
|
||||||
public enum Kind {
|
public enum Kind {
|
||||||
READY, POWERDOWN, DEVICE_TRAY_MOVED, BALLOON_CHANGE, SHUTDOWN
|
READY, POWERDOWN, DEVICE_TRAY_MOVED, BALLOON_CHANGE, SHUTDOWN,
|
||||||
|
SPICE_CONNECTED, SPICE_DISCONNECTED
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Kind kind;
|
private final Kind kind;
|
||||||
|
|
@ -49,8 +50,7 @@ public class MonitorEvent extends Event<Void> {
|
||||||
@SuppressWarnings("PMD.TooFewBranchesForASwitchStatement")
|
@SuppressWarnings("PMD.TooFewBranchesForASwitchStatement")
|
||||||
public static Optional<MonitorEvent> from(JsonNode response) {
|
public static Optional<MonitorEvent> from(JsonNode response) {
|
||||||
try {
|
try {
|
||||||
var kind = MonitorEvent.Kind
|
var kind = Kind.valueOf(response.get("event").asText());
|
||||||
.valueOf(response.get("event").asText());
|
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case POWERDOWN:
|
case POWERDOWN:
|
||||||
return Optional.of(new PowerdownEvent(kind, null));
|
return Optional.of(new PowerdownEvent(kind, null));
|
||||||
|
|
@ -63,6 +63,14 @@ public class MonitorEvent extends Event<Void> {
|
||||||
case SHUTDOWN:
|
case SHUTDOWN:
|
||||||
return Optional
|
return Optional
|
||||||
.of(new ShutdownEvent(kind, response.get(EVENT_DATA)));
|
.of(new ShutdownEvent(kind, response.get(EVENT_DATA)));
|
||||||
|
case SPICE_CONNECTED:
|
||||||
|
return Optional
|
||||||
|
.of(new SpiceConnectedEvent(kind,
|
||||||
|
response.get(EVENT_DATA)));
|
||||||
|
case SPICE_DISCONNECTED:
|
||||||
|
return Optional
|
||||||
|
.of(new SpiceDisconnectedEvent(kind,
|
||||||
|
response.get(EVENT_DATA)));
|
||||||
default:
|
default:
|
||||||
return Optional
|
return Optional
|
||||||
.of(new MonitorEvent(kind, response.get(EVENT_DATA)));
|
.of(new MonitorEvent(kind, response.get(EVENT_DATA)));
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* VM-Operator
|
||||||
|
* Copyright (C) 2023 Michael N. Lipp
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jdrupes.vmoperator.runner.qemu.events;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals a connection from a client.
|
||||||
|
*/
|
||||||
|
public class SpiceConnectedEvent extends SpiceEvent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new spice connected event.
|
||||||
|
*
|
||||||
|
* @param kind the kind
|
||||||
|
* @param data the data
|
||||||
|
*/
|
||||||
|
public SpiceConnectedEvent(Kind kind, JsonNode data) {
|
||||||
|
super(kind, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* VM-Operator
|
||||||
|
* Copyright (C) 2023 Michael N. Lipp
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jdrupes.vmoperator.runner.qemu.events;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals a connection from a client.
|
||||||
|
*/
|
||||||
|
public class SpiceDisconnectedEvent extends SpiceEvent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new spice disconnected event.
|
||||||
|
*
|
||||||
|
* @param kind the kind
|
||||||
|
* @param data the data
|
||||||
|
*/
|
||||||
|
public SpiceDisconnectedEvent(Kind kind, JsonNode data) {
|
||||||
|
super(kind, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* VM-Operator
|
||||||
|
* Copyright (C) 2023 Michael N. Lipp
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jdrupes.vmoperator.runner.qemu.events;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals a connection from a client.
|
||||||
|
*/
|
||||||
|
public class SpiceEvent extends MonitorEvent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new tray moved.
|
||||||
|
*
|
||||||
|
* @param kind the kind
|
||||||
|
* @param data the data
|
||||||
|
*/
|
||||||
|
public SpiceEvent(Kind kind, JsonNode data) {
|
||||||
|
super(kind, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the client's host.
|
||||||
|
*
|
||||||
|
* @return the client's host address
|
||||||
|
*/
|
||||||
|
public String clientHost() {
|
||||||
|
return data().get("client").get("host").asText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,5 +11,6 @@ nodeName = Node
|
||||||
requestedCpus = Requested CPUs
|
requestedCpus = Requested CPUs
|
||||||
requestedRam = Requested RAM
|
requestedRam = Requested RAM
|
||||||
running = Running
|
running = Running
|
||||||
|
usedBy = Used by
|
||||||
vmActions = Actions
|
vmActions = Actions
|
||||||
vmname = Name
|
vmname = Name
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ maximumRam = Maximales RAM
|
||||||
nodeName = Knoten
|
nodeName = Knoten
|
||||||
requestedCpus = Angeforderte CPUs
|
requestedCpus = Angeforderte CPUs
|
||||||
requestedRam = Angefordertes RAM
|
requestedRam = Angefordertes RAM
|
||||||
|
usedBy = Benutzt von
|
||||||
vmActions = Aktionen
|
vmActions = Aktionen
|
||||||
vmname = Name
|
vmname = Name
|
||||||
Value\ is\ above\ maximum = Wert ist zu groß
|
Value\ is\ above\ maximum = Wert ist zu groß
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,8 @@ window.orgJDrupesVmOperatorVmConlet.initView = (viewDom: HTMLElement,
|
||||||
["runningConditionSince", "since"],
|
["runningConditionSince", "since"],
|
||||||
["currentCpus", "currentCpus"],
|
["currentCpus", "currentCpus"],
|
||||||
["currentRam", "currentRam"],
|
["currentRam", "currentRam"],
|
||||||
["nodeName", "nodeName"]
|
["nodeName", "nodeName"],
|
||||||
|
["usedBy", "usedBy"]
|
||||||
], {
|
], {
|
||||||
sortKey: "name",
|
sortKey: "name",
|
||||||
sortOrder: "up"
|
sortOrder: "up"
|
||||||
|
|
@ -179,6 +180,7 @@ JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmconlet.VmConlet",
|
||||||
vmDefinition.name = vmDefinition.metadata.name;
|
vmDefinition.name = vmDefinition.metadata.name;
|
||||||
vmDefinition.currentCpus = vmDefinition.status.cpus;
|
vmDefinition.currentCpus = vmDefinition.status.cpus;
|
||||||
vmDefinition.currentRam = Number(vmDefinition.status.ram);
|
vmDefinition.currentRam = Number(vmDefinition.status.ram);
|
||||||
|
vmDefinition.usedBy = vmDefinition.status.consoleClient || "";
|
||||||
for (const condition of vmDefinition.status.conditions) {
|
for (const condition of vmDefinition.status.conditions) {
|
||||||
if (condition.type === "Running") {
|
if (condition.type === "Running") {
|
||||||
vmDefinition.running = condition.status === "True";
|
vmDefinition.running = condition.status === "True";
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
fill="#000000"
|
||||||
|
width="800"
|
||||||
|
height="533.33331"
|
||||||
|
viewBox="0 0 24 15.999999"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
sodipodi:docname="computer-in-use.svg"
|
||||||
|
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs1">
|
||||||
|
<inkscape:path-effect
|
||||||
|
effect="fillet_chamfer"
|
||||||
|
id="path-effect4"
|
||||||
|
is_visible="true"
|
||||||
|
lpeversion="1"
|
||||||
|
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||||
|
radius="0"
|
||||||
|
unit="px"
|
||||||
|
method="auto"
|
||||||
|
mode="F"
|
||||||
|
chamfer_steps="1"
|
||||||
|
flexible="false"
|
||||||
|
use_knot_distance="true"
|
||||||
|
apply_no_radius="true"
|
||||||
|
apply_with_radius="true"
|
||||||
|
only_selected="false"
|
||||||
|
hide_knots="false" />
|
||||||
|
<linearGradient
|
||||||
|
id="swatch3"
|
||||||
|
inkscape:swatch="solid">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#000000;stop-opacity:0;"
|
||||||
|
offset="0"
|
||||||
|
id="stop3" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:zoom="1.28"
|
||||||
|
inkscape:cx="326.5625"
|
||||||
|
inkscape:cy="548.04688"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1008"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="35"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg1" />
|
||||||
|
<path
|
||||||
|
id="rect1"
|
||||||
|
style="fill-opacity:0;stroke:#000000;stroke-width:1.97262;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:0;paint-order:fill markers stroke"
|
||||||
|
d="M 3.0038192,0.98808897 H 20.99618 V 13.006705 H 3.0038192 Z" />
|
||||||
|
<rect
|
||||||
|
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.00306926;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
|
||||||
|
id="rect2"
|
||||||
|
width="23.995173"
|
||||||
|
height="2.0017407"
|
||||||
|
x="0.0039473679"
|
||||||
|
y="13.998839" />
|
||||||
|
<path
|
||||||
|
id="rect3"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.05373;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
|
||||||
|
d="m 5.6839082,10.94394 c 0.2963594,-0.907428 2.9522319,-1.683971 2.767387,-1.6392261 0,0 1.5028596,1.6181771 3.6459428,1.6129171 2.018383,-0.005 3.362681,-1.6125503 3.362681,-1.6125503 -0.171441,-0.061235 2.778887,0.7741493 2.977303,1.6787203 0.393054,1.791919 0.25928,4.524228 0.25928,4.524228 L 5.3146272,15.391866 c 0,0 -0.181061,-2.762825 0.369281,-4.447926 z"
|
||||||
|
sodipodi:nodetypes="sssssccs" />
|
||||||
|
<ellipse
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:1.02152;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
|
||||||
|
id="path3"
|
||||||
|
cx="11.964992"
|
||||||
|
cy="6.3769712"
|
||||||
|
rx="3.2413731"
|
||||||
|
ry="3.225764" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.3 KiB |
|
|
@ -72,6 +72,7 @@ window.orgJDrupesVmOperatorVmViewer.initPreview = (previewDom: HTMLElement,
|
||||||
previewApi.vmDefinition.spec.vm.state !== 'Stopped'
|
previewApi.vmDefinition.spec.vm.state !== 'Stopped'
|
||||||
&& previewApi.vmDefinition.running);
|
&& previewApi.vmDefinition.running);
|
||||||
const running = computed(() => previewApi.vmDefinition.running);
|
const running = computed(() => previewApi.vmDefinition.running);
|
||||||
|
const inUse = computed(() => previewApi.vmDefinition.usedBy != '');
|
||||||
const permissions = computed(() => previewApi.vmDefinition.spec
|
const permissions = computed(() => previewApi.vmDefinition.spec
|
||||||
? previewApi.vmDefinition.userPermissions : []);
|
? previewApi.vmDefinition.userPermissions : []);
|
||||||
|
|
||||||
|
|
@ -88,7 +89,7 @@ window.orgJDrupesVmOperatorVmViewer.initPreview = (previewDom: HTMLElement,
|
||||||
};
|
};
|
||||||
|
|
||||||
return { localize, resourceBase, vmAction, configured,
|
return { localize, resourceBase, vmAction, configured,
|
||||||
startable, stoppable, running, permissions };
|
startable, stoppable, running, inUse, permissions };
|
||||||
},
|
},
|
||||||
template: `
|
template: `
|
||||||
<table>
|
<table>
|
||||||
|
|
@ -101,7 +102,8 @@ window.orgJDrupesVmOperatorVmViewer.initPreview = (previewDom: HTMLElement,
|
||||||
|| !permissions.includes('accessConsole')"
|
|| !permissions.includes('accessConsole')"
|
||||||
v-on:click="vmAction('openConsole')"
|
v-on:click="vmAction('openConsole')"
|
||||||
:src="resourceBase + (running
|
:src="resourceBase + (running
|
||||||
? 'computer.svg' : 'computer-off.svg')"
|
? (inUse ? 'computer-in-use.svg' : 'computer.svg')
|
||||||
|
: 'computer-off.svg')"
|
||||||
:title="localize('Open console')"></span><span
|
:title="localize('Open console')"></span><span
|
||||||
style="visibility: hidden;"><img
|
style="visibility: hidden;"><img
|
||||||
:src="resourceBase + 'computer.svg'"></span></td>
|
:src="resourceBase + 'computer.svg'"></span></td>
|
||||||
|
|
@ -159,6 +161,7 @@ JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmviewer.VmViewer",
|
||||||
vmDefinition.name = vmDefinition.metadata.name;
|
vmDefinition.name = vmDefinition.metadata.name;
|
||||||
vmDefinition.currentCpus = vmDefinition.status.cpus;
|
vmDefinition.currentCpus = vmDefinition.status.cpus;
|
||||||
vmDefinition.currentRam = Number(vmDefinition.status.ram);
|
vmDefinition.currentRam = Number(vmDefinition.status.ram);
|
||||||
|
vmDefinition.usedBy = vmDefinition.status.consoleClient || "";
|
||||||
for (const condition of vmDefinition.status.conditions) {
|
for (const condition of vmDefinition.status.conditions) {
|
||||||
if (condition.type === "Running") {
|
if (condition.type === "Running") {
|
||||||
vmDefinition.running = condition.status === "True";
|
vmDefinition.running = condition.status === "True";
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue