Prepare release.
This commit is contained in:
parent
659463b3b4
commit
65a5cfd286
39 changed files with 500 additions and 132 deletions
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
|
|
@ -18,10 +18,9 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
ref: main
|
|
||||||
- name: Install graphviz
|
- name: Install graphviz
|
||||||
run: sudo apt-get install graphviz
|
run: sudo apt-get install graphviz
|
||||||
- name: Install podman
|
- name: Install podman
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
[](https://github.com/mnlipp/VM-Operator/actions/workflows/gradle.yml)
|
[](https://github.com/mnlipp/VM-Operator/actions/workflows/gradle.yml)
|
||||||
[](https://app.codacy.com/gh/mnlipp/VM-Operator/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
|
[](https://app.codacy.com/gh/mnlipp/VM-Operator/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
|
||||||

|

|
||||||

|

|
||||||
|
|
@ -6,8 +6,7 @@
|
||||||
# Run Qemu in Kubernetes Pods
|
# Run Qemu in Kubernetes Pods
|
||||||
|
|
||||||
The goal of this project is to provide the means for running Qemu
|
The goal of this project is to provide the means for running Qemu
|
||||||
based VMs in Kubernetes pods.
|
based VMs in Kubernetes pods.
|
||||||
|
|
||||||
See the [project's home page](https://mnlipp.github.io/VM-Operator/)
|
See the [project's home page](https://mnlipp.github.io/VM-Operator/)
|
||||||
for details.
|
for details.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
|
// Apply the common versioning conventions.
|
||||||
|
// Put this at the start, because accessing project.version before
|
||||||
|
// this is applied makes things fail.
|
||||||
|
id 'org.jdrupes.vmoperator.versioning-conventions'
|
||||||
|
|
||||||
// Apply the java Plugin to add support for Java.
|
// Apply the java Plugin to add support for Java.
|
||||||
id 'java'
|
id 'java'
|
||||||
|
|
||||||
|
|
@ -13,9 +18,6 @@ plugins {
|
||||||
|
|
||||||
// Access to git information
|
// Access to git information
|
||||||
id 'org.ajoberstar.grgit'
|
id 'org.ajoberstar.grgit'
|
||||||
|
|
||||||
// Apply the common versioning conventions.
|
|
||||||
id 'org.jdrupes.vmoperator.versioning-conventions'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,13 @@ scmVersion {
|
||||||
}
|
}
|
||||||
var p = shortened.replace('.', '-') + "-"
|
var p = shortened.replace('.', '-') + "-"
|
||||||
if (grgit.branch.current.name != "main"
|
if (grgit.branch.current.name != "main"
|
||||||
&& !grgit.branch.current.name.startsWith("release")) {
|
&& grgit.branch.current.name != "HEAD"
|
||||||
|
&& !grgit.branch.current.name.startsWith("release")
|
||||||
|
&& !grgit.branch.current.name.startsWith("develop")) {
|
||||||
p = p + grgit.branch.current.name.replace('/', '-') + "-"
|
p = p + grgit.branch.current.name.replace('/', '-') + "-"
|
||||||
}
|
}
|
||||||
prefix = p
|
prefix = p
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
version = scmVersion.version
|
project.version = scmVersion.version
|
||||||
ext.isSnapshot = version.endsWith('-SNAPSHOT')
|
ext.isSnapshot = version.endsWith('-SNAPSHOT')
|
||||||
|
|
|
||||||
|
|
@ -1012,7 +1012,12 @@ spec:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
enum: ["start", "stop", "accessConsole", "*"]
|
enum:
|
||||||
|
- start
|
||||||
|
- stop
|
||||||
|
- reset
|
||||||
|
- accessConsole
|
||||||
|
- "*"
|
||||||
default: []
|
default: []
|
||||||
vm:
|
vm:
|
||||||
type: object
|
type: object
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
# Example setup for development
|
# Example setup for development
|
||||||
|
|
||||||
The CRD must be deployed independently. Apart from that, the
|
The CRD must be deployed independently. Apart from that, the
|
||||||
`kustomize.yaml`
|
`kustomize.yaml`
|
||||||
|
|
||||||
* creates a small cdrom image repository and
|
* creates a small cdrom image repository and
|
||||||
|
|
||||||
* deploys the operator in namespace `vmop-dev` with a replica of 0.
|
* deploys the operator in namespace `vmop-dev` with a replica of 0.
|
||||||
|
|
||||||
This allows you to run the manager in your IDE.
|
This allows you to run the manager in your IDE.
|
||||||
|
|
||||||
The `kustomize.yaml` also changes the container image repository for
|
The `kustomize.yaml` also changes the container image repository for
|
||||||
the operator to a private repository for development. You have to
|
the operator to a private repository for development. You have to
|
||||||
adapt this to your own repository if you also want to test your
|
adapt this to your own repository if you also want to test your
|
||||||
development version in a container.
|
development version in a container.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
# Example setup
|
# Example setup
|
||||||
|
|
||||||
The CRD must be deployed independently.
|
The CRD must be deployed independently.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
kubectl apply -f https://github.com/mnlipp/VM-Operator/raw/main/deploy/crds/vms-crd.yaml
|
kubectl apply -f https://github.com/mnlipp/VM-Operator/raw/main/deploy/crds/vms-crd.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
Apart from that, the `kustomize.yaml` defines a namespace for the manager
|
Apart from that, the `kustomize.yaml` defines a namespace for the manager
|
||||||
(and the VMs managed by it) and patches the repository PVC to create
|
(and the VMs managed by it) and patches the repository PVC to create
|
||||||
a small volume using local-path.
|
a small volume using local-path.
|
||||||
|
|
||||||
A second patch provides a new configuration file for the manager
|
A second patch provides a new configuration file for the manager
|
||||||
that makes it use the local-path storage class when creating the
|
that makes it use the local-path storage class when creating the
|
||||||
small volume for a runner's data.
|
small volume for a runner's data.
|
||||||
|
|
||||||
The `kustomize.yaml` does not include the test VM. Before creating
|
The `kustomize.yaml` does not include the test VM. Before creating
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
# Example setup
|
# Example setup
|
||||||
|
|
||||||
The CRD must be deployed independently.
|
The CRD must be deployed independently.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
kubectl apply -f https://github.com/mnlipp/VM-Operator/raw/main/deploy/crds/vms-crd.yaml
|
kubectl apply -f https://github.com/mnlipp/VM-Operator/raw/main/deploy/crds/vms-crd.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
Apart from that, the `kustomize.yaml` defines a namespace for the manager
|
Apart from that, the `kustomize.yaml` defines a namespace for the manager
|
||||||
(and the VMs managed by it) and applies patches to use `rook-cephfs` as
|
(and the VMs managed by it) and applies patches to use `rook-cephfs` as
|
||||||
storage class (instead of the default storage class).
|
storage class (instead of the default storage class).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@
|
||||||
body {
|
body {
|
||||||
background-color:#ffffff;
|
background-color:#ffffff;
|
||||||
color:#353833;
|
color:#353833;
|
||||||
font: normal 16px/1.5 "DejaVu Serif", serif;
|
font: normal 16px/1.5 "DejaVu Sans", Arial, Helvetica, sans-serif;
|
||||||
margin:0;
|
margin:0;
|
||||||
padding:0;
|
padding:0;
|
||||||
height:100%;
|
height:100%;
|
||||||
|
|
@ -71,37 +71,33 @@ a[name] {
|
||||||
color:#353833;
|
color:#353833;
|
||||||
}
|
}
|
||||||
pre {
|
pre {
|
||||||
font-family: "DejaVu Sans Mono", monospace;
|
font-family:'DejaVu Sans Mono', monospace;
|
||||||
}
|
}
|
||||||
h1 {
|
h1 {
|
||||||
font-family: "DejaVu Sans", sans;
|
|
||||||
font-size:20px;
|
font-size:20px;
|
||||||
}
|
}
|
||||||
h2 {
|
h2 {
|
||||||
font-family: "DejaVu Sans", sans;
|
|
||||||
font-size:18px;
|
font-size:18px;
|
||||||
}
|
}
|
||||||
h3 {
|
h3 {
|
||||||
font-family: "DejaVu Sans", sans;
|
font-size:17px;
|
||||||
font-size:16px;
|
|
||||||
}
|
}
|
||||||
h4 {
|
h4 {
|
||||||
font-family: "DejaVu Sans", sans;
|
font-size:16px;
|
||||||
font-size:15px;
|
margin-top: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
h5 {
|
h5 {
|
||||||
font-family: "DejaVu Sans", sans;
|
|
||||||
font-size:14px;
|
font-size:14px;
|
||||||
}
|
}
|
||||||
h6 {
|
h6 {
|
||||||
font-family: "DejaVu Sans", sans;
|
|
||||||
font-size:13px;
|
font-size:13px;
|
||||||
}
|
}
|
||||||
ul {
|
ul {
|
||||||
list-style-type:disc;
|
list-style-type:disc;
|
||||||
}
|
}
|
||||||
code, tt {
|
code, tt {
|
||||||
font-family: "DejaVu Sans Mono", monospace;
|
font-family:'DejaVu Sans Mono', monospace;
|
||||||
}
|
}
|
||||||
:not(h1, h2, h3, h4, h5, h6) > code,
|
:not(h1, h2, h3, h4, h5, h6) > code,
|
||||||
:not(h1, h2, h3, h4, h5, h6) > tt {
|
:not(h1, h2, h3, h4, h5, h6) > tt {
|
||||||
|
|
@ -111,12 +107,12 @@ code, tt {
|
||||||
line-height:1.4em;
|
line-height:1.4em;
|
||||||
}
|
}
|
||||||
dt code {
|
dt code {
|
||||||
font-family: "DejaVu Sans Mono", monospace;
|
font-family:'DejaVu Sans Mono', monospace;
|
||||||
font-size:14px;
|
font-size:14px;
|
||||||
padding-top:4px;
|
padding-top:4px;
|
||||||
}
|
}
|
||||||
.summary-table dt code {
|
.summary-table dt code {
|
||||||
font-family: "DejaVu Sans Mono", monospace;
|
font-family:'DejaVu Sans Mono', monospace;
|
||||||
font-size:14px;
|
font-size:14px;
|
||||||
vertical-align:top;
|
vertical-align:top;
|
||||||
padding-top:4px;
|
padding-top:4px;
|
||||||
|
|
@ -124,7 +120,9 @@ dt code {
|
||||||
sup {
|
sup {
|
||||||
font-size:8px;
|
font-size:8px;
|
||||||
}
|
}
|
||||||
|
button {
|
||||||
|
font-family: 'DejaVu Sans', Arial, Helvetica, sans-serif;
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
* Styles for HTML generated by javadoc.
|
* Styles for HTML generated by javadoc.
|
||||||
*
|
*
|
||||||
|
|
@ -185,7 +183,6 @@ sup {
|
||||||
min-height:2.8em;
|
min-height:2.8em;
|
||||||
padding-top:10px;
|
padding-top:10px;
|
||||||
overflow:hidden;
|
overflow:hidden;
|
||||||
font-family: "DejaVu Sans", sans;
|
|
||||||
font-size:80%;
|
font-size:80%;
|
||||||
}
|
}
|
||||||
.sub-nav {
|
.sub-nav {
|
||||||
|
|
@ -193,7 +190,6 @@ sup {
|
||||||
float:left;
|
float:left;
|
||||||
width:100%;
|
width:100%;
|
||||||
overflow:hidden;
|
overflow:hidden;
|
||||||
font-family: "DejaVu Sans", sans;
|
|
||||||
font-size:80%;
|
font-size:80%;
|
||||||
}
|
}
|
||||||
.sub-nav div {
|
.sub-nav div {
|
||||||
|
|
@ -311,13 +307,16 @@ main {
|
||||||
position:relative;
|
position:relative;
|
||||||
}
|
}
|
||||||
dl.notes > dt {
|
dl.notes > dt {
|
||||||
font-family: "DejaVu Sans", sans;
|
font-family: 'DejaVu Sans', Arial, Helvetica, sans-serif;
|
||||||
|
/* font-size:12px; */
|
||||||
font-weight:bold;
|
font-weight:bold;
|
||||||
margin:10px 0 0 0;
|
margin:10px 0 0 0;
|
||||||
color:#4E4E4E;
|
color:#4E4E4E;
|
||||||
}
|
}
|
||||||
dl.notes > dd {
|
dl.notes > dd {
|
||||||
margin:5px 10px 10px 0;
|
margin:5px 10px 0 0;
|
||||||
|
/* font-size:14px; */
|
||||||
|
font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif;
|
||||||
}
|
}
|
||||||
dl.name-value > dt {
|
dl.name-value > dt {
|
||||||
margin-left:1px;
|
margin-left:1px;
|
||||||
|
|
@ -389,6 +388,11 @@ ul.see-list-long li:not(:last-child):after {
|
||||||
border-bottom:1px solid #EEE;
|
border-bottom:1px solid #EEE;
|
||||||
padding:0;
|
padding:0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.summary-table .col-first {
|
||||||
|
font-family: "DejaVu Sans Mono", monospace;
|
||||||
|
}
|
||||||
|
|
||||||
.caption {
|
.caption {
|
||||||
position:relative;
|
position:relative;
|
||||||
text-align:left;
|
text-align:left;
|
||||||
|
|
@ -402,7 +406,6 @@ ul.see-list-long li:not(:last-child):after {
|
||||||
padding-left:1px;
|
padding-left:1px;
|
||||||
margin:0;
|
margin:0;
|
||||||
white-space:pre;
|
white-space:pre;
|
||||||
font-family: 'DejaVu Sans';
|
|
||||||
}
|
}
|
||||||
.caption a:link, .caption a:visited {
|
.caption a:link, .caption a:visited {
|
||||||
color:#1f389c;
|
color:#1f389c;
|
||||||
|
|
@ -450,9 +453,6 @@ div.table-tabs > button.table-tab {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(10%, max-content) minmax(15%, max-content) minmax(15%, auto);
|
grid-template-columns: minmax(10%, max-content) minmax(15%, max-content) minmax(15%, auto);
|
||||||
}
|
}
|
||||||
#method-summary-table .three-column-summary {
|
|
||||||
grid-template-columns: minmax(10%, 20%) minmax(15%, max-content) minmax(15%, auto);
|
|
||||||
}
|
|
||||||
.four-column-summary {
|
.four-column-summary {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(10%, max-content) minmax(10%, max-content) minmax(10%, max-content) minmax(10%, auto);
|
grid-template-columns: minmax(10%, max-content) minmax(10%, max-content) minmax(10%, max-content) minmax(10%, auto);
|
||||||
|
|
@ -490,7 +490,6 @@ div.table-tabs > button.table-tab {
|
||||||
}
|
}
|
||||||
.table-header {
|
.table-header {
|
||||||
background:#dee3e9;
|
background:#dee3e9;
|
||||||
font-family: 'DejaVu Sans';
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
|
|
@ -508,7 +507,6 @@ div.table-tabs > button.table-tab {
|
||||||
.col-last {
|
.col-last {
|
||||||
white-space:normal;
|
white-space:normal;
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
.col-first a:link, .col-first a:visited,
|
.col-first a:link, .col-first a:visited,
|
||||||
.col-second a:link, .col-second a:visited,
|
.col-second a:link, .col-second a:visited,
|
||||||
.col-first a:link, .col-first a:visited,
|
.col-first a:link, .col-first a:visited,
|
||||||
|
|
@ -520,7 +518,6 @@ div.table-tabs > button.table-tab {
|
||||||
.all-packages-container a:link, .all-packages-container a:visited {
|
.all-packages-container a:link, .all-packages-container a:visited {
|
||||||
font-weight:bold;
|
font-weight:bold;
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
.table-sub-heading-color {
|
.table-sub-heading-color {
|
||||||
background-color:#EEEEFF;
|
background-color:#EEEEFF;
|
||||||
}
|
}
|
||||||
|
|
@ -537,12 +534,9 @@ div.table-tabs > button.table-tab {
|
||||||
margin:0;
|
margin:0;
|
||||||
padding:10px 0;
|
padding:10px 0;
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
div.block {
|
div.block {
|
||||||
font-size:14px;
|
|
||||||
font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif;
|
font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif;
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
.col-last div {
|
.col-last div {
|
||||||
padding-top:0;
|
padding-top:0;
|
||||||
}
|
}
|
||||||
|
|
@ -553,8 +547,7 @@ div.block {
|
||||||
.package-signature,
|
.package-signature,
|
||||||
.type-signature,
|
.type-signature,
|
||||||
.member-signature {
|
.member-signature {
|
||||||
font-family: "DejaVu Sans Mono", monospace;
|
font-family:'DejaVu Sans Mono', monospace;
|
||||||
/* font-size:14px; */
|
|
||||||
margin:14px 0;
|
margin:14px 0;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
@ -593,13 +586,8 @@ h1.hidden {
|
||||||
.deprecated-label, .descfrm-type-label, .implementation-label, .member-name-label, .member-name-link,
|
.deprecated-label, .descfrm-type-label, .implementation-label, .member-name-label, .member-name-link,
|
||||||
.module-label-in-package, .module-label-in-type, .override-specify-label, .package-label-in-type,
|
.module-label-in-package, .module-label-in-type, .override-specify-label, .package-label-in-type,
|
||||||
.package-hierarchy-label, .type-name-label, .type-name-link, .search-tag-link, .preview-label {
|
.package-hierarchy-label, .type-name-label, .type-name-link, .search-tag-link, .preview-label {
|
||||||
font-family: "DejaVu Sans", sans;
|
|
||||||
font-weight:bold;
|
font-weight:bold;
|
||||||
}
|
}
|
||||||
.sub-title, .inheritance, .all-packages-table-tab1.col-first,
|
|
||||||
.summary-table .col-first {
|
|
||||||
font-family: "DejaVu Sans", sans;
|
|
||||||
}
|
|
||||||
.deprecation-comment, .help-footnote, .preview-comment {
|
.deprecation-comment, .help-footnote, .preview-comment {
|
||||||
font-style:italic;
|
font-style:italic;
|
||||||
}
|
}
|
||||||
|
|
@ -658,6 +646,7 @@ main, nav, header, footer, section {
|
||||||
ul.ui-autocomplete {
|
ul.ui-autocomplete {
|
||||||
position:fixed;
|
position:fixed;
|
||||||
z-index:999999;
|
z-index:999999;
|
||||||
|
background-color: #FFFFFF;
|
||||||
}
|
}
|
||||||
ul.ui-autocomplete li {
|
ul.ui-autocomplete li {
|
||||||
float:left;
|
float:left;
|
||||||
|
|
@ -667,6 +656,9 @@ ul.ui-autocomplete li {
|
||||||
.result-highlight {
|
.result-highlight {
|
||||||
font-weight:bold;
|
font-weight:bold;
|
||||||
}
|
}
|
||||||
|
.ui-autocomplete .result-item {
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
#search-input {
|
#search-input {
|
||||||
background-image:url('resources/glass.png');
|
background-image:url('resources/glass.png');
|
||||||
background-size:13px;
|
background-size:13px;
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,8 @@ public class VmDefinitionModel extends K8sDynamicModel {
|
||||||
* Permissions for accessing and manipulating the VM.
|
* Permissions for accessing and manipulating the VM.
|
||||||
*/
|
*/
|
||||||
public enum Permission {
|
public enum Permission {
|
||||||
START("start"), STOP("stop"), ACCESS_CONSOLE("accessConsole");
|
START("start"), STOP("stop"), RESET("reset"),
|
||||||
|
ACCESS_CONSOLE("accessConsole");
|
||||||
|
|
||||||
@SuppressWarnings("PMD.UseConcurrentHashMap")
|
@SuppressWarnings("PMD.UseConcurrentHashMap")
|
||||||
private static Map<String, Permission> reprs = new HashMap<>();
|
private static Map<String, Permission> reprs = new HashMap<>();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* VM-Operator
|
||||||
|
* Copyright (C) 2024 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.manager.events;
|
||||||
|
|
||||||
|
import org.jgrapes.core.Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers a reset of the VM.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("PMD.DataClass")
|
||||||
|
public class ResetVm extends Event<String> {
|
||||||
|
|
||||||
|
private final String vmName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new event.
|
||||||
|
*
|
||||||
|
* @param vmName the vm name
|
||||||
|
*/
|
||||||
|
public ResetVm(String vmName) {
|
||||||
|
this.vmName = vmName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the vm name.
|
||||||
|
*
|
||||||
|
* @return the vm name
|
||||||
|
*/
|
||||||
|
public String vmName() {
|
||||||
|
return vmName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,10 +18,10 @@ dependencies {
|
||||||
implementation 'org.jgrapes:org.jgrapes.http:[3.1.0,4)'
|
implementation 'org.jgrapes:org.jgrapes.http:[3.1.0,4)'
|
||||||
implementation 'org.jgrapes:org.jgrapes.util:[1.34.0,2)'
|
implementation 'org.jgrapes:org.jgrapes.util:[1.34.0,2)'
|
||||||
|
|
||||||
implementation 'org.jgrapes:org.jgrapes.webconsole.base:[1.5.0,2)'
|
implementation 'org.jgrapes:org.jgrapes.webconsole.base:[1.7.0,2)'
|
||||||
implementation 'org.jgrapes:org.jgrapes.webconsole.vuejs:[1.5.0,2)'
|
implementation 'org.jgrapes:org.jgrapes.webconsole.vuejs:[1.5.0,2)'
|
||||||
implementation 'org.jgrapes:org.jgrapes.webconsole.rbac:[1.3.0,2)'
|
implementation 'org.jgrapes:org.jgrapes.webconsole.rbac:[1.3.0,2)'
|
||||||
implementation 'org.jgrapes:org.jgrapes.webconlet.oidclogin:[1.3.0,2)'
|
implementation 'org.jgrapes:org.jgrapes.webconlet.oidclogin:[1.4.0,2)'
|
||||||
implementation 'org.jgrapes:org.jgrapes.webconlet.markdowndisplay:[1.2.0,2)'
|
implementation 'org.jgrapes:org.jgrapes.webconlet.markdowndisplay:[1.2.0,2)'
|
||||||
|
|
||||||
runtimeOnly 'org.jgrapes:org.jgrapes.webconlet.sysinfo:[1.4.0,2)'
|
runtimeOnly 'org.jgrapes:org.jgrapes.webconlet.sysinfo:[1.4.0,2)'
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
You can use the "puzzle piece" icon on the top right corner of the
|
You can use the "puzzle piece" icon on the top right corner of the
|
||||||
page to add display widgets (conlets) to the overview tab.
|
page to add display widgets (conlets) to the overview tab.
|
||||||
|
|
||||||
Use the "full screen" icon on the top right corner of any
|
Use the "full screen" icon on the top right corner of any
|
||||||
conlet (if available) to get a detailed view.
|
conlet (if available) to get a detailed view.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
Verwenden Sie das "Puzzle"-Icon auf der rechten oberen Ecke
|
Verwenden Sie das "Puzzle"-Icon auf der rechten oberen Ecke
|
||||||
der Seite, um Anzeige-Widgets (Conlets) hinzuzufügen.
|
der Seite, um Anzeige-Widgets (Conlets) hinzuzufügen.
|
||||||
|
|
||||||
Wenn sich in der rechten oberen Ecke eines Conlets ein Vollbild-Icon
|
Wenn sich in der rechten oberen Ecke eines Conlets ein Vollbild-Icon
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,12 @@ data:
|
||||||
# Whether a shutdown initiated by the guest stops the pod deployment
|
# Whether a shutdown initiated by the guest stops the pod deployment
|
||||||
guestShutdownStops: ${ cr.spec.guestShutdownStops!false?c }
|
guestShutdownStops: ${ cr.spec.guestShutdownStops!false?c }
|
||||||
|
|
||||||
|
# When incremented, the VM is reset. The value has no default value,
|
||||||
|
# i.e. if you start the VM without a value for this property, and
|
||||||
|
# decide to trigger a reset later, you have to first set the value
|
||||||
|
# and then inrement it.
|
||||||
|
resetCounter: ${ cr.resetCount }
|
||||||
|
|
||||||
# Forward the cloud-init data if provided
|
# Forward the cloud-init data if provided
|
||||||
<#if cr.spec.cloudInit??>
|
<#if cr.spec.cloudInit??>
|
||||||
cloudInit:
|
cloudInit:
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,6 @@ import org.jdrupes.vmoperator.common.K8s;
|
||||||
import static org.jdrupes.vmoperator.manager.Constants.APP_NAME;
|
import static org.jdrupes.vmoperator.manager.Constants.APP_NAME;
|
||||||
import static org.jdrupes.vmoperator.manager.Constants.VM_OP_NAME;
|
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.yaml.snakeyaml.LoaderOptions;
|
import org.yaml.snakeyaml.LoaderOptions;
|
||||||
import org.yaml.snakeyaml.Yaml;
|
import org.yaml.snakeyaml.Yaml;
|
||||||
import org.yaml.snakeyaml.constructor.SafeConstructor;
|
import org.yaml.snakeyaml.constructor.SafeConstructor;
|
||||||
|
|
@ -62,7 +61,6 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
|
||||||
/**
|
/**
|
||||||
* Reconcile.
|
* Reconcile.
|
||||||
*
|
*
|
||||||
* @param event the event
|
|
||||||
* @param model the model
|
* @param model the model
|
||||||
* @param channel the channel
|
* @param channel the channel
|
||||||
* @return the dynamic kubernetes object
|
* @return the dynamic kubernetes object
|
||||||
|
|
@ -70,8 +68,8 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
|
||||||
* @throws TemplateException the template exception
|
* @throws TemplateException the template exception
|
||||||
* @throws ApiException the api exception
|
* @throws ApiException the api exception
|
||||||
*/
|
*/
|
||||||
public DynamicKubernetesObject reconcile(VmDefChanged event,
|
public DynamicKubernetesObject reconcile(Map<String, Object> model,
|
||||||
Map<String, Object> model, VmChannel channel)
|
VmChannel channel)
|
||||||
throws IOException, TemplateException, ApiException {
|
throws IOException, TemplateException, ApiException {
|
||||||
// Get API
|
// Get API
|
||||||
DynamicKubernetesApi cmApi = new DynamicKubernetesApi("", "v1",
|
DynamicKubernetesApi cmApi = new DynamicKubernetesApi("", "v1",
|
||||||
|
|
|
||||||
|
|
@ -181,13 +181,12 @@ public class Controller extends Component {
|
||||||
@Handler
|
@Handler
|
||||||
public void onModifyVm(ModifyVm event, VmChannel channel)
|
public void onModifyVm(ModifyVm event, VmChannel channel)
|
||||||
throws ApiException, IOException {
|
throws ApiException, IOException {
|
||||||
patchVmSpec(channel.client(), event.name(), event.path(),
|
patchVmDef(channel.client(), event.name(), "spec/vm/" + event.path(),
|
||||||
event.value());
|
event.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void patchVmSpec(K8sClient client, String name, String path,
|
private void patchVmDef(K8sClient client, String name, String path,
|
||||||
Object value)
|
Object value) throws ApiException, IOException {
|
||||||
throws ApiException, IOException {
|
|
||||||
var vmStub = K8sDynamicStub.get(client,
|
var vmStub = K8sDynamicStub.get(client,
|
||||||
new GroupVersionKind(VM_OP_GROUP, "", VM_OP_KIND_VM), namespace,
|
new GroupVersionKind(VM_OP_GROUP, "", VM_OP_KIND_VM), namespace,
|
||||||
name);
|
name);
|
||||||
|
|
@ -197,7 +196,7 @@ public class Controller extends Component {
|
||||||
? "\"" + value + "\""
|
? "\"" + value + "\""
|
||||||
: value.toString();
|
: value.toString();
|
||||||
var res = vmStub.patch(V1Patch.PATCH_FORMAT_JSON_PATCH,
|
var res = vmStub.patch(V1Patch.PATCH_FORMAT_JSON_PATCH,
|
||||||
new V1Patch("[{\"op\": \"replace\", \"path\": \"/spec/vm/"
|
new V1Patch("[{\"op\": \"replace\", \"path\": \"/"
|
||||||
+ path + "\", \"value\": " + valueAsText + "}]"),
|
+ path + "\", \"value\": " + valueAsText + "}]"),
|
||||||
client.defaultPatchOptions());
|
client.defaultPatchOptions());
|
||||||
if (!res.isPresent()) {
|
if (!res.isPresent()) {
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ import java.util.Collections;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import static org.jdrupes.vmoperator.common.Constants.APP_NAME;
|
import static org.jdrupes.vmoperator.common.Constants.APP_NAME;
|
||||||
|
|
@ -180,7 +181,8 @@ public class DisplaySecretMonitor
|
||||||
// Check validity
|
// Check validity
|
||||||
var model = stub.model().get();
|
var model = stub.model().get();
|
||||||
@SuppressWarnings("PMD.StringInstantiation")
|
@SuppressWarnings("PMD.StringInstantiation")
|
||||||
var expiry = new String(model.getData().get(DATA_PASSWORD_EXPIRY));
|
var expiry = Optional.ofNullable(model.getData()
|
||||||
|
.get(DATA_PASSWORD_EXPIRY)).map(b -> new String(b)).orElse(null);
|
||||||
if (model.getData().get(DATA_DISPLAY_PASSWORD) != null
|
if (model.getData().get(DATA_DISPLAY_PASSWORD) != null
|
||||||
&& stillValid(expiry)) {
|
&& stillValid(expiry)) {
|
||||||
event.setResult(
|
event.setResult(
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ import org.jose4j.base64url.Base64;
|
||||||
var display = GsonPtr.to(event.vmDefinition().data()).to("spec", "vm",
|
var display = GsonPtr.to(event.vmDefinition().data()).to("spec", "vm",
|
||||||
"display");
|
"display");
|
||||||
if (!display.get(JsonPrimitive.class, "spice", "generateSecret")
|
if (!display.get(JsonPrimitive.class, "spice", "generateSecret")
|
||||||
.map(JsonPrimitive::getAsBoolean).orElse(false)) {
|
.map(JsonPrimitive::getAsBoolean).orElse(true)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ import org.jdrupes.vmoperator.common.K8sDynamicModel;
|
||||||
import org.jdrupes.vmoperator.common.K8sObserver;
|
import org.jdrupes.vmoperator.common.K8sObserver;
|
||||||
import org.jdrupes.vmoperator.common.K8sV1SecretStub;
|
import org.jdrupes.vmoperator.common.K8sV1SecretStub;
|
||||||
import static org.jdrupes.vmoperator.manager.Constants.COMP_DISPLAY_SECRET;
|
import static org.jdrupes.vmoperator.manager.Constants.COMP_DISPLAY_SECRET;
|
||||||
|
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.util.ExtendedObjectWrapper;
|
import org.jdrupes.vmoperator.util.ExtendedObjectWrapper;
|
||||||
|
|
@ -209,13 +210,35 @@ public class Reconciler extends Component {
|
||||||
// Reconcile, use "augmented" vm definition for model
|
// Reconcile, use "augmented" vm definition for model
|
||||||
Map<String, Object> model
|
Map<String, Object> model
|
||||||
= prepareModel(channel.client(), patchCr(event.vmDefinition()));
|
= prepareModel(channel.client(), patchCr(event.vmDefinition()));
|
||||||
var configMap = cmReconciler.reconcile(event, model, channel);
|
var configMap = cmReconciler.reconcile(model, channel);
|
||||||
model.put("cm", configMap.getRaw());
|
model.put("cm", configMap.getRaw());
|
||||||
dsReconciler.reconcile(event, model, channel);
|
dsReconciler.reconcile(event, model, channel);
|
||||||
stsReconciler.reconcile(event, model, channel);
|
stsReconciler.reconcile(event, model, channel);
|
||||||
lbReconciler.reconcile(event, model, channel);
|
lbReconciler.reconcile(event, model, channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the VM by incrementing the reset count and doing a
|
||||||
|
* partial reconcile (configmap only).
|
||||||
|
*
|
||||||
|
* @param event the event
|
||||||
|
* @param channel the channel
|
||||||
|
* @throws IOException
|
||||||
|
* @throws ApiException
|
||||||
|
* @throws TemplateException
|
||||||
|
*/
|
||||||
|
@Handler
|
||||||
|
public void onResetVm(ResetVm event, VmChannel channel)
|
||||||
|
throws ApiException, IOException, TemplateException {
|
||||||
|
var defRoot
|
||||||
|
= GsonPtr.to(channel.vmDefinition().data()).get(JsonObject.class);
|
||||||
|
defRoot.addProperty("resetCount",
|
||||||
|
defRoot.get("resetCount").getAsLong() + 1);
|
||||||
|
Map<String, Object> model
|
||||||
|
= prepareModel(channel.client(), patchCr(channel.vmDefinition()));
|
||||||
|
cmReconciler.reconcile(model, channel);
|
||||||
|
}
|
||||||
|
|
||||||
private DynamicKubernetesObject patchCr(K8sDynamicModel vmDef) {
|
private DynamicKubernetesObject patchCr(K8sDynamicModel vmDef) {
|
||||||
var json = vmDef.data().deepCopy();
|
var json = vmDef.data().deepCopy();
|
||||||
// Adjust cdromImage path
|
// Adjust cdromImage path
|
||||||
|
|
|
||||||
|
|
@ -25,13 +25,13 @@ import io.kubernetes.client.openapi.models.V1ObjectMeta;
|
||||||
import io.kubernetes.client.util.Watch;
|
import io.kubernetes.client.util.Watch;
|
||||||
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.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 java.util.stream.Collectors;
|
||||||
import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP;
|
import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP;
|
||||||
import org.jdrupes.vmoperator.common.K8s;
|
import org.jdrupes.vmoperator.common.K8s;
|
||||||
import org.jdrupes.vmoperator.common.K8sClient;
|
import org.jdrupes.vmoperator.common.K8sClient;
|
||||||
import org.jdrupes.vmoperator.common.K8sDynamicModel;
|
|
||||||
import org.jdrupes.vmoperator.common.K8sDynamicStub;
|
import org.jdrupes.vmoperator.common.K8sDynamicStub;
|
||||||
import org.jdrupes.vmoperator.common.K8sObserver.ResponseType;
|
import org.jdrupes.vmoperator.common.K8sObserver.ResponseType;
|
||||||
import org.jdrupes.vmoperator.common.K8sV1ConfigMapStub;
|
import org.jdrupes.vmoperator.common.K8sV1ConfigMapStub;
|
||||||
|
|
@ -121,7 +121,7 @@ public class VmMonitor extends
|
||||||
}
|
}
|
||||||
if (vmDef.data() != null) {
|
if (vmDef.data() != null) {
|
||||||
// New data, augment and save
|
// New data, augment and save
|
||||||
addDynamicData(channel.client(), vmDef);
|
addDynamicData(channel.client(), vmDef, channel.vmDefinition());
|
||||||
channel.setVmDefinition(vmDef);
|
channel.setVmDefinition(vmDef);
|
||||||
} else {
|
} else {
|
||||||
// Reuse cached
|
// Reuse cached
|
||||||
|
|
@ -151,8 +151,16 @@ public class VmMonitor extends
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addDynamicData(K8sClient client, K8sDynamicModel vmState) {
|
private void addDynamicData(K8sClient client, VmDefinitionModel vmState,
|
||||||
|
VmDefinitionModel prevState) {
|
||||||
var rootNode = GsonPtr.to(vmState.data()).get(JsonObject.class);
|
var rootNode = GsonPtr.to(vmState.data()).get(JsonObject.class);
|
||||||
|
|
||||||
|
// Maintain (or initialize) the resetCount
|
||||||
|
rootNode.addProperty("resetCount", Optional.ofNullable(prevState)
|
||||||
|
.map(ps -> GsonPtr.to(ps.data()))
|
||||||
|
.flatMap(d -> d.getAsLong("resetCount")).orElse(0L));
|
||||||
|
|
||||||
|
// Add defaults in case the VM is not running
|
||||||
rootNode.addProperty("nodeName", "");
|
rootNode.addProperty("nodeName", "");
|
||||||
rootNode.addProperty("nodeAddress", "");
|
rootNode.addProperty("nodeAddress", "");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* Developers may also be interested in the usage of channels
|
* Developers may also be interested in the usage of channels
|
||||||
* by the application's component:
|
* by the application's components:
|
||||||
*
|
*
|
||||||
* 
|
* 
|
||||||
*
|
*
|
||||||
|
|
@ -74,6 +74,8 @@
|
||||||
*
|
*
|
||||||
* Component NioDispatcher as NioDispatcher <<internal>>
|
* Component NioDispatcher as NioDispatcher <<internal>>
|
||||||
* [Manager] *-up- [NioDispatcher]
|
* [Manager] *-up- [NioDispatcher]
|
||||||
|
* Component HttpConnector as HttpConnector <<internal>>
|
||||||
|
* [Manager] *-up- [HttpConnector]
|
||||||
* Component FileSystemWatcher as FileSystemWatcher <<internal>>
|
* Component FileSystemWatcher as FileSystemWatcher <<internal>>
|
||||||
* [Manager] *-up- [FileSystemWatcher]
|
* [Manager] *-up- [FileSystemWatcher]
|
||||||
* Component YamlConfigurationStore as YamlConfigurationStore <<internal>>
|
* Component YamlConfigurationStore as YamlConfigurationStore <<internal>>
|
||||||
|
|
@ -119,6 +121,7 @@
|
||||||
* [WebConsole] *-- [RoleConfigurator]
|
* [WebConsole] *-- [RoleConfigurator]
|
||||||
* [WebConsole] *-- [RoleConletFilter]
|
* [WebConsole] *-- [RoleConletFilter]
|
||||||
* [WebConsole] *-left- [LoginConlet]
|
* [WebConsole] *-left- [LoginConlet]
|
||||||
|
* [WebConsole] *-right- [OidcClient]
|
||||||
*
|
*
|
||||||
* Component "ComponentCollector\nfor page resources" as cpr <<internal>>
|
* Component "ComponentCollector\nfor page resources" as cpr <<internal>>
|
||||||
* [WebConsole] *-- [cpr]
|
* [WebConsole] *-- [cpr]
|
||||||
|
|
@ -147,21 +150,35 @@
|
||||||
* () "guiTransport" as hT
|
* () "guiTransport" as hT
|
||||||
* hT .up. [GuiSocketServer:8080]
|
* hT .up. [GuiSocketServer:8080]
|
||||||
* hT .down. [GuiHttpServer]
|
* hT .down. [GuiHttpServer]
|
||||||
|
* hT .right[hidden]. [HttpConnector]
|
||||||
*
|
*
|
||||||
* [YamlConfigurationStore] -right[hidden]- hT
|
* [YamlConfigurationStore] -right[hidden]- hT
|
||||||
*
|
*
|
||||||
* () "guiHttp" as http
|
* () "guiHttp" as http
|
||||||
* http .up. [GuiHttpServer]
|
* http .up. [GuiHttpServer]
|
||||||
|
* http .up. [HttpConnector]
|
||||||
|
* note top of [HttpConnector]: transport layer com-\nponents omitted
|
||||||
*
|
*
|
||||||
* [PreferencesStore] .right. http
|
* [PreferencesStore] .. http
|
||||||
|
* [OidcClient] .up. http
|
||||||
|
* [LanguageSelector] .left. http
|
||||||
* [InMemorySessionManager] .up. http
|
* [InMemorySessionManager] .up. http
|
||||||
* [LanguageSelector] .up. http
|
|
||||||
*
|
*
|
||||||
* package "Conceptual WebConsole" {
|
* package "Conceptual WebConsole" {
|
||||||
* [ConsoleWeblet] .left. http
|
* [ConsoleWeblet] .right. http
|
||||||
* [ConsoleWeblet] *-down- [WebConsole]
|
* [ConsoleWeblet] *-down- [WebConsole]
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
|
* [Controller] .down[hidden]. [ConsoleWeblet]
|
||||||
|
*
|
||||||
|
* () "console" as console
|
||||||
|
* console .. WebConsole
|
||||||
|
*
|
||||||
|
* [OidcClient] .. console
|
||||||
|
* [LoginConlet] .right. console
|
||||||
|
*
|
||||||
|
* note right of console: More conlets\nconnect here
|
||||||
|
*
|
||||||
* @enduml
|
* @enduml
|
||||||
*/
|
*/
|
||||||
package org.jdrupes.vmoperator.manager;
|
package org.jdrupes.vmoperator.manager;
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,12 @@
|
||||||
# property in the CRD.
|
# property in the CRD.
|
||||||
# "guestShutdownStops":
|
# "guestShutdownStops":
|
||||||
# false
|
# false
|
||||||
|
|
||||||
|
# When incremented, the VM is reset. The value has no default value,
|
||||||
|
# i.e. if you start the VM without a value for this property, and
|
||||||
|
# decide to trigger a reset later, you have to first set the value
|
||||||
|
# and then inrement it.
|
||||||
|
# "resetCounter": 1
|
||||||
|
|
||||||
# Define the VM (required)
|
# Define the VM (required)
|
||||||
"vm":
|
"vm":
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,9 @@ public class Configuration implements Dto {
|
||||||
/** If guest shutdown changes CRD .vm.state to "Stopped". */
|
/** If guest shutdown changes CRD .vm.state to "Stopped". */
|
||||||
public boolean guestShutdownStops;
|
public boolean guestShutdownStops;
|
||||||
|
|
||||||
|
/** Increments of the reset counter trigger a reset of the VM. */
|
||||||
|
public Integer resetCounter;
|
||||||
|
|
||||||
/** The vm. */
|
/** The vm. */
|
||||||
@SuppressWarnings("PMD.ShortVariable")
|
@SuppressWarnings("PMD.ShortVariable")
|
||||||
public Vm vm;
|
public Vm vm;
|
||||||
|
|
|
||||||
|
|
@ -116,8 +116,9 @@ public class DisplayController extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Objects.equals(this.currentPassword, password)) {
|
if (Objects.equals(this.currentPassword, password)) {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
this.currentPassword = password;
|
||||||
logger.fine(() -> "Updating display password");
|
logger.fine(() -> "Updating display password");
|
||||||
fire(new MonitorCommand(new QmpSetDisplayPassword(protocol, password)));
|
fire(new MonitorCommand(new QmpSetDisplayPassword(protocol, password)));
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ import org.apache.commons.cli.Option;
|
||||||
import org.apache.commons.cli.Options;
|
import org.apache.commons.cli.Options;
|
||||||
import static org.jdrupes.vmoperator.common.Constants.APP_NAME;
|
import static org.jdrupes.vmoperator.common.Constants.APP_NAME;
|
||||||
import org.jdrupes.vmoperator.runner.qemu.commands.QmpCont;
|
import org.jdrupes.vmoperator.runner.qemu.commands.QmpCont;
|
||||||
|
import org.jdrupes.vmoperator.runner.qemu.commands.QmpReset;
|
||||||
import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu;
|
import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu;
|
||||||
import org.jdrupes.vmoperator.runner.qemu.events.Exit;
|
import org.jdrupes.vmoperator.runner.qemu.events.Exit;
|
||||||
import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand;
|
import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand;
|
||||||
|
|
@ -215,6 +216,7 @@ public class Runner extends Component {
|
||||||
private CommandDefinition cloudInitImgDefinition;
|
private CommandDefinition cloudInitImgDefinition;
|
||||||
private CommandDefinition qemuDefinition;
|
private CommandDefinition qemuDefinition;
|
||||||
private final QemuMonitor qemuMonitor;
|
private final QemuMonitor qemuMonitor;
|
||||||
|
private Integer resetCounter;
|
||||||
private State state = State.INITIALIZING;
|
private State state = State.INITIALIZING;
|
||||||
|
|
||||||
/** Preparatory actions for QEMU start */
|
/** Preparatory actions for QEMU start */
|
||||||
|
|
@ -615,7 +617,7 @@ public class Runner extends Component {
|
||||||
* @param event the event
|
* @param event the event
|
||||||
*/
|
*/
|
||||||
@Handler(priority = -1000)
|
@Handler(priority = -1000)
|
||||||
public void onConfigureQemu(ConfigureQemu event) {
|
public void onConfigureQemuFinal(ConfigureQemu event) {
|
||||||
if (state == State.STARTING) {
|
if (state == State.STARTING) {
|
||||||
fire(new MonitorCommand(new QmpCont()));
|
fire(new MonitorCommand(new QmpCont()));
|
||||||
state = State.RUNNING;
|
state = State.RUNNING;
|
||||||
|
|
@ -624,6 +626,23 @@ public class Runner extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On configure qemu.
|
||||||
|
*
|
||||||
|
* @param event the event
|
||||||
|
*/
|
||||||
|
@Handler
|
||||||
|
public void onConfigureQemu(ConfigureQemu event) {
|
||||||
|
if (state == State.RUNNING) {
|
||||||
|
if (resetCounter != null
|
||||||
|
&& event.configuration().resetCounter != null
|
||||||
|
&& event.configuration().resetCounter > resetCounter) {
|
||||||
|
fire(new MonitorCommand(new QmpReset()));
|
||||||
|
}
|
||||||
|
resetCounter = event.configuration().resetCounter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On process exited.
|
* On process exited.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* 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.commands;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link QmpCommand} that send a system_reset to the VM.
|
||||||
|
*/
|
||||||
|
public class QmpReset extends QmpCommand {
|
||||||
|
|
||||||
|
@SuppressWarnings({ "PMD.FieldNamingConventions",
|
||||||
|
"PMD.VariableNamingConventions" })
|
||||||
|
private static final JsonNode jsonTemplate
|
||||||
|
= parseJson("{ \"execute\": \"system_reset\" }");
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonNode toJson() {
|
||||||
|
return jsonTemplate.deepCopy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "QmpReset()";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -265,6 +265,18 @@ public class GsonPtr {
|
||||||
return set(selector, new JsonPrimitive(value));
|
return set(selector, new JsonPrimitive(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Short for `set(selector, new JsonPrimitive(value))`.
|
||||||
|
*
|
||||||
|
* @param selector the selector
|
||||||
|
* @param value the value
|
||||||
|
* @return the gson ptr
|
||||||
|
* @see #set(Object, JsonElement)
|
||||||
|
*/
|
||||||
|
public GsonPtr set(Object selector, Long value) {
|
||||||
|
return set(selector, new JsonPrimitive(value));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Short for `set(selector, new JsonPrimitive(value))`.
|
* Short for `set(selector, new JsonPrimitive(value))`.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -52,12 +52,14 @@
|
||||||
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.spec.vm.state != 'Running'"
|
<span role="button"
|
||||||
|
v-if="entry.spec.vm.state != 'Running' && !entry['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-else class="fa fa-play"
|
<span role="button" v-else class="fa fa-play"
|
||||||
aria-disabled="true" :title="localize('Start VM')"></span>
|
aria-disabled="true" :title="localize('Start VM')"></span>
|
||||||
<span role="button" v-if="entry.spec.vm.state != 'Stopped'"
|
<span role="button"
|
||||||
|
v-if="entry.spec.vm.state != 'Stopped' && entry['running']"
|
||||||
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"
|
<span role="button" v-else class="fa fa-stop"
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ plugins {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':org.jdrupes.vmoperator.manager.events')
|
implementation project(':org.jdrupes.vmoperator.manager.events')
|
||||||
|
|
||||||
implementation 'org.jgrapes:org.jgrapes.webconsole.base:[1.3.0,2)'
|
implementation 'org.jgrapes:org.jgrapes.webconsole.base:[1.7.0,2)'
|
||||||
implementation 'org.jgrapes:org.jgrapes.webconsole.provider.vue:[1,2)'
|
implementation 'org.jgrapes:org.jgrapes.webconsole.provider.vue:[1,2)'
|
||||||
implementation 'org.jgrapes:org.jgrapes.webconsole.provider.jgwcvuecomponents:[1.2,2)'
|
implementation 'org.jgrapes:org.jgrapes.webconsole.provider.jgwcvuecomponents:[1.2,2)'
|
||||||
implementation 'org.jgrapes:org.jgrapes.webconsole.provider.chartjs:[1.2,2)'
|
implementation 'org.jgrapes:org.jgrapes.webconsole.provider.chartjs:[1.2,2)'
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<div
|
||||||
|
class="jdrupes-vmoperator-vmviewer jdrupes-vmoperator-vmviewer-confirm-reset">
|
||||||
|
<p>${_("confirmResetMsg")}</p>
|
||||||
|
<p>
|
||||||
|
<span role="button" tabindex="0" class="svg-icon"
|
||||||
|
onclick="orgJDrupesVmOperatorVmViewer.confirmReset('${conletType}', '${conletId}')">
|
||||||
|
<svg viewBox="0 0 1541.33 1535.5083">
|
||||||
|
<path d="m 0,127.9968 v 448 c 0,35 29,64 64,64 h 448 c 35,0 64,-29 64,-64 0,-17 -6.92831,-33.07213 -19,-45 C 264.23058,241.7154 337.19508,314.89599 109,82.996795 c -11.999999,-12 -28,-19 -45,-19 -35,0 -64,29 -64,64.000005 z" />
|
||||||
|
<path d="m 772.97656,1535.5046 c 117.57061,0.3623 236.06134,-26.2848 345.77544,-81.4687 292.5708,-147.1572 459.8088,-465.37411 415.5214,-790.12504 C 1489.9861,339.15993 1243.597,77.463924 922.29883,14.342498 601.00067,-48.778928 274.05699,100.37563 110.62891,384.39133 c -34.855139,60.57216 -14.006492,137.9313 46.5664,172.78516 60.57172,34.85381 137.92941,14.00532 172.78321,-46.56641 109.97944,-191.12927 327.69604,-290.34657 543.53515,-247.94336 215.83913,42.40321 380.18953,216.77543 410.00973,435.44141 29.8203,218.66598 -81.8657,430.94957 -278.4863,529.84567 -196.6206,98.8962 -432.84043,61.8202 -589.90233,-92.6777 -24.91016,-24.5038 -85.48587,-83.3326 -119.02246,-52.9832 -24.01114,21.7292 -35.41741,29.5454 -59.9209,54.4559 -24.50381,24.9102 -35.33636,36.9034 -57.54543,60.4713 -38.1335,40.4667 34.10761,93.9685 59.01808,118.472 145.96311,143.5803 339.36149,219.2087 535.3125,219.8125 z"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
<div title="${_("conletName")}" class="jdrupes-vmoperator-vmviewer-edit"
|
<div title="${_("conletName")}"
|
||||||
data-jgwc-on-load="orgJDrupesVmOperatorVmViewer.initEdit"
|
class="jdrupes-vmoperator-vmviewer jdrupes-vmoperator-vmviewer-edit"
|
||||||
data-jgwc-on-action="orgJDrupesVmOperatorVmViewer.applyEdit"
|
data-jgwc-on-load="orgJDrupesVmOperatorVmViewer.initEdit"
|
||||||
data-jgwc-on-unload="JGConsole.jgwc.unmountVueApps">
|
data-jgwc-on-action="orgJDrupesVmOperatorVmViewer.applyEdit"
|
||||||
|
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>
|
<span>{{ localize("Select VM") }}</span>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<div class="jdrupes-vmoperator-vmviewer jdrupes-vmoperator-vmviewer-preview"
|
<div
|
||||||
|
class="jdrupes-vmoperator-vmviewer jdrupes-vmoperator-vmviewer-preview"
|
||||||
data-conlet-grid-rows="2" data-conlet-grid-columns="2"
|
data-conlet-grid-rows="2" data-conlet-grid-columns="2"
|
||||||
data-jgwc-on-load="orgJDrupesVmOperatorVmViewer.initPreview"
|
data-jgwc-on-load="orgJDrupesVmOperatorVmViewer.initPreview"
|
||||||
data-jgwc-on-unload="JGConsole.jgwc.unmountVueApps"
|
data-jgwc-on-unload="JGConsole.jgwc.unmountVueApps"
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
conletName = VM Console
|
conletName = VM Console
|
||||||
|
|
||||||
okayLabel = Apply and Close
|
okayLabel = Apply and Close
|
||||||
|
|
||||||
|
confirmResetTitle = Confirm reset
|
||||||
|
confirmResetMsg = Resetting the VM may cause loss of data. \
|
||||||
|
Please confirm to continue.
|
||||||
|
|
|
||||||
|
|
@ -5,4 +5,9 @@ Select\ VM = VM ausw
|
||||||
|
|
||||||
Start\ VM = VM starten
|
Start\ VM = VM starten
|
||||||
Stop\ VM = VM anhalten
|
Stop\ VM = VM anhalten
|
||||||
|
Reset\ VM = VM zurücksetzen
|
||||||
Open\ console = Konsole anzeigen
|
Open\ console = Konsole anzeigen
|
||||||
|
|
||||||
|
confirmResetTitle = Zurücksetzen bestätigen
|
||||||
|
confirmResetMsg = Zurücksetzen der VM kann zu Datenverlust führen. \
|
||||||
|
Bitte bestätigen um fortzufahren.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
width="1541.33"
|
||||||
|
height="1535.5083"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
sodipodi:docname="reset-icon2.svg"
|
||||||
|
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
|
||||||
|
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" />
|
||||||
|
<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="0.34987054"
|
||||||
|
inkscape:cx="704.54631"
|
||||||
|
inkscape:cy="711.69181"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1011"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="32"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg1" />
|
||||||
|
<path
|
||||||
|
d="m 0,127.9968 v 448 c 0,35 29,64 64,64 h 448 c 35,0 64,-29 64,-64 0,-17 -6.92831,-33.07213 -19,-45 C 264.23058,241.7154 337.19508,314.89599 109,82.996795 c -11.999999,-12 -28,-19 -45,-19 -35,0 -64,29 -64,64.000005 z"
|
||||||
|
id="path1"
|
||||||
|
sodipodi:nodetypes="sssssscss" />
|
||||||
|
<path
|
||||||
|
style="color:#000000;fill:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0;-inkscape-stroke:none;paint-order:fill markers stroke"
|
||||||
|
d="m 772.97656,1535.5046 c 117.57061,0.3623 236.06134,-26.2848 345.77544,-81.4687 292.5708,-147.1572 459.8088,-465.37411 415.5214,-790.12504 C 1489.9861,339.15993 1243.597,77.463924 922.29883,14.342498 601.00067,-48.778928 274.05699,100.37563 110.62891,384.39133 c -34.855139,60.57216 -14.006492,137.9313 46.5664,172.78516 60.57172,34.85381 137.92941,14.00532 172.78321,-46.56641 109.97944,-191.12927 327.69604,-290.34657 543.53515,-247.94336 215.83913,42.40321 380.18953,216.77543 410.00973,435.44141 29.8203,218.66598 -81.8657,430.94957 -278.4863,529.84567 -196.6206,98.8962 -432.84043,61.8202 -589.90233,-92.6777 -24.91016,-24.5038 -85.48587,-83.3326 -119.02246,-52.9832 -24.01114,21.7292 -35.41741,29.5454 -59.9209,54.4559 -24.50381,24.9102 -35.33636,36.9034 -57.54543,60.4713 -38.1335,40.4667 34.10761,93.9685 59.01808,118.472 145.96311,143.5803 339.36149,219.2087 535.3125,219.8125 z"
|
||||||
|
id="path2"
|
||||||
|
sodipodi:nodetypes="sssscccssscscscs" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
|
|
@ -20,6 +20,7 @@ package org.jdrupes.vmoperator.vmviewer;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonGetter;
|
import com.fasterxml.jackson.annotation.JsonGetter;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
|
@ -54,6 +55,7 @@ import org.jdrupes.vmoperator.common.VmDefinitionModel.Permission;
|
||||||
import org.jdrupes.vmoperator.manager.events.ChannelCache;
|
import org.jdrupes.vmoperator.manager.events.ChannelCache;
|
||||||
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.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.util.GsonPtr;
|
import org.jdrupes.vmoperator.util.GsonPtr;
|
||||||
|
|
@ -90,10 +92,10 @@ import org.jgrapes.webconsole.base.events.UpdateConletType;
|
||||||
import org.jgrapes.webconsole.base.freemarker.FreeMarkerConlet;
|
import org.jgrapes.webconsole.base.freemarker.FreeMarkerConlet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Class VmConlet.
|
* The Class VmViewer.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.ExcessiveImports",
|
@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.ExcessiveImports",
|
||||||
"PMD.CouplingBetweenObjects", "PMD.GodClass" })
|
"PMD.CouplingBetweenObjects", "PMD.GodClass", "PMD.TooManyMethods" })
|
||||||
public class VmViewer extends FreeMarkerConlet<VmViewer.ViewerModel> {
|
public class VmViewer extends FreeMarkerConlet<VmViewer.ViewerModel> {
|
||||||
|
|
||||||
private static final String VM_NAME_PROPERTY = "vmName";
|
private static final String VM_NAME_PROPERTY = "vmName";
|
||||||
|
|
@ -465,12 +467,19 @@ public class VmViewer extends FreeMarkerConlet<VmViewer.ViewerModel> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings({ "PMD.AvoidDecimalLiteralsInBigDecimalConstructor",
|
@SuppressWarnings({ "PMD.AvoidDecimalLiteralsInBigDecimalConstructor",
|
||||||
"PMD.ConfusingArgumentToVarargsMethod" })
|
"PMD.ConfusingArgumentToVarargsMethod", "PMD.NcssCount",
|
||||||
|
"PMD.AvoidLiteralsInIfCondition" })
|
||||||
protected void doUpdateConletState(NotifyConletModel event,
|
protected void doUpdateConletState(NotifyConletModel event,
|
||||||
ConsoleConnection channel, ViewerModel model)
|
ConsoleConnection channel, ViewerModel model)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
event.stop();
|
event.stop();
|
||||||
var both = Optional.ofNullable(event.params().asString(0))
|
if ("selectedVm".equals(event.method())) {
|
||||||
|
selectVm(event, channel, model);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle command for selected VM
|
||||||
|
var both = Optional.ofNullable(model.vmName())
|
||||||
.flatMap(vm -> channelManager.both(vm));
|
.flatMap(vm -> channelManager.both(vm));
|
||||||
if (both.isEmpty()) {
|
if (both.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -479,14 +488,8 @@ public class VmViewer extends FreeMarkerConlet<VmViewer.ViewerModel> {
|
||||||
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 = permissions(vmDef, channel.session());
|
||||||
|
var resourceBundle = resourceBundle(channel.locale());
|
||||||
switch (event.method()) {
|
switch (event.method()) {
|
||||||
case "selectedVm":
|
|
||||||
model.setVmName(event.params().asString(0));
|
|
||||||
String jsonState = objectMapper.writeValueAsString(model);
|
|
||||||
channel.respond(new KeyValueStoreUpdate().update(storagePath(
|
|
||||||
channel.session(), model.getConletId()), jsonState));
|
|
||||||
updateConfig(channel, model);
|
|
||||||
break;
|
|
||||||
case "start":
|
case "start":
|
||||||
if (perms.contains(Permission.START)) {
|
if (perms.contains(Permission.START)) {
|
||||||
fire(new ModifyVm(vmName, "state", "Running", vmChannel));
|
fire(new ModifyVm(vmName, "state", "Running", vmChannel));
|
||||||
|
|
@ -497,6 +500,16 @@ public class VmViewer extends FreeMarkerConlet<VmViewer.ViewerModel> {
|
||||||
fire(new ModifyVm(vmName, "state", "Stopped", vmChannel));
|
fire(new ModifyVm(vmName, "state", "Stopped", vmChannel));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "reset":
|
||||||
|
if (perms.contains(Permission.RESET)) {
|
||||||
|
confirmReset(event, channel, model, resourceBundle);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "resetConfirmed":
|
||||||
|
if (perms.contains(Permission.RESET)) {
|
||||||
|
fire(new ResetVm(vmName), vmChannel);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case "openConsole":
|
case "openConsole":
|
||||||
if (perms.contains(Permission.ACCESS_CONSOLE)) {
|
if (perms.contains(Permission.ACCESS_CONSOLE)) {
|
||||||
var pwQuery = Event.onCompletion(new GetDisplayPassword(vmDef),
|
var pwQuery = Event.onCompletion(new GetDisplayPassword(vmDef),
|
||||||
|
|
@ -510,6 +523,15 @@ public class VmViewer extends FreeMarkerConlet<VmViewer.ViewerModel> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void selectVm(NotifyConletModel event, ConsoleConnection channel,
|
||||||
|
ViewerModel model) throws JsonProcessingException {
|
||||||
|
model.setVmName(event.params().asString(0));
|
||||||
|
String jsonState = objectMapper.writeValueAsString(model);
|
||||||
|
channel.respond(new KeyValueStoreUpdate().update(storagePath(
|
||||||
|
channel.session(), model.getConletId()), jsonState));
|
||||||
|
updateConfig(channel, model);
|
||||||
|
}
|
||||||
|
|
||||||
private void openConsole(String vmName, ConsoleConnection connection,
|
private void openConsole(String vmName, ConsoleConnection connection,
|
||||||
ViewerModel model, String password) {
|
ViewerModel model, String password) {
|
||||||
var vmDef = channelManager.associated(vmName).orElse(null);
|
var vmDef = channelManager.associated(vmName).orElse(null);
|
||||||
|
|
@ -577,6 +599,20 @@ public class VmViewer extends FreeMarkerConlet<VmViewer.ViewerModel> {
|
||||||
.findFirst().or(() -> addrs.stream().findFirst());
|
.findFirst().or(() -> addrs.stream().findFirst());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void confirmReset(NotifyConletModel event,
|
||||||
|
ConsoleConnection channel, ViewerModel model,
|
||||||
|
ResourceBundle resourceBundle) throws TemplateNotFoundException,
|
||||||
|
MalformedTemplateNameException, ParseException, IOException {
|
||||||
|
Template tpl = freemarkerConfig()
|
||||||
|
.getTemplate("VmViewer-confirmReset.ftl.html");
|
||||||
|
channel.respond(new OpenModalDialog(type(), model.getConletId(),
|
||||||
|
processTemplate(event, tpl,
|
||||||
|
fmModel(event, channel, model.getConletId(), model)))
|
||||||
|
.addOption("cancelable", true).addOption("closeLabel", "")
|
||||||
|
.addOption("title",
|
||||||
|
resourceBundle.getString("confirmResetTitle")));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doSetLocale(SetLocale event, ConsoleConnection channel,
|
protected boolean doSetLocale(SetLocale event, ConsoleConnection channel,
|
||||||
String conletId) throws Exception {
|
String conletId) throws Exception {
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,9 @@ declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
orgJDrupesVmOperatorVmViewer: {
|
orgJDrupesVmOperatorVmViewer: {
|
||||||
initPreview?: (previewDom: HTMLElement, isUpdate: boolean) => void,
|
initPreview?: (previewDom: HTMLElement, isUpdate: boolean) => void,
|
||||||
initEdit?: (viewDom: HTMLElement, isUpdate: boolean) => void
|
initEdit?: (viewDom: HTMLElement, isUpdate: boolean) => void,
|
||||||
applyEdit?: (viewDom: HTMLElement, apply: boolean) => void
|
applyEdit?: (viewDom: HTMLElement, apply: boolean) => void,
|
||||||
|
confirmReset?: (conletType: string, conletId: string) => void
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -63,7 +64,16 @@ window.orgJDrupesVmOperatorVmViewer.initPreview = (previewDom: HTMLElement,
|
||||||
vmName: "",
|
vmName: "",
|
||||||
vmDefinition: {}
|
vmDefinition: {}
|
||||||
});
|
});
|
||||||
const vmDef = computed(() => previewApi.vmDefinition);
|
const configured = computed(() => previewApi.vmDefinition.spec);
|
||||||
|
const startable = computed(() => previewApi.vmDefinition.spec &&
|
||||||
|
previewApi.vmDefinition.spec.vm.state !== 'Running'
|
||||||
|
&& !previewApi.vmDefinition.running);
|
||||||
|
const stoppable = computed(() => previewApi.vmDefinition.spec &&
|
||||||
|
previewApi.vmDefinition.spec.vm.state !== 'Stopped'
|
||||||
|
&& previewApi.vmDefinition.running);
|
||||||
|
const running = computed(() => previewApi.vmDefinition.running);
|
||||||
|
const permissions = computed(() => previewApi.vmDefinition.spec
|
||||||
|
? previewApi.vmDefinition.userPermissions : []);
|
||||||
|
|
||||||
watch(() => previewApi.vmName, (name: string) => {
|
watch(() => previewApi.vmName, (name: string) => {
|
||||||
if (name !== "") {
|
if (name !== "") {
|
||||||
|
|
@ -73,41 +83,51 @@ window.orgJDrupesVmOperatorVmViewer.initPreview = (previewDom: HTMLElement,
|
||||||
|
|
||||||
provideApi(previewDom, previewApi);
|
provideApi(previewDom, previewApi);
|
||||||
|
|
||||||
const vmAction = (vmName: string, action: string) => {
|
const vmAction = (action: string) => {
|
||||||
JGConsole.notifyConletModel(conletId, action, vmName);
|
JGConsole.notifyConletModel(conletId, action);
|
||||||
};
|
};
|
||||||
|
|
||||||
return { localize, resourceBase, vmDef, vmAction };
|
return { localize, resourceBase, vmAction, configured,
|
||||||
|
startable, stoppable, running, permissions };
|
||||||
},
|
},
|
||||||
template: `
|
template: `
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td><img role=button
|
<td rowspan="2" style="position: relative"><span
|
||||||
:aria-disabled="!vmDef.running || !vmDef.userPermissions
|
style="position: absolute;"
|
||||||
|| !vmDef.userPermissions.includes('accessConsole')"
|
:class="{ busy: configured && !startable && !stoppable }"
|
||||||
v-on:click="vmAction(vmDef.name, 'openConsole')"
|
><img role=button :aria-disabled="!running
|
||||||
:src="resourceBase + (vmDef.running
|
|| !permissions.includes('accessConsole')"
|
||||||
? 'computer.svg' : 'computer-off.svg')"
|
v-on:click="vmAction('openConsole')"
|
||||||
:title="localize('Open console')"></td>
|
:src="resourceBase + (running
|
||||||
<td v-if="vmDef.spec"
|
? 'computer.svg' : 'computer-off.svg')"
|
||||||
class="jdrupes-vmoperator-vmviewer-preview-action-list">
|
:title="localize('Open console')"></span><span
|
||||||
<span role="button" v-if="vmDef.spec.vm.state != 'Running'"
|
style="visibility: hidden;"><img
|
||||||
:aria-disabled="!vmDef.userPermissions.includes('start')"
|
:src="resourceBase + 'computer.svg'"></span></td>
|
||||||
|
<td class="jdrupes-vmoperator-vmviewer-preview-action-list">
|
||||||
|
<span role="button"
|
||||||
|
:aria-disabled="!startable || !permissions.includes('start')"
|
||||||
tabindex="0" class="fa fa-play" :title="localize('Start VM')"
|
tabindex="0" class="fa fa-play" :title="localize('Start VM')"
|
||||||
v-on:click="vmAction(vmDef.name, 'start')"></span>
|
v-on:click="vmAction('start')"></span>
|
||||||
<span role="button" v-else class="fa fa-play"
|
<span role="button"
|
||||||
aria-disabled="true" :title="localize('Start VM')"></span>
|
:aria-disabled="!stoppable || !permissions.includes('stop')"
|
||||||
<span role="button" v-if="vmDef.spec.vm.state != 'Stopped'"
|
|
||||||
:aria-disabled="!vmDef.userPermissions.includes('stop')"
|
|
||||||
tabindex="0" class="fa fa-stop" :title="localize('Stop VM')"
|
tabindex="0" class="fa fa-stop" :title="localize('Stop VM')"
|
||||||
v-on:click="vmAction(vmDef.name, 'stop')"></span>
|
v-on:click="vmAction('stop')"></span>
|
||||||
<span role="button" v-else class="fa fa-stop"
|
<span role="button"
|
||||||
aria-disabled="true" :title="localize('Stop VM')"></span>
|
:aria-disabled="!running || !permissions.includes('reset')"
|
||||||
</td>
|
tabindex="0" class="svg-icon" :title="localize('Reset VM')"
|
||||||
<td v-else>
|
v-on:click="vmAction('reset')">
|
||||||
|
<svg viewBox="0 0 1541.33 1535.5083">
|
||||||
|
<path d="m 0,127.9968 v 448 c 0,35 29,64 64,64 h 448 c 35,0 64,-29 64,-64 0,-17 -6.92831,-33.07213 -19,-45 C 264.23058,241.7154 337.19508,314.89599 109,82.996795 c -11.999999,-12 -28,-19 -45,-19 -35,0 -64,29 -64,64.000005 z" />
|
||||||
|
<path d="m 772.97656,1535.5046 c 117.57061,0.3623 236.06134,-26.2848 345.77544,-81.4687 292.5708,-147.1572 459.8088,-465.37411 415.5214,-790.12504 C 1489.9861,339.15993 1243.597,77.463924 922.29883,14.342498 601.00067,-48.778928 274.05699,100.37563 110.62891,384.39133 c -34.855139,60.57216 -14.006492,137.9313 46.5664,172.78516 60.57172,34.85381 137.92941,14.00532 172.78321,-46.56641 109.97944,-191.12927 327.69604,-290.34657 543.53515,-247.94336 215.83913,42.40321 380.18953,216.77543 410.00973,435.44141 29.8203,218.66598 -81.8657,430.94957 -278.4863,529.84567 -196.6206,98.8962 -432.84043,61.8202 -589.90233,-92.6777 -24.91016,-24.5038 -85.48587,-83.3326 -119.02246,-52.9832 -24.01114,21.7292 -35.41741,29.5454 -59.9209,54.4559 -24.50381,24.9102 -35.33636,36.9034 -57.54543,60.4713 -38.1335,40.4667 34.10761,93.9685 59.01808,118.472 145.96311,143.5803 339.36149,219.2087 535.3125,219.8125 z"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>`
|
</table>`
|
||||||
});
|
});
|
||||||
|
|
@ -209,3 +229,9 @@ window.orgJDrupesVmOperatorVmViewer.applyEdit =
|
||||||
const vmName = getApi<ref<string>>(dialogDom!)!.value;
|
const vmName = getApi<ref<string>>(dialogDom!)!.value;
|
||||||
JGConsole.notifyConletModel(conletId, "selectedVm", vmName);
|
JGConsole.notifyConletModel(conletId, "selectedVm", vmName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.orgJDrupesVmOperatorVmViewer.confirmReset =
|
||||||
|
(conletType: string, conletId: string) => {
|
||||||
|
JGConsole.instance.closeModalDialog(conletType, conletId);
|
||||||
|
JGConsole.notifyConletModel(conletId, "resetConfirmed");
|
||||||
|
}
|
||||||
|
|
@ -19,7 +19,24 @@
|
||||||
/*
|
/*
|
||||||
* Conlet specific styles.
|
* Conlet specific styles.
|
||||||
*/
|
*/
|
||||||
.jdrupes-vmoperator-vmviewer-preview {
|
.jdrupes-vmoperator-vmviewer {
|
||||||
|
|
||||||
|
span[role="button"].svg-icon {
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 1;
|
||||||
|
/* Align with forkawesome */
|
||||||
|
font-size: 14px;
|
||||||
|
fill: var(--primary);
|
||||||
|
|
||||||
|
&[aria-disabled="true"], &[aria-disabled=""] {
|
||||||
|
fill: var(--disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
height: 2ex;
|
||||||
|
width: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[role=button] {
|
[role=button] {
|
||||||
padding: 0.25rem;
|
padding: 0.25rem;
|
||||||
|
|
@ -28,7 +45,10 @@
|
||||||
box-shadow: var(--darkening);
|
box-shadow: var(--darkening);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.jdrupes-vmoperator-vmviewer.jdrupes-vmoperator-vmviewer-preview {
|
||||||
|
|
||||||
img {
|
img {
|
||||||
height: 3em;
|
height: 3em;
|
||||||
padding: 0.25rem;
|
padding: 0.25rem;
|
||||||
|
|
@ -37,9 +57,42 @@
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.jdrupes-vmoperator-vmviewer-preview-action-list {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.busy::before {
|
||||||
|
font: normal normal normal 14px/1 ForkAwesome;
|
||||||
|
font-size: 1.125em;
|
||||||
|
content: "\f1ce";
|
||||||
|
left: 1.45em;
|
||||||
|
top: 0.7em;
|
||||||
|
color: var(--info);
|
||||||
|
position: absolute;
|
||||||
|
animation: spin 2s linear infinite;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.jdrupes-vmoperator-vmviewer-preview-action-list {
|
.jdrupes-vmoperator-vmviewer.jdrupes-vmoperator-vmviewer-edit {
|
||||||
white-space: nowrap;
|
select {
|
||||||
|
width: 15em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.jdrupes-vmoperator-vmviewer.jdrupes-vmoperator-vmviewer-confirm-reset {
|
||||||
|
p {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
span[role="button"].svg-icon {
|
||||||
|
fill: var(--danger);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 2.5em;
|
||||||
|
height: 2.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue