1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-02-28 17:57:22 +03:00

F #5055: Refactor Remote Consoles (#1132)

This commit is contained in:
Frederick Borges 2021-04-22 17:32:40 +02:00 committed by GitHub
parent e61277c20e
commit 381e86fcdc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 1508 additions and 1909 deletions

View File

@ -2520,6 +2520,7 @@ SUNSTONE_MODELS_FILES="src/sunstone/models/OpenNebulaJSON.rb \
src/sunstone/models/SunstoneServer.rb \
src/sunstone/models/SunstoneViews.rb \
src/sunstone/models/sunstone_vm_helper.rb \
src/sunstone/models/sunstone_remotes.rb \
src/sunstone/models/sunstone_vnc.rb \
src/sunstone/models/sunstone_guac.rb \
src/sunstone/models/sunstone_vmrc.rb \
@ -2554,6 +2555,7 @@ SUNSTONE_VIEWS_FILES="src/sunstone/views/index.erb \
src/sunstone/views/vnc.erb \
src/sunstone/views/vmrc.erb \
src/sunstone/views/spice.erb \
src/sunstone/views/guac.erb \
src/sunstone/views/_login_standard.erb \
src/sunstone/views/_login_x509.erb"
@ -2567,6 +2569,8 @@ 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 \
src/sunstone/public/dist/console/spice.js.map \
src/sunstone/public/dist/console/guacamole.js \
src/sunstone/public/dist/console/guacamole.js.map \
src/sunstone/public/dist/console/vmrc.js \
src/sunstone/public/dist/console/vmrc.js.map"
@ -2579,7 +2583,9 @@ SUNSTONE_ROUTES_FILES="src/sunstone/routes/oneflow.rb \
SUNSTONE_PUBLIC_CSS_FILES="src/sunstone/public/css/app.min.css \
src/sunstone/public/css/opensans/opensans.woff \
src/sunstone/public/css/vmrc-custom.css \
src/sunstone/public/css/novnc-custom.css \
src/sunstone/public/css/guac-custom.css \
src/sunstone/public/css/spice-custom.css"
SUNSTONE_PUBLIC_FONT_AWSOME="src/sunstone/public/bower_components/fontawesome/web-fonts-with-css/webfonts/fa-brands-400.eot \

View File

@ -43,10 +43,12 @@ const vmrcProxy = createProxyMiddleware(endpointVmrc, {
// eslint-disable-next-line consistent-return
router: req => {
if (req && req.url) {
const ticket = req.url.split('/')[3] || ''
// Needs to be reviewed require('path')
const ticket = req.url.split('/')[3]
const filterTicket = ticket.split('?')[0]
try {
const esxi = readFileSync(
`${global.VMRC_TOKENS || ''}/${ticket}`
`${global.VMRC_TOKENS || ''}/${filterTicket}`
).toString()
return esxi
} catch (error) {

View File

@ -76,9 +76,9 @@ if ARGV[0]
when :start
vnc.start
when :stop
vnc.stop(true)
vnc.stop
when :restart
vnc.stop(true)
vnc.stop
sleep 1
vnc.start
when :status
@ -92,4 +92,3 @@ if ARGV[0]
else
exit(-1)
end

View File

@ -23,6 +23,7 @@ require 'json'
require 'opennebula'
require 'base64'
require 'openssl'
require 'sunstone_remotes'
if !ONE_LOCATION
VAR_LOCATION = '/var/lib/one/'
@ -32,93 +33,18 @@ end
FIREEDGE_KEY = VAR_LOCATION + '/.one/fireedge_key'
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
# 66, # HOTPLUG_RESIZE
# 67, # HOTPLUG_SAVEAS_UNDEPLOYED
# 68, # HOTPLUG_SAVEAS_STOPPED
]
# Class for Guacamole connection configuration
class SunstoneGuac
class SunstoneGuac < SunstoneRemoteConnections
attr_reader :proxy_port
def initialize(logger, options = {})
opts={ :json_errors => true }.merge(options)
@options = opts
@logger = logger
super
end
def proxy(vm_resource, type_connection = 'vnc')
# Check configurations and VM attributes
if !GUAC_STATES.include?(vm_resource['LCM_STATE'])
if !allowed_console_states.include?(vm_resource['LCM_STATE'])
error_message = "Wrong state (#{vm_resource['LCM_STATE']})
to open a Guacamole session"
return error(400, error_message)
@ -175,13 +101,6 @@ class SunstoneGuac
private
def error(code, msg)
@logger.error(msg)
return [code, msg] unless @options[:json_error]
[code, OpenNebula::Error.new(msg).to_json]
end
def get_config_vnc(vm_resource)
# If it is a vCenter VM
if vm_resource['USER_TEMPLATE/HYPERVISOR'] == 'vcenter'
@ -194,7 +113,8 @@ class SunstoneGuac
return error(400, error_message)
end
else
hostname = vm_resource['/VM/HISTORY_RECORDS/HISTORY[last()]/HOSTNAME']
hostname =
vm_resource['/VM/HISTORY_RECORDS/HISTORY[last()]/HOSTNAME']
end
{
@ -211,10 +131,11 @@ class SunstoneGuac
end
def get_config_rdp(vm_resource)
hostname = vm_resource["TEMPLATE/NIC[RDP='YES'][1]/EXTERNAL_IP"] ||
vm_resource["TEMPLATE/NIC[RDP='YES'][1]/IP"] ||
vm_resource["TEMPLATE/NIC_ALIAS[RDP='YES'][1]/EXTERNAL_IP"] ||
vm_resource["TEMPLATE/NIC_ALIAS[RDP='YES'][1]/IP"]
hostname =
vm_resource["TEMPLATE/NIC[RDP='YES'][1]/EXTERNAL_IP"] ||
vm_resource["TEMPLATE/NIC[RDP='YES'][1]/IP"] ||
vm_resource["TEMPLATE/NIC_ALIAS[RDP='YES'][1]/EXTERNAL_IP"] ||
vm_resource["TEMPLATE/NIC_ALIAS[RDP='YES'][1]/IP"]
if hostname.nil?
error_message = 'Wrong configuration. Cannot find a NIC with RDP'
@ -237,10 +158,11 @@ class SunstoneGuac
end
def get_config_ssh(vm_resource)
hostname = vm_resource["TEMPLATE/NIC[SSH='YES'][1]/EXTERNAL_IP"] ||
vm_resource["TEMPLATE/NIC[SSH='YES'][1]/IP"] ||
vm_resource["TEMPLATE/NIC_ALIAS[SSH='YES'][1]/EXTERNAL_IP"] ||
vm_resource["TEMPLATE/NIC_ALIAS[SSH='YES'][1]/IP"]
hostname =
vm_resource["TEMPLATE/NIC[SSH='YES'][1]/EXTERNAL_IP"] ||
vm_resource["TEMPLATE/NIC[SSH='YES'][1]/IP"] ||
vm_resource["TEMPLATE/NIC_ALIAS[SSH='YES'][1]/EXTERNAL_IP"] ||
vm_resource["TEMPLATE/NIC_ALIAS[SSH='YES'][1]/IP"]
if hostname.nil?
error_message = 'Wrong configuration. Cannot find a NIC with SSH'

View File

@ -0,0 +1,123 @@
# -------------------------------------------------------------------------- #
# Copyright 2002-2021, 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. #
#--------------------------------------------------------------------------- #
require 'rubygems'
require 'json'
require 'opennebula'
# The following states are the ones where
# the remote connections are allowed.
ALLOWED_CONSOLE_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
# '66', # HOTPLUG_RESIZE
# '67', # HOTPLUG_SAVEAS_UNDEPLOYED
# '68', # HOTPLUG_SAVEAS_STOPPED
]
# This class provides an abstracion with the common code
# inside the remote connections classes.
class SunstoneRemoteConnections
attr_accessor :options, :logger
def initialize(logger, options = {})
opts = {
:json_errors => true
}.merge(options)
@options = opts
@logger = logger
end
def allowed_console_states
ALLOWED_CONSOLE_STATES
end
protected
def error(code, msg)
@logger.error(msg)
return [code, msg] unless @options[:json_error]
[code, OpenNebula::Error.new(msg).to_json]
end
end

View File

@ -25,6 +25,7 @@ require 'base64'
require 'openssl'
require 'vcenter_driver'
require 'fileutils'
require 'sunstone_remotes'
if !ONE_LOCATION
VMRC_TICKETS = '/var/lib/one/sunstone_vmrc_tokens/'
@ -34,93 +35,18 @@ end
FileUtils.mkdir_p VMRC_TICKETS
VMRC_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
# 66, # HOTPLUG_RESIZE
# 67, # HOTPLUG_SAVEAS_UNDEPLOYED
# 68, # HOTPLUG_SAVEAS_STOPPED
]
# Class for necessary VMRC ticket creation
class SunstoneVMRC
class SunstoneVMRC < SunstoneRemoteConnections
attr_reader :proxy_port
def initialize(logger, options = {})
opts={ :json_errors => true }.merge(options)
@options = opts
@logger = logger
super
end
def proxy(vm_resource, client = nil)
# Check configurations and VM attributes
unless VMRC_STATES.include?(vm_resource['LCM_STATE'])
unless allowed_console_states.include?(vm_resource['LCM_STATE'])
error_message = "Wrong state (#{vm_resource['LCM_STATE']}) to
open a VMRC session"
return error(400, error_message)
@ -158,7 +84,11 @@ class SunstoneVMRC
:ticket => parameters[:ticket]
}
file = File.open(VMRC_TICKETS + VCenterDriver::FileHelper.sanitize(data[:ticket]), 'w')
file = File.open(
VMRC_TICKETS +
VCenterDriver::FileHelper.sanitize(data[:ticket]),
'w'
)
file.write('https://' + data[:host] + ':' + data[:port].to_s)
file.close
@ -168,14 +98,4 @@ class SunstoneVMRC
[200, { :data => data, :info => encode_info }.to_json]
end
private
def error(code, msg)
if @options[:json_errors]
[code, OpenNebula::Error.new(msg).to_json]
else
[code, msg]
end
end
end

View File

@ -14,106 +14,40 @@
# limitations under the License. #
#--------------------------------------------------------------------------- #
#
# This class provides support for launching and stopping a websockify proxy
#
require 'rubygems'
require 'json'
require 'opennebula'
require 'sunstone_remotes'
if !ONE_LOCATION
NOVNC_LOCK_FILE = "/var/lock/one/.novnc.lock"
NOVNC_LOCK_FILE = '/var/lock/one/.novnc.lock'
else
NOVNC_LOCK_FILE= ONE_LOCATION + "/var/.novnc.lock"
NOVNC_LOCK_FILE= ONE_LOCATION + '/var/.novnc.lock'
end
TOKEN_EXPIRE_SECONDS = 4
VNC_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
#66 #HOTPLUG_RESIZE
#67, #HOTPLUG_SAVEAS_UNDEPLOYED
#68, #HOTPLUG_SAVEAS_STOPPED
]
class SunstoneVNC
#
# This class provides support for launching and stopping a websockify proxy
#
class SunstoneVNC < SunstoneRemoteConnections
attr_reader :proxy_port
def initialize(config, logger, options = {})
opts={ :json_errors => true,
:token_folder_name => 'sunstone_vnc_tokens'}.merge(options)
super(logger, options)
# Add token folder to options
opts = {
:token_folder_name => 'sunstone_vnc_tokens'
}.merge(@options)
@options = opts
# Create configuration variables
@pipe = nil
@token_folder = File.join(VAR_LOCATION, opts[:token_folder_name])
@proxy_path = File.join(SHARE_LOCATION, "websockify/run")
@proxy_path = File.join(SHARE_LOCATION, 'websockify/run')
@proxy_port = config[:vnc_proxy_port]
@proxy_ipv6 = config[:vnc_proxy_ipv6]
@ -122,20 +56,18 @@ class SunstoneVNC
@lock_file = NOVNC_LOCK_FILE
if (@wss == "yes") || (@wss == "only") || (@wss == true)
if (@wss == 'yes') || (@wss == 'only') || (@wss == true)
@enable_wss = true
@cert = config[:vnc_proxy_cert]
@key = config[:vnc_proxy_key]
else
@enable_wss = false
end
@options = opts
@logger = logger
end
def start
if is_running?
message="VNC server already running"
if running?
message='VNC server already running'
STDERR.puts message
@logger.info message
return false
@ -147,32 +79,32 @@ class SunstoneVNC
if @enable_wss
proxy_options << " --cert #{@cert}"
proxy_options << " --key #{@key}" if @key && @key.size > 0
proxy_options << " --ssl-only" if @wss == "only"
proxy_options << " --key #{@key}" if @key && !@key.empty?
proxy_options << ' --ssl-only' if @wss == 'only'
end
if @proxy_ipv6
proxy_options << " -6"
proxy_options << ' -6'
end
python = 'python3' if system("python3 -c True")
python = 'python' if system("python -c True")
python = 'python3' if system('python3 -c True')
python = 'python' if system('python -c True')
cmd ="#{python} #{@proxy_path} #{proxy_options} #{@proxy_port}"
begin
@logger.info { "Starting VNC proxy: #{cmd}" }
pid=start_daemon(cmd, VNC_LOG)
rescue Exception => e
rescue StandardError => e
@logger.error e.message
return false
end
begin
File.open(@lock_file, "w") do |f|
File.open(@lock_file, 'w') do |f|
f.write(pid.to_s)
end
rescue Exception => e
rescue StandardError => e
@logger.error e.message
Process.kill('-KILL', pid)
@ -181,8 +113,8 @@ class SunstoneVNC
sleep 1
if !is_running?
message="Error starting VNC proxy"
if !running?
message='Error starting VNC proxy'
STDERR.puts message
@logger.error message
File.delete(@lock_file) if File.exist?(@lock_file)
@ -190,24 +122,35 @@ class SunstoneVNC
return false
end
STDOUT.puts "VNC proxy started"
STDOUT.puts 'VNC proxy started'
true
end
def proxy(vm_resource)
# Check configurations and VM attributes
unless is_lockfile?
return error(400, "VNC Proxy is not running")
unless lockfile?
return error(400, 'VNC Proxy is not running')
end
if !VNC_STATES.include?(vm_resource['LCM_STATE'])
return error(400,"Wrong state (#{vm_resource['LCM_STATE']}) to open a VNC session")
if !allowed_console_states.include?(vm_resource['LCM_STATE'])
return error(
400,
"Wrong state (#{vm_resource['LCM_STATE']}) " \
'to open a VNC session'
)
end
if vm_resource['TEMPLATE/GRAPHICS/TYPE'].nil? ||
!(["vnc", "spice"].include?(vm_resource['TEMPLATE/GRAPHICS/TYPE'].downcase))
return error(400,"VM has no VNC configured")
if vm_resource['TEMPLATE/GRAPHICS/TYPE'].nil?
return error(400, 'VM has no VNC configured')
end
include_vnc_or_spice = %w[vnc spice].include?(
vm_resource['TEMPLATE/GRAPHICS/TYPE'].downcase
)
if !include_vnc_or_spice
return error(400, 'VM has no VNC configured')
end
# Proxy data
@ -216,16 +159,21 @@ class SunstoneVNC
vnc_pw = vm_resource['TEMPLATE/GRAPHICS/PASSWD']
# If it is a vCenter VM
if vm_resource['USER_TEMPLATE/HYPERVISOR'] == "vcenter"
if vm_resource['MONITORING/VCENTER_ESX_HOST']
host = 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")
if vm_resource['USER_TEMPLATE/HYPERVISOR'] == 'vcenter'
unless vm_resource['MONITORING/VCENTER_ESX_HOST']
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
host = vm_resource['MONITORING/VCENTER_ESX_HOST']
end
# Generate token random_str: host:port
random_str = rand(36**20).to_s(36) #random string a-z0-9 length 20
random_str = rand(36**20).to_s(36) # Random string a-z0-9 length 20
token = "#{random_str}: #{host}:#{vnc_port}"
token_file = 'one-'+vm_resource['ID']
@ -234,53 +182,45 @@ class SunstoneVNC
f = File.open(File.join(@token_folder, token_file), 'w')
f.write(token)
f.close
rescue Exception => e
rescue StandardError => e
@logger.error e.message
return error(500, "Cannot create VNC proxy token")
return error(500, 'Cannot create VNC proxy token')
end
info = SunstoneVMHelper.get_remote_info(vm_resource)
encode_info = Base64.encode64(info.to_json)
info = {
info = {
:password => vnc_pw,
:token => random_str,
:info => encode_info
}
return [200, info.to_json]
[200, info.to_json]
end
# Delete proxy token file
def delete_token(filename)
begin
File.delete(File.join(@token_folder, filename))
rescue => e
rescue StandardError => e
@logger.error "Error deleting token file for VM #{vm_id}"
@logger.error e.message
end
end
def stop(force=false)
def stop
pid=get_pid
if pid
@logger.info "Killing VNC proxy"
@logger.info 'Killing VNC proxy'
signal=(force ? '-KILL' : '-TERM')
Process.kill(signal ,pid)
Process.kill('-KILL', pid)
sleep 1
begin
Process.getpgid(pid)
Process.kill('-KILL', pid)
rescue
end
if is_running?
message="VNC server is still running"
if running?
message = 'VNC server is still running'
STDERR.puts message
@logger.error message
return false
@ -288,9 +228,9 @@ class SunstoneVNC
delete_token_dir
STDOUT.puts "VNC proxy stopped"
STDOUT.puts 'VNC proxy stopped'
else
message="VNC server is not running"
message = 'VNC server is not running'
@logger.info message
STDERR.puts message
end
@ -298,49 +238,41 @@ class SunstoneVNC
end
def status
if is_running?
STDOUT.puts "VNC is running"
if running?
STDOUT.puts 'VNC is running'
true
else
STDOUT.puts "VNC is NOT running"
STDOUT.puts 'VNC 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 create_token_dir
delete_token_dir
begin
Dir.mkdir(@token_folder) if !File.exist?(@token_folder)
rescue Exception => e
@logger.error "Cannot create token folder"
Dir.mkdir(@token_folder) unless File.exist?(@token_folder)
rescue StandardError => e
@logger.error 'Cannot create token folder'
@logger.error e.message
end
end
def delete_token_dir
if File.exist?(@token_folder)
begin
Dir.glob("#{@token_folder}/*").each do |file|
File.delete(file)
end
rescue => e
@logger.error "Error deleting token folder"
@logger.error e.message
return unless File.exist?(@token_folder)
begin
Dir.glob("#{@token_folder}/*").each do |file|
File.delete(file)
end
rescue StandardError => e
@logger.error 'Error deleting token folder'
@logger.error e.message
end
end
def is_running?
def running?
if File.exist?(@lock_file)
pid=File.read(@lock_file).strip
@ -348,15 +280,15 @@ class SunstoneVNC
return pid.to_i
end
@logger.info "Deleting stale lock file"
@logger.info 'Deleting stale lock file'
File.delete(@lock_file)
end
false
end
alias_method :get_pid, :is_running?
alias get_pid running?
def is_lockfile?
def lockfile?
dn = File.dirname(@lock_file)
bn = File.basename(@lock_file)
@ -370,37 +302,20 @@ class SunstoneVNC
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(VNC_LOG, "a")
$stderr.reopen(VNC_LOG, "a")
$stdin.close
# Detach process from the parent
Process.setsid
exec(*command)
}
end
end
def start_daemon(cmd, log)
options={
:pgroup => true,
:in => :close,
[:out, :err] => [log, "a"],
:close_others => true }
[:out, :err] => [log, 'a'],
:close_others => true
}
params=cmd.split(" ")+[options]
pid=spawn( *params )
params=cmd.split(' ')+[options]
pid=spawn(*params)
Process.detach(pid)
pid
end
end
end

