F #2755: Add Guacamole Apache for remote access (#92)

This commit is contained in:
Sergio Betanzos authored and GitHub committed 2020-07-22 12:34:00 +02:00
1 parent 4a88858350
commit c3fcbcae55
49 files changed
+1651 -181

No files matched your search

+3
View File
@@ -45,6 +45,9 @@ src/sunstone/public/css/app.min.css
src/sunstone/public/dist/
src/sunstone/public/locale/languages/*.js
src/sunstone/public/package-lock.json
src/sunstone/guac/node_modules
src/sunstone/guac/dist
src/sunstone/guac/package-lock.json
.tx/config
src/fireedge/node_modules
+8
View File
@@ -739,6 +739,7 @@ INSTALL_SUNSTONE_FILES=(
INSTALL_SUNSTONE_PUBLIC_MINIFIED_FILES=(
SUNSTONE_PUBLIC_JS_FILES:$SUNSTONE_LOCATION/public/dist
SUNSTONE_PUBLIC_JS_GUAC_FILES:$SUNSTONE_LOCATION/guac/dist
SUNSTONE_PUBLIC_JS_CONSOLE_FILES:$SUNSTONE_LOCATION/public/dist/console
SUNSTONE_PUBLIC_FONT_AWSOME:$SUNSTONE_LOCATION/public/bower_components/fontawesome/web-fonts-with-css/webfonts
SUNSTONE_PUBLIC_CSS_FILES:$SUNSTONE_LOCATION/public/css
@@ -767,6 +768,7 @@ INSTALL_SUNSTONE_PUBLIC_MINIFIED_FILES=(
INSTALL_SUNSTONE_PUBLIC_DEV_DIR=(
SUNSTONE_PUBLIC_DEV_DIR:$SUNSTONE_LOCATION
SUNSTONE_GUAC_DEV_DIR:$SUNSTONE_LOCATION
)
INSTALL_SUNSTONE_ETC_FILES=(
@@ -895,6 +897,7 @@ RUBY_LIB_FILES="src/mad/ruby/ActionManager.rb \
src/vnm_mad/one_vnm.rb \
src/oca/ruby/opennebula.rb \
src/sunstone/OpenNebulaVNC.rb \
src/sunstone/OpenNebulaGuac.rb \
src/sunstone/OpenNebulaAddons.rb \
src/vmm_mad/remotes/vcenter/vcenter_driver.rb \
src/vmm_mad/remotes/nsx/nsx_driver.rb \
@@ -2373,6 +2376,7 @@ SUNSTONE_FILES="src/sunstone/sunstone-server.rb \
src/sunstone/config.ru"
SUNSTONE_BIN_FILES="src/sunstone/bin/sunstone-server \
src/sunstone/bin/guac-server \
src/sunstone/bin/novnc-server"
SUNSTONE_ETC_FILES="src/sunstone/etc/sunstone-server.conf \
@@ -2436,6 +2440,10 @@ SUNSTONE_PUBLIC_JS_FILES="src/sunstone/public/dist/login.js \
src/sunstone/public/dist/main.js.map \
src/sunstone/public/dist/main-dist.js"
SUNSTONE_PUBLIC_JS_GUAC_FILES="src/sunstone/guac/dist/guac.js"
SUNSTONE_GUAC_DEV_DIR="src/sunstone/guac"
SUNSTONE_PUBLIC_JS_CONSOLE_FILES="src/sunstone/public/dist/console/vnc.js \
src/sunstone/public/dist/console/vnc.js.map \
src/sunstone/public/dist/console/spice.js \
+325
View File
@@ -0,0 +1,325 @@
# -------------------------------------------------------------------------- #
# Copyright 2002-2020, OpenNebula Project, OpenNebula Systems #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
# not use this file except in compliance with the License. You may obtain #
# a copy of the License at #
# #
# http://www.apache.org/licenses/LICENSE-2.0 #
# #
# Unless required by applicable law or agreed to in writing, software #
# distributed under the License is distributed on an "AS IS" BASIS, #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
# See the License for the specific language governing permissions and #
# limitations under the License. #
#--------------------------------------------------------------------------- #
#
# This class provides support for launching and stopping a websockify proxy
#
require 'rubygems'
require 'json'
require 'opennebula'
require 'base64'
require 'openssl'
if !ONE_LOCATION
GUAC_LOCK_FILE = "/var/lock/one/.guac.lock"
GUACD_PID_FILE = "/var/run/one/guacd.pid"
else
GUAC_LOCK_FILE= ONE_LOCATION + "/var/.guac.lock"
GUACD_PID_FILE= ONE_LOCATION + "/var/guacd.pid"
end
GUAC_STATES = [
#0, #LCM_INIT
#1, #PROLOG
#2, #BOOT
"3", #RUNNING
"4", #MIGRATE
#5, #SAVE_STOP
#6, #SAVE_SUSPEND
#7, #SAVE_MIGRATE
#8, #PROLOG_MIGRATE
#9, #PROLOG_RESUME
#10, #EPILOG_STOP
#11, #EPILOG
"12", #SHUTDOWN
"13", #CANCEL
#14, #FAILURE
#15, #CLEANUP_RESUBMIT
"16", #UNKNOWN
"17", #HOTPLUG
"18", #SHUTDOWN_POWEROFF
#19, #BOOT_UNKNOWN
#20, #BOOT_POWEROFF
#21, #BOOT_SUSPENDED
#22, #BOOT_STOPPED
#23, #CLEANUP_DELETE
"24", #HOTPLUG_SNAPSHOT
"25", #HOTPLUG_NIC
"26", #HOTPLUG_SAVEAS
"27", #HOTPLUG_SAVEAS_POWEROFF
"28", #HOTPLUG_SAVEAS_SUSPENDED
"29", #SHUTDOWN_UNDEPLOY
#30, #EPILOG_UNDEPLOY
#31, #PROLOG_UNDEPLOY
#32, #BOOT_UNDEPLOY
#33, #HOTPLUG_PROLOG_POWEROFF
#34, #HOTPLUG_EPILOG_POWEROFF
#35, #BOOT_MIGRATE
#36, #BOOT_FAILURE
#37, #BOOT_MIGRATE_FAILURE
#38, #PROLOG_MIGRATE_FAILURE
#39, #PROLOG_FAILURE
#40, #EPILOG_FAILURE
#41, #EPILOG_STOP_FAILURE
#42, #EPILOG_UNDEPLOY_FAILURE
#43, #PROLOG_MIGRATE_POWEROFF
#44, #PROLOG_MIGRATE_POWEROFF_FAILURE
#45, #PROLOG_MIGRATE_SUSPEND
#46, #PROLOG_MIGRATE_SUSPEND_FAILURE
#47, #BOOT_UNDEPLOY_FAILURE
#48, #BOOT_STOPPED_FAILURE
#49, #PROLOG_RESUME_FAILURE
#50, #PROLOG_UNDEPLOY_FAILURE
#51, #DISK_SNAPSHOT_POWEROFF
#52, #DISK_SNAPSHOT_REVERT_POWEROFF
#53, #DISK_SNAPSHOT_DELETE_POWEROFF
#54, #DISK_SNAPSHOT_SUSPENDED
#55, #DISK_SNAPSHOT_REVERT_SUSPENDED
#56, #DISK_SNAPSHOT_DELETE_SUSPENDED
"57", #DISK_SNAPSHOT
"58", #DISK_SNAPSHOT_REVERT
#59, #DISK_SNAPSHOT_DELETE
#60, #PROLOG_MIGRATE_UNKNOWN
#61, #PROLOG_MIGRATE_UNKNOWN_FAILURE
"62" #DISK_RESIZE
#63, #DISK_RESIZE_POWEROFF
#64 #DISK_RESIZE_UNDEPLOYED
#65 #HOTPLUG_NIC_POWEROFF
]
class OpenNebulaGuac
attr_reader :proxy_port
def initialize(config, logger, options = {})
opts={ :json_errors => true }.merge(options)
@lock_file = GUAC_LOCK_FILE
@guacd_pid_file = GUACD_PID_FILE
@options = opts
@logger = logger
end
def proxy(vm_resource, type_connection = "vnc")
# Check configurations and VM attributes
unless is_lockfile?
return error(400, "Guacamole server is not running")
end
if !is_running?
return error(400, "Guacamole server is not running")
end
if !is_guacd_running?
return error(400, "Guacamole proxy daemon is not running")
end
# Check configurations and VM attributes
if !GUAC_STATES.include?(vm_resource['LCM_STATE'])
return error(400, "Wrong state (#{vm_resource['LCM_STATE']}) to open a Guacamole session")
end
if !(["vnc", "rdp", "ssh"].include?(type_connection.downcase))
return error(400, "Type connection not supported by Guacamole")
end
# TODO (* : optional) ===============================
# =======================================================
# type : telnet
# hostname : vm_resource["TEMPLATE/NIC[1]/IP"]
# port* : 23 by default
# username* : vm_resource['TEMPLATE/CONTEXT/USERNAME']
# password* : vm_resource['TEMPLATE/CONTEXT/PASSWORD']
settings = case type_connection.downcase
when "vnc" then get_config_vnc(vm_resource)
when "rdp" then get_config_rdp(vm_resource)
when "ssh" then get_config_ssh(vm_resource)
else {:error => error(400, "Type connection not supported by Guacamole")}
end
return settings[:error] if settings.key?(:error)
data = encrypt_data({
"connection": {
"type": type_connection.downcase,
"settings": {
"security": "any",
"ignore-cert": "true",
"enable-drive": "true",
"enable-audio": "true",
"create-drive-path": "true",
}.merge!(settings)
}
})
[200, { :token => data }.to_json]
end
def status
if is_running?
STDOUT.puts "Guacamole server is running"
true
else
STDOUT.puts "Guacamole server is NOT running"
false
end
end
private
def error(code, msg)
if @options[:json_errors]
return [code,OpenNebula::Error.new(msg).to_json]
else
return [code,msg]
end
end
def is_running?
if File.exist?(@lock_file)
pid=File.read(@lock_file).strip
if system("ps #{pid} 1> /dev/null")
return pid.to_i
end
@logger.info "Deleting stale lock file"
File.delete(@lock_file)
end
false
end
alias_method :get_guac_pid, :is_running?
def is_guacd_running?
if File.exist?(@guacd_pid_file)
pid=File.read(@guacd_pid_file).strip
if system("ps #{pid} 1> /dev/null")
return pid.to_i
end
end
false
end
alias_method :get_guacd_pid, :is_guacd_running?
def is_lockfile?
dn = File.dirname(@lock_file)
bn = File.basename(@lock_file)
# Due to restrictions which doesn't allow to stat the lock file
# when Sunstone is running under Apache with SELinux enabled,
# as a workaround we read content of the whole lock directory.
begin
Dir.entries(dn).include?(bn)
rescue StandardError
false
end
end
if RUBY_VERSION<'1.9'
def spawn(*args)
fork {
command=args[0..-2]
# Close stdin and point out and err to log file
$stdout.reopen(GUACAMOLE_LOG, "a")
$stderr.reopen(GUACAMOLE_LOG, "a")
$stdin.close
# Detach process from the parent
Process.setsid
exec(*command)
}
end
end
def get_config_vnc(vm_resource)
# If it is a vCenter VM
if vm_resource['USER_TEMPLATE/HYPERVISOR'] == "vcenter"
if vm_resource['MONITORING/VCENTER_ESX_HOST']
hostname = vm_resource['MONITORING/VCENTER_ESX_HOST']
else
return error(400,"Could not determine the vCenter ESX host where the VM is running.
Wait till the VCENTER_ESX_HOST attribute is retrieved once the host has been monitored")
end
else
hostname = vm_resource['/VM/HISTORY_RECORDS/HISTORY[last()]/HOSTNAME']
end
{ "hostname": "localhost", "port": "5900", "password": nil }.merge({
"hostname": hostname,
"port": vm_resource['TEMPLATE/GRAPHICS/PORT'],
"password": vm_resource['TEMPLATE/GRAPHICS/PASSWD']
})
end
def get_config_rdp(vm_resource)
hostname = vm_resource["TEMPLATE/NIC[RDP='YES'][1]/IP | TEMPLATE/NIC_ALIAS[RDP='YES'][1]/IP"]
return {
:error => error(400, "Wrong configuration. Cannot find a NIC with RDP")
} if hostname.nil?
{ "hostname": "localhost", "port": "3389", "username": nil, "password": nil }.merge({
"hostname": hostname,
"port": vm_resource['TEMPLATE/CONTEXT/RDP_PORT'],
"username": vm_resource['TEMPLATE/CONTEXT/USERNAME'],
"password": vm_resource['TEMPLATE/CONTEXT/PASSWORD']
})
end
def get_config_ssh(vm_resource)
hostname = vm_resource["TEMPLATE/NIC[SSH='YES'][1]/IP | TEMPLATE/NIC_ALIAS[SSH='YES'][1]/IP"]
return {
:error => error(400, "Wrong configuration. Cannot find a NIC with SSH")
} if hostname.nil?
{ "hostname": "localhost", "port": "22", "username": nil, "password": nil }.merge({
"hostname": hostname,
"port": vm_resource['TEMPLATE/CONTEXT/SSH_PORT'],
"username": vm_resource['TEMPLATE/CONTEXT/USERNAME'],
"password": vm_resource['TEMPLATE/CONTEXT/PASSWORD']
})
end
def encrypt_data(data)
iv = OpenSSL::Random.random_bytes(16)
key = 'LSIOGCKYLSIOGCKYLSIOGCKYLSIOGCKY' #OpenSSL::Random.random_bytes(32)
cipher = OpenSSL::Cipher::AES256.new(:CBC)
cipher.encrypt
cipher.key = key
cipher.iv = iv
crypted = cipher.update(data.to_json)
crypted << cipher.final
token = {
iv: Base64.encode64(iv).strip,
value: Base64.strict_encode64(crypted).strip
}
return Base64.strict_encode64(token.to_json).encode('utf-8').strip
end
end
+2
View File
@@ -285,6 +285,8 @@ class OpenNebulaVNC
end
delete_token_dir
STDOUT.puts "VNC proxy stopped"
else
message="VNC server is not running"
@logger.info message
+196
View File
@@ -0,0 +1,196 @@
#!/bin/bash
# -------------------------------------------------------------------------- #
# Copyright 2002-2020, OpenNebula Project, OpenNebula Systems #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
# not use this file except in compliance with the License. You may obtain #
# a copy of the License at #
# #
# http://www.apache.org/licenses/LICENSE-2.0 #
# #
# Unless required by applicable law or agreed to in writing, software #
# distributed under the License is distributed on an "AS IS" BASIS, #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
# See the License for the specific language governing permissions and #
# limitations under the License. #
#--------------------------------------------------------------------------- #
GUACD_BIN=/usr/local/sbin/guacd
if [ -z "$ONE_LOCATION" ]; then
GUAC_SERVER_DIR=/usr/lib/one/sunstone/guac
GUAC_LOCK_FILE=/var/lock/one/.guac.lock
GUAC_LOG=/var/log/one/guac.log
GUAC_LOG_ERROR=/var/log/one/guac.error
GUAC_PID=/var/run/one/guac.pid
GUACD_PID=/var/run/one/guacd.pid
SUNSTONE_CONF=/etc/one/sunstone-server.conf
else
GUAC_LOCK_FILE=$ONE_LOCATION/var/.guac.lock
GUAC_LOG=$ONE_LOCATION/var/guac.log
GUAC_LOG_ERROR=$ONE_LOCATION/var/guac.error
GUAC_PID=$ONE_LOCATION/var/guac.pid
GUACD_PID=$ONE_LOCATION/var/guacd.pid
SUNSTONE_CONF=$ONE_LOCATION/etc/sunstone-server.conf
fi
get_pid()
{
if [ -f "$1" ]; then
read PID < "$1"
# if pidfile contains PID and PID is valid
if [ -n "$PID" ] && ps "$PID" > /dev/null 2>&1; then
echo "$PID"
return 0
fi
fi
# pidfile/pid not found, or process is dead
return 1
}
start_guacd()
{
if [ ! -f "$GUACD_BIN" ]; then
echo "Cannot find $GUACD_BIN."
return 1
fi
PID=`get_pid $GUACD_PID`
if [ $? -eq 0 ]; then
echo "Guacamole proxy daemon already running"
return 1
fi
echo -n "Starting guacd: "
$GUACD_BIN -p "$GUACD_PID" > /dev/null 2>&1
case "$?" in
0)
echo "SUCCESS"
;;
*)
echo "FAIL"
;;
esac
}
stop_guacd()
{
echo -n "Stopping guacd: "
PID=`get_pid $GUACD_PID`
case "$?" in
0)
if kill -9 $PID > /dev/null 2>&1
then
echo "SUCCESS"
return 0
fi
echo "FAIL"
return 0
;;
*)
echo "SUCCESS (not running)"
return 0
;;
esac
}
setup()
{
if [ -f $GUAC_LOCK_FILE ]; then
PID=`get_pid $GUAC_PID`
if [ $? -eq 0 ]; then
echo -n "Guacamole Server is still running (PID:$PID). Please "
echo "try 'guac-server stop' first."
exit 1
fi
echo "Stale .lock detected. Erasing it."
rm $GUAC_LOCK_FILE
fi
}
start()
{
ENV_MODE=$(grep ^:env: $SUNSTONE_CONF|awk '{print $2}')
if [ "$ENV_MODE" = "dev" ]; then
GUAC_SERVER=$GUAC_SERVER_DIR/app.js
else
GUAC_SERVER=$GUAC_SERVER_DIR/dist/guac.js
fi
if [ ! -f "$GUAC_SERVER" ]; then
echo "Cannot find $GUAC_SERVER."
exit 1
fi
touch $GUAC_LOCK_FILE
# Start the guac-server daemon
GUAC_PORT=$(grep ^:guac_port: $SUNSTONE_CONF|awk '{print $2}')
GUACD_PORT=$(grep ^:guacd_port: $SUNSTONE_CONF|awk '{print $2}')
PORT="$GUAC_PORT" GUACD_PORT="$GUACD_PORT" node "$GUAC_SERVER" \
> $GUAC_LOG 2>$GUAC_LOG_ERROR &
LASTRC=$?
LASTPID=$!
if [ $LASTRC -ne 0 ]; then
echo "Error executing guac-server."
echo "Check $GUAC_LOG_ERROR and $GUAC_LOG for more information"
exit 1
else
echo $LASTPID > $GUAC_PID
fi
sleep 2
ps $LASTPID > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo "Error executing guac-server."
echo "Check $GUAC_LOG_ERROR and $GUAC_LOG for more information"
exit 1
fi
echo "guac-server started"
}
#
# Function that stops the daemon/service
#
stop()
{
if [ ! -f $GUAC_PID ]; then
echo "Couldn't find guac-server process pid."
exit 1
fi
# Kill the guac-server daemon
kill -9 `cat $GUAC_PID` > /dev/null 2>&1
# Remove pid files
rm -f $GUAC_PID > /dev/null 2>&1
rm -f $GUAC_LOCK_FILE &> /dev/null
echo "guac-server stopped"
}
case "$1" in
start)
start_guacd
setup
start
;;
stop)
stop_guacd
stop
;;
*)
echo "Usage: guac-server {start|stop}" >&2
exit 3
;;
esac
+36 -12
View File
@@ -25,6 +25,7 @@ if [ -z "$ONE_LOCATION" ]; then
SUNSTONE_LOG_ERROR=/var/log/one/sunstone.error
SUNSTONE_CONF=/etc/one/sunstone-server.conf
NOVNC_SERVER=/usr/bin/novnc-server
GUACAMOLE_SERVER=/usr/bin/guac-server
else
SUNSTONE_PID=$ONE_LOCATION/var/sunstone.pid
SUNSTONE_SERVER=$ONE_LOCATION/lib/sunstone/sunstone-server.rb
@@ -33,6 +34,7 @@ else
SUNSTONE_LOG_ERROR=$ONE_LOCATION/var/sunstone.error
SUNSTONE_CONF=$ONE_LOCATION/etc/sunstone-server.conf
NOVNC_SERVER=$ONE_LOCATION/bin/novnc-server
GUACAMOLE_SERVER=$ONE_LOCATION/bin/guac-server
fi
setup()
@@ -59,12 +61,15 @@ setup()
fi
}
start()
{
if [ "$1" = "-n" ]; then
novnc=1
fi
while getopts "ng" opt; do
case "${opt}" in
n) novnc=1 ;;
g) guacamole=1 ;;
*) echo "Invalid option: $OPTARG" 1>&2 ;;
esac
done
if [ ! -f "$SUNSTONE_SERVER" ]; then
echo "Cannot find $SUNSTONE_SERVER."
@@ -80,6 +85,15 @@ start()
fi
fi
if [ "$guacamole" = "1" ]; then
# Start guacamole server
$GUACAMOLE_SERVER start
if [ "$?" != "0" ]; then
echo "Could not start Guacamole server" 1>&2
fi
fi
# Start the sunstone daemon
touch $SUNSTONE_LOCK_FILE
@@ -87,6 +101,7 @@ start()
echo "Error creating lock file" 1>&2
$NOVNC_SERVER stop
$GUACAMOLE_SERVER stop
exit 1
fi
@@ -97,6 +112,7 @@ start()
echo "Error executing sunstone-server."
echo "Check $SUNSTONE_LOG_ERROR and $SUNSTONE_LOG for more information"
[ "$novnc" = "1" ] && $NOVNC_SERVER stop
[ "$guacamole" = "1" ] && $GUACAMOLE_SERVER stop
exit 1
else
echo $LASTPID > $SUNSTONE_PID
@@ -109,6 +125,7 @@ start()
echo "Error executing sunstone-server."
echo "Check $SUNSTONE_LOG_ERROR and $SUNSTONE_LOG for more information"
[ "$novnc" = "1" ] && $NOVNC_SERVER stop
[ "$guacamole" = "1" ] && $GUACAMOLE_SERVER stop
exit 1
fi
@@ -120,13 +137,20 @@ start()
#
stop()
{
if [ "$1" = "-n" ]; then
novnc=1
fi
while getopts "ng" opt; do
case "${opt}" in
n) novnc=1 ;;
g) guacamole=1 ;;
*) echo "Invalid option: $OPTARG" 1>&2 ;;
esac
done
# Stop novnc server
[ "$novnc" = "1" ] && $NOVNC_SERVER stop
# Stop guacamole server
[ "$guacamole" = "1" ] && $GUACAMOLE_SERVER stop
if [ ! -f $SUNSTONE_PID ]; then
echo "Couldn't find sunstone-server process pid." >&2
return 1
@@ -146,16 +170,16 @@ stop()
case "$1" in
start)
setup
start -n
start -ng
;;
stop)
stop -n
stop -ng
exit $?
;;
restart)
stop -n 2> /dev/null
stop -ng 2> /dev/null
setup
start -n
start -ng
;;
start-sunstone)
setup
@@ -165,7 +189,7 @@ case "$1" in
stop
;;
restart-sunstone)
stop -n 2> /dev/null
stop -ng 2> /dev/null
setup
start
;;
+3
View File
@@ -158,6 +158,9 @@
:vnc_request_password: false
:allow_vnc_federation: no
:guac_port: 29877
:guacd_port: 4822
# Login Session Length in seconds, defaults to 1 hour.
#:session_expire_time: 3600
@@ -420,6 +420,9 @@ tabs:
VM.menu_labels: true
VM.startvnc: true
VM.startspice: true
VM.vnc: true
VM.ssh: true
VM.rdp: true
VM.save_rdp: true
VM.save_virt_viewer: true
oneflow-services-tab:
@@ -93,6 +93,9 @@ tabs:
VM.unlock: true
VM.startvnc: true
VM.startspice: true
VM.vnc: true
VM.ssh: true
VM.rdp: true
VM.save_rdp: true
VM.save_virt_viewer: true
dashboard:
@@ -419,6 +419,9 @@ tabs:
VM.menu_labels: true
VM.startvnc: true
VM.startspice: true
VM.vnc: true
VM.ssh: true
VM.rdp: true
VM.save_rdp: true
VM.save_virt_viewer: true
oneflow-services-tab:
@@ -410,10 +410,13 @@ tabs:
VM.unlock: true
VM.edit_labels: true
VM.menu_labels: true
VM.save_rdp: true
VM.save_virt_viewer: true
VM.startvnc: true
VM.startspice: true
VM.vnc: true
VM.ssh: true
VM.rdp: true
VM.save_rdp: true
VM.save_virt_viewer: true
oneflow-services-tab:
panel_tabs:
service_info_tab: true
@@ -420,6 +420,9 @@ tabs:
VM.menu_labels: true
VM.startvnc: true
VM.startspice: true
VM.vnc: true
VM.ssh: true
VM.rdp: true
VM.save_rdp: true
VM.save_virt_viewer: true
oneflow-services-tab:
@@ -93,6 +93,9 @@ tabs:
VM.unlock: true
VM.startvnc: true
VM.startspice: true
VM.vnc: true
VM.ssh: true
VM.rdp: true
VM.save_rdp: true
VM.save_virt_viewer: true
dashboard:
@@ -419,6 +419,9 @@ tabs:
VM.menu_labels: true
VM.startvnc: true
VM.startspice: true
VM.vnc: true
VM.ssh: true
VM.rdp: true
VM.save_rdp: true
VM.save_virt_viewer: true
oneflow-services-tab:
@@ -412,6 +412,9 @@ tabs:
VM.menu_labels: true
VM.startvnc: true
VM.startspice: true
VM.vnc: true
VM.ssh: true
VM.rdp: true
VM.save_rdp: true
VM.save_virt_viewer: true
oneflow-services-tab:
@@ -418,6 +418,9 @@ tabs:
VM.menu_labels: true
VM.startvnc: true
VM.startspice: true
VM.vnc: true
VM.ssh: true
VM.rdp: true
VM.save_rdp: true
VM.save_virt_viewer: true
oneflow-services-tab:
@@ -94,6 +94,9 @@ tabs:
VM.unlock: true
VM.startvnc: true
VM.startspice: true
VM.vnc: true
VM.ssh: true
VM.rdp: true
VM.save_rdp: true
VM.save_virt_viewer: true
dashboard:
@@ -419,6 +419,9 @@ tabs:
VM.menu_labels: true
VM.startvnc: true
VM.startspice: true
VM.vnc: true
VM.ssh: true
VM.rdp: true
VM.save_rdp: true
VM.save_virt_viewer: true
oneflow-services-tab:
@@ -412,6 +412,9 @@ tabs:
VM.menu_labels: true
VM.startvnc: true
VM.startspice: true
VM.vnc: true
VM.ssh: true
VM.rdp: true
VM.save_rdp: true
VM.save_virt_viewer: true
oneflow-services-tab:
+35
View File
@@ -0,0 +1,35 @@
// Guacamole Client
//// Application Variables ////
var helmet = require('helmet');
var app = require('express')();
var http = require('http').Server(app);
var GuacamoleLite = require('guacamole-lite');
var { clientOptions, clientCallbacks } = require('./options');
/**
* CONSTANTS
* */
GUAC_PORT = process.env.PORT || 29877
GUACD_PORT = process.env.GUACD || 4822
/**
* Spinup the Guac websocket proxy on port 29877 if guacd is running
* */
var guacServer = new GuacamoleLite(
{ server: http, path: '/guacamole' },
{ host: '127.0.0.1', port: GUACD_PORT },
clientOptions,
clientCallbacks
);
/**
* Helmet helps you secure your Express apps
* */
app.use(helmet());
// Spin up application on port 29877
http.listen(GUAC_PORT, function(){
console.log('listening on *:' + GUAC_PORT);
});
+60
View File
@@ -0,0 +1,60 @@
/**
* CLIENT OPTIONS
*/
var clientOptions = {
crypt: {
cypher: 'AES-256-CBC',
key: 'LSIOGCKYLSIOGCKYLSIOGCKYLSIOGCKY'
},
allowedUnencryptedConnectionSettings: {
rdp: [
'width',
'height',
'dpi'
],
vnc: [
'width',
'height',
'dpi'
],
ssh: [
'color-scheme',
'font-name',
'font-size',
'width',
'height',
'dpi'
],
telnet: [
'color-scheme',
'font-name',
'font-size',
'width',
'height',
'dpi'
]
},
log: { verbose: false }
};
/**
* CALLBACKS
*/
var callbacks = {
processConnectionSettings: function (settings, callback) {
if (settings.expiration && settings.expiration < Date.now()) {
return callback(new Error('Token expired'));
}
//settings.connection['drive-path'] = '/tmp/guacamole_'
//settings.connection['enable-drive'] = false
//settings.connection['create-drive-path'] = false
callback(null, settings);
}
};
module.exports = {
clientOptions,
callbacks
}
+21
View File
@@ -0,0 +1,21 @@
{
"name": "one-guac",
"version": "1.0.0",
"description": "OpenNebula Guacamole Client",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --mode=production app.js"
},
"author": "Sergio Betanzos",
"license": "ISC",
"dependencies": {
"express": "^4.17.1",
"guacamole-lite": "^0.6.2",
"helmet": "^3.23.1"
},
"devDependencies": {
"webpack": "^4.43.0",
"webpack-cli": "^3.3.12"
}
}
+13
View File
@@ -0,0 +1,13 @@
var path = require('path');
module.exports = {
entry: './app.js',
target: 'node',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'guac.js'
},
stats: {
warningsFilter: /^(?!CriticalDependenciesWarning$)/
}
};
+29
View File
@@ -20,6 +20,7 @@ require 'OpenNebulaJSON'
include OpenNebulaJSON
require 'OpenNebulaVNC'
require 'OpenNebulaGuac'
require 'OpenNebulaAddons'
require 'OpenNebulaJSON/JSONUtils'
#include JSONUtils
@@ -301,6 +302,34 @@ class SunstoneServer < CloudServer
return vnc.proxy(resource)
end
########################################################################
# Guacamole
########################################################################
def startguac(id, type_connection, guac)
resource = retrieve_resource("vm", id)
if OpenNebula.is_error?(resource)
return [404, resource.to_json]
end
client = @client
vm_pool = VirtualMachinePool.new(client, -1)
user_pool = UserPool.new(client)
rc = user_pool.info
if OpenNebula.is_error?(rc)
puts rc.message
exit -1
end
rc = vm_pool.info
if OpenNebula.is_error?(rc)
puts rc.message
exit -1
end
return guac.proxy(resource, type_connection)
end
########################################################################
# Accounting & Monitoring
########################################################################
+3
View File
@@ -29,6 +29,9 @@ require.config({
/** Wickedpicker */
"wickedpicker": "../bower_components/wickedpicker/dist/wickedpicker.min",
/** Guacamole-common-js */
"guacamole-common-js": "../bower_components/guacamole-common-js/dist/guacamole-common.min",
/* DataTables */
"datatables.net": "../bower_components/datatables/media/js/jquery.dataTables",
"datatables.foundation": "../bower_components/datatables/media/js/dataTables.foundation",
+78 -21
View File
@@ -512,6 +512,27 @@ define(function(require) {
}
});
},
"guac" : function(params) {
var callback = params.success;
var callback_error = params.error;
var id = params.data.id;
var typeConnection = params.data.extra_param;
var resource = RESOURCE;
var request = OpenNebulaHelper.request(resource, null, params.data);
$.ajax({
url: "vm/" + id + "/guac/" + typeConnection,
type: "POST",
dataType: "json",
success: function(response) {
return callback ? callback(request, response) : null;
},
error: function(response) {
return callback_error ?
callback_error(request, OpenNebulaError(response)) : null;
}
});
},
"append": function(params) {
var action_obj = {"template_raw" : params.data.extra_param, append : true};
OpenNebulaAction.simple_action(params, RESOURCE, "update", action_obj);
@@ -666,13 +687,14 @@ define(function(require) {
"isDiskGraphsSupported": isDiskGraphsSupported,
"isNICAttachSupported": isNICAttachSupported,
"isVNCSupported": isVNCSupported,
"isRDPSupported": isRDPSupported,
"isConnectionSupported": isConnectionSupported,
"isSPICESupported": isSPICESupported,
"isWFileSupported": isWFileSupported,
"buttonVnc": buttonVnc,
"buttonSpice": buttonSpice,
"buttonWFile": buttonWFile,
"buttonRDP": buttonRDP,
"dropdownRDP": dropdownRDP,
"buttonSSH": buttonSSH,
"getName": function(id){
return OpenNebulaAction.getName(id, RESOURCE);
}
@@ -925,7 +947,7 @@ define(function(require) {
// returns true if the vnc button should be enabled
function isVNCSupported(element) {
return (Config.isTabActionEnabled("vms-tab", "VM.startvnc") && graphicSupported(element, "vnc"))
return (Config.isTabActionEnabled("vms-tab", "VM.vnc") && graphicSupported(element, "vnc"))
? true : false;
}
@@ -948,34 +970,43 @@ define(function(require) {
: false;
}
// returns true if the RDP button should be enabled
function isRDPSupported(element) {
var hasRdp = false;
/**
* returns NIC object if the SSH/RDP should be enabled
* @param {Object} element
* @param {String} typeConnection
*/
function isConnectionSupported(element, typeConnection) {
var isEnabled = false;
if (
Config.isTabActionEnabled("vms-tab", "VM.save_rdp") &&
$.inArray(String(typeConnection).toLowerCase(), ['rdp', 'ssh']) > -1 &&
Config.isTabActionEnabled("vms-tab", "VM."+ String(typeConnection).toLowerCase()) &&
element && element.TEMPLATE && element.TEMPLATE.GRAPHICS && element.LCM_STATE
) {
var template = element.TEMPLATE;
var state = parseInt(element.LCM_STATE);
if ($.inArray(state, RDP_STATES) != -1 && template.NIC) {
hasRdp = hasRDP(template.NIC);
if ($.inArray(state, RDP_STATES) > -1 && template.NIC) {
isEnabled = hasConnection(template.NIC, typeConnection);
if (!hasRdp && template.NIC_ALIAS) {
hasRdp = hasRDP(template.NIC_ALIAS);
if (!isEnabled && template.NIC_ALIAS) {
isEnabled = hasConnection(template.NIC_ALIAS, typeConnection);
}
}
}
return hasRdp;
return isEnabled;
}
function hasRDP(nics) {
function hasConnection(nics, typeConnection) {
typeConnection = String(typeConnection).toUpperCase();
var activated = false;
nics = Array.isArray(nics) ? nics : [nics];
$.each(nics, function(_, nic) {
if (nic.RDP && String(nic.RDP).toLowerCase() === "yes") {
if (
nic[typeConnection] &&
String(nic[typeConnection]).toLowerCase() === "yes"
) {
activated = nic;
}
});
@@ -997,23 +1028,48 @@ define(function(require) {
}
function buttonVnc(id = "") {
return '<button class="vnc remote_vm" data-id="' + id + '">\
return '<button class="vnc remote-vm" data-id="' + id + '">\
<i class="fas fa-desktop"></i></button>';
}
function buttonSSH(id = "") {
return '<button class="ssh remote-vm" data-id="' + id + '">\
<i class="fas fa-terminal"></i></button>';
}
function buttonSpice(id = "") {
return '<button class="spice remote_vm" data-id="' + id + '">\
return '<button class="spice remote-vm" data-id="' + id + '">\
<i class="fas fa-desktop"></i></button>';
}
function buttonWFile(id = "", data = {}) {
return '<button class="w_file remote_vm" data-id="' + id + '"\
return '<button class="w-file remote-vm" data-id="' + id + '"\
data-type="' + data.type + '" data-port="' + data.port + '" data-hostname="' + data.hostname + '">\
<i class="fas fa-external-link-square-alt"></i></button>';
}
function buttonRDP(ip = "", vm = {}) {
var username, password;
function dropdownRDP(id = "", ip = "", vm = {}) {
return '<ul class="dropdown menu" id="rdp-buttons" data-dropdown-menu> \
<li>\
<button type="button" class="remote-vm"> \
<i class="fab fa-windows"></i> \
</button>\
<ul class="menu">\
<li>' + buttonRDP(id) + '</li> \
<li>' + buttonSaveRDP(ip, vm) + '</li> \
</ul>\
</li>\
</ul>';
}
function buttonRDP(id = "") {
var styleIcon = 'style="width: 2em;text-align: center;"';
return '<a class="rdp" data-id="' + id + '"> \
<i class="fas fa-desktop" ' + styleIcon + '></i><span>' + Locale.tr("HTML") + '</span></a>';
}
function buttonSaveRDP(ip = "", vm = {}) {
var username, password, styleIcon = 'style="width: 2em;text-align: center;"';
if (vm && vm.TEMPLATE && vm.TEMPLATE.CONTEXT) {
var context = vm.TEMPLATE.CONTEXT;
@@ -1023,13 +1079,14 @@ define(function(require) {
(propUpperCase === "PASSWORD") && (password = context[prop]);
}
}
var button = '<button class="rdp remote_vm" data-name="' + vm.NAME + '" data-ip="' + ip + '"';
var button = '<a class="save-rdp" data-name="' + vm.NAME + '" data-ip="' + ip + '"';
username && (button += ' data-username="' + username + '"');
password && (button += ' data-password="' + password + '"');
button += '><i class="fab fa-windows"></i></button>';
button += '><i class="fas fa-file" ' + styleIcon + '></i><span>' + Locale.tr("RDP Client") + '</span></a>';
return button;
}
return VM;
});
@@ -169,6 +169,7 @@ define(function(require) {
"scaleFactor": _config["view"]["features"]["instantiate_cpu_factor"],
"filterView": _config["view"]["filter-view"],
"doCountAnimation": _config["view"]["do_count_animation"],
"guacPort": _config["user_config"]["guac_port"],
"allTabs": function() {
return Object.keys(_config["view"]["tabs"]);
@@ -227,8 +227,11 @@ define(function(require) {
var wFile = OpenNebulaVM.isWFileSupported(data.VM);
actions += wFile ? OpenNebulaVM.buttonWFile(id, wFile) : "";
var rdp = OpenNebulaVM.isRDPSupported(data.VM);
actions += rdp ? OpenNebulaVM.buttonRDP(rdp.IP, data.VM) : "";
var rdp = OpenNebulaVM.isConnectionSupported(data.VM, 'rdp');
actions += rdp ? OpenNebulaVM.dropdownRDP(id, rdp.ID, data.VM) : "";
var ssh = OpenNebulaVM.isConnectionSupported(data.VM, 'ssh');
actions += ssh ? OpenNebulaVM.buttonSSH(id) : "";
}
roleVms[index] = rowInfoRoleVm(id, name, uname, gname, ips, actions);
@@ -71,6 +71,7 @@ define(function(require) {
WizardTab.prototype.addNicTab = _addNicTab;
WizardTab.prototype.notify = _notify;
WizardTab.prototype.enableRDP = _enableRDP;
WizardTab.prototype.enableSSH = _enableSSH;
return WizardTab;
@@ -210,6 +211,7 @@ define(function(require) {
that.renameTabLinks(context);
that.enableRDP(context);
that.enableSSH(context);
if (templateJSON.NIC) {
delete templateJSON.NIC;
@@ -282,6 +284,7 @@ define(function(require) {
that.renameTabLinks(context);
that.enableRDP(context);
that.enableSSH(context);
that.nicTabObjects[that.numberOfNics] = nicTab;
// close icon: removing the tab on click
@@ -333,6 +336,13 @@ define(function(require) {
else $("fieldset#rdp_connection", that.context).hide();
}
function _enableSSH(context) {
const canSSH = $("fieldset#ssh_connection input[type='checkbox']:not(#" + that.nicTabId + "_ssh):checked", context).length === 0;
if (canSSH) $("fieldset#ssh_connection").show();
else $("fieldset#ssh_connection", that.context).hide();
}
function _notify(context, templateJSON) {
if (templateJSON.VROUTER == "YES"){
while($("i.remove-tab", context).length > 0){
@@ -317,7 +317,12 @@ define(function(require) {
context.on("change", "#" + that.nicTabId + "_rdp", function() {
const isRDPActivated = $(this).prop('checked');
_hide_rdp(that.nicTabId, isRDPActivated, context);
_hide_connection(that.nicTabId, isRDPActivated, context, 'rdp');
});
context.on("change", "#" + that.nicTabId + "_ssh", function() {
const isSSHActivated = $(this).prop('checked');
_hide_connection(that.nicTabId, isSSHActivated, context, 'ssh');
});
}
@@ -372,6 +377,10 @@ define(function(require) {
nicJSON["RDP"] = "YES";
}
if($("input#" + this.nicTabId + "_ssh", context).prop("checked")) {
nicJSON["SSH"] = "YES";
}
return nicJSON;
}
@@ -488,6 +497,14 @@ define(function(require) {
$("input#" + this.nicTabId + "_rdp", context).prop("checked", isRDPActivated);
const isSSHActivated = (
templateJSON["SSH"] &&
templateJSON["SSH"] === "YES" &&
$("fieldset#ssh_connection input:not(#" + that.nicTabId + "_ssh):checked", context).length === 0
) ? true : false;
$("input#" + this.nicTabId + "_ssh", context).prop("checked", isSSHActivated);
WizardFields.fill(context, templateJSON);
}
@@ -526,9 +543,12 @@ define(function(require) {
});
}
function _hide_rdp(nicTabId, isRDPActivated, context) {
$("#template_create_network_tabs_content > div:not(#" + nicTabId + ") fieldset#rdp_connection", context).each(function() {
if (isRDPActivated) {
function _hide_connection(nicTabId, isActivated, context, typeConnection) {
tabSelector = "#template_create_network_tabs_content";
fieldsetSelector = "fieldset#" + typeConnection + "_connection";
$(tabSelector + " > div:not(#" + nicTabId + ") " + fieldsetSelector, context).each(function() {
if (isActivated) {
$(this).hide();
} else {
$(this).show();
@@ -16,75 +16,88 @@
{{!-- {{#isFeatureEnabled "automatic_selection_vnet"}} --}}
<div class="only_create">
<div class="row autoContainer">
<div class="large-12 large-centered columns" style="display:inline-flex;justify-content:start;">
<fieldset id="{{nicTabId}}interface_type" class="small-12 large-4 columns">
<legend style="margin:0px;">{{tr "Interface type"}}</legend>
<div class="switch left">
<input class="switch-input" id="{{nicTabId}}_interface_type" type="checkbox">
<label class="switch-paddle" for="{{nicTabId}}_interface_type">
</label>
</div>
<label class="left">
&nbsp;&nbsp;
{{tr "Alias"}}
{{{tip (tr "The NIC will be attached as an alias")}}}
<fieldset id="{{nicTabId}}interface_type" class="small-12 large-6 columns">
<legend style="margin:0px;">{{tr "Interface type"}}</legend>
<div class="switch left">
<input class="switch-input" id="{{nicTabId}}_interface_type" type="checkbox">
<label class="switch-paddle" for="{{nicTabId}}_interface_type">
</label>
</div>
<label class="left">
&nbsp;&nbsp;
{{tr "Alias"}}
{{{tip (tr "The NIC will be attached as an alias")}}}
</label>
&nbsp;&nbsp;
<span id="{{nicTabId}}_no_alias" class="radius warning label">
<i class="fas fa-exclamation-triangle"/> {{tr "NIC has alias"}}
</span>
<select id="{{nicTabId}}_alias_parent" style="width: 200px; margin-left: 5px"></select>
</fieldset>
<fieldset class="small-12 large-6 columns">
<legend style="margin:0px;">{{tr "Network selection"}}</legend>
<div class="switch left network_selection">
<input class="switch-input" id="{{nicTabId}}_network_mode" type="checkbox">
<label class="switch-paddle" for="{{nicTabId}}_network_mode">
</label>
</div>
<label class="left network_selection">
&nbsp;&nbsp;
{{tr "Automatic selection"}}
{{{tip (tr "The Schedule will decide which is the best virtual network")}}}
</label>
</fieldset>
</div>
<div class="row autoContainer">
<fieldset id="rdp_connection" class="small-12 large-6 columns">
<legend style="margin:0px;">{{tr "RDP connection"}}</legend>
<div class="switch left">
<input class="switch-input" id="{{nicTabId}}_rdp" type="checkbox">
<label class="switch-paddle" for="{{nicTabId}}_rdp">
</label>
</div>
<label class="left">
&nbsp;&nbsp;
<span id="{{nicTabId}}_no_alias" class="radius warning label">
<i class="fas fa-exclamation-triangle"/> {{tr "NIC has alias"}}
</span>
<select id="{{nicTabId}}_alias_parent" style="width: 200px; margin-left: 5px"></select>
</fieldset>
<fieldset class="small-12 large-4 columns">
<legend style="margin:0px;">{{tr "Network selection"}}</legend>
<div class="switch left network_selection">
<input class="switch-input" id="{{nicTabId}}_network_mode" type="checkbox">
<label class="switch-paddle" for="{{nicTabId}}_network_mode">
</label>
</div>
<label class="left network_selection">
&nbsp;&nbsp;
{{tr "Automatic selection"}}
{{{tip (tr "The Schedule will decide which is the best virtual network")}}}
</label>
</fieldset class="small-12 large-4 columns">
<fieldset id="rdp_connection" class="small-12 large-4 columns">
<legend style="margin:0px;">{{tr "RDP connection"}}</legend>
<div class="switch left">
<input class="switch-input" id="{{nicTabId}}_rdp" type="checkbox">
<label class="switch-paddle" for="{{nicTabId}}_rdp">
</label>
</div>
<label class="left">
&nbsp;&nbsp;
{{tr "Activate"}}
{{{tip (tr "RDP will be activate in this network")}}}
</label>
</fieldset>
</div>
{{tr "Activate"}}
{{{tip (tr "RDP will be activate in this network")}}}
</label>
</fieldset>
<fieldset id="ssh_connection" class="small-12 large-6 columns">
<legend style="margin:0px;">{{tr "SSH connection"}}</legend>
<div class="switch left">
<input class="switch-input" id="{{nicTabId}}_ssh" type="checkbox">
<label class="switch-paddle" for="{{nicTabId}}_ssh">
</label>
</div>
<label class="left">
&nbsp;&nbsp;
{{tr "Activate"}}
{{{tip (tr "SSH will be enable in this network")}}}
</label>
</fieldset>
</div>
<div class="auto">
<fieldset>
<legend>{{tr "Network Requirements"}}</legend>
<div class="row">
<div class="large-12 columns text-center">
<input type="radio" id="vnet_req" name="{{nicTabId}}_req_select" value="vnet_select">
<label for="vnet_req">{{tr "Select Vnets "}}</label>
<input type="radio" id="custom_req" name="{{nicTabId}}_req_select" value="custom" checked>
<label for="custom_req">{{tr "Custom expression "}}</label>
</div>
<legend>{{tr "Network Requirements"}}</legend>
<div class="row">
<div class="large-12 columns text-center">
<input type="radio" id="vnet_req" name="{{nicTabId}}_req_select" value="vnet_select">
<label for="vnet_req">{{tr "Select Vnets "}}</label>
<input type="radio" id="custom_req" name="{{nicTabId}}_req_select" value="custom" checked>
<label for="custom_req">{{tr "Custom expression "}}</label>
</div>
<br>
<div id="{{nicTabId}}_vnetTable" class="net_select" hidden>{{{vnetsTableAutoSelectHTML}}}</div>
<div class="row">
<div class="large-12 columns">
<label for="{{nicTabId}}_SCHED_REQUIREMENTS">
{{tr "Expression"}}
{{{tip (tr "Boolean expression that rules out entries from the pool of datastores suitable to run this VM.")}}}
</label>
<input type="text" id="{{nicTabId}}_SCHED_REQUIREMENTS" name="requirements" />
</div>
</div>
<br>
<div id="{{nicTabId}}_vnetTable" class="net_select" hidden>{{{vnetsTableAutoSelectHTML}}}</div>
<div class="row">
<div class="large-12 columns">
<label for="{{nicTabId}}_SCHED_REQUIREMENTS">
{{tr "Expression"}}
{{{tip (tr "Boolean expression that rules out entries from the pool of datastores suitable to run this VM.")}}}
</label>
<input type="text" id="{{nicTabId}}_SCHED_REQUIREMENTS" name="requirements" />
</div>
</div>
</fieldset>
<div class="row autoContainer">
<fieldset class="nic_rank">
+1
View File
@@ -37,6 +37,7 @@ define(function(require) {
require("./vms-tab/dialogs/revert"),
require("./vms-tab/dialogs/vnc"),
require("./vms-tab/dialogs/spice"),
require("./vms-tab/dialogs/guac"),
require("./vms-tab/dialogs/saveas-template")
];
@@ -31,6 +31,7 @@ define(function(require) {
var MIGRATE_DIALOG_ID = require('./dialogs/migrate/dialogId');
var VNC_DIALOG_ID = require('./dialogs/vnc/dialogId');
var SPICE_DIALOG_ID = require('./dialogs/spice/dialogId');
var GUAC_DIALOG_ID = require('./dialogs/guac/dialogId');
var SAVE_AS_TEMPLATE_DIALOG_ID = require('./dialogs/saveas-template/dialogId');
var UPDATECONF_FORM_ID = require('./form-panels/updateconf/formPanelId');
@@ -310,6 +311,52 @@ define(function(require) {
},
notify: true
},
"VM.guac_vnc" : {
type: "custom",
call: function() {
$.each(Sunstone.getDataTable(TAB_ID).elements(), function(index, elem) {
Sunstone.runAction("VM.startguac_action", elem, 'vnc');
});
},
error: function(req, resp) {
Notifier.onError(req, resp);
},
},
"VM.guac_rdp" : {
type: "custom",
call: function() {
$.each(Sunstone.getDataTable(TAB_ID).elements(), function(index, elem) {
Sunstone.runAction("VM.startguac_action", elem, 'rdp');
});
},
error: function(req, resp) {
Notifier.onError(req, resp);
},
},
"VM.guac_ssh" : {
type: "custom",
call: function() {
$.each(Sunstone.getDataTable(TAB_ID).elements(), function(index, elem) {
Sunstone.runAction("VM.startguac_action", elem, 'ssh');
});
},
error: function(req, resp) {
Notifier.onError(req, resp);
},
},
"VM.startguac_action" : {
type: "single",
call: OpenNebulaVM.guac,
callback: function(request, response) {
var dialog = Sunstone.getDialog(GUAC_DIALOG_ID);
dialog.setElement(response);
dialog.show();
},
error: function(req, resp) {
Notifier.onError(req, resp);
},
notify: true
},
"VM.save_as_template" : {
type: "single",
call: function() {
@@ -212,9 +212,27 @@ define(function(require) {
layout: "vmsremote_buttons",
custom_classes: "only-sunstone-info vv-sunstone-info"
},
"VM.guac_vnc" : {
type: "action",
text: Locale.tr("GUAC VNC"),
layout: "vmsremote_buttons",
custom_classes: "only-sunstone-info vnc-sunstone-info"
},
"VM.guac_ssh" : {
type: "action",
text: Locale.tr("GUAC SSH"),
layout: "vmsremote_buttons",
custom_classes: "only-sunstone-info ssh-sunstone-info"
},
"VM.guac_rdp" : {
type: "action",
text: Locale.tr("GUAC RDP"),
layout: "vmsremote_buttons",
custom_classes: "only-sunstone-info rdp-sunstone-info"
},
"VM.save_rdp" : {
type: "action",
text: Locale.tr("RDP"),
text: Locale.tr("RDP Client"),
layout: "vmsremote_buttons",
custom_classes: "only-sunstone-info rdp-sunstone-info"
},
@@ -167,6 +167,8 @@ define(function(require) {
$(".off_vms").text(this.offVms);
VMsTableUtils.tooltipCharters()
$("#rdp-buttons").foundation();
}
function _initialize(opts) {
@@ -175,7 +177,7 @@ define(function(require) {
TabDataTable.prototype.initialize.call(this, opts);
//download virt-viewer file
$('#' + this.dataTableId).on("click", '.w_file', function(){
$('#' + this.dataTableId).on("click", '.w-file', function(){
var data = $(this).data();
(data.hasOwnProperty("id") && data.hasOwnProperty("hostname") && data.hasOwnProperty("type") && data.hasOwnProperty("port"))
@@ -190,7 +192,7 @@ define(function(require) {
});
//download RDP file
$('#' + this.dataTableId).on("click", '.rdp', function() {
$('#' + this.dataTableId).on("click", '.save-rdp', function() {
var data = $(this).data();
(data.hasOwnProperty("ip") && data.hasOwnProperty("name"))
@@ -200,6 +202,36 @@ define(function(require) {
return false;
});
$('#' + this.dataTableId).on("click", ".rdp", function() {
var data = $(this).data();
(data.hasOwnProperty("id"))
? Sunstone.runAction("VM.startguac_action", String(data.id), 'rdp')
: Notifier.notifyError(Locale.tr("RDP - Invalid action"));
return false;
});
$('#' + this.dataTableId).on("click", ".ssh", function() {
var data = $(this).data();
(data.hasOwnProperty("id"))
? Sunstone.runAction("VM.startguac_action", String(data.id), 'ssh')
: Notifier.notifyError(Locale.tr("SSH - Invalid action"));
return false;
});
$('#' + this.dataTableId).on("click", ".guac-vnc", function() {
var data = $(this).data();
(data.hasOwnProperty("id"))
? Sunstone.runAction("VM.startguac_action", String(data.id), 'vnc')
: Notifier.notifyError(Locale.tr("VNC - Invalid action"));
return false;
});
$('#' + this.dataTableId).on("click", '.vnc', function() {
var data = $(this).data();
@@ -0,0 +1,92 @@
/* -------------------------------------------------------------------------- */
/* Copyright 2002-2020, OpenNebula Project, OpenNebula Systems */
/* */
/* Licensed under the Apache License, Version 2.0 (the "License"); you may */
/* not use this file except in compliance with the License. You may obtain */
/* a copy of the License at */
/* */
/* http://www.apache.org/licenses/LICENSE-2.0 */
/* */
/* Unless required by applicable law or agreed to in writing, software */
/* distributed under the License is distributed on an "AS IS" BASIS, */
/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */
/* See the License for the specific language governing permissions and */
/* limitations under the License. */
/* -------------------------------------------------------------------------- */
define(function(require) {
/*
DEPENDENCIES
*/
var BaseDialog = require('utils/dialogs/dialog');
var TemplateHTML = require('hbs!./guac/html');
var Sunstone = require('sunstone');
var GClient = require('utils/gclient');
/*
CONSTANTS
*/
var DIALOG_ID = require('./guac/dialogId');
var TAB_ID = require('../tabId')
/*
CONSTRUCTOR
*/
function Dialog() {
this.dialogId = DIALOG_ID;
this.gClient = new GClient();
BaseDialog.call(this);
};
Dialog.DIALOG_ID = DIALOG_ID;
Dialog.prototype = Object.create(BaseDialog.prototype);
Dialog.prototype.constructor = Dialog;
Dialog.prototype.html = _html;
Dialog.prototype.onShow = _onShow;
Dialog.prototype.onClose = _onClose;
Dialog.prototype.setup = _setup;
Dialog.prototype.setElement = _setElement;
return Dialog;
/*
FUNCTION DEFINITIONS
*/
function _html() {
return TemplateHTML({
'dialogId': this.dialogId
});
}
function _setup(context) {
$("#open_in_a_new_window_gclient", context).on("click", function() {
var dialog = Sunstone.getDialog(DIALOG_ID);
dialog.hide();
});
return false;
}
function _onShow(context) {
this.gClient.connect(this.element);
this.gClient.mouse(true);
this.gClient.keyboard(true);
return false;
}
function _onClose(context) {
this.gClient.disconnect();
this.gClient.mouse(false);
this.gClient.keyboard(false);
return false;
}
function _setElement(element) {
this.element = element
}
});
@@ -0,0 +1,19 @@
/* -------------------------------------------------------------------------- */
/* Copyright 2002-2020, OpenNebula Project, OpenNebula Systems */
/* */
/* Licensed under the Apache License, Version 2.0 (the "License"); you may */
/* not use this file except in compliance with the License. You may obtain */
/* a copy of the License at */
/* */
/* http://www.apache.org/licenses/LICENSE-2.0 */
/* */
/* Unless required by applicable law or agreed to in writing, software */
/* distributed under the License is distributed on an "AS IS" BASIS, */
/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */
/* See the License for the specific language governing permissions and */
/* limitations under the License. */
/* -------------------------------------------------------------------------- */
define(function(require) {
return 'guacVMDialog';
});
@@ -0,0 +1,34 @@
{{! -------------------------------------------------------------------------- }}
{{! Copyright 2002-2020, OpenNebula Project, OpenNebula Systems }}
{{! }}
{{! Licensed under the Apache License, Version 2.0 (the "License"); you may }}
{{! not use this file except in compliance with the License. You may obtain }}
{{! a copy of the License at }}
{{! }}
{{! http://www.apache.org/licenses/LICENSE-2.0 }}
{{! }}
{{! Unless required by applicable law or agreed to in writing, software }}
{{! distributed under the License is distributed on an "AS IS" BASIS, }}
{{! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. }}
{{! See the License for the specific language governing permissions and }}
{{! limitations under the License. }}
{{! -------------------------------------------------------------------------- }}
<div id="{{dialogId}}" class="reveal full" data-reveal>
<div class="row">
<div class="large-12 columns">
<h5 class="subheader" id="guacamole_dialog">
<span id="guacamole-state"></span>
<span id="guacamole-loading">
<i class="fas fa-spinner fa-spin"></i>
</span>
<span id="guacamole-buttons" class="right">
<button class="button secondary" data-close aria-label="{{tr "Close modal"}}" type="button" title="Close Guacamole">
<i class="fas fa-times-circle"></i>
</button>
</span>
</h5>
</div>
</div>
<div id="guacamole-display" class="guacamole-display"></div>
</div>
@@ -41,13 +41,20 @@ define(function(require) {
StateActions.disableAllStateActions();
StateActions.enableStateActions(element.STATE, element.LCM_STATE);
var isVNCSupported = OpenNebulaVM.isVNCSupported(element),
isSPICESupported = OpenNebulaVM.isSPICESupported(element),
isWFileSupported = OpenNebulaVM.isWFileSupported(element),
isRDPSupported = OpenNebulaVM.isRDPSupported(element);
var isVNCSupported = Boolean(OpenNebulaVM.isVNCSupported(element)),
isSPICESupported = Boolean(OpenNebulaVM.isSPICESupported(element)),
isWFileSupported = Boolean(OpenNebulaVM.isWFileSupported(element)),
isRDPSupported = Boolean(OpenNebulaVM.isConnectionSupported(element, 'rdp')),
isSSHSupported = Boolean(OpenNebulaVM.isConnectionSupported(element, 'ssh'));
// All remote buttons are disabled
var allDisabled = (!isVNCSupported && !isSPICESupported && !isWFileSupported && !isRDPSupported);
var allDisabled = (
!isVNCSupported &&
!isSPICESupported &&
!isWFileSupported &&
!isRDPSupported &&
!isSSHSupported
);
$("#vmsremote_buttons").toggle(!allDisabled);
if (isVNCSupported) {
@@ -64,10 +71,13 @@ define(function(require) {
}
// Show / hide virt-viewer button
$(".vv-sunstone-info").toggle(Boolean(isWFileSupported));
$(".vv-sunstone-info").toggle(!!isWFileSupported);
// Show / hide rdp button
$(".rdp-sunstone-info").toggle(Boolean(isRDPSupported));
$(".rdp-sunstone-info").toggle(!!isRDPSupported);
// Show / hide ssh button
$(".ssh-sunstone-info").toggle(!!isSSHSupported);
if(config &&
config["system_config"] &&
@@ -218,9 +218,13 @@ define(function(require) {
actions = '';
}
// RDP icon
var rdp = OpenNebulaVM.isRDPSupported(element);
rdp && (actions += OpenNebulaVM.buttonRDP(rdp.IP, element));
// RDP icons
var rdp = OpenNebulaVM.isConnectionSupported(element, 'rdp');
rdp && (actions += OpenNebulaVM.dropdownRDP(element.ID, rdp.IP, element));
// SSH icon
var ssh = OpenNebulaVM.isConnectionSupported(element, 'ssh');
ssh && (actions += OpenNebulaVM.buttonSSH(element.ID));
var cpuMonitoring = 0, memoryMonitoring = 0;
if (element.MONITORING) {
@@ -275,7 +279,7 @@ define(function(require) {
hostname,
OpenNebulaVM.ipsDropdown(element),
Humanize.prettyTimeDatatable(element.STIME),
actions && "<div style='display: inline-flex'>"+actions+"</div>",
actions && "<div style='display: flex; align-items: end;'>"+actions+"</div>",
TemplateUtils.htmlEncode(TemplateUtils.templateToString(element)),
(LabelsUtils.labelsStr(element[TEMPLATE_ATTR])||''),
btoa(unescape(encodeURIComponent(JSON.stringify(search)))),
+243
View File
@@ -0,0 +1,243 @@
/* -------------------------------------------------------------------------- */
/* Copyright 2002-2020, OpenNebula Project, OpenNebula Systems */
/* */
/* Licensed under the Apache License, Version 2.0 (the "License"); you may */
/* not use this file except in compliance with the License. You may obtain */
/* a copy of the License at */
/* */
/* http://www.apache.org/licenses/LICENSE-2.0 */
/* */
/* Unless required by applicable law or agreed to in writing, software */
/* distributed under the License is distributed on an "AS IS" BASIS, */
/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */
/* See the License for the specific language governing permissions and */
/* limitations under the License. */
/* -------------------------------------------------------------------------- */
define(function(require) {
/**
* DEPENDENCIES
*/
require("guacamole-common-js")
var Config = require("sunstone-config");
var Notifier = require("utils/notifier");
var Locale = require("utils/locale");
/**
* CONSTRUCTOR
*/
function GClient() {
this._display = null;
this._client = null;
this._mouse = null;
this._keyboard = null;
this._clientErrorText = Locale.tr("The OpenNebula service for remote console is not running, please contact your administrator.")
return this;
};
GClient.prototype = {
"connect": connect,
"mouse": mouse,
"keyboard": keyboard,
"snapshot": getCanvas,
"disconnect": disconnect,
};
return GClient;
function setStatus(state = "") {
$('#guacamole-state').text(state).animate();
}
function setLoading(isLoading = false) {
$('#guacamole-loading')[isLoading ? 'fadeIn' : 'fadeOut']('fast');
}
function connect(response) {
var that = this;
if (!response.token) {
Notifier.notifyError(this._clientErrorText)
return null;
}
else setLoading(true);
var port = Config.guacPort || '29877';
var host = window.location.hostname || 'localhost';
var wsprotocol = (window.location.protocol == 'https:') ? 'wss:' : 'ws:';
var tunnel = new Guacamole.WebSocketTunnel(wsprotocol + '//' + host + ':' + port + '/guacamole')
var guac = this._client = new Guacamole.Client(tunnel);
// Client display
this._display = $("#guacamole-display");
this._display.html(this._client.getDisplay().getElement());
// client error handler
guac.onerror = function() {
Notifier.notifyError(that._clientErrorText)
};
// websoket error handler
tunnel.onerror = function() {
disconnect();
Notifier.notifyError(that._clientErrorText)
};
guac.onstatechange = function(state) {
switch (state) {
case 0:
setStatus("Client IDLE");
setLoading(true);
break;
case 1:
setStatus("Client CONNECTING");
setLoading(true);
break;
case 2:
setStatus("Client WAITING");
setLoading(true);
break;
case 3:
setStatus("Client CONNECTED");
setLoading(false);
setTimeout(function() {
rescale(that);
guac.getDisplay().showCursor(false);
}, 100);
break;
case 4:
setStatus("Client DISCONNECTING");
setLoading(true);
break;
case 5:
setStatus("Client DISCONNECTED");
setLoading(false);
break;
default:
setStatus("Client ERROR");
setLoading(false);
break;
}
}
// Connect
var params = [
'token=' + response.token,
'width=' + this._display.width()
];
try {
guac.connect(params.join('&'));
} catch (error) {
console.log(error)
}
// Disconnect on close
window.onunload = function () {
disconnect();
};
// Scale display when window resize
$(window).resize(function () {
rescale(that);
});
}
/*
GuacamoleWrapper.mouse
- handles mouse interaction
*/
function mouse(enable = true) {
var that = this;
if (!this._client) return;
if (enable) {
this._mouse = new Guacamole.Mouse(this._client.getDisplay().getElement());
// apply sendState function
this._mouse.onmousedown =
this._mouse.onmouseup = function(mouseState) {
that._client.sendMouseState(mouseState);
};
this._mouse.onmousemove = function(mouseState) {
mouseState.y = mouseState.y / that._client.getDisplay().getScale();
mouseState.x = mouseState.x / that._client.getDisplay().getScale();
that._client.sendMouseState(mouseState);
};
}
}
/*
GuacamoleWrapper.keyboard
- handles keyboard interaction
*/
function keyboard(enable = true) {
var that = this;
if (!this._client) return;
if (enable) {
this._keyboard = new Guacamole.Keyboard(document);
this._keyboard.onkeydown = function(keysym) {
that._client.sendKeyEvent(1, keysym);
}
this._keyboard.onkeyup = function(keysym) {
that._client.sendKeyEvent(0, keysym);
}
} else {
this._keyboard.onkeydown = null;
this._keyboard.onkeyup = null;
this._keyboard = null;
}
}
/*
GuacamoleWrapper.getCanvas
- shortcut for returning default guac layer (active tunnel viewport)
*/
function getCanvas() {
return (this._client) ? this._client.getDisplay().getDefaultLayer().getCanvas() : false;
}
function disconnect() {
if (this._client) {
this._client.disconnect();
this._scale = 1;
$(window).off('resize');
setStatus()
}
}
/**
* Updates the scale of the attached Guacamole.Client based on current window
* size and "auto-fit" setting.
*/
function rescale(thisContext) {
var gclientDisplay = thisContext._client.getDisplay();
//Get screen resolution.
var origHeigth = Math.max(gclientDisplay.getHeight(), 1);
var origWidth = Math.max(gclientDisplay.getWidth(), 1);
var htmlWidth = thisContext._display.width();
var htmlHeigth = thisContext._display.height();
var xscale = htmlWidth / origWidth;
var yscale = htmlHeigth / origHeigth;
// This is done to handle both X and Y axis slacing
var scale = Math.min(yscale, xscale);
// Limit to 1
scale = Math.min(scale, 1);
if (scale !== 0) {
gclientDisplay.scale(scale);
// Set minimum height container display
thisContext._display.css('min-height', gclientDisplay.getHeight());
}
}
});
+37 -5
View File
@@ -297,6 +297,7 @@ define(function(require) {
var displayType = true;
var displaySelection = true;
var displayRDP = true;
var displaySSH = true;
if(
template_json &&
template_json.VMTEMPLATE &&
@@ -306,9 +307,11 @@ define(function(require) {
var templateType = template_json.VMTEMPLATE.TEMPLATE.SUNSTONE.NETWORK_ALIAS;
var templateSelection = template_json.VMTEMPLATE.TEMPLATE.SUNSTONE.NETWORK_AUTO;
var templateRDP = template_json.VMTEMPLATE.TEMPLATE.SUNSTONE.NETWORK_RDP;
var templateSSH = template_json.VMTEMPLATE.TEMPLATE.SUNSTONE.NETWORK_SSH;
displayType = templateType && templateType.toUpperCase()==='NO'? false : true;
displaySelection = templateSelection && templateSelection.toUpperCase()==='NO'? false : true;
displayRDP = templateRDP && templateRDP.toUpperCase()==='NO'? false : true;
displaySSH = templateSSH && templateSSH.toUpperCase()==='NO'? false : true;
}
var dd_context = $(TemplateDD({
vnetsTableHTML: vnetsTable.dataTableHTML,
@@ -318,7 +321,8 @@ define(function(require) {
options: options,
displayType: displayType,
displaySelection: displaySelection,
displayRDP: displayRDP
displayRDP: displayRDP,
displaySSH: displaySSH,
})).appendTo(context);
dd_context["nic_id"] = nicId;
@@ -468,7 +472,13 @@ define(function(require) {
$("input#provision_accordion_dd_" + provision_nic_accordion_dd_id + "_rdp", dd_context).on("change", function() {
const isRDPActivated = $(this).prop('checked');
const idAccordion = "#provision_accordion_dd_" + dd_context["dd_id"];
_hide_rdp(idAccordion, isRDPActivated, context);
_hide_connection(idAccordion, isRDPActivated, context, 'rdp');
});
$("input#provision_accordion_dd_" + provision_nic_accordion_dd_id + "_ssh", dd_context).on("change", function() {
const isSSHActivated = $(this).prop('checked');
const idAccordion = "#provision_accordion_dd_" + dd_context["dd_id"];
_hide_connection(idAccordion, isSSHActivated, context, 'ssh');
});
if ( options.nic && options.nic["NETWORK_MODE"] && options.nic["NETWORK_MODE"] === "auto" ) {
@@ -535,6 +545,17 @@ define(function(require) {
$("input#provision_accordion_dd_" + provision_nic_accordion_dd_id + "_rdp", context).prop("checked", isRDPActivated);
_enableRDP("#provision_accordion_dd_" + provision_nic_accordion_dd_id, context)
// fill ssh connection
const isSSHActivated = (
options.nic &&
options.nic["SSH"] &&
options.nic["SSH"] === "YES" &&
$("fieldset#ssh_connection input:not(#provision_accordion_dd_" + provision_nic_accordion_dd_id + "_ssh):checked", context).length === 0
) ? true : false;
$("input#provision_accordion_dd_" + provision_nic_accordion_dd_id + "_ssh", context).prop("checked", isSSHActivated);
_enableSSH("#provision_accordion_dd_" + provision_nic_accordion_dd_id, context)
provision_nic_accordion_dd_id += 1;
vnetsTable.initialize();
@@ -584,6 +605,7 @@ define(function(require) {
nicId --;
_enableRDP("#provision_accordion_dd_"+dd_context["nic_id"], context)
_enableSSH("#provision_accordion_dd_"+dd_context["nic_id"], context)
return false;
});
@@ -637,6 +659,7 @@ define(function(require) {
nicId ++;
_enableRDP("#provision_accordion_dd_" + provision_nic_accordion_dd_id, context)
_enableSSH("#provision_accordion_dd_" + provision_nic_accordion_dd_id, context)
});
if (options.click_add_button == true){
@@ -704,9 +727,11 @@ define(function(require) {
});
}
function _hide_rdp(idAccordion, isRDPActivated, context) {
$(".accordion-item > div:not(" + idAccordion + ") fieldset#rdp_connection", context).each(function() {
if (isRDPActivated) {
function _hide_connection(idAccordion, isActivated, context, typeConnection) {
fieldsetSelector = "fieldset#" + typeConnection + "_connection";
$(".accordion-item > div:not(" + idAccordion + ") " + fieldsetSelector, context).each(function() {
if (isActivated) {
$(this).hide();
} else {
$(this).show();
@@ -720,4 +745,11 @@ define(function(require) {
if (canRDP) $("fieldset#rdp_connection", context).has("input:not(:checked)").show();
else $("fieldset#rdp_connection", context).has("input:not(:checked)").hide();
}
function _enableSSH(idAccordion, context) {
const canSSH = $("fieldset#ssh_connection input[type='checkbox']:not(" + idAccordion + "_ssh):checked", context).length === 0;
if (canSSH) $("fieldset#ssh_connection", context).has("input:not(:checked)").show();
else $("fieldset#ssh_connection", context).has("input:not(:checked)").hide();
}
});
@@ -23,61 +23,79 @@
<div id="provision_accordion_dd_{{provision_nic_accordion_dd_id}}" class="accordion-content nic-section-entry" data-tab-content>
<div class="only_template">
<div class="row autoContainer">
<div class="large-12 large-centered columns" style="margin:0px;display:inline-flex;justify-content:start;">
{{#if displayType}}
<fieldset class="small-12 large-4 columns" id="provision_accordion_dd_{{provision_nic_accordion_dd_id}}_interface_type_section">
<legend style="margin:0px;">{{tr "Interface type"}}</legend>
<div class="switch left">
<input class="switch-input" id="provision_accordion_dd_{{provision_nic_accordion_dd_id}}_interface_type" type="checkbox">
<label class="switch-paddle" for="provision_accordion_dd_{{provision_nic_accordion_dd_id}}_interface_type">
</label>
</div>
<label class="left">
&nbsp;&nbsp;
{{tr "Alias"}}
{{{tip (tr "The NIC will be attached as an alias")}}}
</label>
&nbsp;&nbsp;
<span id="provision_accordion_dd_{{provision_nic_accordion_dd_id}}_no_alias" class="radius warning label">
<i class="fas fa-exclamation-triangle"/> {{tr "NIC has alias"}}
</span>
<select id="provision_accordion_dd_{{provision_nic_accordion_dd_id}}_alias_parent" style="width: 200px; margin-left: 5px"></select>
</fieldset>
{{/if}}
{{#if displaySelection}}
<fieldset class="small-12 large-4 columns" style="margin:0px;">
<legend style="margin:0px;">{{tr "Network selection"}}</legend>
<div id="provision_accordion_dd_{{provision_nic_accordion_dd_id}}_network_selection">
<div class="switch left">
<input class="switch-input" id="provision_accordion_dd_{{provision_nic_accordion_dd_id}}_network_mode" type="checkbox">
<label class="switch-paddle" for="provision_accordion_dd_{{provision_nic_accordion_dd_id}}_network_mode">
</label>
</div>
<label class="left">
&nbsp;&nbsp;
{{tr "Automatic selection"}}
{{{tip (tr "The Schedule will decide which is the best virtual network")}}}
</label>
</div>
</fieldset>
{{/if}}
{{#if displayRDP}}
<fieldset id="rdp_connection" class="small-12 large-4 columns" style="margin:0px;">
<legend style="margin:0px;">{{tr "RDP connection"}}</legend>
<div id="provision_accordion_dd_{{provision_nic_accordion_dd_id}}_rdp_wrapper">
<div class="switch left">
<input class="switch-input" id="provision_accordion_dd_{{provision_nic_accordion_dd_id}}_rdp" type="checkbox">
<label class="switch-paddle" for="provision_accordion_dd_{{provision_nic_accordion_dd_id}}_rdp">
</label>
</div>
<label class="left">
&nbsp;&nbsp;
{{tr "Activate"}}
{{{tip (tr "RDP will be enabled in this network")}}}
</label>
</div>
</fieldset>
{{/if}}
{{#if displayType}}
<fieldset class="small-12 large-6 columns" id="provision_accordion_dd_{{provision_nic_accordion_dd_id}}_interface_type_section">
<legend style="margin:0px;">{{tr "Interface type"}}</legend>
<div class="switch left">
<input class="switch-input" id="provision_accordion_dd_{{provision_nic_accordion_dd_id}}_interface_type" type="checkbox">
<label class="switch-paddle" for="provision_accordion_dd_{{provision_nic_accordion_dd_id}}_interface_type">
</label>
</div>
<label class="left">
&nbsp;&nbsp;
{{tr "Alias"}}
{{{tip (tr "The NIC will be attached as an alias")}}}
</label>
&nbsp;&nbsp;
<span id="provision_accordion_dd_{{provision_nic_accordion_dd_id}}_no_alias" class="radius warning label">
<i class="fas fa-exclamation-triangle"/> {{tr "NIC has alias"}}
</span>
<select id="provision_accordion_dd_{{provision_nic_accordion_dd_id}}_alias_parent" style="width: 200px; margin-left: 5px"></select>
</fieldset>
{{/if}}
{{#if displaySelection}}
<fieldset class="small-12 large-6 columns" style="margin:0px;">
<legend style="margin:0px;">{{tr "Network selection"}}</legend>
<div id="provision_accordion_dd_{{provision_nic_accordion_dd_id}}_network_selection">
<div class="switch left">
<input class="switch-input" id="provision_accordion_dd_{{provision_nic_accordion_dd_id}}_network_mode" type="checkbox">
<label class="switch-paddle" for="provision_accordion_dd_{{provision_nic_accordion_dd_id}}_network_mode">
</label>
</div>
<label class="left">
&nbsp;&nbsp;
{{tr "Automatic selection"}}
{{{tip (tr "The Schedule will decide which is the best virtual network")}}}
</label>
</div>
</fieldset>
{{/if}}
</div>
<div class="row autoContainer">
{{#if displayRDP}}
<fieldset id="rdp_connection" class="small-12 large-6 columns" style="margin:0px;">
<legend style="margin:0px;">{{tr "RDP connection"}}</legend>
<div id="provision_accordion_dd_{{provision_nic_accordion_dd_id}}_rdp_wrapper">
<div class="switch left">
<input class="switch-input" id="provision_accordion_dd_{{provision_nic_accordion_dd_id}}_rdp" type="checkbox">
<label class="switch-paddle" for="provision_accordion_dd_{{provision_nic_accordion_dd_id}}_rdp">
</label>
</div>
<label class="left">
&nbsp;&nbsp;
{{tr "Activate"}}
{{{tip (tr "RDP will be enabled in this network")}}}
</label>
</div>
</fieldset>
{{/if}}
{{#if displaySSH}}
<fieldset id="ssh_connection" class="small-12 large-6 columns" style="margin:0px;">
<legend style="margin:0px;">{{tr "SSH connection"}}</legend>
<div id="provision_accordion_dd_{{provision_nic_accordion_dd_id}}_ssh_wrapper">
<div class="switch left">
<input class="switch-input" id="provision_accordion_dd_{{provision_nic_accordion_dd_id}}_ssh" type="checkbox">
<label class="switch-paddle" for="provision_accordion_dd_{{provision_nic_accordion_dd_id}}_ssh">
</label>
</div>
<label class="left">
&nbsp;&nbsp;
{{tr "Activate"}}
{{{tip (tr "SSH will be enabled in this network")}}}
</label>
</div>
</fieldset>
{{/if}}
</div>
<div class="auto">
<fieldset>
+1
View File
@@ -19,6 +19,7 @@
"sprintf": "~1.0.3",
"jquery-ui": "~1.12.1",
"wickedpicker": "https://github.com/OpenNebula/sunstone-deps.git#9398b3f",
"guacamole-common-js": "https://github.com/OpenNebula/sunstone-deps.git#1633556e63",
"webauthn-json": "https://registry.npmjs.org/@github/webauthn-json/-/webauthn-json-0.4.1.tgz"
},
"authors": [
+9
View File
@@ -16,6 +16,7 @@ usage() {
clean() {
rm -rf dist node_modules bower_components
rm -rf ../guac/dist ../guac/node_modules
}
dependencies() {
@@ -46,6 +47,13 @@ install_enterprise_patch() {
done
}
install_guac() {
GUAC_DIR="../guac"
npm i --prefix $GUAC_DIR
npm run build --prefix $GUAC_DIR
}
install_patch() {
npm install
@@ -78,6 +86,7 @@ install_patch() {
mv ./main.js dist/main.js
fi
install_guac
}
#-------------------------------------------------------------------------------
+14 -1
View File
@@ -159,4 +159,17 @@ progress{
background-color: $busy;
}
}
}
}
.guacamole-display {
cursor: none;
width: 100%;
height: fit-content;
display: flex;
align-items: center;
place-content: center;
background-color: #282828;
& > div {
z-index: 1;
}
}
+24 -2
View File
@@ -136,14 +136,24 @@ button.search-dropdown {
border-style: none !important;
}
button.remote_vm {
button.remote-vm {
margin: 0 5px;
font-size: 1.2em;
font-size: 1.4em;
&.spice i {
-webkit-text-stroke: 0.7px;
}
&.ssh {
border: .15em solid $anchor-color;
padding: .16em .2em;
font-size: .8em;
&:hover {
border-color: $anchor-color-hover;
}
}
i {
color: $anchor-color;
}
@@ -159,3 +169,15 @@ button.remote_vm {
outline: 0;
}
}
a.rdp,
a.save-rdp {
display: flex !important;
align-items: baseline;
&:hover {
background-color: rgba($color: $anchor-color-hover, $alpha: .6);
color: #ffffff;
font-weight: 600;
}
}
+13 -1
View File
@@ -251,9 +251,11 @@ if $conf[:webauthn_avail]
end
#start VNC proxy
$vnc = OpenNebulaVNC.new($conf, logger)
#init Guacamole server
$guac = OpenNebulaGuac.new($conf, logger)
configure do
set :run, false
set :vnc, $vnc
@@ -1035,6 +1037,16 @@ post '/vm/:id/startvnc' do
@SunstoneServer.startvnc(vm_id, $vnc)
end
##############################################################################
# Start Guacamole Session for a target VM
##############################################################################
post '/vm/:id/guac/:type' do
vm_id = params[:id]
type_connection = params[:type]
@SunstoneServer.startguac(vm_id, type_connection, $guac)
end
##############################################################################
# Perform an action on a Resource
##############################################################################
+1
View File
@@ -41,6 +41,7 @@
'threshold_min' : '<%= $conf[:threshold_min] %>',
'threshold_low' : '<%= $conf[:threshold_low] %>',
'threshold_high' : '<%= $conf[:threshold_high] %>',
'guac_port' : '<%= $conf[:guac_port] %>',
},
'system_config' : {
'paginate': '<%= $conf[:paginate] %>',