Rename vmviewer to vmaccess.
This commit is contained in:
parent
00adeba625
commit
27f983c18d
36 changed files with 58 additions and 56 deletions
10
org.jdrupes.vmoperator.vmaccess/.checkstyle
Normal file
10
org.jdrupes.vmoperator.vmaccess/.checkstyle
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<fileset-config file-format-version="1.2.0" simple-config="false" sync-formatter="false">
|
||||
<local-check-config name="Project Checks" location="/VM-Operator/checkstyle.xml" type="project" description="">
|
||||
<additional-data name="protect-config-file" value="false"/>
|
||||
</local-check-config>
|
||||
<fileset name="all" enabled="true" check-config-name="Project Checks" local="true">
|
||||
<file-match-pattern match-pattern="." include-pattern="true"/>
|
||||
</fileset>
|
||||
</fileset-config>
|
||||
7
org.jdrupes.vmoperator.vmaccess/.eclipse-pmd
Normal file
7
org.jdrupes.vmoperator.vmaccess/.eclipse-pmd
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<eclipse-pmd xmlns="http://acanda.ch/eclipse-pmd/0.8" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://acanda.ch/eclipse-pmd/0.8 http://acanda.ch/eclipse-pmd/eclipse-pmd-0.8.xsd">
|
||||
<analysis enabled="true" />
|
||||
<rulesets>
|
||||
<ruleset name="Custom Rules" ref="VM-Operator/ruleset.xml" refcontext="workspace" />
|
||||
</rulesets>
|
||||
</eclipse-pmd>
|
||||
1
org.jdrupes.vmoperator.vmaccess/.eslintignore
Normal file
1
org.jdrupes.vmoperator.vmaccess/.eslintignore
Normal file
|
|
@ -0,0 +1 @@
|
|||
rollup.config.mjs
|
||||
15
org.jdrupes.vmoperator.vmaccess/.eslintrc.json
Normal file
15
org.jdrupes.vmoperator.vmaccess/.eslintrc.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": { "project": ["./tsconfig.json"] },
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
"constructor-super": "off"
|
||||
}
|
||||
}
|
||||
|
||||
4
org.jdrupes.vmoperator.vmaccess/.gitignore
vendored
Normal file
4
org.jdrupes.vmoperator.vmaccess/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
/bin/
|
||||
/bin_test/
|
||||
/generated/
|
||||
/build/
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
build.commands=org.eclipse.jdt.core.javabuilder
|
||||
connection.arguments=
|
||||
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
|
||||
connection.java.home=null
|
||||
connection.jvm.arguments=
|
||||
connection.project.dir=..
|
||||
derived.resources=.gradle,generated
|
||||
eclipse.preferences.version=1
|
||||
natures=org.eclipse.jdt.groovy.core.groovyNature,org.eclipse.jdt.core.javanature
|
||||
project.path=\:org.jgrapes.osgi.conlets.services
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
eclipse.preferences.version=1
|
||||
encoding/<project>=UTF-8
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
eclipse.preferences.version=1
|
||||
line.separator=\n
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
eclipse.preferences.version=1
|
||||
editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
|
||||
formatter_profile=_JGrapes
|
||||
formatter_settings_version=13
|
||||
sp_cleanup.add_default_serial_version_id=true
|
||||
sp_cleanup.add_generated_serial_version_id=false
|
||||
sp_cleanup.add_missing_annotations=true
|
||||
sp_cleanup.add_missing_deprecated_annotations=true
|
||||
sp_cleanup.add_missing_methods=false
|
||||
sp_cleanup.add_missing_nls_tags=false
|
||||
sp_cleanup.add_missing_override_annotations=true
|
||||
sp_cleanup.add_missing_override_annotations_interface_methods=true
|
||||
sp_cleanup.add_serial_version_id=false
|
||||
sp_cleanup.always_use_blocks=true
|
||||
sp_cleanup.always_use_parentheses_in_expressions=false
|
||||
sp_cleanup.always_use_this_for_non_static_field_access=false
|
||||
sp_cleanup.always_use_this_for_non_static_method_access=false
|
||||
sp_cleanup.convert_functional_interfaces=false
|
||||
sp_cleanup.convert_to_enhanced_for_loop=false
|
||||
sp_cleanup.correct_indentation=false
|
||||
sp_cleanup.format_source_code=true
|
||||
sp_cleanup.format_source_code_changes_only=false
|
||||
sp_cleanup.insert_inferred_type_arguments=false
|
||||
sp_cleanup.make_local_variable_final=true
|
||||
sp_cleanup.make_parameters_final=false
|
||||
sp_cleanup.make_private_fields_final=true
|
||||
sp_cleanup.make_type_abstract_if_missing_method=false
|
||||
sp_cleanup.make_variable_declarations_final=false
|
||||
sp_cleanup.never_use_blocks=false
|
||||
sp_cleanup.never_use_parentheses_in_expressions=true
|
||||
sp_cleanup.on_save_use_additional_actions=false
|
||||
sp_cleanup.organize_imports=false
|
||||
sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
|
||||
sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
|
||||
sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
|
||||
sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
|
||||
sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
|
||||
sp_cleanup.remove_private_constructors=true
|
||||
sp_cleanup.remove_redundant_type_arguments=false
|
||||
sp_cleanup.remove_trailing_whitespaces=false
|
||||
sp_cleanup.remove_trailing_whitespaces_all=true
|
||||
sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
|
||||
sp_cleanup.remove_unnecessary_casts=true
|
||||
sp_cleanup.remove_unnecessary_nls_tags=false
|
||||
sp_cleanup.remove_unused_imports=false
|
||||
sp_cleanup.remove_unused_local_variables=false
|
||||
sp_cleanup.remove_unused_private_fields=true
|
||||
sp_cleanup.remove_unused_private_members=false
|
||||
sp_cleanup.remove_unused_private_methods=true
|
||||
sp_cleanup.remove_unused_private_types=true
|
||||
sp_cleanup.sort_members=false
|
||||
sp_cleanup.sort_members_all=false
|
||||
sp_cleanup.use_anonymous_class_creation=false
|
||||
sp_cleanup.use_blocks=false
|
||||
sp_cleanup.use_blocks_only_for_return_and_throw=false
|
||||
sp_cleanup.use_lambda=true
|
||||
sp_cleanup.use_parentheses_in_expressions=false
|
||||
sp_cleanup.use_this_for_non_static_field_access=false
|
||||
sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
|
||||
sp_cleanup.use_this_for_non_static_method_access=false
|
||||
sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
|
||||
sp_jautodoc.cleanup.add_header=false
|
||||
sp_jautodoc.cleanup.replace_header=false
|
||||
57
org.jdrupes.vmoperator.vmaccess/build.gradle
Normal file
57
org.jdrupes.vmoperator.vmaccess/build.gradle
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
plugins {
|
||||
id 'org.jdrupes.vmoperator.java-library-conventions'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':org.jdrupes.vmoperator.manager.events')
|
||||
|
||||
implementation 'org.jgrapes:org.jgrapes.webconsole.base:[2.1.0,3)'
|
||||
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.chartjs:[1.2,2)'
|
||||
|
||||
}
|
||||
|
||||
apply plugin: 'com.github.node-gradle.node'
|
||||
|
||||
node {
|
||||
download = true
|
||||
}
|
||||
|
||||
task extractDependencies(type: Copy) {
|
||||
from configurations.compileClasspath
|
||||
.findAll{ it.name.contains('.provider.')
|
||||
|| it.name.contains('org.jgrapes.webconsole.base')
|
||||
}
|
||||
.collect{ zipTree (it) }
|
||||
exclude '*.class'
|
||||
into 'build/unpacked'
|
||||
duplicatesStrategy 'include'
|
||||
}
|
||||
|
||||
task compileTs(type: NodeTask) {
|
||||
dependsOn ':npmInstall'
|
||||
dependsOn extractDependencies
|
||||
inputs.dir project.file('src')
|
||||
inputs.file project.file('tsconfig.json')
|
||||
inputs.file project.file('rollup.config.mjs')
|
||||
outputs.dir project.file('build/generated/resources')
|
||||
script = file("${rootProject.rootDir}/node_modules/rollup/dist/bin/rollup")
|
||||
args = ["-c"]
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
resources {
|
||||
srcDir project.file('build/generated/resources')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processResources {
|
||||
dependsOn compileTs
|
||||
}
|
||||
|
||||
eclipse {
|
||||
autoBuildTasks compileTs
|
||||
}
|
||||
1
org.jdrupes.vmoperator.vmaccess/package.json
Normal file
1
org.jdrupes.vmoperator.vmaccess/package.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{}
|
||||
|
|
@ -0,0 +1 @@
|
|||
org.jdrupes.vmoperator.vmaccess.VmAccessFactory
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<div
|
||||
class="jdrupes-vmoperator-vmaccess jdrupes-vmoperator-vmaccess-confirm-reset">
|
||||
<p>${_("confirmResetMsg")}</p>
|
||||
<p>
|
||||
<span role="button" tabindex="0" class="svg-icon"
|
||||
onclick="orgJDrupesVmOperatorVmAccess.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>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<div title="${_("conletName")}"
|
||||
class="jdrupes-vmoperator-vmaccess jdrupes-vmoperator-vmaccess-edit"
|
||||
data-jgwc-on-load="orgJDrupesVmOperatorVmAccess.initEdit"
|
||||
data-jgwc-on-action="orgJDrupesVmOperatorVmAccess.applyEdit"
|
||||
data-jgwc-on-unload="JGConsole.jgwc.unmountVueApps">
|
||||
<form :id="formId" ref="formDom" onsubmit="return false;">
|
||||
<section>
|
||||
<span>{{ localize("Select VM") }}</span>
|
||||
<p>
|
||||
<label>
|
||||
<span>{{ localize("VM") }}</span>
|
||||
<select v-model="vmNameInput">
|
||||
<#list vmNames as name>
|
||||
<option value="${name}">${name}</option>
|
||||
</#list>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
</section>
|
||||
</form>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const l10nBundles = new Map();
|
||||
let entries = null;
|
||||
// <#list supportedLanguages() as l>
|
||||
entries = new Map();
|
||||
l10nBundles.set("${l.locale.toLanguageTag()}", entries);
|
||||
// <#list l.l10nBundle.keys as key>
|
||||
entries.set("${key}", "${l.l10nBundle.getString(key)}");
|
||||
// </#list>
|
||||
// </#list>
|
||||
|
||||
export default l10nBundles;
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<div
|
||||
class="jdrupes-vmoperator-vmaccess jdrupes-vmoperator-vmaccess-preview"
|
||||
data-conlet-grid-rows="2" data-conlet-grid-columns="2"
|
||||
data-jgwc-on-load="orgJDrupesVmOperatorVmAccess.initPreview"
|
||||
data-jgwc-on-unload="JGConsole.jgwc.unmountVueApps"
|
||||
data-conlet-resource-base="${conletResource('')}">
|
||||
</div>
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
<?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="0.90509668"
|
||||
inkscape:cx="345.81941"
|
||||
inkscape:cy="376.2029"
|
||||
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 4.7729709,13.006705 -1.7691517,0 V 0.98808897 H 20.99618 V 13.006705 l -1.639132,0"
|
||||
sodipodi:nodetypes="cccccc" />
|
||||
<path
|
||||
id="rect2"
|
||||
style="opacity:1;stroke-width:0.00145614;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:none;paint-order:fill markers stroke"
|
||||
d="m 0,13.998258 h 5.4336202 v 2.001741 H 0 Z"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<path
|
||||
id="rect3"
|
||||
style="fill:none;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.489072 0.25928,4.489072 l -13.3818748,0.001 c 0,0 -0.181061,-2.844856 0.369281,-4.529957 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" />
|
||||
<path
|
||||
id="rect2-2"
|
||||
style="stroke-width:0.00145614;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:none;paint-order:fill markers stroke"
|
||||
d="M 18.56638,13.998258 H 24 v 2.001741 h -5.43362 z"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
|
|
@ -0,0 +1,74 @@
|
|||
<?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-off.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">
|
||||
<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.3435029"
|
||||
inkscape:cx="377.74389"
|
||||
inkscape:cy="227.01849"
|
||||
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
|
||||
id="rect1"
|
||||
style="fill-opacity:1;stroke:#000000;stroke-width:1.97262;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:0;paint-order:fill markers stroke;fill:#545454"
|
||||
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" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
|
|
@ -0,0 +1,74 @@
|
|||
<?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.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">
|
||||
<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.3435029"
|
||||
inkscape:cx="377.74389"
|
||||
inkscape:cy="227.01849"
|
||||
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
|
||||
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" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
|
|
@ -0,0 +1,7 @@
|
|||
conletName = VM Access
|
||||
|
||||
okayLabel = Apply and Close
|
||||
|
||||
confirmResetTitle = Confirm reset
|
||||
confirmResetMsg = Resetting the VM may cause loss of data. \
|
||||
Please confirm to continue.
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
conletName = VM-Zugriff
|
||||
|
||||
okayLabel = Anwenden und Schließen
|
||||
Select\ VM = VM auswählen
|
||||
|
||||
Start\ VM = VM starten
|
||||
Stop\ VM = VM anhalten
|
||||
Reset\ VM = VM zurücksetzen
|
||||
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 |
35
org.jdrupes.vmoperator.vmaccess/rollup.config.mjs
Normal file
35
org.jdrupes.vmoperator.vmaccess/rollup.config.mjs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import typescript from 'rollup-plugin-typescript2';
|
||||
import postcss from 'rollup-plugin-postcss';
|
||||
|
||||
let packagePath = "org/jdrupes/vmoperator/vmaccess";
|
||||
let baseName = "VmAccess"
|
||||
let module = "build/generated/resources/" + packagePath
|
||||
+ "/" + baseName + "-functions.js";
|
||||
|
||||
let pathsMap = {
|
||||
"aash-plugin": "../../page-resource/aash-vue-components/lib/aash-vue-components.js",
|
||||
"jgconsole": "../../console-base-resource/jgconsole.js",
|
||||
"jgwc": "../../page-resource/jgwc-vue-components/jgwc-components.js",
|
||||
"l10nBundles": "./" + baseName + "-l10nBundles.ftl.js",
|
||||
"vue": "../../page-resource/vue/vue.esm-browser.js"
|
||||
}
|
||||
|
||||
export default {
|
||||
external: ['aash-plugin', 'jgconsole', 'jgwc', 'l10nBundles', 'vue', 'chartjs'],
|
||||
input: "src/" + packagePath + "/browser/" + baseName + "-functions.ts",
|
||||
output: [
|
||||
{
|
||||
format: "esm",
|
||||
file: module,
|
||||
sourcemap: true,
|
||||
sourcemapPathTransform: (relativeSourcePath, _sourcemapPath) => {
|
||||
return relativeSourcePath.replace(/^([^/]*\/){12}/, "./");
|
||||
},
|
||||
paths: pathsMap
|
||||
}
|
||||
],
|
||||
plugins: [
|
||||
typescript(),
|
||||
postcss()
|
||||
]
|
||||
};
|
||||
|
|
@ -0,0 +1,707 @@
|
|||
/*
|
||||
* VM-Operator
|
||||
* Copyright (C) 2023,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.vmaccess;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonGetter;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import freemarker.core.ParseException;
|
||||
import freemarker.template.MalformedTemplateNameException;
|
||||
import freemarker.template.Template;
|
||||
import freemarker.template.TemplateNotFoundException;
|
||||
import io.kubernetes.client.util.Strings;
|
||||
import java.io.IOException;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.time.Duration;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import org.bouncycastle.util.Objects;
|
||||
import org.jdrupes.vmoperator.common.K8sObserver;
|
||||
import org.jdrupes.vmoperator.common.VmDefinition;
|
||||
import org.jdrupes.vmoperator.common.VmDefinition.Permission;
|
||||
import org.jdrupes.vmoperator.manager.events.ChannelTracker;
|
||||
import org.jdrupes.vmoperator.manager.events.GetDisplayPassword;
|
||||
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.VmDefChanged;
|
||||
import org.jgrapes.core.Channel;
|
||||
import org.jgrapes.core.Components;
|
||||
import org.jgrapes.core.Event;
|
||||
import org.jgrapes.core.Manager;
|
||||
import org.jgrapes.core.annotation.Handler;
|
||||
import org.jgrapes.http.Session;
|
||||
import org.jgrapes.util.events.ConfigurationUpdate;
|
||||
import org.jgrapes.util.events.KeyValueStoreQuery;
|
||||
import org.jgrapes.util.events.KeyValueStoreUpdate;
|
||||
import org.jgrapes.webconsole.base.Conlet.RenderMode;
|
||||
import org.jgrapes.webconsole.base.ConletBaseModel;
|
||||
import org.jgrapes.webconsole.base.ConsoleConnection;
|
||||
import org.jgrapes.webconsole.base.ConsoleRole;
|
||||
import org.jgrapes.webconsole.base.ConsoleUser;
|
||||
import org.jgrapes.webconsole.base.WebConsoleUtils;
|
||||
import org.jgrapes.webconsole.base.events.AddConletRequest;
|
||||
import org.jgrapes.webconsole.base.events.AddConletType;
|
||||
import org.jgrapes.webconsole.base.events.AddPageResources.ScriptResource;
|
||||
import org.jgrapes.webconsole.base.events.ConletDeleted;
|
||||
import org.jgrapes.webconsole.base.events.ConsoleConfigured;
|
||||
import org.jgrapes.webconsole.base.events.ConsolePrepared;
|
||||
import org.jgrapes.webconsole.base.events.ConsoleReady;
|
||||
import org.jgrapes.webconsole.base.events.DeleteConlet;
|
||||
import org.jgrapes.webconsole.base.events.NotifyConletModel;
|
||||
import org.jgrapes.webconsole.base.events.NotifyConletView;
|
||||
import org.jgrapes.webconsole.base.events.OpenModalDialog;
|
||||
import org.jgrapes.webconsole.base.events.RenderConlet;
|
||||
import org.jgrapes.webconsole.base.events.RenderConletRequestBase;
|
||||
import org.jgrapes.webconsole.base.events.SetLocale;
|
||||
import org.jgrapes.webconsole.base.events.UpdateConletType;
|
||||
import org.jgrapes.webconsole.base.freemarker.FreeMarkerConlet;
|
||||
|
||||
/**
|
||||
* The Class {@link VmAccess}. The component supports the following
|
||||
* configuration properties:
|
||||
*
|
||||
* * `displayResource`: a map with the following entries:
|
||||
* - `preferredIpVersion`: `ipv4` or `ipv6` (default: `ipv4`).
|
||||
* Determines the IP addresses uses in the generated
|
||||
* connection file.
|
||||
* * `deleteConnectionFile`: `true` or `false` (default: `true`).
|
||||
* If `true`, the downloaded connection file will be deleted by
|
||||
* the remote viewer when opened.
|
||||
* * `syncPreviewsFor`: a list objects with either property `user` or
|
||||
* `role` and the associated name (default: `[]`).
|
||||
* The remote viewer will synchronize the previews for the specified
|
||||
* users and roles.
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.ExcessiveImports",
|
||||
"PMD.CouplingBetweenObjects", "PMD.GodClass", "PMD.TooManyMethods" })
|
||||
public class VmAccess extends FreeMarkerConlet<VmAccess.ViewerModel> {
|
||||
|
||||
private static final String VM_NAME_PROPERTY = "vmName";
|
||||
private static final String RENDERED
|
||||
= VmAccess.class.getName() + ".rendered";
|
||||
private static final String PENDING
|
||||
= VmAccess.class.getName() + ".pending";
|
||||
private static final Set<RenderMode> MODES = RenderMode.asSet(
|
||||
RenderMode.Preview, RenderMode.Edit);
|
||||
private static final Set<RenderMode> MODES_FOR_GENERATED = RenderMode.asSet(
|
||||
RenderMode.Preview, RenderMode.StickyPreview);
|
||||
private final ChannelTracker<String, VmChannel,
|
||||
VmDefinition> channelTracker = new ChannelTracker<>();
|
||||
private static ObjectMapper objectMapper
|
||||
= new ObjectMapper().registerModule(new JavaTimeModule());
|
||||
private Class<?> preferredIpVersion = Inet4Address.class;
|
||||
private Set<String> syncUsers = Collections.emptySet();
|
||||
private Set<String> syncRoles = Collections.emptySet();
|
||||
private boolean deleteConnectionFile = true;
|
||||
|
||||
/**
|
||||
* The periodically generated update event.
|
||||
*/
|
||||
public static class Update extends Event<Void> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new component with its channel set to the given channel.
|
||||
*
|
||||
* @param componentChannel the channel that the component's handlers listen
|
||||
* on by default and that {@link Manager#fire(Event, Channel...)}
|
||||
* sends the event to
|
||||
*/
|
||||
public VmAccess(Channel componentChannel) {
|
||||
super(componentChannel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the component.
|
||||
*
|
||||
* @param event the event
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "PMD.AvoidDuplicateLiterals" })
|
||||
@Handler
|
||||
public void onConfigurationUpdate(ConfigurationUpdate event) {
|
||||
event.structured(componentPath()).ifPresent(c -> {
|
||||
try {
|
||||
var dispRes = (Map<String, Object>) c
|
||||
.getOrDefault("displayResource", Collections.emptyMap());
|
||||
switch ((String) dispRes.getOrDefault("preferredIpVersion",
|
||||
"")) {
|
||||
case "ipv6":
|
||||
preferredIpVersion = Inet6Address.class;
|
||||
break;
|
||||
case "ipv4":
|
||||
default:
|
||||
preferredIpVersion = Inet4Address.class;
|
||||
break;
|
||||
}
|
||||
|
||||
// Delete connection file
|
||||
deleteConnectionFile
|
||||
= Optional.ofNullable(c.get("deleteConnectionFile"))
|
||||
.filter(v -> v instanceof String).map(v -> (String) v)
|
||||
.map(Boolean::parseBoolean).orElse(true);
|
||||
|
||||
// Users or roles for which previews should be synchronized
|
||||
syncUsers = ((List<Map<String, String>>) c.getOrDefault(
|
||||
"syncPreviewsFor", Collections.emptyList())).stream()
|
||||
.map(m -> m.get("user"))
|
||||
.filter(s -> s != null).collect(Collectors.toSet());
|
||||
logger.finest(() -> "Syncing previews for users: "
|
||||
+ syncUsers.toString());
|
||||
syncRoles = ((List<Map<String, String>>) c.getOrDefault(
|
||||
"syncPreviewsFor", Collections.emptyList())).stream()
|
||||
.map(m -> m.get("role"))
|
||||
.filter(s -> s != null).collect(Collectors.toSet());
|
||||
logger.finest(() -> "Syncing previews for roles: "
|
||||
+ syncRoles.toString());
|
||||
} catch (ClassCastException e) {
|
||||
logger.config("Malformed configuration: " + e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean syncPreviews(Session session) {
|
||||
return WebConsoleUtils.userFromSession(session)
|
||||
.filter(u -> syncUsers.contains(u.getName())).isPresent()
|
||||
|| WebConsoleUtils.rolesFromSession(session).stream()
|
||||
.filter(cr -> syncRoles.contains(cr.getName())).findAny()
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
/**
|
||||
* On {@link ConsoleReady}, fire the {@link AddConletType}.
|
||||
*
|
||||
* @param event the event
|
||||
* @param channel the channel
|
||||
* @throws TemplateNotFoundException the template not found exception
|
||||
* @throws MalformedTemplateNameException the malformed template name
|
||||
* exception
|
||||
* @throws ParseException the parse exception
|
||||
* @throws IOException Signals that an I/O exception has occurred.
|
||||
*/
|
||||
@Handler
|
||||
public void onConsoleReady(ConsoleReady event, ConsoleConnection channel)
|
||||
throws TemplateNotFoundException, MalformedTemplateNameException,
|
||||
ParseException, IOException {
|
||||
// Add conlet resources to page
|
||||
channel.respond(new AddConletType(type())
|
||||
.setDisplayNames(
|
||||
localizations(channel.supportedLocales(), "conletName"))
|
||||
.addRenderMode(RenderMode.Preview)
|
||||
.addScript(new ScriptResource().setScriptType("module")
|
||||
.setScriptUri(event.renderSupport().conletResource(
|
||||
type(), "VmAccess-functions.js"))));
|
||||
channel.session().put(RENDERED, new HashSet<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* On console configured.
|
||||
*
|
||||
* @param event the event
|
||||
* @param connection the console connection
|
||||
* @throws InterruptedException the interrupted exception
|
||||
*/
|
||||
@Handler
|
||||
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
|
||||
public void onConsoleConfigured(ConsoleConfigured event,
|
||||
ConsoleConnection connection) throws InterruptedException,
|
||||
IOException {
|
||||
@SuppressWarnings("unchecked")
|
||||
final var rendered = (Set<String>) connection.session().get(RENDERED);
|
||||
connection.session().remove(RENDERED);
|
||||
if (!syncPreviews(connection.session())) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean foundMissing = false;
|
||||
for (var vmName : accessibleVms(connection)) {
|
||||
if (rendered.contains(vmName)) {
|
||||
continue;
|
||||
}
|
||||
if (!foundMissing) {
|
||||
// Suspending to allow rendering of conlets to be noticed
|
||||
var failSafe = Components.schedule(t -> event.resumeHandling(),
|
||||
Duration.ofSeconds(1));
|
||||
event.suspendHandling(failSafe::cancel);
|
||||
connection.setAssociated(PENDING, event);
|
||||
foundMissing = true;
|
||||
}
|
||||
fire(new AddConletRequest(event.event().event().renderSupport(),
|
||||
VmAccess.class.getName(),
|
||||
RenderMode.asSet(RenderMode.Preview))
|
||||
.addProperty(VM_NAME_PROPERTY, vmName),
|
||||
connection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On console prepared.
|
||||
*
|
||||
* @param event the event
|
||||
* @param connection the connection
|
||||
*/
|
||||
@Handler
|
||||
public void onConsolePrepared(ConsolePrepared event,
|
||||
ConsoleConnection connection) {
|
||||
if (syncPreviews(connection.session())) {
|
||||
connection.respond(new UpdateConletType(type()));
|
||||
}
|
||||
}
|
||||
|
||||
private String storagePath(Session session, String conletId) {
|
||||
return "/" + WebConsoleUtils.userFromSession(session)
|
||||
.map(ConsoleUser::getName).orElse("")
|
||||
+ "/" + VmAccess.class.getName() + "/" + conletId;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<ViewerModel> createNewState(AddConletRequest event,
|
||||
ConsoleConnection connection, String conletId) throws Exception {
|
||||
var model = new ViewerModel(conletId);
|
||||
model.vmName = (String) event.properties().get(VM_NAME_PROPERTY);
|
||||
if (model.vmName != null) {
|
||||
model.setGenerated(true);
|
||||
}
|
||||
String jsonState = objectMapper.writeValueAsString(model);
|
||||
connection.respond(new KeyValueStoreUpdate().update(
|
||||
storagePath(connection.session(), model.getConletId()), jsonState));
|
||||
return Optional.of(model);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<ViewerModel> createStateRepresentation(Event<?> event,
|
||||
ConsoleConnection connection, String conletId) throws Exception {
|
||||
var model = new ViewerModel(conletId);
|
||||
String jsonState = objectMapper.writeValueAsString(model);
|
||||
connection.respond(new KeyValueStoreUpdate().update(
|
||||
storagePath(connection.session(), model.getConletId()), jsonState));
|
||||
return Optional.of(model);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("PMD.EmptyCatchBlock")
|
||||
protected Optional<ViewerModel> recreateState(Event<?> event,
|
||||
ConsoleConnection channel, String conletId) throws Exception {
|
||||
KeyValueStoreQuery query = new KeyValueStoreQuery(
|
||||
storagePath(channel.session(), conletId), channel);
|
||||
newEventPipeline().fire(query, channel);
|
||||
try {
|
||||
if (!query.results().isEmpty()) {
|
||||
var json = query.results().get(0).values().stream().findFirst()
|
||||
.get();
|
||||
ViewerModel model
|
||||
= objectMapper.readValue(json, ViewerModel.class);
|
||||
return Optional.of(model);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// Means we have no result.
|
||||
}
|
||||
|
||||
// Fall back to creating default state.
|
||||
return createStateRepresentation(event, channel, conletId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({ "PMD.AvoidInstantiatingObjectsInLoops", "unchecked" })
|
||||
protected Set<RenderMode> doRenderConlet(RenderConletRequestBase<?> event,
|
||||
ConsoleConnection channel, String conletId, ViewerModel model)
|
||||
throws Exception {
|
||||
ResourceBundle resourceBundle = resourceBundle(channel.locale());
|
||||
Set<RenderMode> renderedAs = EnumSet.noneOf(RenderMode.class);
|
||||
if (event.renderAs().contains(RenderMode.Preview)) {
|
||||
channel.associated(PENDING, Event.class)
|
||||
.ifPresent(e -> {
|
||||
e.resumeHandling();
|
||||
channel.setAssociated(PENDING, null);
|
||||
});
|
||||
|
||||
// Remove conlet if definition has been removed
|
||||
if (model.vmName() != null
|
||||
&& !channelTracker.associated(model.vmName()).isPresent()) {
|
||||
channel.respond(
|
||||
new DeleteConlet(conletId, Collections.emptySet()));
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
// Don't render if user has not at least one permission
|
||||
if (model.vmName() != null
|
||||
&& channelTracker.associated(model.vmName())
|
||||
.map(d -> permissions(d, channel.session()).isEmpty())
|
||||
.orElse(true)) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
// Render
|
||||
Template tpl
|
||||
= freemarkerConfig().getTemplate("VmAccess-preview.ftl.html");
|
||||
channel.respond(new RenderConlet(type(), conletId,
|
||||
processTemplate(event, tpl,
|
||||
fmModel(event, channel, conletId, model)))
|
||||
.setRenderAs(
|
||||
RenderMode.Preview.addModifiers(event.renderAs()))
|
||||
.setSupportedModes(syncPreviews(channel.session())
|
||||
? MODES_FOR_GENERATED
|
||||
: MODES));
|
||||
renderedAs.add(RenderMode.Preview);
|
||||
if (!Strings.isNullOrEmpty(model.vmName())) {
|
||||
Optional.ofNullable(channel.session().get(RENDERED))
|
||||
.ifPresent(s -> ((Set<String>) s).add(model.vmName()));
|
||||
updateConfig(channel, model);
|
||||
}
|
||||
}
|
||||
if (event.renderAs().contains(RenderMode.Edit)) {
|
||||
Template tpl = freemarkerConfig()
|
||||
.getTemplate("VmAccess-edit.ftl.html");
|
||||
var fmModel = fmModel(event, channel, conletId, model);
|
||||
fmModel.put("vmNames", accessibleVms(channel));
|
||||
channel.respond(new OpenModalDialog(type(), conletId,
|
||||
processTemplate(event, tpl, fmModel))
|
||||
.addOption("cancelable", true)
|
||||
.addOption("okayLabel",
|
||||
resourceBundle.getString("okayLabel")));
|
||||
}
|
||||
return renderedAs;
|
||||
}
|
||||
|
||||
private List<String> accessibleVms(ConsoleConnection channel) {
|
||||
return channelTracker.associated().stream()
|
||||
.filter(d -> !permissions(d, channel.session()).isEmpty())
|
||||
.map(d -> d.getMetadata().getName()).sorted().toList();
|
||||
}
|
||||
|
||||
private Set<Permission> permissions(VmDefinition vmDef, Session session) {
|
||||
var user = WebConsoleUtils.userFromSession(session)
|
||||
.map(ConsoleUser::getName).orElse(null);
|
||||
var roles = WebConsoleUtils.rolesFromSession(session)
|
||||
.stream().map(ConsoleRole::getName).toList();
|
||||
return vmDef.permissionsFor(user, roles);
|
||||
}
|
||||
|
||||
private void updateConfig(ConsoleConnection channel, ViewerModel model) {
|
||||
channel.respond(new NotifyConletView(type(),
|
||||
model.getConletId(), "updateConfig", model.vmName()));
|
||||
updateVmDef(channel, model);
|
||||
}
|
||||
|
||||
private void updateVmDef(ConsoleConnection channel, ViewerModel model) {
|
||||
if (Strings.isNullOrEmpty(model.vmName())) {
|
||||
return;
|
||||
}
|
||||
channelTracker.value(model.vmName()).ifPresent(item -> {
|
||||
try {
|
||||
var vmDef = item.associated();
|
||||
var data = Map.of("metadata",
|
||||
Map.of("namespace", vmDef.namespace(),
|
||||
"name", vmDef.name()),
|
||||
"spec", vmDef.spec(),
|
||||
"status", vmDef.getStatus(),
|
||||
"userPermissions",
|
||||
permissions(vmDef, channel.session()).stream()
|
||||
.map(Permission::toString).toList());
|
||||
channel.respond(new NotifyConletView(type(),
|
||||
model.getConletId(), "updateVmDefinition", data));
|
||||
} catch (JsonSyntaxException e) {
|
||||
logger.log(Level.SEVERE, e,
|
||||
() -> "Failed to serialize VM definition");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doConletDeleted(ConletDeleted event,
|
||||
ConsoleConnection channel, String conletId, ViewerModel conletState)
|
||||
throws Exception {
|
||||
if (event.renderModes().isEmpty()) {
|
||||
channel.respond(new KeyValueStoreUpdate().delete(
|
||||
storagePath(channel.session(), conletId)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Track the VM definitions.
|
||||
*
|
||||
* @param event the event
|
||||
* @param channel the channel
|
||||
* @throws IOException
|
||||
*/
|
||||
@Handler(namedChannels = "manager")
|
||||
@SuppressWarnings({ "PMD.ConfusingTernary", "PMD.CognitiveComplexity",
|
||||
"PMD.AvoidInstantiatingObjectsInLoops", "PMD.AvoidDuplicateLiterals",
|
||||
"PMD.ConfusingArgumentToVarargsMethod" })
|
||||
public void onVmDefChanged(VmDefChanged event, VmChannel channel)
|
||||
throws IOException {
|
||||
var vmDef = event.vmDefinition();
|
||||
var vmName = vmDef.name();
|
||||
if (event.type() == K8sObserver.ResponseType.DELETED) {
|
||||
channelTracker.remove(vmName);
|
||||
} else {
|
||||
channelTracker.put(vmName, channel, vmDef);
|
||||
}
|
||||
for (var entry : conletIdsByConsoleConnection().entrySet()) {
|
||||
var connection = entry.getKey();
|
||||
for (var conletId : entry.getValue()) {
|
||||
var model = stateFromSession(connection.session(), conletId);
|
||||
if (model.isEmpty()
|
||||
|| !Objects.areEqual(model.get().vmName(), vmName)) {
|
||||
continue;
|
||||
}
|
||||
if (event.type() == K8sObserver.ResponseType.DELETED) {
|
||||
connection.respond(
|
||||
new DeleteConlet(conletId, Collections.emptySet()));
|
||||
} else {
|
||||
updateVmDef(connection, model.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({ "PMD.AvoidDecimalLiteralsInBigDecimalConstructor",
|
||||
"PMD.ConfusingArgumentToVarargsMethod", "PMD.NcssCount",
|
||||
"PMD.AvoidLiteralsInIfCondition" })
|
||||
protected void doUpdateConletState(NotifyConletModel event,
|
||||
ConsoleConnection channel, ViewerModel model)
|
||||
throws Exception {
|
||||
event.stop();
|
||||
if ("selectedVm".equals(event.method())) {
|
||||
selectVm(event, channel, model);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle command for selected VM
|
||||
var both = Optional.ofNullable(model.vmName())
|
||||
.flatMap(vm -> channelTracker.value(vm));
|
||||
if (both.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
var vmChannel = both.get().channel();
|
||||
var vmDef = both.get().associated();
|
||||
var vmName = vmDef.metadata().getName();
|
||||
var perms = permissions(vmDef, channel.session());
|
||||
var resourceBundle = resourceBundle(channel.locale());
|
||||
switch (event.method()) {
|
||||
case "start":
|
||||
if (perms.contains(Permission.START)) {
|
||||
fire(new ModifyVm(vmName, "state", "Running", vmChannel));
|
||||
}
|
||||
break;
|
||||
case "stop":
|
||||
if (perms.contains(Permission.STOP)) {
|
||||
fire(new ModifyVm(vmName, "state", "Stopped", vmChannel));
|
||||
}
|
||||
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":
|
||||
if (perms.contains(Permission.ACCESS_CONSOLE)) {
|
||||
var user = WebConsoleUtils.userFromSession(channel.session())
|
||||
.map(ConsoleUser::getName).orElse("");
|
||||
var pwQuery
|
||||
= Event.onCompletion(new GetDisplayPassword(vmDef, user),
|
||||
e -> openConsole(vmName, channel, model,
|
||||
e.password().orElse(null)));
|
||||
fire(pwQuery, vmChannel);
|
||||
}
|
||||
break;
|
||||
default:// ignore
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void selectVm(NotifyConletModel event, ConsoleConnection channel,
|
||||
ViewerModel model) throws JsonProcessingException {
|
||||
model.setVmName(event.param(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,
|
||||
ViewerModel model, String password) {
|
||||
var vmDef = channelTracker.associated(vmName).orElse(null);
|
||||
if (vmDef == null) {
|
||||
return;
|
||||
}
|
||||
var addr = displayIp(vmDef);
|
||||
if (addr.isEmpty()) {
|
||||
logger.severe(() -> "Failed to find display IP for " + vmName);
|
||||
return;
|
||||
}
|
||||
var port = vmDef.<Number> fromVm("display", "spice", "port")
|
||||
.map(Number::longValue);
|
||||
if (port.isEmpty()) {
|
||||
logger.severe(() -> "No port defined for display of " + vmName);
|
||||
return;
|
||||
}
|
||||
StringBuffer data = new StringBuffer(100)
|
||||
.append("[virt-viewer]\ntype=spice\nhost=")
|
||||
.append(addr.get().getHostAddress()).append("\nport=")
|
||||
.append(port.get().toString())
|
||||
.append('\n');
|
||||
if (password != null) {
|
||||
data.append("password=").append(password).append('\n');
|
||||
}
|
||||
vmDef.<String> fromVm("display", "spice", "proxyUrl")
|
||||
.ifPresent(u -> {
|
||||
if (!Strings.isNullOrEmpty(u)) {
|
||||
data.append("proxy=").append(u).append('\n');
|
||||
}
|
||||
});
|
||||
if (deleteConnectionFile) {
|
||||
data.append("delete-this-file=1\n");
|
||||
}
|
||||
connection.respond(new NotifyConletView(type(),
|
||||
model.getConletId(), "openConsole", "application/x-virt-viewer",
|
||||
Base64.getEncoder().encodeToString(data.toString().getBytes())));
|
||||
}
|
||||
|
||||
private Optional<InetAddress> displayIp(VmDefinition vmDef) {
|
||||
Optional<String> server = vmDef.fromVm("display", "spice", "server");
|
||||
if (server.isPresent()) {
|
||||
var srv = server.get();
|
||||
try {
|
||||
var addr = InetAddress.getByName(srv);
|
||||
logger.fine(() -> "Using IP address from CRD for "
|
||||
+ vmDef.getMetadata().getName() + ": " + addr);
|
||||
return Optional.of(addr);
|
||||
} catch (UnknownHostException e) {
|
||||
logger.log(Level.SEVERE, e, () -> "Invalid server address "
|
||||
+ srv + ": " + e.getMessage());
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
var addrs = Optional.<List<String>> ofNullable(vmDef
|
||||
.extra("nodeAddresses")).orElse(Collections.emptyList()).stream()
|
||||
.map(a -> {
|
||||
try {
|
||||
return InetAddress.getByName(a);
|
||||
} catch (UnknownHostException e) {
|
||||
logger.warning(() -> "Invalid IP address: " + a);
|
||||
return null;
|
||||
}
|
||||
}).filter(a -> a != null).toList();
|
||||
logger.fine(() -> "Known IP addresses for "
|
||||
+ vmDef.name() + ": " + addrs);
|
||||
return addrs.stream()
|
||||
.filter(a -> preferredIpVersion.isAssignableFrom(a.getClass()))
|
||||
.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("VmAccess-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
|
||||
protected boolean doSetLocale(SetLocale event, ConsoleConnection channel,
|
||||
String conletId) throws Exception {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Class VmsModel.
|
||||
*/
|
||||
@SuppressWarnings("PMD.DataClass")
|
||||
public static class ViewerModel extends ConletBaseModel {
|
||||
|
||||
private String vmName;
|
||||
private boolean generated;
|
||||
|
||||
/**
|
||||
* Instantiates a new vms model.
|
||||
*
|
||||
* @param conletId the conlet id
|
||||
*/
|
||||
public ViewerModel(@JsonProperty("conletId") String conletId) {
|
||||
super(conletId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the vm name.
|
||||
*
|
||||
* @return the vmName
|
||||
*/
|
||||
@JsonGetter("vmName")
|
||||
public String vmName() {
|
||||
return vmName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the vm name.
|
||||
*
|
||||
* @param vmName the vmName to set
|
||||
*/
|
||||
public void setVmName(String vmName) {
|
||||
this.vmName = vmName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if is generated.
|
||||
*
|
||||
* @return the generated
|
||||
*/
|
||||
public boolean isGenerated() {
|
||||
return generated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the generated.
|
||||
*
|
||||
* @param generated the generated to set
|
||||
*/
|
||||
public void setGenerated(boolean generated) {
|
||||
this.generated = generated;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.vmaccess;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.jgrapes.core.Channel;
|
||||
import org.jgrapes.core.ComponentType;
|
||||
import org.jgrapes.webconsole.base.ConletComponentFactory;
|
||||
|
||||
/**
|
||||
* The factory service for {@link VmAccess}s.
|
||||
*/
|
||||
public class VmAccessFactory implements ConletComponentFactory {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.jgrapes.core.ComponentFactory#componentType()
|
||||
*/
|
||||
@Override
|
||||
public Class<? extends ComponentType> componentType() {
|
||||
return VmAccess.class;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.jgrapes.core.ComponentFactory#create(org.jgrapes.core.Channel,
|
||||
* java.util.Map)
|
||||
*/
|
||||
@Override
|
||||
public Optional<ComponentType> create(Channel componentChannel,
|
||||
Map<?, ?> properties) {
|
||||
return Optional.of(new VmAccess(componentChannel));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,240 @@
|
|||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
reactive, ref, createApp, computed, watch
|
||||
} from "vue";
|
||||
import JGConsole from "jgconsole";
|
||||
import JgwcPlugin, { JGWC } from "jgwc";
|
||||
import { provideApi, getApi } from "aash-plugin";
|
||||
import l10nBundles from "l10nBundles";
|
||||
|
||||
import "./VmAccess-style.scss";
|
||||
|
||||
// For global access
|
||||
declare global {
|
||||
interface Window {
|
||||
orgJDrupesVmOperatorVmAccess: {
|
||||
initPreview?: (previewDom: HTMLElement, isUpdate: boolean) => void,
|
||||
initEdit?: (viewDom: HTMLElement, isUpdate: boolean) => void,
|
||||
applyEdit?: (viewDom: HTMLElement, apply: boolean) => void,
|
||||
confirmReset?: (conletType: string, conletId: string) => void
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.orgJDrupesVmOperatorVmAccess = {};
|
||||
|
||||
interface Api {
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
vmName: string;
|
||||
vmDefinition: any;
|
||||
}
|
||||
|
||||
const localize = (key: string) => {
|
||||
return JGConsole.localize(
|
||||
l10nBundles, JGWC.lang(), key);
|
||||
};
|
||||
|
||||
window.orgJDrupesVmOperatorVmAccess.initPreview = (previewDom: HTMLElement,
|
||||
_isUpdate: boolean) => {
|
||||
const app = createApp({
|
||||
setup(_props: object) {
|
||||
const conletId = (<HTMLElement>previewDom.closest(
|
||||
"[data-conlet-id]")!).dataset["conletId"]!;
|
||||
const resourceBase = (<HTMLElement>previewDom.closest(
|
||||
"*[data-conlet-resource-base]")!).dataset.conletResourceBase;
|
||||
|
||||
const previewApi: Api = reactive({
|
||||
vmName: "",
|
||||
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 inUse = computed(() => previewApi.vmDefinition.usedBy != '');
|
||||
const permissions = computed(() => previewApi.vmDefinition.spec
|
||||
? previewApi.vmDefinition.userPermissions : []);
|
||||
|
||||
watch(() => previewApi.vmName, (name: string) => {
|
||||
if (name !== "") {
|
||||
JGConsole.instance.updateConletTitle(conletId, name);
|
||||
}
|
||||
});
|
||||
|
||||
provideApi(previewDom, previewApi);
|
||||
|
||||
const vmAction = (action: string) => {
|
||||
JGConsole.notifyConletModel(conletId, action);
|
||||
};
|
||||
|
||||
return { localize, resourceBase, vmAction, configured,
|
||||
startable, stoppable, running, inUse, permissions };
|
||||
},
|
||||
template: `
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="2" style="position: relative"><span
|
||||
style="position: absolute;"
|
||||
:class="{ busy: configured && !startable && !stoppable }"
|
||||
><img role=button :aria-disabled="!running
|
||||
|| !permissions.includes('accessConsole')"
|
||||
v-on:click="vmAction('openConsole')"
|
||||
:src="resourceBase + (running
|
||||
? (inUse ? 'computer-in-use.svg' : 'computer.svg')
|
||||
: 'computer-off.svg')"
|
||||
:title="localize('Open console')"></span><span
|
||||
style="visibility: hidden;"><img
|
||||
:src="resourceBase + 'computer.svg'"></span></td>
|
||||
<td class="jdrupes-vmoperator-vmaccess-preview-action-list">
|
||||
<span role="button"
|
||||
:aria-disabled="!startable || !permissions.includes('start')"
|
||||
tabindex="0" class="fa fa-play" :title="localize('Start VM')"
|
||||
v-on:click="vmAction('start')"></span>
|
||||
<span role="button"
|
||||
:aria-disabled="!stoppable || !permissions.includes('stop')"
|
||||
tabindex="0" class="fa fa-stop" :title="localize('Stop VM')"
|
||||
v-on:click="vmAction('stop')"></span>
|
||||
<span role="button"
|
||||
:aria-disabled="!running || !permissions.includes('reset')"
|
||||
tabindex="0" class="svg-icon" :title="localize('Reset VM')"
|
||||
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>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>`
|
||||
});
|
||||
app.use(JgwcPlugin, []);
|
||||
app.config.globalProperties.window = window;
|
||||
app.mount(previewDom);
|
||||
};
|
||||
|
||||
JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmaccess.VmAccess",
|
||||
"updateConfig", function(conletId: string, vmName: string) {
|
||||
const conlet = JGConsole.findConletPreview(conletId);
|
||||
if (!conlet) {
|
||||
return;
|
||||
}
|
||||
const api = getApi<Api>(conlet.element().querySelector(
|
||||
":scope .jdrupes-vmoperator-vmaccess-preview"))!;
|
||||
api.vmName = vmName;
|
||||
});
|
||||
|
||||
JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmaccess.VmAccess",
|
||||
"updateVmDefinition", function(conletId: string, vmDefinition: any) {
|
||||
const conlet = JGConsole.findConletPreview(conletId);
|
||||
if (!conlet) {
|
||||
return;
|
||||
}
|
||||
const api = getApi<Api>(conlet.element().querySelector(
|
||||
":scope .jdrupes-vmoperator-vmaccess-preview"))!;
|
||||
// Add some short-cuts for rendering
|
||||
vmDefinition.name = vmDefinition.metadata.name;
|
||||
vmDefinition.currentCpus = vmDefinition.status.cpus;
|
||||
vmDefinition.currentRam = Number(vmDefinition.status.ram);
|
||||
vmDefinition.usedBy = vmDefinition.status.consoleClient || "";
|
||||
for (const condition of vmDefinition.status.conditions) {
|
||||
if (condition.type === "Running") {
|
||||
vmDefinition.running = condition.status === "True";
|
||||
vmDefinition.runningConditionSince
|
||||
= new Date(condition.lastTransitionTime);
|
||||
break;
|
||||
}
|
||||
}
|
||||
api.vmDefinition = vmDefinition;
|
||||
});
|
||||
|
||||
JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmaccess.VmAccess",
|
||||
"openConsole", function(_conletId: string, mimeType: string, data: string) {
|
||||
let target = document.getElementById(
|
||||
"org.jdrupes.vmoperator.vmaccess.VmAccess.target");
|
||||
if (!target) {
|
||||
target = document.createElement("iframe");
|
||||
target.id = "org.jdrupes.vmoperator.vmaccess.VmAccess.target";
|
||||
target.setAttribute("name", target.id);
|
||||
target.setAttribute("style", "display: none;");
|
||||
document.querySelector("body")!.append(target);
|
||||
}
|
||||
const url = "data:" + mimeType + ";base64," + data;
|
||||
window.open(url, target.id);
|
||||
});
|
||||
|
||||
window.orgJDrupesVmOperatorVmAccess.initEdit = (dialogDom: HTMLElement,
|
||||
isUpdate: boolean) => {
|
||||
if (isUpdate) {
|
||||
return;
|
||||
}
|
||||
const app = createApp({
|
||||
setup() {
|
||||
const formId = (<HTMLElement>dialogDom
|
||||
.closest("*[data-conlet-id]")!).id + "-form";
|
||||
|
||||
const localize = (key: string) => {
|
||||
return JGConsole.localize(
|
||||
l10nBundles, JGWC.lang()!, key);
|
||||
};
|
||||
|
||||
const vmNameInput = ref<string>("");
|
||||
const conletId = (<HTMLElement>dialogDom.closest(
|
||||
"[data-conlet-id]")!).dataset["conletId"]!;
|
||||
const conlet = JGConsole.findConletPreview(conletId);
|
||||
if (conlet) {
|
||||
const api = getApi<Api>(conlet.element().querySelector(
|
||||
":scope .jdrupes-vmoperator-vmaccess-preview"))!;
|
||||
vmNameInput.value = api.vmName;
|
||||
}
|
||||
|
||||
provideApi(dialogDom, vmNameInput);
|
||||
|
||||
return { formId, localize, vmNameInput };
|
||||
}
|
||||
});
|
||||
app.use(JgwcPlugin);
|
||||
app.mount(dialogDom);
|
||||
}
|
||||
|
||||
window.orgJDrupesVmOperatorVmAccess.applyEdit =
|
||||
(dialogDom: HTMLElement, apply: boolean) => {
|
||||
if (!apply) {
|
||||
return;
|
||||
}
|
||||
const conletId = (<HTMLElement>dialogDom.closest("[data-conlet-id]")!)
|
||||
.dataset["conletId"]!;
|
||||
const vmName = getApi<ref<string>>(dialogDom!)!.value;
|
||||
JGConsole.notifyConletModel(conletId, "selectedVm", vmName);
|
||||
}
|
||||
|
||||
window.orgJDrupesVmOperatorVmAccess.confirmReset =
|
||||
(conletType: string, conletId: string) => {
|
||||
JGConsole.instance.closeModalDialog(conletType, conletId);
|
||||
JGConsole.notifyConletModel(conletId, "resetConfirmed");
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Conlet specific styles.
|
||||
*/
|
||||
.jdrupes-vmoperator-vmaccess {
|
||||
|
||||
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] {
|
||||
padding: 0.25rem;
|
||||
|
||||
&:not([aria-disabled]):hover, &[aria-disabled='false']:hover {
|
||||
box-shadow: var(--darkening);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.jdrupes-vmoperator-vmaccess.jdrupes-vmoperator-vmaccess-preview {
|
||||
|
||||
img {
|
||||
height: 3em;
|
||||
padding: 0.25rem;
|
||||
|
||||
&[aria-disabled=''], &[aria-disabled='true'] {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
.jdrupes-vmoperator-vmaccess-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;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.jdrupes-vmoperator-vmaccess.jdrupes-vmoperator-vmaccess-edit {
|
||||
select {
|
||||
width: 15em;
|
||||
}
|
||||
}
|
||||
|
||||
.jdrupes-vmoperator-vmaccess.jdrupes-vmoperator-vmaccess-confirm-reset {
|
||||
p {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
span[role="button"].svg-icon {
|
||||
fill: var(--danger);
|
||||
|
||||
svg {
|
||||
width: 2.5em;
|
||||
height: 2.5em;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export default new Map<string, Map<string, string>>();
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* VM-Operator
|
||||
* Copyright (C) 2023, 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.vmaccess;
|
||||
23
org.jdrupes.vmoperator.vmaccess/tsconfig.json
Normal file
23
org.jdrupes.vmoperator.vmaccess/tsconfig.json
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2015",
|
||||
"module": "es2015",
|
||||
"sourceMap": true,
|
||||
"inlineSources": true,
|
||||
"declaration": true,
|
||||
"importHelpers": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "node",
|
||||
"experimentalDecorators": true,
|
||||
"lib": ["DOM", "ES2020"],
|
||||
"paths": {
|
||||
"aash-plugin": ["./build/unpacked/org/jgrapes/webconsole/provider/jgwcvuecomponents/aash-vue-components/lib/AashPlugin"],
|
||||
"jgconsole": ["./build/unpacked/org/jgrapes/webconsole/base/JGConsole"],
|
||||
"jgwc": ["./build/unpacked/org/jgrapes/webconsole/provider/jgwcvuecomponents/jgwc-vue-components/jgwc-components"],
|
||||
"l10nBundles": ["./src/org/jdrupes/vmoperator/vmaccess/browser/l10nBundles-stub"],
|
||||
"vue": ["./build/unpacked/org/jgrapes/webconsole/provider/vue/vue/vue"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules", "l10nBundles-stub.ts"]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue