diff --git a/actor/src/UDSActorConfig.py b/actor/src/UDSActorConfig.py
index 4f83b63fd..2c014388d 100644
--- a/actor/src/UDSActorConfig.py
+++ b/actor/src/UDSActorConfig.py
@@ -29,52 +29,76 @@
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
+# pylint: disable=invalid-name
import sys
import os
import logging
+import typing
import PyQt5 # pylint: disable=unused-import
-from PyQt5.QtWidgets import QApplication, QDialog
+from PyQt5.QtWidgets import QApplication, QDialog, QFileDialog
+
+import udsactor
from ui.setup_dialog_ui import Ui_UdsActorSetupDialog
-# pylint: disable=invalid-name
+# Not imported at runtime, just for type checking
+if typing.TYPE_CHECKING:
+ from PyQt5.QtWidgets import QLineEdit # pylint: disable=ungrouped-imports
logger = logging.getLogger('actor')
class UDSConfigDialog(QDialog):
- def __init__(self, data=None, parent=None):
- QDialog.__init__(self, parent)
+ def __init__(self):
+ QDialog.__init__(self, None)
self.ui = Ui_UdsActorSetupDialog()
self.ui.setupUi(self)
- if data is not None:
- pass
+ self.ui.host.setText('172.27.0.1:8443')
+ self.ui.username.setText('admin')
+ self.ui.password.setText('temporal')
+ self.ui.postConfigCommand.setText(r'c:\windows\post-uds.bat')
+ self.ui.preCommand.setText(r'c:\windows\pre-uds.bat')
+ self.ui.runonceCommand.setText(r'c:\windows\runonce.bat')
- def _getCfg(self):
- return {
- 'host': self.ui.host.text(),
- 'username': self.ui.username.text(),
- 'password': self.ui.password.text(),
- 'validateCertificate': self.ui.validateCertificate.currentIndex() == 1,
- 'logLevel': (self.ui.logLevelComboBox.currentIndex() + 1) * 10000
- }
+ def browse(self, lineEdit: 'QLineEdit', caption: str) -> None:
+ name = QFileDialog.getOpenFileName(parent=self, caption='')[0] # Returns tuple (filename, filter)
+ if name:
+ lineEdit.setText(name)
+
+ def browsePreconnect(self) -> None:
+ self.browse(self.ui.preCommand, 'Select Preconnect command')
+
+ def browseRunOnce(self) -> None:
+ self.browse(self.ui.runonceCommand, 'Select Runonce command')
+
+ def browsePostConfig(self) -> None:
+ self.browse(self.ui.postConfigCommand, 'Select Postconfig command')
def textChanged(self):
enableButtons = self.ui.host.text() != '' and self.ui.username.text() != '' and self.ui.password.text() != ''
- self.ui.testButton.setEnabled(enableButtons)
- self.ui.saveButton.setEnabled(enableButtons)
+ self.ui.registerButton.setEnabled(enableButtons)
- def cancelAndDiscard(self):
- logger.debug('Cancelling changes')
+ def finish(self):
self.close()
- def testParameters(self):
- pass
+ def registerWithUDS(self):
+ api = udsactor.rest.REST(self.ui.host.text(), self.ui.validateCertificate.currentIndex() == 1)
- def acceptAndSave(self):
- pass
+ data: udsactor.types.InterfaceInfo = next(udsactor.operations.getNetworkInfo())
+
+ key = api.register(
+ self.ui.username.text(),
+ self.ui.password.text(),
+ data.ip or '', # IP
+ data.mac or '', # first mac
+ self.ui.preCommand.text(),
+ self.ui.runonceCommand.text(),
+ self.ui.postConfigCommand.text()
+ )
+
+ print(key)
if __name__ == "__main__":
diff --git a/actor/src/designer/setup-dialog.ui b/actor/src/designer/setup-dialog.ui
index 0a002feb6..d16637657 100644
--- a/actor/src/designer/setup-dialog.ui
+++ b/actor/src/designer/setup-dialog.ui
@@ -10,8 +10,8 @@
0
0
- 400
- 293
+ 590
+ 253
@@ -26,8 +26,11 @@
9
+
+ Qt::DefaultContextMenu
+
- UDS Actor Setup
+ UDS Actor Configuration Tool
@@ -45,18 +48,24 @@
true
-
+
false
- 20
+ 10
220
- 361
+ 181
23
+
+
+ 181
+ 0
+
+
Click to test the selecter parameters
@@ -64,18 +73,15 @@
<html><head/><body><p>Click on this button to test the server host and master key parameters.</p><p>A window will be displayed with results after the test is executed.</p><p><br/></p><p>This button will only be active if all parameters are filled.</p></body></html>
- Test parameters
+ Register with UDS
-
-
- false
-
+
- 20
- 250
- 101
+ 410
+ 220
+ 171
23
@@ -85,30 +91,11 @@
0
-
- Accepts changes and saves them
-
-
- Clicking on this button will accept all changes and save them, closing the configuration window
-
-
- Accept && Save
-
-
-
-
-
- 260
- 250
- 121
- 23
-
-
-
-
- 0
- 0
-
+
+
+ 171
+ 0
+
Cancel all changes and discard them
@@ -117,147 +104,296 @@
Discards all changes and closes the configuration window
- Cancel && Discard
+ Close
-
+
- 20
- 20
- 361
- 191
+ 10
+ 10
+ 571
+ 201
-
-
- QFormLayout::AllNonFixedFieldsGrow
-
-
- 16
-
- -
-
-
- UDS Server
+
+ 0
+
+
+
+ UDS Server
+
+
+
+
+ 10
+ 10
+ 551
+ 151
+
+
+
+
+ QFormLayout::AllNonFixedFieldsGrow
-
-
- -
-
-
- false
+
+ 16
-
- Uds Broker Server Addres. Use IP or FQDN
-
-
- Enter here the UDS Broker Addres using either its IP address or its FQDN address
-
-
-
- -
-
-
- Username
-
-
-
- -
-
-
- UDS user with administration rights (Will not be stored on template)
-
-
- <html><head/><body><p>Administrator user on UDS Server.</p><p>Note: This credential will not be stored on client. Will be used to obtain an unique key for this image.</p></body></html>
-
-
-
- -
-
-
- Password for user (Will not be stored on template)
-
-
- <html><head/><body><p>Administrator password for the user on UDS Server.</p><p>Note: This credential will not be stored on client. Will be used to obtain an unique key for this image.</p></body></html>
-
-
- QLineEdit::Password
-
-
-
- -
-
-
- Password
-
-
-
- -
-
-
- Security
-
-
-
- -
-
-
- Select communication security with broker
-
-
- <html><head/><body><p>Select the security for communications with UDS Broker.</p><p>The recommended method of communication is <span style=" font-weight:600;">Use SSL</span>, but selection needs to be acording to your broker configuration.</p></body></html>
-
-
-
-
- Ignore certificate
-
+
-
+
+
+ UDS Server
+
+
- -
-
- Verify certificate
-
+
-
+
+
+ false
+
+
+ Uds Broker Server Addres. Use IP or FQDN
+
+
+ Enter here the UDS Broker Addres using either its IP address or its FQDN address
+
+
-
-
- -
-
-
- 1
+
-
+
+
+ Username
+
+
+
+ -
+
+
+ UDS user with administration rights (Will not be stored on template)
+
+
+ <html><head/><body><p>Administrator user on UDS Server.</p><p>Note: This credential will not be stored on client. Will be used to obtain an unique key for this image.</p></body></html>
+
+
+
+ -
+
+
+ Password
+
+
+
+ -
+
+
+ Password for user (Will not be stored on template)
+
+
+ <html><head/><body><p>Administrator password for the user on UDS Server.</p><p>Note: This credential will not be stored on client. Will be used to obtain an unique key for this image.</p></body></html>
+
+
+ QLineEdit::Password
+
+
+
+ -
+
+
+ Security
+
+
+
+ -
+
+
+ Select communication security with broker
+
+
+ <html><head/><body><p>Select the security for communications with UDS Broker.</p><p>The recommended method of communication is <span style=" font-weight:600;">Use SSL</span>, but selection needs to be acording to your broker configuration.</p></body></html>
+
+
-
+
+ Ignore certificate
+
+
+ -
+
+ Verify certificate
+
+
+
+
+
+
+
+
+
+ Advanced
+
+
+
+
+ 10
+ 10
+ 551
+ 151
+
+
+
+
+ QFormLayout::AllNonFixedFieldsGrow
-
- true
+
+ 16
- -
-
- DEBUG
-
+
-
+
+
+ Preconnect
+
+
- -
-
- INFO
-
+
-
+
+
+ 4
+
+
+ 0
+
+
-
+
+
+ false
+
+
+ Pre connection command. Executed just before the user is connected to machine.
+
+
+
+
+
+
+ -
+
+
+ Browse
+
+
+
+
- -
-
- ERROR
-
+
-
+
+
+ Runonce
+
+
- -
-
- FATAL
-
+
-
+
+
+ 4
+
+
+ 0
+
+
-
+
+
+ Run once command. Executed on first boot, just before UDS does anything.
+
+
+
+
+
+
+ -
+
+
+ Browse
+
+
+
+
-
-
- -
-
-
- Log Level
-
-
-
-
+ -
+
+
+ Postconfig
+
+
+
+ -
+
+
+ 4
+
+
+ 0
+
+
-
+
+
+ Command to execute after UDS finalizes the VM configuration.
+
+
+
+
+
+ QLineEdit::Normal
+
+
+
+ -
+
+
+ Browse
+
+
+
+
+
+ -
+
+
+ Log Level
+
+
+
+ -
+
+
+ 1
+
+
+ true
+
+
-
+
+ DEBUG
+
+
+ -
+
+ INFO
+
+
+ -
+
+ ERROR
+
+
+ -
+
+ FATAL
+
+
+
+
+
+
+
@@ -265,10 +401,10 @@
- cancelButton
- pressed()
+ closeButton
+ clicked()
UdsActorSetupDialog
- cancelAndDiscard()
+ finish()
315
@@ -281,10 +417,10 @@
- testButton
- pressed()
+ registerButton
+ clicked()
UdsActorSetupDialog
- testParameters()
+ registerWithUDS()
239
@@ -296,22 +432,6 @@
-
- saveButton
- pressed()
- UdsActorSetupDialog
- acceptAndSave()
-
-
- 71
- 165
-
-
- 124
- 181
-
-
-
host
textChanged(QString)
@@ -319,12 +439,12 @@
textChanged()
- 237
- 32
+ 239
+ 59
199
- 146
+ 150
@@ -335,12 +455,12 @@
textChanged()
- 237
- 71
+ 239
+ 98
199
- 146
+ 150
@@ -351,20 +471,70 @@
textChanged()
- 237
- 110
+ 239
+ 137
199
- 146
+ 150
+
+
+
+
+ browsePreconnectButton
+ clicked()
+ UdsActorSetupDialog
+ browsePreconnect()
+
+
+ 430
+ 60
+
+
+ 243
+ 150
+
+
+
+
+ browsePostConfigButton
+ clicked()
+ UdsActorSetupDialog
+ browsePostConfig()
+
+
+ 430
+ 142
+
+
+ 243
+ 150
+
+
+
+
+ browseRunOnceButton
+ clicked()
+ UdsActorSetupDialog
+ browseRunOnce()
+
+
+ 430
+ 101
+
+
+ 243
+ 150
textChanged()
- cancelAndDiscard()
- testParameters()
- acceptAndSave()
+ finish()
+ registerWithUDS()
+ browsePreconnect()
+ browseRunOnce()
+ browsePostConfig()
diff --git a/actor/src/udsactor/__init__.py b/actor/src/udsactor/__init__.py
index 445bb4722..f4fca9776 100644
--- a/actor/src/udsactor/__init__.py
+++ b/actor/src/udsactor/__init__.py
@@ -25,11 +25,21 @@
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
-VERSION = '3.0.0'
+import sys
+
+from . import types
+from . import rest
+
+if sys.platform == 'win32':
+ from .windows import operations
+else:
+ from .linux import operations
+
+from .info import VERSION
+
__title__ = 'udsactor'
__version__ = VERSION
@@ -37,3 +47,4 @@ __build__ = 0x010756
__author__ = 'Adolfo Gómez '
__license__ = "BSD 3-clause"
__copyright__ = "Copyright 2014-2019 VirtualCable S.L.U."
+
diff --git a/actor/src/udsactor/info.py b/actor/src/udsactor/info.py
new file mode 100644
index 000000000..aaa426448
--- /dev/null
+++ b/actor/src/udsactor/info.py
@@ -0,0 +1 @@
+VERSION = '3.0.0'
diff --git a/actor/src/udsactor/linux/operations.py b/actor/src/udsactor/linux/operations.py
index e29127d54..bbc97164a 100644
--- a/actor/src/udsactor/linux/operations.py
+++ b/actor/src/udsactor/linux/operations.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
-# Copyright (c) 2014 Virtual Cable S.L.
+# Copyright (c) 2014-2019 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@@ -29,8 +29,6 @@
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
-from __future__ import unicode_literals
-
import socket
import platform
import fcntl
@@ -40,49 +38,45 @@ import ctypes.util
import subprocess
import struct
import array
-import six
-from udsactor import utils
+import typing
+
+from udsactor import types
+
from .renamer import rename
-def _getMacAddr(ifname):
+def _getMacAddr(ifname: str) -> typing.Optional[str]:
'''
Returns the mac address of an interface
Mac is returned as unicode utf-8 encoded
'''
- if isinstance(ifname, list):
- return dict([(name, _getMacAddr(name)) for name in ifname])
- if isinstance(ifname, six.text_type):
- ifname = ifname.encode('utf-8') # If unicode, convert to bytes (or str in python 2.7)
+ ifnameBytes = ifname.encode('utf-8') # If unicode, convert to bytes
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- info = bytearray(fcntl.ioctl(s.fileno(), 0x8927, struct.pack(str('256s'), ifname[:15])))
- return six.text_type(''.join(['%02x:' % char for char in info[18:24]])[:-1]).upper()
+ info = bytearray(fcntl.ioctl(s.fileno(), 0x8927, struct.pack(str('256s'), ifnameBytes[:15])))
+ return str(''.join(['%02x:' % char for char in info[18:24]])[:-1]).upper()
except Exception:
return None
-def _getIpAddr(ifname):
+def _getIpAddr(ifname: str) -> typing.Optional[str]:
'''
Returns the ip address of an interface
Ip is returned as unicode utf-8 encoded
'''
- if isinstance(ifname, list):
- return dict([(name, _getIpAddr(name)) for name in ifname])
- if isinstance(ifname, six.text_type):
- ifname = ifname.encode('utf-8') # If unicode, convert to bytes (or str in python 2.7)
+ ifnameBytes = ifname.encode('utf-8') # If unicode, convert to bytes (or str in python 2.7)
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- return six.text_type(socket.inet_ntoa(fcntl.ioctl(
+ return str(socket.inet_ntoa(fcntl.ioctl(
s.fileno(),
0x8915, # SIOCGIFADDR
- struct.pack(str('256s'), ifname[:15])
+ struct.pack(str('256s'), ifnameBytes[:15])
)[20:24]))
except Exception:
return None
-def _getInterfaces():
+def _getInterfaces() -> typing.List[str]:
'''
Returns a list of interfaces names coded in utf-8
'''
@@ -107,76 +101,68 @@ def _getInterfaces():
return [namestr[i:i + offset].split(b'\0', 1)[0].decode('utf-8') for i in range(0, outbytes, length)]
-def _getIpAndMac(ifname):
+def _getIpAndMac(ifname: str) -> typing.Tuple[typing.Optional[str], typing.Optional[str]]:
ip, mac = _getIpAddr(ifname), _getMacAddr(ifname)
return (ip, mac)
-def getComputerName():
+def getComputerName() -> str:
'''
Returns computer name, with no domain
'''
return socket.gethostname().split('.')[0]
-def getNetworkInfo():
+def getNetworkInfo() -> typing.Iterable[types.InterfaceInfo]:
for ifname in _getInterfaces():
ip, mac = _getIpAndMac(ifname)
- if mac != '00:00:00:00:00:00' and ip.startswith('169.254') is False: # Skips local interfaces & interfaces with no dhcp IPs
- yield utils.Bunch(name=ifname, mac=mac, ip=ip)
+ if mac != '00:00:00:00:00:00' and mac and ip and ip.startswith('169.254') is False: # Skips local interfaces & interfaces with no dhcp IPs
+ yield types.InterfaceInfo(name=ifname, mac=mac, ip=ip)
-def getDomainName():
+def getDomainName() -> str:
return ''
-def getLinuxVersion():
- lv = platform.linux_distribution()
+def getLinuxVersion() -> str:
+ import distro
+
+ lv = distro.linux_distribution()
return lv[0] + ', ' + lv[1]
-def reboot(flags=0):
+def reboot(flags: int = 0):
'''
Simple reboot using os command
'''
- # Workaround for dummy thread
- if six.PY3 is False:
- import threading
- threading._DummyThread._Thread__stop = lambda x: 42
-
subprocess.call(['/sbin/shutdown', 'now', '-r'])
-def loggoff():
+def loggoff() -> None:
'''
Right now restarts the machine...
'''
- # Workaround for dummy thread
- if six.PY3 is False:
- import threading
- threading._DummyThread._Thread__stop = lambda x: 42
-
subprocess.call(['/usr/bin/pkill', '-u', os.environ['USER']])
# subprocess.call(['/sbin/shutdown', 'now', '-r'])
# subprocess.call(['/usr/bin/systemctl', 'reboot', '-i'])
-def renameComputer(newName):
+def renameComputer(newName: str) -> None:
rename(newName)
-def joinDomain(domain, ou, account, password, executeInOneStep=False):
+def joinDomain(domain: str, ou: str, account: str, password: str, executeInOneStep: bool = False):
pass
-def changeUserPassword(user, oldPassword, newPassword):
+def changeUserPassword(user: str, oldPassword: str, newPassword: str) -> None:
'''
Simple password change for user using command line
'''
os.system('echo "{1}\n{1}" | /usr/bin/passwd {0} 2> /dev/null'.format(user, newPassword))
-class XScreenSaverInfo(ctypes.Structure):
+class XScreenSaverInfo(ctypes.Structure): # pylint: disable=too-few-public-methods
_fields_ = [('window', ctypes.c_long),
('state', ctypes.c_int),
('kind', ctypes.c_int),
@@ -198,26 +184,18 @@ try:
xss.XScreenSaverQueryExtension.restype = ctypes.c_int
xss.XScreenSaverAllocInfo.restype = ctypes.POINTER(XScreenSaverInfo) # Result in a XScreenSaverInfo structure
display = xlib.XOpenDisplay(None)
- info = xss.XScreenSaverAllocInfo()
+ xssInfo = xss.XScreenSaverAllocInfo()
except Exception: # Libraries not accesible, not found or whatever..
- xlib = xss = display = info = None
+ xlib = xss = display = xssInfo = None
-def initIdleDuration(atLeastSeconds):
- '''
- On linux we set the screensaver to at least required seconds, or we never will get "idle"
- '''
- # Workaround for dummy thread
- if six.PY3 is False:
- import threading
- threading._DummyThread._Thread__stop = lambda x: 42
-
+def initIdleDuration(atLeastSeconds: int) -> None:
subprocess.call(['/usr/bin/xset', 's', '{}'.format(atLeastSeconds + 30)])
# And now reset it
subprocess.call(['/usr/bin/xset', 's', 'reset'])
-def getIdleDuration():
+def getIdleDuration() -> float:
'''
Returns idle duration, in seconds
'''
@@ -232,16 +210,16 @@ def getIdleDuration():
if available != 1:
return 0 # No screen saver is available, no way of getting idle
- xss.XScreenSaverQueryInfo(display, xlib.XDefaultRootWindow(display), info)
+ xss.XScreenSaverQueryInfo(display, xlib.XDefaultRootWindow(display), xssInfo)
# Centos seems to set state to 1?? (weird, but it's happening don't know why... will try this way)
- if info.contents.state != 0 and 'centos' not in platform.linux_distribution()[0].lower().strip():
+ if xssInfo.contents.state != 0 and 'centos' not in platform.linux_distribution()[0].lower().strip():
return 3600 * 100 * 1000 # If screen saver is active, return a high enough value
- return info.contents.idle / 1000.0
+ return xssInfo.contents.idle / 1000.0
-def getCurrentUser():
+def getCurrentUser() -> str:
'''
Returns current logged in user
'''
diff --git a/actor/src/udsactor/rest.py b/actor/src/udsactor/rest.py
index e05e8786b..141292861 100644
--- a/actor/src/udsactor/rest.py
+++ b/actor/src/udsactor/rest.py
@@ -37,8 +37,7 @@ import typing
import requests
-from udsactor import VERSION
-
+from .info import VERSION
from .utils import exceptionToMessage
from .log import logger
@@ -74,6 +73,7 @@ try:
except Exception:
pass # In fact, isn't too important, but will log warns to logging file
+# Constants
def ensureResultIsOk(result: typing.Any) -> None:
if 'error' not in result:
@@ -92,7 +92,7 @@ class REST:
def __init__(self, host: str, validateCert: bool) -> None:
self.host = host
self.validateCert = validateCert
- self.url = "{}://{}/rest/actor/".format(('https', 'https'), self.host)
+ self.url = "https://{}/uds/rest/actor/v2".format(self.host)
# Disable logging requests messages except for errors, ...
logging.getLogger("requests").setLevel(logging.CRITICAL)
# Tries to disable all warnings
@@ -101,6 +101,31 @@ class REST:
except Exception:
pass
+ @staticmethod
+ def headers():
+ return {'content-type': 'application/json'}
+
+ def register(self, username: str, password: str, ip: str, mac: str, preCommand: str, runOnceCommand: str, postCommand: str) -> str:
+ data = {
+ 'username': username,
+ 'password': password,
+ 'ip': ip,
+ 'mac': mac,
+ 'preCommand': preCommand,
+ 'runOnceCommand': runOnceCommand,
+ 'postCommand': postCommand
+ }
+ try:
+ result: requests.Response = requests.post(self.url + '/register', data=json.dumps(data), headers=REST.headers(), verify=self.validateCert)
+ if result.ok:
+ return result.json()['result']
+ except (requests.ConnectionError, requests.ConnectTimeout) as e:
+ raise RESTConnectionError(str(e))
+ except Exception as e:
+ pass
+
+ raise RESTError(result.content)
+
def _getUrl(self, method, key=None, ids=None):
url = self.url + method
params = []
@@ -120,7 +145,7 @@ class REST:
if data is None:
# Old requests version does not support verify, but they do not checks ssl certificate by default
if self.newerRequestLib:
- r = requests.get(url, verify=VERIFY_CERT)
+ r = requests.get(url, verify=self.validateCert)
else:
logger.debug('Requesting with old')
r = requests.get(url) # Always ignore certs??
@@ -128,14 +153,14 @@ class REST:
if data == '':
data = '{"dummy": true}' # Ensures no proxy rewrites POST as GET because body is empty...
if self.newerRequestLib:
- r = requests.post(url, data=data, headers={'content-type': 'application/json'}, verify=VERIFY_CERT)
+ r = requests.post(url, data=data, headers={'content-type': 'application/json'}, verify=self.validateCert)
else:
logger.debug('Requesting with old')
r = requests.post(url, data=data, headers={'content-type': 'application/json'})
# From versions of requests, content maybe bytes or str. We need str for json.loads
content = r.content
- if not isinstance(content, six.text_type):
+ if not isinstance(content, str):
content = content.decode('utf8')
r = json.loads(content) # Using instead of r.json() to make compatible with oooold rquests lib versions
except requests.exceptions.RequestException as e:
@@ -184,7 +209,7 @@ class REST:
raise ConnectionError('REST api has not been initialized')
if processData:
- if data and not isinstance(data, six.text_type):
+ if data and not isinstance(data, str):
data = data.decode('utf8')
data = json.dumps({'data': data})
url = self._getUrl('/'.join([self.uuid, msg]))
diff --git a/actor/src/udsactor/types.py b/actor/src/udsactor/types.py
new file mode 100644
index 000000000..2e6307299
--- /dev/null
+++ b/actor/src/udsactor/types.py
@@ -0,0 +1,6 @@
+import typing
+
+class InterfaceInfo(typing.NamedTuple):
+ name: str
+ mac: typing.Optional[str]
+ ip: typing.Optional[str]
diff --git a/actor/src/udsactor/windows/operations.py b/actor/src/udsactor/windows/operations.py
index eb04653d8..18d5fdae3 100644
--- a/actor/src/udsactor/windows/operations.py
+++ b/actor/src/udsactor/windows/operations.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
-# Copyright (c) 2014 Virtual Cable S.L.
+# Copyright (c) 2014-2019 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@@ -29,13 +29,11 @@
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
-from __future__ import unicode_literals
-
-import win32com.client # @UnresolvedImport, pylint: disable=import-error
-import win32net # @UnresolvedImport, pylint: disable=import-error
-import win32security # @UnresolvedImport, pylint: disable=import-error
-import win32api # @UnresolvedImport, pylint: disable=import-error
-import win32con # @UnresolvedImport, pylint: disable=import-error
+import win32com.client
+import win32net
+import win32security
+import win32api
+import win32con
import ctypes
from ctypes.wintypes import DWORD, LPCWSTR
import os
diff --git a/actor/src/ui/setup_dialog_ui.py b/actor/src/ui/setup_dialog_ui.py
index 87cd2f91f..4935a3f87 100644
--- a/actor/src/ui/setup_dialog_ui.py
+++ b/actor/src/ui/setup_dialog_ui.py
@@ -12,7 +12,7 @@ class Ui_UdsActorSetupDialog(object):
def setupUi(self, UdsActorSetupDialog):
UdsActorSetupDialog.setObjectName("UdsActorSetupDialog")
UdsActorSetupDialog.setWindowModality(QtCore.Qt.WindowModal)
- UdsActorSetupDialog.resize(400, 293)
+ UdsActorSetupDialog.resize(590, 253)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
@@ -22,6 +22,7 @@ class Ui_UdsActorSetupDialog(object):
font.setFamily("Verdana")
font.setPointSize(9)
UdsActorSetupDialog.setFont(font)
+ UdsActorSetupDialog.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu)
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(":/img/img/uds-icon.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
UdsActorSetupDialog.setWindowIcon(icon)
@@ -29,29 +30,27 @@ class Ui_UdsActorSetupDialog(object):
UdsActorSetupDialog.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates))
UdsActorSetupDialog.setSizeGripEnabled(False)
UdsActorSetupDialog.setModal(True)
- self.testButton = QtWidgets.QPushButton(UdsActorSetupDialog)
- self.testButton.setEnabled(False)
- self.testButton.setGeometry(QtCore.QRect(20, 220, 361, 23))
- self.testButton.setObjectName("testButton")
- self.saveButton = QtWidgets.QPushButton(UdsActorSetupDialog)
- self.saveButton.setEnabled(False)
- self.saveButton.setGeometry(QtCore.QRect(20, 250, 101, 23))
+ self.registerButton = QtWidgets.QPushButton(UdsActorSetupDialog)
+ self.registerButton.setEnabled(False)
+ self.registerButton.setGeometry(QtCore.QRect(10, 220, 181, 23))
+ self.registerButton.setMinimumSize(QtCore.QSize(181, 0))
+ self.registerButton.setObjectName("registerButton")
+ self.closeButton = QtWidgets.QPushButton(UdsActorSetupDialog)
+ self.closeButton.setGeometry(QtCore.QRect(410, 220, 171, 23))
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
- sizePolicy.setHeightForWidth(self.saveButton.sizePolicy().hasHeightForWidth())
- self.saveButton.setSizePolicy(sizePolicy)
- self.saveButton.setObjectName("saveButton")
- self.cancelButton = QtWidgets.QPushButton(UdsActorSetupDialog)
- self.cancelButton.setGeometry(QtCore.QRect(260, 250, 121, 23))
- sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
- sizePolicy.setHorizontalStretch(0)
- sizePolicy.setVerticalStretch(0)
- sizePolicy.setHeightForWidth(self.cancelButton.sizePolicy().hasHeightForWidth())
- self.cancelButton.setSizePolicy(sizePolicy)
- self.cancelButton.setObjectName("cancelButton")
- self.layoutWidget = QtWidgets.QWidget(UdsActorSetupDialog)
- self.layoutWidget.setGeometry(QtCore.QRect(20, 20, 361, 191))
+ sizePolicy.setHeightForWidth(self.closeButton.sizePolicy().hasHeightForWidth())
+ self.closeButton.setSizePolicy(sizePolicy)
+ self.closeButton.setMinimumSize(QtCore.QSize(171, 0))
+ self.closeButton.setObjectName("closeButton")
+ self.tabWidget = QtWidgets.QTabWidget(UdsActorSetupDialog)
+ self.tabWidget.setGeometry(QtCore.QRect(10, 10, 571, 201))
+ self.tabWidget.setObjectName("tabWidget")
+ self.tab_uds = QtWidgets.QWidget()
+ self.tab_uds.setObjectName("tab_uds")
+ self.layoutWidget = QtWidgets.QWidget(self.tab_uds)
+ self.layoutWidget.setGeometry(QtCore.QRect(10, 10, 551, 151))
self.layoutWidget.setObjectName("layoutWidget")
self.formLayout = QtWidgets.QFormLayout(self.layoutWidget)
self.formLayout.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow)
@@ -71,13 +70,13 @@ class Ui_UdsActorSetupDialog(object):
self.username = QtWidgets.QLineEdit(self.layoutWidget)
self.username.setObjectName("username")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.username)
+ self.label_password = QtWidgets.QLabel(self.layoutWidget)
+ self.label_password.setObjectName("label_password")
+ self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_password)
self.password = QtWidgets.QLineEdit(self.layoutWidget)
self.password.setEchoMode(QtWidgets.QLineEdit.Password)
self.password.setObjectName("password")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.password)
- self.label_password = QtWidgets.QLabel(self.layoutWidget)
- self.label_password.setObjectName("label_password")
- self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_password)
self.label_security = QtWidgets.QLabel(self.layoutWidget)
self.label_security.setObjectName("label_security")
self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.label_security)
@@ -86,7 +85,68 @@ class Ui_UdsActorSetupDialog(object):
self.validateCertificate.addItem("")
self.validateCertificate.addItem("")
self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.validateCertificate)
- self.logLevelComboBox = QtWidgets.QComboBox(self.layoutWidget)
+ self.tabWidget.addTab(self.tab_uds, "")
+ self.tab_advanced = QtWidgets.QWidget()
+ self.tab_advanced.setObjectName("tab_advanced")
+ self.layoutWidget_2 = QtWidgets.QWidget(self.tab_advanced)
+ self.layoutWidget_2.setGeometry(QtCore.QRect(10, 10, 551, 151))
+ self.layoutWidget_2.setObjectName("layoutWidget_2")
+ self.formLayout_2 = QtWidgets.QFormLayout(self.layoutWidget_2)
+ self.formLayout_2.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow)
+ self.formLayout_2.setContentsMargins(0, 0, 0, 0)
+ self.formLayout_2.setVerticalSpacing(16)
+ self.formLayout_2.setObjectName("formLayout_2")
+ self.label_host_2 = QtWidgets.QLabel(self.layoutWidget_2)
+ self.label_host_2.setObjectName("label_host_2")
+ self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label_host_2)
+ self.horizontalLayout = QtWidgets.QHBoxLayout()
+ self.horizontalLayout.setContentsMargins(-1, 0, -1, -1)
+ self.horizontalLayout.setSpacing(4)
+ self.horizontalLayout.setObjectName("horizontalLayout")
+ self.preCommand = QtWidgets.QLineEdit(self.layoutWidget_2)
+ self.preCommand.setAcceptDrops(False)
+ self.preCommand.setWhatsThis("")
+ self.preCommand.setObjectName("preCommand")
+ self.horizontalLayout.addWidget(self.preCommand)
+ self.browsePreconnectButton = QtWidgets.QPushButton(self.layoutWidget_2)
+ self.browsePreconnectButton.setObjectName("browsePreconnectButton")
+ self.horizontalLayout.addWidget(self.browsePreconnectButton)
+ self.formLayout_2.setLayout(0, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout)
+ self.label_username_2 = QtWidgets.QLabel(self.layoutWidget_2)
+ self.label_username_2.setObjectName("label_username_2")
+ self.formLayout_2.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_username_2)
+ self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
+ self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1)
+ self.horizontalLayout_2.setSpacing(4)
+ self.horizontalLayout_2.setObjectName("horizontalLayout_2")
+ self.runonceCommand = QtWidgets.QLineEdit(self.layoutWidget_2)
+ self.runonceCommand.setWhatsThis("")
+ self.runonceCommand.setObjectName("runonceCommand")
+ self.horizontalLayout_2.addWidget(self.runonceCommand)
+ self.browseRunOnceButton = QtWidgets.QPushButton(self.layoutWidget_2)
+ self.browseRunOnceButton.setObjectName("browseRunOnceButton")
+ self.horizontalLayout_2.addWidget(self.browseRunOnceButton)
+ self.formLayout_2.setLayout(1, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_2)
+ self.label_password_2 = QtWidgets.QLabel(self.layoutWidget_2)
+ self.label_password_2.setObjectName("label_password_2")
+ self.formLayout_2.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_password_2)
+ self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
+ self.horizontalLayout_3.setContentsMargins(-1, 0, -1, -1)
+ self.horizontalLayout_3.setSpacing(4)
+ self.horizontalLayout_3.setObjectName("horizontalLayout_3")
+ self.postConfigCommand = QtWidgets.QLineEdit(self.layoutWidget_2)
+ self.postConfigCommand.setWhatsThis("")
+ self.postConfigCommand.setEchoMode(QtWidgets.QLineEdit.Normal)
+ self.postConfigCommand.setObjectName("postConfigCommand")
+ self.horizontalLayout_3.addWidget(self.postConfigCommand)
+ self.browsePostConfigButton = QtWidgets.QPushButton(self.layoutWidget_2)
+ self.browsePostConfigButton.setObjectName("browsePostConfigButton")
+ self.horizontalLayout_3.addWidget(self.browsePostConfigButton)
+ self.formLayout_2.setLayout(2, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_3)
+ self.label_loglevel = QtWidgets.QLabel(self.layoutWidget_2)
+ self.label_loglevel.setObjectName("label_loglevel")
+ self.formLayout_2.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.label_loglevel)
+ self.logLevelComboBox = QtWidgets.QComboBox(self.layoutWidget_2)
self.logLevelComboBox.setFrame(True)
self.logLevelComboBox.setObjectName("logLevelComboBox")
self.logLevelComboBox.addItem("")
@@ -97,47 +157,56 @@ class Ui_UdsActorSetupDialog(object):
self.logLevelComboBox.setItemText(2, "ERROR")
self.logLevelComboBox.addItem("")
self.logLevelComboBox.setItemText(3, "FATAL")
- self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.logLevelComboBox)
- self.label_loglevel = QtWidgets.QLabel(self.layoutWidget)
- self.label_loglevel.setObjectName("label_loglevel")
- self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.label_loglevel)
+ self.formLayout_2.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.logLevelComboBox)
+ self.tabWidget.addTab(self.tab_advanced, "")
self.retranslateUi(UdsActorSetupDialog)
+ self.tabWidget.setCurrentIndex(0)
self.logLevelComboBox.setCurrentIndex(1)
- self.cancelButton.pressed.connect(UdsActorSetupDialog.cancelAndDiscard)
- self.testButton.pressed.connect(UdsActorSetupDialog.testParameters)
- self.saveButton.pressed.connect(UdsActorSetupDialog.acceptAndSave)
+ self.closeButton.clicked.connect(UdsActorSetupDialog.finish)
+ self.registerButton.clicked.connect(UdsActorSetupDialog.registerWithUDS)
self.host.textChanged['QString'].connect(UdsActorSetupDialog.textChanged)
self.username.textChanged['QString'].connect(UdsActorSetupDialog.textChanged)
self.password.textChanged['QString'].connect(UdsActorSetupDialog.textChanged)
+ self.browsePreconnectButton.clicked.connect(UdsActorSetupDialog.browsePreconnect)
+ self.browsePostConfigButton.clicked.connect(UdsActorSetupDialog.browsePostConfig)
+ self.browseRunOnceButton.clicked.connect(UdsActorSetupDialog.browseRunOnce)
QtCore.QMetaObject.connectSlotsByName(UdsActorSetupDialog)
def retranslateUi(self, UdsActorSetupDialog):
_translate = QtCore.QCoreApplication.translate
- UdsActorSetupDialog.setWindowTitle(_translate("UdsActorSetupDialog", "UDS Actor Setup"))
- self.testButton.setToolTip(_translate("UdsActorSetupDialog", "Click to test the selecter parameters"))
- self.testButton.setWhatsThis(_translate("UdsActorSetupDialog", "
Click on this button to test the server host and master key parameters.
A window will be displayed with results after the test is executed.
This button will only be active if all parameters are filled.
"))
- self.testButton.setText(_translate("UdsActorSetupDialog", "Test parameters"))
- self.saveButton.setToolTip(_translate("UdsActorSetupDialog", "Accepts changes and saves them"))
- self.saveButton.setWhatsThis(_translate("UdsActorSetupDialog", "Clicking on this button will accept all changes and save them, closing the configuration window"))
- self.saveButton.setText(_translate("UdsActorSetupDialog", "Accept && Save"))
- self.cancelButton.setToolTip(_translate("UdsActorSetupDialog", "Cancel all changes and discard them"))
- self.cancelButton.setWhatsThis(_translate("UdsActorSetupDialog", "Discards all changes and closes the configuration window"))
- self.cancelButton.setText(_translate("UdsActorSetupDialog", "Cancel && Discard"))
+ UdsActorSetupDialog.setWindowTitle(_translate("UdsActorSetupDialog", "UDS Actor Configuration Tool"))
+ self.registerButton.setToolTip(_translate("UdsActorSetupDialog", "Click to test the selecter parameters"))
+ self.registerButton.setWhatsThis(_translate("UdsActorSetupDialog", "Click on this button to test the server host and master key parameters.
A window will be displayed with results after the test is executed.
This button will only be active if all parameters are filled.
"))
+ self.registerButton.setText(_translate("UdsActorSetupDialog", "Register with UDS"))
+ self.closeButton.setToolTip(_translate("UdsActorSetupDialog", "Cancel all changes and discard them"))
+ self.closeButton.setWhatsThis(_translate("UdsActorSetupDialog", "Discards all changes and closes the configuration window"))
+ self.closeButton.setText(_translate("UdsActorSetupDialog", "Close"))
self.label_host.setText(_translate("UdsActorSetupDialog", "UDS Server"))
self.host.setToolTip(_translate("UdsActorSetupDialog", "Uds Broker Server Addres. Use IP or FQDN"))
self.host.setWhatsThis(_translate("UdsActorSetupDialog", "Enter here the UDS Broker Addres using either its IP address or its FQDN address"))
self.label_username.setText(_translate("UdsActorSetupDialog", "Username"))
self.username.setToolTip(_translate("UdsActorSetupDialog", "UDS user with administration rights (Will not be stored on template)"))
self.username.setWhatsThis(_translate("UdsActorSetupDialog", "Administrator user on UDS Server.
Note: This credential will not be stored on client. Will be used to obtain an unique key for this image.
"))
+ self.label_password.setText(_translate("UdsActorSetupDialog", "Password"))
self.password.setToolTip(_translate("UdsActorSetupDialog", "Password for user (Will not be stored on template)"))
self.password.setWhatsThis(_translate("UdsActorSetupDialog", "Administrator password for the user on UDS Server.
Note: This credential will not be stored on client. Will be used to obtain an unique key for this image.
"))
- self.label_password.setText(_translate("UdsActorSetupDialog", "Password"))
self.label_security.setText(_translate("UdsActorSetupDialog", "Security"))
self.validateCertificate.setToolTip(_translate("UdsActorSetupDialog", "Select communication security with broker"))
self.validateCertificate.setWhatsThis(_translate("UdsActorSetupDialog", "Select the security for communications with UDS Broker.
The recommended method of communication is Use SSL, but selection needs to be acording to your broker configuration.
"))
self.validateCertificate.setItemText(0, _translate("UdsActorSetupDialog", "Ignore certificate"))
self.validateCertificate.setItemText(1, _translate("UdsActorSetupDialog", "Verify certificate"))
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_uds), _translate("UdsActorSetupDialog", "UDS Server"))
+ self.label_host_2.setText(_translate("UdsActorSetupDialog", "Preconnect"))
+ self.preCommand.setToolTip(_translate("UdsActorSetupDialog", "Pre connection command. Executed just before the user is connected to machine."))
+ self.browsePreconnectButton.setText(_translate("UdsActorSetupDialog", "Browse"))
+ self.label_username_2.setText(_translate("UdsActorSetupDialog", "Runonce"))
+ self.runonceCommand.setToolTip(_translate("UdsActorSetupDialog", "Run once command. Executed on first boot, just before UDS does anything."))
+ self.browseRunOnceButton.setText(_translate("UdsActorSetupDialog", "Browse"))
+ self.label_password_2.setText(_translate("UdsActorSetupDialog", "Postconfig"))
+ self.postConfigCommand.setToolTip(_translate("UdsActorSetupDialog", "Command to execute after UDS finalizes the VM configuration."))
+ self.browsePostConfigButton.setText(_translate("UdsActorSetupDialog", "Browse"))
self.label_loglevel.setText(_translate("UdsActorSetupDialog", "Log Level"))
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_advanced), _translate("UdsActorSetupDialog", "Advanced"))
from ui import uds_rc
diff --git a/actors/src/udsactor/__init__.py b/actors/src/udsactor/__init__.py
index 339282daa..7155431f8 100644
--- a/actors/src/udsactor/__init__.py
+++ b/actors/src/udsactor/__init__.py
@@ -34,7 +34,7 @@ from __future__ import unicode_literals
# On centos, old six release does not includes byte2int, nor six.PY2
import six
-VERSION = '2.5.0'
+VERSION = '3.0.0'
__title__ = 'udsactor'
__version__ = VERSION
diff --git a/server/src/uds/REST/__init__.py b/server/src/uds/REST/__init__.py
index 5a795d158..4a331bacb 100644
--- a/server/src/uds/REST/__init__.py
+++ b/server/src/uds/REST/__init__.py
@@ -57,7 +57,7 @@ from . import processors
logger = logging.getLogger(__name__)
-__all__ = [str(v) for v in ['Handler', 'Dispatcher']]
+__all__ = ['Handler', 'Dispatcher']
AUTH_TOKEN_HEADER = 'X-Auth-Token'
diff --git a/actor/src/udsactor/operations.py b/server/src/uds/REST/methods/actor_v2.py
similarity index 53%
rename from actor/src/udsactor/operations.py
rename to server/src/uds/REST/methods/actor_v2.py
index fc241ab48..703922691 100644
--- a/actor/src/udsactor/operations.py
+++ b/server/src/uds/REST/methods/actor_v2.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
+
#
-# Copyright (c) 2014 Virtual Cable S.L.
+# Copyright (c) 2014-2019 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@@ -26,15 +27,55 @@
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-'''
+"""
@author: Adolfo Gómez, dkmaster at dkmon dot com
-'''
-# pylint: disable=unused-wildcard-import,wildcard-import
+"""
+import logging
+import typing
-from __future__ import unicode_literals
+from django.utils.translation import ugettext as _
-import sys
-if sys.platform == 'win32':
- from .windows.operations import * # @UnusedWildImport
-else:
- from .linux.operations import * # @UnusedWildImport
+from uds.models.util import getSqlDatetimeAsUnix
+from uds.core import VERSION
+from ..handlers import Handler
+
+logger = logging.getLogger(__name__)
+
+def actorResult(result: typing.Any = None, error: typing.Optional[str] = None) -> typing.MutableMapping[str, typing.Any]:
+ result = result or ''
+ res = {'result': result, 'stamp': getSqlDatetimeAsUnix()}
+ if error:
+ res['error'] = error
+ return res
+
+# Enclosed methods under /actor path
+class ActorV2(Handler):
+ """
+ Processes actor requests
+ """
+ authenticated = False # Actor requests are not authenticated normally
+ path = 'actor'
+ name = 'v2'
+
+ def get(self): # pylint: disable=too-many-return-statements
+ """
+ Processes get requests
+ """
+ logger.debug('Actor args for GET: %s', self._args)
+
+ return actorResult({'version': VERSION, 'required': '3.0.0'})
+
+class ActorV2Register(Handler):
+ """
+ Tests the process
+ """
+ authenticated = False # Actor requests are not authenticated normally
+ path = 'actor/v2'
+ name = 'register'
+
+ def get(self):
+ return actorResult('Ok')
+
+ def post(self):
+ logger.debug('Args: %s, Params: %s', self._args, self._params)
+ return actorResult('ok')
diff --git a/server/src/uds/transports/HTML5RDP/html5rdp.py b/server/src/uds/transports/HTML5RDP/html5rdp.py
index 74d957027..bf0fa326a 100644
--- a/server/src/uds/transports/HTML5RDP/html5rdp.py
+++ b/server/src/uds/transports/HTML5RDP/html5rdp.py
@@ -251,9 +251,11 @@ class HTML5RDPTransport(transports.Transport):
ticket = models.TicketStore.create(params, validity=self.ticketValidity.num())
- return HttpResponseRedirect("{}/transport/?{}.{}&{}".format(
- self.guacamoleServer.value,
- ticket,
- scrambler,
- request.build_absolute_uri(reverse('utility.closer'))
- ))
+ return HttpResponseRedirect(
+ "{}/transport/?{}.{}&{}".format(
+ self.guacamoleServer.value,
+ ticket,
+ scrambler,
+ 'javascript:window.close();'
+ )
+ )