View File

@ -93,6 +93,11 @@ module.exports = function(grunt) {
name: "console/spice",
include: ["almond"],
insertRequire: ["console/spice"]
},
{
name: "console/guacamole",
include: ["almond"],
insertRequire: ["console/guacamole"]
}
]
}

View File

@ -15,5 +15,24 @@
/* -------------------------------------------------------------------------- */
define(function(require) {
return 'vmrcVMDialog';
var GuacController = require('utils/guacamole/controller');
try {
var endpoint = new URL(window.location.href);
var encoded_socket = endpoint.searchParams.get("socket");
var socket_string = atob(encoded_socket);
var url = new URL(socket_string);
var params = url.searchParams;
var token = params.get("token");
var info = params.get("info");
var controller = new GuacController();
controller.setInformation(info);
controller.setConnection(token);
} catch (error) {
console.log(error);
$('#guacamole-state').empty().text('Failed');
}
});

View File

@ -29,70 +29,43 @@ define(function(require) {
document.cookie = name + "=" + value + expires + "; path=/";
};
function spice_query_var(name, defvalue) {
var match = RegExp('[?&]' + name + '=([^&]*)')
.exec(window.location.search);
return match ?
decodeURIComponent(match[1].replace(/\+/g, ' '))
: defvalue;
}
function spice_error(e) {
disconnect();
}
function connect() {
var info = spice_query_var('info', undefined);
var endpoint = new URL(window.location.href);
var encoded_socket = endpoint.searchParams.get("socket");
var socket_string = atob(encoded_socket);
var socket_endpoint = new URL(socket_string);
var password = socket_endpoint.searchParams.get("password");
var token = socket_endpoint.searchParams.get("token");
var info = socket_endpoint.searchParams.get("info");
var info_decode = UtilsConnection.decodeInfoConnection(info);
UtilsConnection.printInfoConnection($('.SPICE_info'), info_decode)
if (info_decode && info_decode.name) {
document.title = info_decode.name
}
// By default, use the host and port of server that served this file
var host = spice_query_var('host', window.location.hostname);
// Note that using the web server port only makes sense
// if your web server has a reverse proxy to relay the WebSocket
// traffic to the correct destination port.
var default_port = window.location.port;
if (!default_port) {
if (window.location.protocol == 'http:') {
default_port = 80;
} else if (window.location.protocol == 'https:') {
default_port = 443;
}
}
var scheme = "ws://"
if (window.location.protocol == 'https:') {
scheme = "wss://";
}
// If a token variable is passed in, set the parameter in a cookie.
// This is used by nova-spiceproxy.
var token = spice_query_var('token', null);
if (token) {
spice_set_cookie('token', token, 1)
}
var password = spice_query_var('password', '');
var port = spice_query_var('port', default_port);
if ((!host) || (!port)) {
return;
}
if (sc) {
sc.stop();
}
var uri = scheme + host + ":" + port + "?token=" + token;
try {
sc = new SpiceMainConn({uri: uri, screen_id: "spice-screen", dump_id: "debug-div",
message_id: "message-div", password: password, onerror: spice_error, onagent: agent_connected});
sc = new SpiceMainConn({
uri: socket_string,
screen_id: "spice-screen",
dump_id: "debug-div",
message_id: "message-div",
password: password,
onerror: spice_error,
onagent: agent_connected
});
}
catch (e) {
alert(e.toString());
@ -101,7 +74,6 @@ define(function(require) {
}
function disconnect() {
console.log(">> disconnect");
if (sc) {
sc.stop();
}
@ -111,7 +83,6 @@ define(function(require) {
document.getElementById('spice-area').removeEventListener('dragover', handle_file_dragover, false);
document.getElementById('spice-area').removeEventListener('drop', handle_file_drop, false);
}
console.log("<< disconnect");
}
function agent_connected(sc) {

View File

@ -26,16 +26,16 @@ define(function(require) {
function setStatus(message="", status=""){
$(".VMRC_message").text(message);
$("#VMRC_status").text(status);
$("#VMRC_status_msg").text(status);
}
function connected(){
setStatus(null, "VMRC " + _wmks.connectionState + " (" + _is_encrypted + ") to: " + _wmks.vm_name);
setStatus(null, "VMRC " + _wmks.connectionState);
}
function disconnectedFromServer(e){
if (e.detail.clean) {
setStatus(null, "VMRC " + _wmks.connectionState + " (" + _is_encrypted + ") to: " + _wmks.vm_name);
setStatus(null, "VMRC " + _wmks.connectionState);
} else {
setStatus("Something went wrong, connection is closed", "Failed");
}
@ -45,76 +45,44 @@ define(function(require) {
if (_wmks) { _wmks.sendCAD(); }
}
function updateState(state, msg) {
var s, sb, cad, level;
s = document.querySelector("#VMRC_status");
sb = document.querySelector("#VMRC_status_bar");
cad = document.querySelector("#sendCtrlAltDelButton");
switch (state) {
case "failed": level = "error"; break;
case "fatal": level = "error"; break;
case "normal": level = "normal"; break;
case "disconnected": level = "normal"; break;
case "loaded": level = "normal"; break;
default: level = "warn"; break;
}
function enterFullScreen() {
if (_wmks) { _wmks.enterFullScreen(); }
}
function selectLanguage() {
if(!_wmks) return;
var keyboardLayoutId = $('#selectLanguage').find(":selected").val();
_wmks.setOption('keyboardLayoutId',keyboardLayoutId);
}
if (state === "normal") {
cad.disabled = false;
} else {
cad.disabled = true;
xvpInit(0);
}
if (typeof(msg) !== "undefined") {
sb.setAttribute("class", "VMRC_status_" + level);
s.innerHTML = msg;
}
function keyboardSelector() {
$('#selectLanguage').toggle();
}
function getQueryVariable(variable)
{
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if(pair[0] == variable){return pair[1];}
}
return(false);
function updateScreen() {
_wmks.updateScreen();
}
var URL = "";
var host = getQueryVariable("host");
var port = getQueryVariable("port");
var ticket = getQueryVariable("ticket");
var info = spice_query_var('info', undefined);
var info_decode = UtilsConnection.decodeInfoConnection(info);
UtilsConnection.printInfoConnection($('.VMRC_info'), info_decode)
if (info_decode && info_decode.name) {
document.title = info_decode.name
}
if (window.location.protocol === "https:") {
URL = "wss";
_is_encrypted = "encrypted";
} else {
URL = "ws";
_is_encrypted = "unencrypted";
}
URL += "://" + host;
URL += ":" + port;
URL += "/vmrc/" + ticket;
document.querySelector("#sendCtrlAltDelButton").style.display = "inline";
document.querySelector("#sendCtrlAltDelButton").onclick = sendCtrlAltDel;
document.querySelector("#fullScreenButton").onclick = enterFullScreen;
document.querySelector("#keyboardSelector").onclick = keyboardSelector;
document.querySelector("#selectLanguage").onchange = selectLanguage;
if ((!host) || (!port)) {
updateState("failed",
"Must specify host and port in URL");
return;
}
var endpoint = new URL(window.location.href);
var encoded_socket = endpoint.searchParams.get("socket");
var socket_string = atob(encoded_socket);
var socket_endpoint = new URL(socket_string);
var host = socket_endpoint.searchParams.get("host");
var port = socket_endpoint.searchParams.get("port");
var info = socket_endpoint.searchParams.get("info");
var ticket = socket_endpoint.searchParams.get("ticket");
var info_decode = UtilsConnection.decodeInfoConnection(info);
UtilsConnection.printInfoConnection($('.VMRC_info'), info_decode)
try{
_wmks = WMKS.createWMKS("wmksContainer", {})
@ -129,9 +97,11 @@ define(function(require) {
_wmks.eventHandlers["connectionstatechange"].push(connected);
_wmks.eventHandlers["disconnect"] = disconnectedFromServer;
_wmks.vm_name = info_decode && info_decode.name;
_wmks.connect(URL);
_wmks.connect(socket_string);
}catch(err){
setStatus("Something went wrong, connection is closed", "Failed");
}
$(window).resize(updateScreen);
});

View File

@ -24,16 +24,16 @@ define(function(require) {
function setStatus(message="", status=""){
$(".NOVNC_message").text(message);
$("#noVNC_status").text(status);
$("#noVNC_status_msg").text(status);
}
function connected(){
setStatus(null, "VNC " + _rfb._rfb_connection_state + " (" + _is_encrypted + ") to: " + _rfb._fb_name);
setStatus(null, "VNC " + _rfb._rfb_connection_state);
}
function disconnectedFromServer(e){
if (e.detail.clean) {
setStatus(null, "VNC " + _rfb._rfb_connection_state + " (" + _is_encrypted + ") to: " + _rfb._fb_name);
setStatus(null, "VNC " + _rfb._rfb_connection_state);
} else {
setStatus("Something went wrong, connection is closed", "Failed");
}
@ -41,7 +41,7 @@ define(function(require) {
function desktopNameChange(e) {
if (e.detail.name) {
setStatus(null, "VNC " + _rfb._rfb_connection_state + " (" + _is_encrypted + ") to: " + e.detail.name);
setStatus(null, "VNC " + _rfb._rfb_connection_state);
}
}
@ -106,69 +106,27 @@ define(function(require) {
}
}
function getQueryVariable(variable) {
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if (pair[0] == variable) {
return pair[1];
}
}
return false;
}
var URL = "";
var proxy_host = window.location.hostname;
var proxy_port = Config.vncProxyPort;
var token = getQueryVariable("token");
var password = getQueryVariable("password");
var endpoint = new URL(window.location.href);
var encoded_socket = endpoint.searchParams.get("socket");
var socket_string = atob(encoded_socket);
var socket_endpoint = new URL(socket_string);
var password = socket_endpoint.searchParams.get("password");
var info = socket_endpoint.searchParams.get("info");
var info = getQueryVariable('info') || undefined;
var info_decode = UtilsConnection.decodeInfoConnection(info);
UtilsConnection.printInfoConnection($('.NOVNC_info'), info_decode);
if (info_decode && info_decode.name) {
document.title = info_decode.name;
}
var rfbConfig = password? { "credentials": { "password": password } } : {};
if (window.location.protocol === "https:") {
URL = "wss";
_is_encrypted = "encrypted";
} else {
URL = "ws";
_is_encrypted = "unencrypted";
}
URL += "://" + window.location.hostname;
URL += ":" + proxy_port;
URL += "?host=" + proxy_host;
URL += "&port=" + proxy_port;
if(token){
URL += "&token=" + token;
}
URL += "&encrypt=" + Config.vncWSS;
document.querySelector("#sendCtrlAltDelButton").style.display = "inline";
document.querySelector("#sendCtrlAltDelButton").onclick = sendCtrlAltDel;
document.querySelector("#xvpShutdownButton").onclick = xvpShutdown;
document.querySelector("#xvpRebootButton").onclick = xvpReboot;
document.querySelector("#xvpResetButton").onclick = xvpReset;
if ((!proxy_host) || (!proxy_port)) {
updateState("failed",
"Must specify host and port in URL");
return;
}
try {
_rfb = new RFB(document.querySelector("#VNC_canvas"), URL, rfbConfig);
_rfb = new RFB(document.querySelector("#VNC_canvas"), socket_string, rfbConfig);
_rfb.addEventListener("connect", connected);
_rfb.addEventListener("disconnect", disconnectedFromServer);
_rfb.addEventListener("desktopname", desktopNameChange);

View File

@ -743,8 +743,6 @@ define(function(require) {
require("./vms-tab/dialogs/attach-nic"),
require("./vms-tab/dialogs/revert"),
require("./vms-tab/dialogs/snapshot"),
require("./vms-tab/dialogs/vnc"),
require("./vms-tab/dialogs/spice"),
require("./users-tab/dialogs/login-token")
];

View File

@ -38,9 +38,6 @@ define(function(require) {
var TAB_ID = require("../tabId");
var _accordionId = 0;
var VNC_DIALOG_ID = require("tabs/vms-tab/dialogs/vnc/dialogId");
var SPICE_DIALOG_ID = require("tabs/vms-tab/dialogs/spice/dialogId");
return {
"generate": generate_provision_vms_list,
"show": show_provision_vm_list,
@ -796,15 +793,15 @@ define(function(require) {
success: function(_, response){
if (OpenNebulaVM.isVNCSupported(vm_data)) {
var dialog = Sunstone.getDialog(VNC_DIALOG_ID);
dialog.setElement(response);
dialog.show();
var urlAndLink = Vnc.getURLAndLink(response);
// Open in a new tab the noVNC connection
window.open(urlAndLink.link);
button.removeAttr("disabled");
} else if (OpenNebulaVM.isSPICESupported(vm_data)) {
var dialog = Sunstone.getDialog(SPICE_DIALOG_ID);
dialog.setElement(response);
dialog.show();
var urlAndLink = Spice.getURLAndLink(response);
// Open in a new tab the noVNC connection
window.open(urlAndLink.link);
button.removeAttr("disabled");
} else {

View File

@ -19,7 +19,6 @@ define(function(require) {
var Buttons = require("./vms-tab/buttons");
var Actions = require("./vms-tab/actions");
var Table = require("./vms-tab/datatable");
require("utils/vnc");
var TAB_ID = require("./vms-tab/tabId");
var DATATABLE_ID = "dataTableVms";
@ -35,10 +34,6 @@ define(function(require) {
require("./vms-tab/dialogs/attach-nic"),
require("./vms-tab/dialogs/snapshot"),
require("./vms-tab/dialogs/revert"),
require("./vms-tab/dialogs/vnc"),
require("./vms-tab/dialogs/vmrc"),
require("./vms-tab/dialogs/spice"),
require("./vms-tab/dialogs/guac"),
require("./vms-tab/dialogs/saveas-template")
];

View File

@ -21,23 +21,16 @@ define(function(require) {
var Locale = require('utils/locale');
var OpenNebulaVM = require('opennebula/vm');
var CommonActions = require('utils/common-actions');
var Vnc = require('utils/vnc');
var Vmrc = require('utils/vmrc');
var Spice = require('utils/spice');
var Files = require('utils/files');
var CREATE_APP_DIALOG_ID = require('tabs/marketplaceapps-tab/form-panels/create/formPanelId');
var CREATE_DIALOG_ID = require('./form-panels/create/formPanelId');
var DEPLOY_DIALOG_ID = require('./dialogs/deploy/dialogId');
var GUAC_DIALOG_ID = require('./dialogs/guac/dialogId');
var MARKETPLACEAPPS_TAB_ID = require('tabs/marketplaceapps-tab/tabId');
var MIGRATE_DIALOG_ID = require('./dialogs/migrate/dialogId');
var SAVE_AS_TEMPLATE_DIALOG_ID = require('./dialogs/saveas-template/dialogId');
var SPICE_DIALOG_ID = require('./dialogs/spice/dialogId');
var TAB_ID = require('./tabId');
var UPDATECONF_FORM_ID = require('./form-panels/updateconf/formPanelId');
var VMRC_DIALOG_ID = require('./dialogs/vmrc/dialogId');
var VNC_DIALOG_ID = require('./dialogs/vnc/dialogId');
var XML_ROOT = "VM";
var RESOURCE = "VM";
@ -257,13 +250,7 @@ define(function(require) {
type: "custom",
call: function() {
$.each(Sunstone.getDataTable(TAB_ID).elements(), function(index, elem) {
if (!Vnc.lockStatus()) {
Vnc.lock();
Sunstone.runAction("VM.startvnc_action", elem);
} else {
Notifier.notifyError(Locale.tr("VNC Connection in progress"))
return false;
}
Sunstone.runAction("VM.startvnc_action", elem);
});
}
},
@ -271,13 +258,20 @@ define(function(require) {
type: "single",
call: OpenNebulaVM.vnc,
callback: function(request, response) {
var dialog = Sunstone.getDialog(VNC_DIALOG_ID);
dialog.setElement(response);
dialog.show();
var link = getLink(response,{
port: Config.vncProxyPort,
connnection_type: 'vnc',
extra_params: [
'port=' + Config.vncProxyPort,
'encrypt=' + Config.vncWSS,
!Config.requestVNCPassword && 'password=' + response.password
]
});
// Open in a new tab the noVNC connection
window.open(link);
},
error: function(req, resp) {
Notifier.onError(req, resp);
Vnc.unlock();
},
notify: true
},
@ -285,14 +279,8 @@ define(function(require) {
type: "custom",
call: function() {
$.each(Sunstone.getDataTable(TAB_ID).elements(), function(index, elem) {
if (!Vmrc.lockStatus()) {
Vmrc.lock();
var vm_name = OpenNebulaVM.getName(elem);
Sunstone.runAction("VM.startvmrc_action", elem, vm_name);
} else {
Notifier.notifyError(Locale.tr("VMRC Connection in progress"))
return false;
}
});
}
},
@ -300,14 +288,19 @@ define(function(require) {
type: "single",
call: OpenNebulaVM.vmrc,
callback: function(request, response) {
var dialog = Sunstone.getDialog(VMRC_DIALOG_ID);
response["vm_name"] = request.request.data[0].extra_param;
dialog.setElement(response);
dialog.show();
response["vm_name"] = request.request.data[0].extra_param;
var fireedge_endpoint = new URL(Config.publicFireedgeEndpoint);
var link = getLink(response,{
host: fireedge_endpoint.hostname,
port: fireedge_endpoint.port,
connnection_type: 'vmrc',
extra_path: '/fireedge/vmrc/' + response.data.ticket,
});
// Open in a new tab the noVNC connection
window.open(link);
},
error: function(req, resp) {
Notifier.onError(req, resp);
Vmrc.unlock();
},
notify: true
},
@ -315,13 +308,7 @@ define(function(require) {
type: "custom",
call: function() {
$.each(Sunstone.getDataTable(TAB_ID).elements(), function(index, elem) {
if (!Spice.lockStatus()) {
Spice.lock();
Sunstone.runAction("VM.startspice_action", elem);
} else {
Notifier.notifyError(Locale.tr("SPICE Connection in progress"))
return false;
}
Sunstone.runAction("VM.startspice_action", elem);
});
}
},
@ -329,13 +316,19 @@ define(function(require) {
type: "single",
call: OpenNebulaVM.vnc,
callback: function(request, response) {
var dialog = Sunstone.getDialog(SPICE_DIALOG_ID);
dialog.setElement(response);
dialog.show();
var link = getLink(response, {
port: Config.vncProxyPort,
connnection_type: 'spice',
extra_params: [
'password=' + response.password,
'encrypt=' + config.user_config.vnc_wss,
]
});
// Open in a new tab the noVNC connection
window.open(link);
},
error: function(req, resp) {
Notifier.onError(req, resp);
Spice.unlock();
},
notify: true
},
@ -376,9 +369,12 @@ define(function(require) {
type: "single",
call: OpenNebulaVM.guac,
callback: function(_, response) {
var dialog = Sunstone.getDialog(GUAC_DIALOG_ID);
dialog.setElement(response);
dialog.show();
var link = getLink(response, {
connnection_type: 'guac',
extra_path: '/fireedge/guacamole'
});
// Open in a new tab the noVNC connection
window.open(link);
},
error: function(req, resp) {
Notifier.onError(req, resp);
@ -461,5 +457,46 @@ define(function(require) {
};
/**
*
* @param {Object} response Callback response with the token and info
* @param {Object} options
* @returns
*/
function getLink(response, options){
options = $.extend({
host: undefined,
port: undefined,
connnection_type: '',
extra_path: '',
extra_params: []
}, options);
var params = options.extra_params.concat([
response.token && 'token=' + response.token,
response.info && 'info=' + response.info
]).filter(Boolean);
var endpoint = new URL(window.location.href);
var websocketProtocol = endpoint.protocol === 'https:' ? 'wss:' : 'ws:';
var websocket = websocketProtocol + '//';
if (options.host && options.port)
websocket += options.host + ':' + options.port
else if (options.port)
websocket += endpoint.hostname + ':' + options.port
else
websocket += endpoint.host;
websocket += options.extra_path + '?' + params.join("&");
var encoded_socket = btoa(websocket);
var link = endpoint.origin + "/" + options.connnection_type + "?socket=" + encoded_socket;
return link;
}
return _actions;
});

View File

@ -201,6 +201,7 @@ define(function(require) {
},
"VM.startvnc" : {
type: "action",
id: "vm_vnc_action",
text: Locale.tr("VNC"),
layout: "vmsremote_buttons",
custom_classes: "only-sunstone-info vnc-sunstone-info vnc-button"

View File

@ -1,89 +0,0 @@
/* -------------------------------------------------------------------------- */
/* Copyright 2002-2021, 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) {
var BaseDialog = require('utils/dialogs/dialog');
var GuacController = require('utils/guacamole/controller');
var Locale = require("utils/locale");
var Notifier = require("utils/notifier");
var Sunstone = require('sunstone');
var TemplateHTML = require('hbs!./guac/html');
var DIALOG_ID = require('./guac/dialogId');
function Dialog() {
this.dialogId = DIALOG_ID;
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() {
var token = this.element.token;
var info = this.element.info;
if (!token) {
Notifier.notifyError(
Locale.tr("The OpenNebula service for remote console is not running, please contact your administrator.")
);
return null;
}
this.controller = new GuacController();
this.controller.setInformation(info);
this.controller.setConnection(token);
return false;
}
function _onClose() {
this.controller.disconnect();
return false;
}
function _setElement(element) {
this.element = element;
}
});

View File

@ -1,19 +0,0 @@
/* -------------------------------------------------------------------------- */
/* Copyright 2002-2021, 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';
});

View File

@ -1,63 +0,0 @@
{{! -------------------------------------------------------------------------- }}
{{! Copyright 2002-2021, 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">
<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 alert" id="sendCtrlAltDelButton_gclient">
{{tr "Send CtrlAltDel"}}
</button>
<button class="button primary" id="oskButton_gclient">
<i class="fas fa-keyboard fa-fw"></i>
</button>
<button class="button primary" id="mouseButton_gclient">
<i class="fas fa-mouse-pointer fa-fw"></i>
</button>
<button class="button primary" id="takeScreenshot_gclient">
<i class="fas fa-camera fa-fw" title="{{tr 'Take screenshot'}}"></i>
</button>
<button class="button secondary" data-close aria-label="{{tr "Close modal"}}" type="button" title="Close Guacamole">
<i class="fas fa-times-circle fa-fw"></i>
</button>
</span>
</h5>
</div>
<div class="large-12 columns">
<div class="guacamole_info"></div>
</div>
</div>
<div id="guacamole-main" class="guacamole-main">
<div id="guacamole-display" class="guacamole-display"></div>
</div>
<!-- On-screen keyboard -->
<div class="osk-container" id="osk-container">
<div class="osk-container-header" id="osk-container-header">
<div class="buttons">
<button class="close" id="osk-close">x</button>
</div>
<div class="layouts">
<select id="osk-qwerty"></select>
</div>
</div>
<div class="osk" id="osk"></div>
</div>
</div>

View File

@ -1,90 +0,0 @@
/* -------------------------------------------------------------------------- */
/* Copyright 2002-2021, 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!./spice/html');
var Sunstone = require('sunstone');
var Spice = require('utils/spice');
/*
CONSTANTS
*/
var DIALOG_ID = require('./spice/dialogId');
var TAB_ID = require('../tabId')
/*
CONSTRUCTOR
*/
function Dialog() {
this.dialogId = DIALOG_ID;
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) {
var that = this;
$("#open_in_a_new_window_spice", context).on("click", function() {
var dialog = Sunstone.getDialog(DIALOG_ID);
dialog.hide();
});
return false;
}
function _onShow(context) {
Spice.spiceCallback(this.element);
return false;
}
function _onClose(context) {
Spice.disconnect();
Spice.unlock();
return false;
}
function _setElement(element) {
this.element = element
}
});

View File

@ -1,19 +0,0 @@
/* -------------------------------------------------------------------------- */
/* Copyright 2002-2021, 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 'spiceVMDialog';
});

View File

@ -1,42 +0,0 @@
{{! -------------------------------------------------------------------------- }}
{{! Copyright 2002-2021, 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">
<h3 class="subheader" id="spice_dialog">
{{tr "SPICE"}}
<span id="vnc_buttons" class="right">
<a class="button" id="open_in_a_new_window_spice" href="#" target="_blank" title="{{tr "Open in a new window"}}">
<i class="fas fa-external-link-alt detach-spice-icon"/>
</a>
<button class="button secondary" data-close aria-label="{{tr "Close modal"}}" aria-label="{{tr "Close modal"}}">
<i class="fas fa-times-circle"></i>
</button>
</span>
</h3>
</div>
<div class="large-12 columns">
<div class="SPICE_info"></div>
</div>
</div>
<div class="reveal-body" style="width:100%; overflow-x:auto">
<div id="spice-area" style="text-align: center; background: rgb(40, 40, 40);">
<div id="spice-screen" class="spice-screen"></div>
</div>
</div>
<div id="message-div" class="spice-message" hidden></div>
</div>

View File

@ -1,94 +0,0 @@
/* -------------------------------------------------------------------------- */
/* Copyright 2002-2021, 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 Sunstone = require('sunstone');
var TemplateHTML = require('hbs!./vmrc/html');
var Vmrc = require('utils/vmrc');
/*
CONSTANTS
*/
var DIALOG_ID = require('./vmrc/dialogId');
/*
CONSTRUCTOR
*/
function Dialog() {
this.dialogId = DIALOG_ID;
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", context).on("click", function() {
var dialog = Sunstone.getDialog(DIALOG_ID);
dialog.hide();
});
$('#sendCtrlAltDelButton', context).click(function() {
Vmrc.sendCtrlAltDel();
return false;
});
$(window).resize(Vmrc.updateScreen);
return false;
}
function _onShow(context) {
Vmrc.vmrcCallback(this.element);
return false;
}
function _onClose(context) {
Vmrc.disconnect();
Vmrc.unlock();
return false;
}
function _setElement(element) {
this.element = element
}
});

View File

@ -1,42 +0,0 @@
{{! -------------------------------------------------------------------------- }}
{{! Copyright 2002-2021, 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="vmrc_dialog">
<span id="VMRC_status"></span>
<span id="VMRC_buttons" class="right">
<button class="button alert" id="sendCtrlAltDelButton">
{{tr "Send CtrlAltDel"}}
</button>
<a class="button" id="open_in_a_new_window" href="#" target="_blank" title="{{tr "Open in a new window"}}">
<i class="fas fa-external-link-alt detach-vmrc-icon" />
</a>
<button class="button secondary" data-close aria-label="{{tr "Close modal"}}" title="{{tr "Close VMRC"}}">
<i class="fas fa-times-circle"></i>
</button>
</span>
</h5>
</div>
<div class="large-12 columns">
<div class="VMRC_info"></div>
</div>
</div>
<div class="reveal-body text-center" style="width:100%; overflow-x:auto">
<div id="wmksContainer" style="position:absolute;width:100%;height:100%; left:0em"></div>
</div>
</div>

View File

@ -1,95 +0,0 @@
/* -------------------------------------------------------------------------- */
/* Copyright 2002-2021, 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!./vnc/html');
var Sunstone = require('sunstone');
var Vnc = require('utils/vnc');
/*
CONSTANTS
*/
var DIALOG_ID = require('./vnc/dialogId');
var TAB_ID = require('../tabId')
/*
CONSTRUCTOR
*/
function Dialog() {
this.dialogId = DIALOG_ID;
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) {
var that = this;
$("#open_in_a_new_window", context).on("click", function() {
var dialog = Sunstone.getDialog(DIALOG_ID);
dialog.hide();
});
$('#sendCtrlAltDelButton', context).click(function() {
Vnc.sendCtrlAltDel();
return false;
});
return false;
}
function _onShow(context) {
Vnc.vncCallback(this.element);
return false;
}
function _onClose(context) {
Vnc.disconnect();
Vnc.unlock();
return false;
}
function _setElement(element) {
this.element = element
}
});

View File

@ -1,19 +0,0 @@
/* -------------------------------------------------------------------------- */
/* Copyright 2002-2021, 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 'vncVMDialog';
});

View File

@ -1,45 +0,0 @@
{{! -------------------------------------------------------------------------- }}
{{! Copyright 2002-2021, 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="vnc_dialog">
<span id="VNC_status"></span>
<span id="VNC_buttons" class="right">
<button class="button alert" id="sendCtrlAltDelButton">
{{tr "Send CtrlAltDel"}}
</button>
<a class="button" id="open_in_a_new_window" href="#" target="_blank" title="{{tr "Open in a new window"}}">
<i class="fas fa-external-link-alt detach-vnc-icon"/>
</a>
<button class="button secondary" data-close aria-label="{{tr "Close modal"}}" title="{{tr "Close VNC"}}">
<i class="fas fa-times-circle"></i>
</button>
</span>
</h5>
</div>
<div class="large-12 columns">
<div class="NOVNC_info"></div>
</div>
</div>
<div class="reveal-body text-center" style="width:100%; overflow-x:auto">
<div id="VNC_canvas" width="640px">
<div class="NOVNC_message"></div>
</div>
<div id="VNC_status_bar" class="VNC_status_bar"></div>
</div>
</div>

View File

@ -38,10 +38,10 @@ define(function(require) {
closeOskButton: document.getElementById('osk-close'),
/* Buttons */
sendCtrlAltDelButton: document.getElementById('sendCtrlAltDelButton_gclient'),
mouseButton: document.getElementById('mouseButton_gclient'),
screenshotButton: document.getElementById('takeScreenshot_gclient'),
oskButton: document.getElementById('oskButton_gclient'),
sendCtrlAltDelButton: document.getElementById('sendCtrlAltDelButton'),
mouseButton: document.getElementById('mouseButton'),
screenshotButton: document.getElementById('takeScreenshot'),
oskButton: document.getElementById('oskButton'),
};
var throttleResizeFunction = Utils.throttle(containerResized, 250);

View File

@ -26,7 +26,7 @@ define(function(require) {
loadLayouts();
changeLayout(DEFAULT_LAYOUT);
$('#osk-container').draggable();
// $('#osk-container').draggable();
function loadLayouts() {
$('#osk-qwerty').empty();

View File

@ -27,6 +27,10 @@ define(function(require) {
context.empty()
info && context.append(TemplateInfo(info))
UtilsFoundation.update(context);
if (info && info.name) {
document.title = info.name;
}
}
function decodeInfoConnection(info_encode) {

View File

@ -19,27 +19,15 @@ define(function(require) {
Sunstone = require('sunstone'),
Config = require("sunstone-config"),
OpenNebulaVM = require("opennebula/vm"),
Vnc = require('utils/vnc'),
Spice = require('utils/spice'),
FireedgeValidator = require('utils/fireedge-validator'),
Notifier = require('utils/notifier');
function _callSpice(data) {
if (!Spice.lockStatus() && data.hasOwnProperty('id')) {
Spice.lock();
Sunstone.runAction('VM.startspice_action', String(data.id));
} else {
Notifier.notifyError(Locale.tr('SPICE Connection in progress'));
}
if (data.hasOwnProperty('id')) Sunstone.runAction('VM.startspice_action', String(data.id));
}
function _callVNC(data) {
if (!Vnc.lockStatus() && data.hasOwnProperty('id')) {
Vnc.lock();
Sunstone.runAction('VM.startvnc_action', String(data.id));
} else {
Notifier.notifyError(Locale.tr('VNC Connection in progress'));
}
if (data.hasOwnProperty('id')) Sunstone.runAction('VM.startvnc_action', String(data.id));
}
function _callSaveRDP(data) {

View File

@ -1,106 +0,0 @@
/* -------------------------------------------------------------------------- */
/* Copyright 2002-2021, 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) {
require('spice-main');
var Config = require('sunstone-config');
var UtilsConnection = require("utils/info-connection/utils");
var _lock = false;
var _sc;
return {
'lockStatus': lockStatus,
'lock': lock,
'unlock': unlock,
'spiceCallback': spiceCallback,
'disconnect': disconnect
}
function lockStatus() {
return _lock;
}
function lock() {
_lock = true;
}
function unlock() {
_lock = false;
}
function spice_error() {
disconnect();
}
function disconnect() {
try {
if (_sc) {
_sc.stop();
}
} catch (e) {}
}
function agent_connected(sc) {
window.addEventListener('resize', handle_resize);
window.spice_connection = this;
resize_helper(this);
}
function spiceCallback(response) {
var scheme = "ws://";
if (Config.vncWSS == "yes") {
scheme = "wss://";
}
var host = window.location.hostname;
var port = Config.vncProxyPort;
var password = response["password"];
var token = response["token"];
var info = response.info;
var info_decode = UtilsConnection.decodeInfoConnection(info);
UtilsConnection.printInfoConnection($('.SPICE_info'), info_decode)
if ((!host) || (!port)) {
return;
}
disconnect()
uri = scheme + host + ":" + port + "?token=" + token;
try {
_sc = new SpiceMainConn({uri: uri, screen_id: "spice-screen", dump_id: "debug-div",
message_id: "message-div", password: password, onerror: spice_error, onagent: agent_connected});
}
catch (e) {
disconnect()
}
var url = "spice?";
url += "host=" + host;
url += "&port=" + port;
url += "&token=" + token;
url += "&password=" + password;
url += "&encrypt=" + config['user_config']['vnc_wss'];
url += "&info=" + info;
$("#open_in_a_new_window_spice").attr('href', url);
}
});

View File

@ -1,153 +0,0 @@
/* -------------------------------------------------------------------------- */
/* Copyright 2002-2021, 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) {
var WMKS = require("wmks");
var Config = require("sunstone-config");
var UtilsConnection = require("utils/info-connection/utils");
var _lock = false;
var _wmks;
var _is_encrypted = "";
var vm_name = ""
return {
"lockStatus": lockStatus,
"lock": lock,
"unlock": unlock,
"vmrcCallback": vmrcCallback,
"disconnect": disconnect,
"sendCtrlAltDel": sendCtrlAltDel,
"updateScreen": updateScreen
};
function lockStatus() {
return _lock;
}
function lock() {
_lock = true;
}
function unlock() {
_lock = false;
}
function setStatus(message = "", status = "") {
$(".VMRC_message").text(message);
$("#VMRC_status").text(status);
}
function connected() {
setStatus(null, "VMRC " + _wmks.connectionState + " (" + _is_encrypted + ") to: " + vm_name);
}
function disconnectedFromServer(e) {
if (e.detail.clean) {
setStatus(null, "VMRC " + _wmks.connectionState + " (" + _is_encrypted + ") to: " + vm_name);
} else {
setStatus("Something went wrong, connection is closed", "Failed");
}
}
function render(ticket, host_vmrc, port_vmrc, response){
var hostname = window.location.hostname;
var port = window.location.port;
var protocol = window.location.protocol;
var fireedge_endpoint = Config.publicFireedgeEndpoint.split("//")[1];
var fireedge_host = fireedge_endpoint.split(":")[0];
var fireedge_port = fireedge_endpoint.split(":")[1];
var info = response.info;
var info_decode = UtilsConnection.decodeInfoConnection(info);
UtilsConnection.printInfoConnection($('.VMRC_info'), info_decode);
// set vm name on title
vm_name = (info_decode && info_decode.name) ? info_decode.name : "";
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
// Content of response.data
var ticket = ticket || urlParams.get('ticket');
var host_vmrc = host_vmrc || urlParams.get('host');
var port_vmrc = port_vmrc || urlParams.get('port');
var URL = "";
if (protocol === "https:") {
URL = "wss";
_is_encrypted ="encrypted";
} else {
URL = "ws";
_is_encrypted ="unencrypted";
}
URL += "://" + fireedge_endpoint + "/";
var regex = new RegExp("^(ws|wss):\\/\\/[\\w\\D]*?\\/", "gi");
var link = URL.replace(regex, protocol + "//" + hostname + ":" + port + "fireedge/vmrc?");
URL += "fireedge/vmrc/" + ticket;
link += "host=" + fireedge_host;
link += "&port=" + fireedge_port;
link += "&ticket=" + ticket;
link += "&info=" + info;
try {
_wmks = WMKS.createWMKS('wmksContainer', {})
.register(WMKS.CONST.Events.CONNECTION_STATE_CHANGE,
function (_, data) {
if (data.state === WMKS.CONST.ConnectionState.CONNECTED) {
console.log("connection state change: connected");
}
}
);
_wmks.eventHandlers["connectionstatechange"].push(connected);
_wmks.eventHandlers["disconnect"] = disconnectedFromServer;
_wmks.connect(URL);
$("#VMRC_buttons #open_in_a_new_window").attr("href",link);
} catch (err) {
setStatus("Something went wrong, connection is closed", "Failed");
}
}
function vmrcCallback(response) {
if (response.data) {
render(
response.data.ticket,
response.data.host,
response.data.port,
response
);
}
}
function disconnect() {
if (_wmks) { _wmks.disconnect(); }
}
function sendCtrlAltDel() {
if (_wmks) { _wmks.sendCAD(); }
}
function updateScreen() {
_wmks.updateScreen();
}
});

View File

@ -1,136 +0,0 @@
/* -------------------------------------------------------------------------- */
/* Copyright 2002-2021, 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) {
var Config = require("sunstone-config");
var UtilsConnection = require("utils/info-connection/utils");
var RFB = require("vnc-rfb").default;
var _lock = false;
var _rfb;
var _message = "";
var _status = "Loading";
var _is_encrypted = "";
return {
"lockStatus": lockStatus,
"lock": lock,
"unlock": unlock,
"vncCallback": vncCallback,
"disconnect": disconnect,
"sendCtrlAltDel": sendCtrlAltDel
};
function lockStatus() {
return _lock;
}
function lock() {
_lock = true;
}
function unlock() {
_lock = false;
}
function setStatus(message="", status=""){
_message = message;
_status = status;
$(".NOVNC_message").text(_message);
$("#VNC_status").text(_status);
}
function connected(){
setStatus(null, "VNC " + _rfb._rfb_connection_state + " (" + _is_encrypted + ") to: " + _rfb._fb_name);
}
function disconnectedFromServer(e){
if (e.detail.clean) {
setStatus(null, "VNC " + _rfb._rfb_connection_state + " (" + _is_encrypted + ") to: " + _rfb._fb_name);
} else {
setStatus("Something went wrong, connection is closed", "Failed");
}
}
function desktopNameChange(e) {
if (e.detail.name) {
setStatus(null, "VNC " + _rfb._rfb_connection_state + " (" + _is_encrypted + ") to: " + e.detail.name);
}
}
function credentialsRequired(e) {
setStatus("Something went wrong, more credentials must be given to continue", "Failed");
}
function vncCallback(response) {
var URL = "";
var proxy_port = Config.vncProxyPort;
var pw = response.password;
var token = response.token;
var info_decode = UtilsConnection.decodeInfoConnection(response.info);
UtilsConnection.printInfoConnection($('.NOVNC_info'), info_decode);
var proxy_host = window.location.hostname;
var protocol = window.location.protocol;
var hostname = window.location.hostname;
var port = window.location.port;
var rfbConfig = pw ? { "credentials": { "password": pw } } : {};
if (protocol === "https:") {
URL = "wss";
_is_encrypted ="encrypted";
} else {
URL = "ws";
_is_encrypted ="unencrypted";
}
URL += "://" + hostname;
URL += ":" + proxy_port;
URL += "?host=" + proxy_host;
URL += "&port=" + proxy_port;
URL += "&token=" + token;
URL += "&encrypt=" + Config.vncWSS;
URL += "&info=" + response.info;
if (!Config.requestVNCPassword) {
URL += "&password=" + pw;
}
var re = new RegExp("^(ws|wss):\\/\\/[\\w\\D]*?\\?", "gi");
var link = URL.replace(re, protocol + "//" + hostname + ":" + port + "/vnc?");
try{
_rfb = new RFB(document.querySelector("#VNC_canvas"), URL, rfbConfig);
_rfb.addEventListener("connect", connected);
_rfb.addEventListener("disconnect", disconnectedFromServer);
_rfb.addEventListener("desktopname", desktopNameChange);
_rfb.addEventListener("credentialsrequired", credentialsRequired);
}catch(err){
setStatus("Something went wrong, connection is closed", "Failed");
}
$("#open_in_a_new_window").attr("href", link);
}
function disconnect() {
if (_rfb) { _rfb.disconnect(); }
}
function sendCtrlAltDel() {
if (_rfb) { _rfb.sendCtrlAltDel(); }
}
});

View File

@ -0,0 +1,327 @@
body {
margin:0;
padding:0;
font-family: Helvetica;
height:100%;
}
html {
height:100%;
}
.remote-buttons {
white-space: nowrap;
}
.main {
width: 100%;
display: inline-flex;
height: 100%;
flex-direction: column;
background-color: rgb(40, 40, 40);
}
.guacamole-main{
flex-grow: 1;
}
.guacamole-status {
text-align: center;
position: relative;
width: 100%;
}
.remote-logo{
border-radius: 50%;
background-color: #282828;
padding: 0.4em;
margin-right: 1em;
height: 40px;
width: 40px;
}
.container{
display: flex;
justify-content: center;
align-items: center;
}
.guacamole-state{
white-space: nowrap;
max-width: 20em;
overflow: hidden;
text-overflow: ellipsis;
}
/* left-align the status text on lower resolutions */
@media screen and (max-width: 800px){
.guacamole-status {
z-index: 1;
position: relative;
width: auto;
float: left;
}
}
.guacamole-main {
width: 100%;
height: fit-content;
display: flex;
align-items: center;
place-content: center;
background-color: #282828;
}
.guacamole-main > div {
z-index: 1;
}
.guacamole-main .guacamole-display {
cursor: none;
}
.osk-container {
z-index: 2;
background: rgba(0, 0, 0, 0.59);
position: absolute;
top: 30%;
left: 0;
display: none;
border: 1px solid #acacac;
border-radius: 6px;
box-shadow: 0 0 20px #acacac;
}
.osk-container-header {
background: linear-gradient(to top, #ebebeb, #d5d5d5);
color: #4d494d;
font-size: 11pt;
line-height: 20px;
text-align: center;
width: 100%;
height: 28px;
user-select: none;
cursor: default;
border-top: 1px solid #f3f1f3;
border-bottom: 1px solid #b1aeb1;
border-top-left-radius: 6px;
border-top-right-radius: 6px;
}
.osk-container-header .buttons {
padding-left: 8px;
padding-top: 3px;
float: left;
line-height: 0;
}
.osk-container-header .buttons .close {
background: #ff5c5c;
font-size: 13px;
font-weight: bold;
width: 15px;
height: 15px;
border-radius: 50%;
display: inline-block;
}
.osk-container-header .layouts {
padding-right: 8px;
padding-top: 3px;
float: right;
line-height: 0;
}
.guac-keyboard {
display: inline-block;
width: 100%;
margin: 0;
padding: 0;
cursor: default;
text-align: left;
vertical-align: middle;
}
.guac-keyboard,
.guac-keyboard * {
overflow: hidden;
white-space: nowrap;
}
.guac-keyboard .guac-keyboard-key-container {
display: inline-block;
margin: 0.05em;
position: relative;
}
.guac-keyboard .guac-keyboard-key {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: #444;
border: 0.125em solid #666;
-moz-border-radius: 0.25em;
-webkit-border-radius: 0.25em;
-khtml-border-radius: 0.25em;
border-radius: 0.25em;
color: white;
font-size: 40%;
font-weight: lighter;
text-align: center;
white-space: pre;
text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.25),
1px -1px 0 rgba(0, 0, 0, 0.25),
-1px 1px 0 rgba(0, 0, 0, 0.25),
-1px -1px 0 rgba(0, 0, 0, 0.25);
}
.guac-keyboard .guac-keyboard-key:hover {
cursor: pointer;
}
.guac-keyboard .guac-keyboard-key.highlight {
background: #666;
border-color: #666;
}
/* Align some keys to the left */
.guac-keyboard .guac-keyboard-key-caps,
.guac-keyboard .guac-keyboard-key-enter,
.guac-keyboard .guac-keyboard-key-tab,
.guac-keyboard .guac-keyboard-key-lalt,
.guac-keyboard .guac-keyboard-key-ralt,
.guac-keyboard .guac-keyboard-key-alt-gr,
.guac-keyboard .guac-keyboard-key-lctrl,
.guac-keyboard .guac-keyboard-key-rctrl,
.guac-keyboard .guac-keyboard-key-lshift,
.guac-keyboard .guac-keyboard-key-rshift {
text-align: left;
padding-left: 0.75em;
}
/* Active shift */
.guac-keyboard.guac-keyboard-modifier-shift .guac-keyboard-key-rshift,
.guac-keyboard.guac-keyboard-modifier-shift .guac-keyboard-key-lshift,
/* Active ctrl */
.guac-keyboard.guac-keyboard-modifier-control .guac-keyboard-key-rctrl,
.guac-keyboard.guac-keyboard-modifier-control .guac-keyboard-key-lctrl,
/* Active alt */
.guac-keyboard.guac-keyboard-modifier-alt .guac-keyboard-key-ralt,
.guac-keyboard.guac-keyboard-modifier-alt .guac-keyboard-key-lalt,
/* Active alt-gr */
.guac-keyboard.guac-keyboard-modifier-alt-gr .guac-keyboard-key-alt-gr,
/* Active caps */
.guac-keyboard.guac-keyboard-modifier-caps .guac-keyboard-key-caps,
/* Active super */
.guac-keyboard.guac-keyboard-modifier-super .guac-keyboard-key-super {
background: #882;
border-color: #DD4;
}
.guac-keyboard .guac-keyboard-key.guac-keyboard-pressed {
background: #822;
border-color: #D44;
}
.guac-keyboard .guac-keyboard-group {
line-height: 0;
}
.guac-keyboard .guac-keyboard-group.guac-keyboard-alpha,
.guac-keyboard .guac-keyboard-group.guac-keyboard-movement {
display: inline-block;
text-align: center;
vertical-align: top;
}
.guac-keyboard .guac-keyboard-group.guac-keyboard-main {
/* IE10 */
display: -ms-flexbox;
-ms-flex-align: stretch;
-ms-flex-direction: row;
/* Ancient Mozilla */
display: -moz-box;
-moz-box-align: stretch;
-moz-box-orient: horizontal;
/* Ancient WebKit */
display: -webkit-box;
-webkit-box-align: stretch;
-webkit-box-orient: horizontal;
/* Old WebKit */
display: -webkit-flex;
-webkit-align-items: stretch;
-webkit-flex-direction: row;
/* W3C */
display: flex;
align-items: stretch;
flex-direction: row;
}
.guac-keyboard .guac-keyboard-group.guac-keyboard-movement {
-ms-flex: 1 1 auto;
-moz-box-flex: 1;
-webkit-box-flex: 1;
-webkit-flex: 1 1 auto;
flex: 1 1 auto;
}
.guac-keyboard .guac-keyboard-gap {
display: inline-block;
}
/* Hide keycaps requiring modifiers which are NOT currently active. */
.guac-keyboard:not(.guac-keyboard-modifier-caps)
.guac-keyboard-cap.guac-keyboard-requires-caps,
.guac-keyboard:not(.guac-keyboard-modifier-shift)
.guac-keyboard-cap.guac-keyboard-requires-shift,
.guac-keyboard:not(.guac-keyboard-modifier-alt-gr)
.guac-keyboard-cap.guac-keyboard-requires-alt-gr,
/* Hide keycaps NOT requiring modifiers which ARE currently active, where that
modifier is used to determine which cap is displayed for the current key. */
.guac-keyboard.guac-keyboard-modifier-shift
.guac-keyboard-key.guac-keyboard-uses-shift
.guac-keyboard-cap:not(.guac-keyboard-requires-shift),
.guac-keyboard.guac-keyboard-modifier-caps
.guac-keyboard-key.guac-keyboard-uses-caps
.guac-keyboard-cap:not(.guac-keyboard-requires-caps),
.guac-keyboard.guac-keyboard-modifier-alt-gr
.guac-keyboard-key.guac-keyboard-uses-alt-gr
.guac-keyboard-cap:not(.guac-keyboard-requires-alt-gr) {
display: none;
}
/* Fade out keys which do not use AltGr if AltGr is active */
.guac-keyboard.guac-keyboard-modifier-alt-gr
.guac-keyboard-key:not(.guac-keyboard-uses-alt-gr):not(.guac-keyboard-key-alt-gr) {
opacity: 0.5;
}

View File

@ -112,6 +112,7 @@ html {
display: inline-flex;
height: 100%;
flex-direction: column;
background-color: rgb(40, 40, 40);
}
#noVNC_screen > #VNC_canvas{
flex-grow: 1;
@ -392,6 +393,28 @@ html {
width: 100%;
}
.remote_logo{
border-radius: 50%;
background-color: #282828;
padding: 0.4em;
margin-right: 1em;
height: 40px;
width: 40px;
}
#noVNC_status_msg{
white-space: nowrap;
max-width: 20em;
overflow: hidden;
text-overflow: ellipsis;
}
.container{
display: flex;
justify-content: center;
align-items: center;
}
#showExtraKeysButton { display: none; }
#toggleCtrlButton { display: inline; }
#toggleAltButton { display: inline; }

View File

@ -55,3 +55,29 @@ main {
.spice-message-error {
color: red;
}
#VMRC_buttons {
white-space: nowrap;
}
.remote_logo{
border-radius: 50%;
background-color: #282828;
padding: 0.4em;
margin-right: 1em;
height: 40px;
width: 40px;
}
#spice_status_msg{
white-space: nowrap;
max-width: 20em;
overflow: hidden;
text-overflow: ellipsis;
}
.container{
display: flex;
justify-content: center;
align-items: center;
}

View File

@ -0,0 +1,503 @@
/*
* Based on the VMRC base CSS
* Copyright (C) 2012 Joel Martin
* Copyright (C) 2013 Samuel Mannehed for Cendio AB
* VMRC is licensed under the MPL 2.0 (see LICENSE.txt)
* This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
*/
body {
margin:0;
padding:0;
font-family: Helvetica;
/*Background image with light grey curve.*/
height:100%;
}
html {
height:100%;
}
#VMRC_controls ul {
list-style: none;
margin: 0;
padding: 0;
}
#VMRC_controls li {
padding-bottom:8px;
}
#VMRC_host {
width:150px;
}
#VMRC_port {
width: 80px;
}
#VMRC_password {
width: 150px;
}
#VMRC_path {
width: 100px;
}
#VMRC_connect_button {
width: 110px;
float:right;
}
#VMRC_buttons {
white-space: nowrap;
}
#VMRC_view_drag_button {
display: none;
}
#VMRC_xvp_buttons {
display: none;
}
#VMRC_mobile_buttons {
display: none;
}
#VMRC_extra_keys {
display: inline;
list-style-type: none;
padding: 0;
margin: 0;
position: relative;
}
.VMRC-buttons-left {
float: left;
z-index: 1;
position: relative;
}
.VMRC-buttons-right {
float:right;
right: 0;
z-index: 2;
position: absolute;
}
#VMRC_status {
text-align: center;
}
#VMRC_settings_menu {
margin: 3px;
text-align: left;
}
#VMRC_settings_menu ul {
list-style: none;
margin: 0;
padding: 0;
}
#VMRC_apply {
float:right;
}
/* Do not set width/height for VNC_screen or VNC_canvas or incorrect
* scaling will occur. Canvas resizes to remote VNC settings */
#VMRC_screen_pad {
margin: 0;
padding: 0;
height: 36px;
}
#VMRC_screen {
width: 100%;
display: flex;
height: 100%;
flex-direction: column;
background-color: rgb(40, 40, 40);
}
#VMRC_screen > #VNC_canvas{
flex-grow: 1;
}
#VMRC_container, #VMRC_canvas {
margin: 0;
padding: 0;
}
#VMRC_canvas {
flex-grow: 1;
}
#VNC_clipboard_clear_button {
float:right;
}
#VNC_clipboard_text {
font-size: 11px;
}
#VMRC_clipboard_clear_button {
float:right;
}
/*Bubble contents divs*/
#VMRC_settings {
display:none;
margin-top:73px;
right:20px;
position:fixed;
}
#VMRC_controls {
display:none;
margin-top:73px;
right:12px;
position:fixed;
}
#VMRC_controls.top:after {
right:15px;
}
#VMRC_description {
display:none;
position:fixed;
margin-top:73px;
right:20px;
left:20px;
padding:15px;
color:#000;
border:2px solid #E0E0E0;
}
#VMRC_popup_status_panel {
display:none;
position: fixed;
z-index: 1;
margin:15px;
margin-top:60px;
padding:15px;
width:auto;
text-align:center;
font-weight:bold;
word-wrap:break-word;
background:rgba(0,0,0,0.65);
-webkit-border-radius:10px;
-moz-border-radius:10px;
border-radius:10px;
}
#VMRC_xvp {
display:none;
margin-top:73px;
right:30px;
position:fixed;
}
#VMRC_xvp.top:after {
right:125px;
}
#VMRC_clipboard {
display:none;
margin-top:73px;
right:30px;
position:fixed;
}
#VMRC_clipboard.top:after {
right:85px;
}
#keyboardinput {
width:1px;
height:1px;
background-color:#fff;
color:#fff;
border:0;
position: relative;
left: -40px;
z-index: -1;
}
/*
* Advanced Styling
*/
.VMRC_status_normal {
color: #b2bdcd; /* Old browsers */
}
.VMRC_status_error {
color: #f04040; /* Old browsers */
}
.VMRC_status_warn {
color: #f0f040; /* Old browsers */
}
/* Control bar */
#VMRC-control-bar {
position:fixed;
display:block;
height:36px;
left:0;
top:0;
width:100%;
z-index:200;
}
.VMRC_status_button {
padding: 4px 4px;
vertical-align: middle;
border:1px solid #869dbc;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
background: #b2bdcd; /* Old browsers */
background: -moz-linear-gradient(top, #b2bdcd 0%, #899cb3 49%, #7e93af 51%, #6e84a3 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b2bdcd), color-stop(49%,#899cb3), color-stop(51%,#7e93af), color-stop(100%,#6e84a3)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Opera11.10+ */
background: -ms-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* IE10+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#b2bdcd', endColorstr='#6e84a3',GradientType=0 ); /* IE6-9 */
background: linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* W3C */
/*box-shadow:inset 0.4px 0.4px 0.4px #000000;*/
}
.VMRC_status_button_selected {
padding: 4px 4px;
vertical-align: middle;
border:1px solid #4366a9;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
background: #779ced; /* Old browsers */
background: -moz-linear-gradient(top, #779ced 0%, #3970e0 49%, #2160dd 51%, #2463df 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#779ced), color-stop(49%,#3970e0), color-stop(51%,#2160dd), color-stop(100%,#2463df)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* Opera11.10+ */
background: -ms-linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* IE10+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#779ced', endColorstr='#2463df',GradientType=0 ); /* IE6-9 */
background: linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* W3C */
/*box-shadow:inset 0.4px 0.4px 0.4px #000000;*/
}
/*Settings Bubble*/
.triangle-right {
position:relative;
padding:15px;
margin:1em 0 3em;
color:#fff;
background:#fff; /* default background for browsers without gradient support */
/* css3 */
/*background:-webkit-gradient(linear, 0 0, 0 100%, from(#2e88c4), to(#075698));
background:-moz-linear-gradient(#2e88c4, #075698);
background:-o-linear-gradient(#2e88c4, #075698);
background:linear-gradient(#2e88c4, #075698);*/
-webkit-border-radius:10px;
-moz-border-radius:10px;
border-radius:10px;
color:#000;
border:2px solid #E0E0E0;
}
.triangle-right.top:after {
border-color: transparent #E0E0E0;
border-width: 20px 20px 0 0;
bottom: auto;
left: auto;
right: 50px;
top: -20px;
}
.triangle-right:after {
content:"";
position:absolute;
bottom:-20px; /* value = - border-top-width - border-bottom-width */
left:50px; /* controls horizontal position */
border-width:20px 0 0 20px; /* vary these values to change the angle of the vertex */
border-style:solid;
border-color:#E0E0E0 transparent;
/* reduce the damage in FF3.0 */
display:block;
width:0;
}
.triangle-right.top:after {
top:-40px; /* value = - border-top-width - border-bottom-width */
right:50px; /* controls horizontal position */
bottom:auto;
left:auto;
border-width:40px 40px 0 0; /* vary these values to change the angle of the vertex */
border-color:transparent #E0E0E0;
}
/*Default VMRC logo.*/
/* From: http://fonts.googleapis.com/css?family=Orbitron:700 */
@font-face {
font-family: 'Orbitron';
font-style: normal;
font-weight: 700;
src: local('?'), url('Orbitron700.woff') format('woff'),
url('Orbitron700.ttf') format('truetype');
}
#VMRC_logo {
margin-top: 170px;
margin-left: 10px;
color:yellow;
text-align:left;
font-family: 'Orbitron', 'OrbitronTTF', sans-serif;
line-height:90%;
text-shadow:
5px 5px 0 #000,
-1px -1px 0 #000,
1px -1px 0 #000,
-1px 1px 0 #000,
1px 1px 0 #000;
}
#VMRC_logo span{
color:green;
}
/* ----------------------------------------
* Media sizing
* ----------------------------------------
*/
.VMRC_status_button {
font-size: 12px;
}
#VMRC_clipboard_text {
width: 500px;
}
#VMRC_logo {
font-size: 180px;
}
.VMRC-buttons-left {
padding-left: 10px;
}
.VMRC-buttons-right {
padding-right: 10px;
}
#VMRC_status {
position: absolute;
width: 100%;
}
#wmksContainer {
position: relative;
height: 100%;
width: 100%;
padding: 1em 0;
}
.remote_logo{
border-radius: 50%;
background-color: #282828;
padding: 0.4em;
margin-right: 1em;
height: 40px;
width: 40px;
}
#VMRC_status_msg{
white-space: nowrap;
max-width: 20em;
overflow: hidden;
text-overflow: ellipsis;
}
.container{
display: flex;
justify-content: center;
align-items: center;
}
#showExtraKeysButton { display: none; }
#toggleCtrlButton { display: inline; }
#toggleAltButton { display: inline; }
#sendTabButton { display: inline; }
#sendEscButton { display: inline; }
/* left-align the status text on lower resolutions */
@media screen and (max-width: 800px){
#VMRC_status {
z-index: 1;
position: relative;
width: auto;
float: left;
}
}
@media screen and (max-width: 640px){
#VMRC_clipboard_text {
width: 410px;
}
#VMRC_logo {
font-size: 150px;
}
.VMRC_status_button {
font-size: 10px;
}
.VMRC-buttons-left {
padding-left: 0;
}
.VMRC-buttons-right {
padding-right: 0;
}
/* collapse the extra keys on lower resolutions */
#showExtraKeysButton {
display: inline;
}
#toggleCtrlButton {
display: none;
position: absolute;
top: 30px;
left: 0;
}
#toggleAltButton {
display: none;
position: absolute;
top: 65px;
left: 0;
}
#sendTabButton {
display: none;
position: absolute;
top: 100px;
left: 0;
}
#sendEscButton {
display: none;
position: absolute;
top: 135px;
left: 0;
}
}
@media screen and (min-width: 321px) and (max-width: 480px) {
#VMRC_clipboard_text {
width: 250px;
}
#VMRC_logo {
font-size: 110px;
}
}
@media screen and (max-width: 320px) {
.VMRC_status_button {
font-size: 9px;
}
#VMRC_clipboard_text {
width: 220px;
}
#VMRC_logo {
font-size: 90px;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -159,263 +159,4 @@ progress{
background-color: $busy;
}
}
}
.guacamole-main {
width: 100%;
height: fit-content;
display: flex;
align-items: center;
place-content: center;
background-color: #282828;
& > div {
z-index: 1;
}
& .guacamole-display {
cursor: none;
}
}
#guacVMDialog {
overflow: hidden;
}
.osk-container {
z-index: 2;
background: rgba(0, 0, 0, 0.59);
position: absolute;
top: 30%;
left: 0;
display: none;
border: 1px solid #acacac;
border-radius: 6px;
box-shadow: 0 0 20px #acacac;
& > .osk-container-header {
background: linear-gradient(to top, #ebebeb, #d5d5d5);
color: #4d494d;
font-size: 11pt;
line-height: 20px;
text-align: center;
width: 100%;
height: 28px;
user-select: none;
cursor: default;
border-top: 1px solid #f3f1f3;
border-bottom: 1px solid #b1aeb1;
border-top-left-radius: 6px;
border-top-right-radius: 6px;
& .buttons {
padding-left: 8px;
padding-top: 3px;
float: left;
line-height: 0;
& .close {
background: #ff5c5c;
font-size: 13px;
font-weight: bold;
width: 15px;
height: 15px;
border-radius: 50%;
display: inline-block;
}
}
& .layouts {
padding-right: 8px;
padding-top: 3px;
float: right;
line-height: 0;
}
}
}
.guac-keyboard {
display: inline-block;
width: 100%;
margin: 0;
padding: 0;
cursor: default;
text-align: left;
vertical-align: middle;
}
.guac-keyboard,
.guac-keyboard * {
overflow: hidden;
white-space: nowrap;
}
.guac-keyboard .guac-keyboard-key-container {
display: inline-block;
margin: 0.05em;
position: relative;
}
.guac-keyboard .guac-keyboard-key {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: #444;
border: 0.125em solid #666;
-moz-border-radius: 0.25em;
-webkit-border-radius: 0.25em;
-khtml-border-radius: 0.25em;
border-radius: 0.25em;
color: white;
font-size: 40%;
font-weight: lighter;
text-align: center;
white-space: pre;
text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.25),
1px -1px 0 rgba(0, 0, 0, 0.25),
-1px 1px 0 rgba(0, 0, 0, 0.25),
-1px -1px 0 rgba(0, 0, 0, 0.25);
}
.guac-keyboard .guac-keyboard-key:hover {
cursor: pointer;
}
.guac-keyboard .guac-keyboard-key.highlight {
background: #666;
border-color: #666;
}
/* Align some keys to the left */
.guac-keyboard .guac-keyboard-key-caps,
.guac-keyboard .guac-keyboard-key-enter,
.guac-keyboard .guac-keyboard-key-tab,
.guac-keyboard .guac-keyboard-key-lalt,
.guac-keyboard .guac-keyboard-key-ralt,
.guac-keyboard .guac-keyboard-key-alt-gr,
.guac-keyboard .guac-keyboard-key-lctrl,
.guac-keyboard .guac-keyboard-key-rctrl,
.guac-keyboard .guac-keyboard-key-lshift,
.guac-keyboard .guac-keyboard-key-rshift {
text-align: left;
padding-left: 0.75em;
}
/* Active shift */
.guac-keyboard.guac-keyboard-modifier-shift .guac-keyboard-key-rshift,
.guac-keyboard.guac-keyboard-modifier-shift .guac-keyboard-key-lshift,
/* Active ctrl */
.guac-keyboard.guac-keyboard-modifier-control .guac-keyboard-key-rctrl,
.guac-keyboard.guac-keyboard-modifier-control .guac-keyboard-key-lctrl,
/* Active alt */
.guac-keyboard.guac-keyboard-modifier-alt .guac-keyboard-key-ralt,
.guac-keyboard.guac-keyboard-modifier-alt .guac-keyboard-key-lalt,
/* Active alt-gr */
.guac-keyboard.guac-keyboard-modifier-alt-gr .guac-keyboard-key-alt-gr,
/* Active caps */
.guac-keyboard.guac-keyboard-modifier-caps .guac-keyboard-key-caps,
/* Active super */
.guac-keyboard.guac-keyboard-modifier-super .guac-keyboard-key-super {
background: #882;
border-color: #DD4;
}
.guac-keyboard .guac-keyboard-key.guac-keyboard-pressed {
background: #822;
border-color: #D44;
}
.guac-keyboard .guac-keyboard-group {
line-height: 0;
}
.guac-keyboard .guac-keyboard-group.guac-keyboard-alpha,
.guac-keyboard .guac-keyboard-group.guac-keyboard-movement {
display: inline-block;
text-align: center;
vertical-align: top;
}
.guac-keyboard .guac-keyboard-group.guac-keyboard-main {
/* IE10 */
display: -ms-flexbox;
-ms-flex-align: stretch;
-ms-flex-direction: row;
/* Ancient Mozilla */
display: -moz-box;
-moz-box-align: stretch;
-moz-box-orient: horizontal;
/* Ancient WebKit */
display: -webkit-box;
-webkit-box-align: stretch;
-webkit-box-orient: horizontal;
/* Old WebKit */
display: -webkit-flex;
-webkit-align-items: stretch;
-webkit-flex-direction: row;
/* W3C */
display: flex;
align-items: stretch;
flex-direction: row;
}
.guac-keyboard .guac-keyboard-group.guac-keyboard-movement {
-ms-flex: 1 1 auto;
-moz-box-flex: 1;
-webkit-box-flex: 1;
-webkit-flex: 1 1 auto;
flex: 1 1 auto;
}
.guac-keyboard .guac-keyboard-gap {
display: inline-block;
}
/* Hide keycaps requiring modifiers which are NOT currently active. */
.guac-keyboard:not(.guac-keyboard-modifier-caps)
.guac-keyboard-cap.guac-keyboard-requires-caps,
.guac-keyboard:not(.guac-keyboard-modifier-shift)
.guac-keyboard-cap.guac-keyboard-requires-shift,
.guac-keyboard:not(.guac-keyboard-modifier-alt-gr)
.guac-keyboard-cap.guac-keyboard-requires-alt-gr,
/* Hide keycaps NOT requiring modifiers which ARE currently active, where that
modifier is used to determine which cap is displayed for the current key. */
.guac-keyboard.guac-keyboard-modifier-shift
.guac-keyboard-key.guac-keyboard-uses-shift
.guac-keyboard-cap:not(.guac-keyboard-requires-shift),
.guac-keyboard.guac-keyboard-modifier-caps
.guac-keyboard-key.guac-keyboard-uses-caps
.guac-keyboard-cap:not(.guac-keyboard-requires-caps),
.guac-keyboard.guac-keyboard-modifier-alt-gr
.guac-keyboard-key.guac-keyboard-uses-alt-gr
.guac-keyboard-cap:not(.guac-keyboard-requires-alt-gr) {
display: none;
}
/* Fade out keys which do not use AltGr if AltGr is active */
.guac-keyboard.guac-keyboard-modifier-alt-gr
.guac-keyboard-key:not(.guac-keyboard-uses-alt-gr):not(.guac-keyboard-key-alt-gr) {
opacity: 0.5;
}

View File

@ -550,7 +550,7 @@ before do
@request_body = request.body.read
request.body.rewind
unless %w(/ /login /vnc /spice /version /webauthn_options_for_get /ws /vmrc).include?(request.path)
unless %w(/ /login /vnc /spice /version /webauthn_options_for_get /ws /vmrc /guac).include?(request.path)
halt [401, "csrftoken"] unless authorized? && valid_csrftoken?
end
@ -747,6 +747,20 @@ get '/vnc' do
end
end
get '/guac' do
content_type 'text/html', :charset => 'utf-8'
if !authorized?
erb :login
else
erb :guac, :locals => {
:logos_conf => $conf[:locals][:logos_conf],
:oned_conf => $conf[:locals][:oned_conf],
:support => $conf[:locals][:support],
:upgrade => $conf[:locals][:upgrade]
}
end
end
get '/vmrc' do
content_type 'text/html', :charset => 'utf-8'
if !authorized?

119
src/sunstone/views/guac.erb Normal file
View File

@ -0,0 +1,119 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<link rel="apple-touch-startup-image" href="images/screen_320x460.png" />
<link rel="apple-touch-icon" href="images/screen_57x57.png">
<!-- Stylesheets -->
<link rel="stylesheet" type="text/css" href="css/app.css" title="plain">
<link rel="stylesheet" type="text/css" href="css/guac-custom.css" title="plain">
</head>
<body>
<div class="main">
<div style="background: #f7f7f7; padding: 1.5em 1.5em 0.8em;">
<div style="max-width: 1250px; margin: 0 auto;">
<div style="display: flex; align-items: center;">
<img src="images/one_small_logo.png" style="height:40px;">
<h5 class="guacamole-status">
<div class="container">
<div class="remote-logo">
<img src="images/remote_console/guacamole.png">
</div>
<div id="guacamole-state" class="guacamole-state">
<span id="guacamole-loading">
<i class="fas fa-spinner fa-spin"></i>
</span>
</div>
</div>
</h5>
<div class="remote-buttons">
<button class="button alert" id="sendCtrlAltDelButton">
Send CtrlAltDel
</button>
<button class="button primary" id="oskButton" style="display: none !important;">
<i class="fas fa-keyboard fa-fw"></i>
</button>
<button class="button primary" id="mouseButton">
<i class="fas fa-mouse-pointer fa-fw"></i>
</button>
<button class="button primary" id="takeScreenshot">
<i class="fas fa-camera fa-fw" title="Take screenshot"></i>
</button>
</div>
</div>
<div class="guacVNC_info"></div>
</div>
</div>
<div id="guacamole-main" class="guacamole-main">
<div id="guacamole-display" class="guacamole-display"></div>
</div>
<!-- On-screen keyboard -->
<div class="osk-container" id="osk-container">
<div class="osk-container-header" id="osk-container-header">
<div class="buttons">
<button class="close" id="osk-close">x</button>
</div>
<div class="layouts">
<select id="osk-qwerty"></select>
</div>
</div>
<div class="osk" id="osk"></div>
</div>
</div>
<script src="dist/console/guacamole.js?v=<%= OpenNebula::VERSION %>"></script>
<% view = $views_config.view(session[:user], session[:user_gname], session[:default_view]) %>
<script type="text/javascript">
var csrftoken = '<%= session[:csrftoken] %>';
var view = JSON.parse('<%= view.to_json %>')
var available_views = JSON.parse('["<%=
$views_config.available_views(session[:user], session[:user_gname]).join('","')
%>"]')
var all_labels = JSON.parse('["<%=
$views_config.get_all_labels(session[:user_gname]).join('","')
%>"]')
var all_views = JSON.parse('["<%=
$views_config.get_all_views.join('","')
%>"]')
if ('<%= $conf[:addons] %>'){
var addons = JSON.parse('<%= $conf[:addons].to_json %>');
}
var config = {
'user_config' : {
'lang' : '<%= session[:lang] %>',
'vnc_wss' : '<%= session[:vnc_wss] %>',
'table_order' : '<%= session[:table_order] %>',
'default_view' : '<%= session[:default_view] %>',
'page_length' : '<%= session[:page_length] %>'
},
'system_config' : {
'marketplace_url' : '<%= $conf[:marketplace_url] %>',
'vnc_request_password' : <%= $conf[:vnc_request_password] || false %>,
'vnc_proxy_port' : '<%= $vnc.proxy_port %>',
'vnc_client_port' : '<%= $conf[:vnc_client_port] %>',
'max_upload_file_size' : <%= $conf[:max_upload_file_size] ? $conf[:max_upload_file_size] : "undefined" %>,
'public_fireedge_endpoint': '<%= $conf[:public_fireedge_endpoint] %>'
},
'view' : view,
'available_views' : available_views,
'all_labels' : all_labels,
'all_views' : all_views,
'user_id' : '<%= session[:user_id] %>',
'user_gid' : '<%= session[:user_gid] %>',
'display_name' : '<%= session[:display_name] %>',
'zone_name' : '<%= session[:zone_name] %>',
'zone_id' : '<%= session[:zone_id] %>',
'federation_mode' : '<%= session[:federation_mode] %>',
'vm_logos' : <%= logos_conf.to_json %>,
'oned_conf' : <%= oned_conf.to_json %>,
'support' : <%= support.to_json %>,
'upgrade' : <%= upgrade.to_json %>,
'mode' : '<%= session[:mode] %>'
};
</script>
</body>
</html>

View File

@ -10,7 +10,8 @@
<link rel="apple-touch-startup-image" href="images/screen_320x460.png" />
<link rel="apple-touch-icon" href="images/screen_57x57.png">
<!-- Stylesheets -->
<link rel="stylesheet" type="text/css" href="css/novnc-custom.css" title="plain">
<link rel="stylesheet" type="text/css" href="css/app.css" title="plain">
<link rel="stylesheet" type="text/css" href="css/vmrc-custom.css" title="plain">
<% view = $views_config.view(session[:user], session[:user_gname], session[:default_view]) %>
<!-- JQuery -->
<script src="bower_components/jquery/dist/jquery.min.js"></script>
@ -68,27 +69,45 @@
</head>
<body style="margin: 0px;">
<div id="VMRC_screen">
<div style="background: #f7f7f7; border-bottom: 1px solid #dfdfdf; padding: 10px 0px 15px 0px">
<div id="VMRC_status_bar" class="VMRC_status_bar" style="margin-top: 0px;">
<table border="0" width="100%">
<tr>
<td width="1%" >
<img src="images/one_small_logo.png" style="height:40px; vertical-align:top; margin-left: 30px"></td>
<td>
<div id="VMRC_status" style="position: relative; height: auto; color: #000; text-align:center;">Loading</div>
</td>
<td width="1%" >
<div id="VMRC_buttons" style="margin-right: 30px">
<input type=button value="Send CtrlAltDel"
id="sendCtrlAltDelButton">
<div style="background: #f7f7f7; padding: 1.5em 1.5em 0.8em;">
<div style="max-width: 1250px; margin: 0 auto;">
<div id="VMRC_status_bar" class="noVNC_status_bar" style="display: flex; align-items: center;">
<img src="images/one_small_logo.png" style="height:40px;">
<h5 id="VMRC_status" style="position: relative;">
<div class="container">
<div class="remote_logo">
<img src="images/remote_console/vmrc.png">
</div>
</td>
</tr>
</table>
<div id="VMRC_status_msg">
Loading
</div>
</div>
</h5>
<div id="VMRC_buttons">
<button class="button alert" id="sendCtrlAltDelButton">Send CtrlAltDel</button>
<button class="button info" id="fullScreenButton"><i class="fas fa-expand"></i></button>
<button class="button info" id="keyboardSelector"><i class="fas fa-keyboard"></i></button>
<select style="width: auto;" id="selectLanguage" hidden>
<option value="en-US">English</option>
<option value="ja-JP_106/109">Japanese</option>
<option value="de-DE">German</option>
<option value="it-IT">Italian</option>
<option value="es-ES">Spanish</option>
<option value="pt-PT">Portuguese</option>
<option value="fr-FR">French</option>
<option value="fr-CH">Swiss-French</option>
<option value="de-CH">Swiss-German</option>
</select>
</div>
</div>
<div class="VMRC_info"></div>
</div>
</div>
<div id="wmksContainer" style="position:absolute;width:100%;height:100%; left:0em"></div>
<div id="VMRC_canvas" width="640px" height="20px">
<div id="VMRC_canvas">
<div id="wmksContainer" class="wmksContainer"></div>
<div class="VMRC_message"></div>
</div>
</div>

View File

@ -18,7 +18,16 @@
<div style="max-width: 1250px; margin: 0 auto;">
<div id="noVNC_status_bar" class="noVNC_status_bar" style="display: flex; align-items: center;">
<img src="images/one_small_logo.png" style="height:40px;">
<h5 id="noVNC_status" style="position: relative;">Loading</h5>
<h5 id="noVNC_status" style="position: relative;">
<div class="container">
<div class="remote_logo">
<img src="images/remote_console/noVNC.png">
</div>
<div id="noVNC_status_msg">
Loading
</div>
</div>
</h5>
<div id="noVNC_buttons">
<button class="button alert" id="sendCtrlAltDelButton">Send CtrlAltDel</button>
<span id="noVNC_xvp_buttons">