Compare commits

...

10 Commits

Author SHA1 Message Date
06dad9e52d Use FreeSimpleGui instead 2025-07-03 22:43:35 +02:00
jpattWPC
f714f794b9 Set PySimpleGUI version
As PySimpleGUI will be licensed starting at version 5.0.0, I am capping the version to prevent this. Alternate UI libraries will need to be implemented.
2024-02-12 18:22:24 -06:00
jpattWPC
e9a653ae98 Update build_vdiclient.bat 2024-01-15 10:20:58 -06:00
jpattWPC
6d250faed5 Fix inidebug window 2023-11-24 17:44:44 -06:00
joshpatten
4966609e45
Merge pull request #75 from dariomolinari/patch-1
Update README.md
2023-10-17 16:00:35 -05:00
Dario Molinari
b7491f9a97
Update README.md
Added a paragraph for Fedora/CentOS/RHEL
2023-10-17 16:26:13 +01:00
jpattWPC
3e69e77044 Update README.md
Change python version
2023-10-15 20:01:41 -05:00
jpattWPC
d95c96cf25 Update README.md 2023-10-15 19:17:27 -05:00
jpattWPC
865496bf79 Update README 2023-10-15 19:15:47 -05:00
jpattWPC
f407057fcb Add Multi-Cluster Support
Please review changes to the INI file. Legacy format is still supported, but format must be changed if you wish to use multiple clusters.
2023-10-15 19:04:22 -05:00
10 changed files with 388 additions and 102 deletions

View File

@ -2,6 +2,8 @@
This project's focus is to create a simple VDI client intended for mass deployment. This VDI client connects directly to Proxmox VE and allows users to connect (via Spice) to any VMs they have permission to access. This project's focus is to create a simple VDI client intended for mass deployment. This VDI client connects directly to Proxmox VE and allows users to connect (via Spice) to any VMs they have permission to access.
Defining multiple Proxmox clusters is possible and can allow end users to easily select which 'server group' they wish to connect to:
![Login Screen](screenshots/login.png) ![Login Screen](screenshots/login.png)
![Login Screen with OTP](screenshots/login-totp.png) ![Login Screen with OTP](screenshots/login-totp.png)
@ -20,7 +22,7 @@ PVE VDI Client **REQUIRES** a configuration file to function. The client searche
* /etc/vdiclient/vdiclient.ini * /etc/vdiclient/vdiclient.ini
* /usr/local/etc/vdiclient/vdiclient.ini * /usr/local/etc/vdiclient/vdiclient.ini
Please refer to **vdiclient.ini.example** for all available config file options Please refer to [vdiclient.ini.example](https://github.com/joshpatten/PVE-VDIClient/blob/main/vdiclient.ini.example) for all available config file options
If you encounter any issues feel free to submit an issue report. If you encounter any issues feel free to submit an issue report.
@ -64,7 +66,7 @@ Please visit the [releases](https://github.com/joshpatten/PVE-VDIClient/releases
If you need to customize the installation, such as to sign the executable and MSI, you may download and install the [WIX toolset](https://wixtoolset.org/releases/) and use the build_vdiclient.bat file to build a new MSI. If you need to customize the installation, such as to sign the executable and MSI, you may download and install the [WIX toolset](https://wixtoolset.org/releases/) and use the build_vdiclient.bat file to build a new MSI.
you will need to download the latest 3.10 python release, and run the following commands to install the necessary packages: you will need to download the latest 3.12 python release, and run the following commands to install the necessary packages:
requirements.bat requirements.bat
@ -80,6 +82,18 @@ Run the following commands on a Debian/Ubuntu Linux system to install the approp
cp vdiclient.py /usr/local/bin cp vdiclient.py /usr/local/bin
chmod +x /usr/local/bin/vdiclient.py chmod +x /usr/local/bin/vdiclient.py
## Fedora/CentOS/RHEL Installation
Run the following commands on a Debian/Ubuntu Linux system to install the appropriate prerequisites
dnf install python3-pip python3-tkinter virt-viewer git
git clone https://github.com/joshpatten/PVE-VDIClient.git
cd ./PVE-VDIClient/
chmod +x requirements.sh
./requirements.sh
cp vdiclient.py /usr/local/bin
chmod +x /usr/local/bin/vdiclient.py
## Build Debian/Ubuntu Linux Binary ## Build Debian/Ubuntu Linux Binary
Run the following commands if you wish to build a binary on a Debian/Ubuntu Linux system Run the following commands if you wish to build a binary on a Debian/Ubuntu Linux system

View File

@ -1,24 +1,7 @@
@echo off @echo off
pyinstaller --noconsole --noconfirm --hidden-import proxmoxer.backends --hidden-import proxmoxer.backends.https --hidden-import proxmoxer.backends.https.AuthenticationError --hidden-import proxmoxer.core --hidden-import proxmoxer.core.ResourceException --hidden-import subprocess.TimeoutExpired --hidden-import subprocess.CalledProcessError --hidden-import requests.exceptions --hidden-import requests.exceptions.ReadTimeout --hidden-import requests.exceptions.ConnectTimeout --hidden-import requests.exceptions.ConnectionError -i vdiicon.ico vdiclient.py pyinstaller --noconsole --noconfirm --hidden-import proxmoxer.backends --hidden-import proxmoxer.backends.https --hidden-import proxmoxer.backends.https.AuthenticationError --hidden-import proxmoxer.core --hidden-import proxmoxer.core.ResourceException --hidden-import subprocess.TimeoutExpired --hidden-import subprocess.CalledProcessError --hidden-import requests.exceptions --hidden-import requests.exceptions.ReadTimeout --hidden-import requests.exceptions.ConnectTimeout --hidden-import requests.exceptions.ConnectionError --noupx -i vdiicon.ico vdiclient.py
copy vdiclient.png dist\vdiclient copy vdiclient.png dist\vdiclient
copy vdiicon.ico dist\vdiclient copy vdiicon.ico dist\vdiclient
del dist\vdiclient\opengl32sw.dll
del dist\vdiclient\libGLESv2.dll
del dist\vdiclient\d3dcompiler_47.dll
del dist\vdiclient\Qt5Pdf.dll
del dist\vdiclient\Qt5VirtualKeyboard.dll
del dist\vdiclient\Qt5WebSockets.dll
del dist\vdiclient\Qt5Quick.dll
del dist\vdiclient\PySide2\plugins\imageformats\qgif.dll
del dist\vdiclient\PySide2\plugins\imageformats\qjpeg.dll
del dist\vdiclient\PySide2\plugins\imageformats\qpdf.dll
del dist\vdiclient\PySide2\plugins\imageformats\qsvg.dll
del dist\vdiclient\PySide2\plugins\imageformats\qtga.dll
del dist\vdiclient\PySide2\plugins\imageformats\qtiff.dll
del dist\vdiclient\PySide2\plugins\imageformats\qwbmp.dll
del dist\vdiclient\PySide2\plugins\imageformats\qwebp.dll
del dist\vdiclient\PySide2\plugins\platforminputcontexts\qtvirtualkeyboardplugin.dll
del /Q dist\vdiclient\PySide2\translations\*
cd dist cd dist
python createmsi.py vdiclient.json python createmsi.py vdiclient.json
cd .. cd ..

2
dist/vdiclient.json vendored
View File

@ -1,6 +1,6 @@
{ {
"upgrade_guid" : "46cbad92-353e-4b28-9bee-83950991dad8", "upgrade_guid" : "46cbad92-353e-4b28-9bee-83950991dad8",
"version" : "1.3.01", "version" : "2.0.2",
"product_name" : "VDI Client", "product_name" : "VDI Client",
"manufacturer" : "Josh Patten", "manufacturer" : "Josh Patten",
"name" : "VDI Client", "name" : "VDI Client",

View File

@ -1,6 +1,6 @@
@echo off @echo off
pip install pyinstaller pip install pyinstaller
pip install proxmoxer pip install proxmoxer
pip install PySimpleGUI pip install "PySimpleGUI<5.0.0"
pip install requests pip install requests
pip install pywin32 pip install pywin32

4
requirements.sh Normal file → Executable file
View File

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
pip3 install proxmoxer pip3 install proxmoxer
pip3 install PySimpleGUI pip3 install FreeSimpleGUI
pip3 install requests pip3 install requests

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -23,7 +23,17 @@ guest_type = both
#window_width = 800 #window_width = 800
#window_height = 600 #window_height = 600
[Authentication] # PVE-VDIClient supports multiple clusters. Define them with sections that start with Hosts. followed by the name
# you wish to display to your end users. This example is Hosts.PVE which would display PVE to your users
[Hosts.PVE]
# JSON dictionary of servers in the cluster
# Format is 'IP/FQDN': PORT
# NOTE: MAKE SURE THAT ALL LINES ARE INDENTED
hostpool = {
"10.10.10.100" : 8006,
"10.10.10.111" : 8006,
"pve1.example.com" : 8006
}
# This is the authentication backend that will be used to authenticate # This is the authentication backend that will be used to authenticate
auth_backend = pve auth_backend = pve
# If enabled, 2FA TOTP entry dialog will show # If enabled, 2FA TOTP entry dialog will show
@ -31,6 +41,35 @@ auth_totp = false
# If disabled, TLS certificate will not be checked # If disabled, TLS certificate will not be checked
tls_verify = false tls_verify = false
# User name (if using token) # User name (if using token)
# NOTE: If only one cluster is defined, this will auto-login
# If user, token_name, and token_value are set
#user = user
# API Token Name
#token_name = dvi
# API Token Value
#token_value = xxx-x-x-x-xxx
# Password Reset Command Launch. Has to be full executable Command
#pwresetcmd = start chrome --app=http://pwreset.example.com
# Automatically connect to a VMID upon authentication
#auto_vmid = 100
# An additional cluster definition
#[Hosts.PVE2]
# JSON dictionary of servers in the cluster
# Format is 'IP/FQDN': PORT
#hostpool = {
# "10.10.10.100" : 8006,
# "10.10.10.111" : 8006,
# "pve1.example.com" : 8006
# }
# This is the authentication backend that will be used to authenticate
#auth_backend = pve
# If enabled, 2FA TOTP entry dialog will show
#auth_totp = false
# If disabled, TLS certificate will not be checked
#tls_verify = false
# User name (if using token)
# NOTE: If only one cluster is defined, this will auto-login
#user = user #user = user
# API Token Name # API Token Name
#token_name = dvi #token_name = dvi
@ -41,10 +80,6 @@ tls_verify = false
# Automatically connect to a VMID upon authentication # Automatically connect to a VMID upon authentication
#auto_vmid = 100 #auto_vmid = 100
[Hosts]
# Hosts are entered as `IP/FQDN = Port`
10.10.10.100 = 8006
pve1.example.com = 8006
[SpiceProxyRedirect] [SpiceProxyRedirect]
# The Spice Proxy provided by the Proxmox API may need to have its host/port rewritten # The Spice Proxy provided by the Proxmox API may need to have its host/port rewritten

392
vdiclient.py Normal file → Executable file
View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import proxmoxer # pip install proxmoxer import proxmoxer # pip install proxmoxer
import PySimpleGUI as sg # pip install PySimpleGUI import FreeSimpleGUI as sg # pip install PySimpleGUI
gui = 'TK' gui = 'TK'
import requests import requests
from datetime import datetime from datetime import datetime
@ -9,6 +9,7 @@ import argparse
import random import random
import sys import sys
import os import os
import json
import subprocess import subprocess
from time import sleep from time import sleep
from io import StringIO from io import StringIO
@ -16,30 +17,23 @@ from io import StringIO
class G: class G:
hostpool = []
spiceproxy_conv = {} spiceproxy_conv = {}
proxmox = None proxmox = None
icon = None icon = None
vvcmd = None vvcmd = None
scaling = 1 scaling = 1
######### #########
title = 'VDI Login' inidebug = False
backend = 'pve' addl_params = None
user = ""
token_name = None
token_value = None
totp = False
imagefile = None imagefile = None
kiosk = False kiosk = False
viewer_kiosk = True viewer_kiosk = True
fullscreen = True fullscreen = True
verify_ssl = True
inidebug = False
show_reset = False show_reset = False
show_hibernate = False show_hibernate = False
addl_params = None current_hostset = 'DEFAULT'
pwresetcmd = None title = 'VDI Login'
auto_vmid = None hosts = {}
theme = 'LightBlue' theme = 'LightBlue'
guest_type = 'both' guest_type = 'both'
width = None width = None
@ -123,37 +117,99 @@ def loadconfig(config_location = None, config_type='file', config_username = Non
G.width = config['General'].getint('window_width') G.width = config['General'].getint('window_width')
if 'window_height' in config['General']: if 'window_height' in config['General']:
G.height = config['General'].getint('window_height') G.height = config['General'].getint('window_height')
if not 'Authentication' in config: if 'Authentication' in config: #Legacy configuration
win_popup_button(f'Unable to read supplied configuration:\nNo `Authentication` section defined!', 'OK') G.hosts['DEFAULT'] = {
return False 'hostpool' : [],
else: 'backend' : 'pve',
if 'auth_backend' in config['Authentication']: 'user' : "",
G.backend = config['Authentication']['auth_backend'] 'token_name' : None,
if 'auth_totp' in config['Authentication']: 'token_value' : None,
G.totp = config['Authentication'].getboolean('auth_totp') 'totp' : False,
if 'tls_verify' in config['Authentication']: 'verify_ssl' : True,
G.verify_ssl = config['Authentication'].getboolean('tls_verify') 'pwresetcmd' : None,
if 'user' in config['Authentication']: 'auto_vmid' : None,
G.user = config['Authentication']['user'] 'knock_seq': []
if 'token_name' in config['Authentication']: }
G.token_name = config['Authentication']['token_name']
if 'token_value' in config['Authentication']:
G.token_value = config['Authentication']['token_value']
if 'pwresetcmd' in config['Authentication']:
G.pwresetcmd = config['Authentication']['pwresetcmd']
if 'auto_vmid' in config['Authentication']:
G.auto_vmid = config['Authentication'].getint('auto_vmid')
if 'knock_ip' in config['Authentication']:
G.knock
if not 'Hosts' in config: if not 'Hosts' in config:
win_popup_button(f'Unable to read supplied configuration:\nNo `Hosts` section defined!', 'OK') win_popup_button(f'Unable to read supplied configuration:\nNo `Hosts` section defined!', 'OK')
return False return False
else:
for key in config['Hosts']: for key in config['Hosts']:
G.hostpool.append({ G.hosts['DEFAULT']['hostpool'].append({
'host': key, 'host': key,
'port': int(config['Hosts'][key]) 'port': int(config['Hosts'][key])
}) })
if 'auth_backend' in config['Authentication']:
G.hosts['DEFAULT']['backend'] = config['Authentication']['auth_backend']
if 'user' in config['Authentication']:
G.hosts['DEFAULT']['user'] = config['Authentication']['user']
if 'token_name' in config['Authentication']:
G.hosts['DEFAULT']['token_name'] = config['Authentication']['token_name']
if 'token_value' in config['Authentication']:
G.hosts['DEFAULT']['token_value'] = config['Authentication']['token_value']
if 'auth_totp' in config['Authentication']:
G.hosts['DEFAULT']['totp'] = config['Authentication'].getboolean('auth_totp')
if 'tls_verify' in config['Authentication']:
G.hosts['DEFAULT']['verify_ssl'] = config['Authentication'].getboolean('tls_verify')
if 'pwresetcmd' in config['Authentication']:
G.hosts['DEFAULT']['pwresetcmd'] = config['Authentication']['pwresetcmd']
if 'auto_vmid' in config['Authentication']:
G.hosts['DEFAULT']['auto_vmid'] = config['Authentication'].getint('auto_vmid')
if 'knock_seq' in config['Authentication']:
try:
G.hosts['DEFAULT']['knock_seq'] = json.loads(config['Authentication']['knock_seq'])
except Exception as e:
win_popup_button(f'Knock sequence not valid JSON, skipping!\n{e!r}', 'OK')
else: # New style config
i = 0
for section in config.sections():
if section.startswith('Hosts.'):
_, group = section.split('.', 1)
if i == 0:
G.current_hostset = group
G.hosts[group] = {
'hostpool' : [],
'backend' : 'pve',
'user' : "",
'token_name' : None,
'token_value' : None,
'totp' : False,
'verify_ssl' : True,
'pwresetcmd' : None,
'auto_vmid' : None,
'knock_seq': []
}
try:
hostjson = json.loads(config[section]['hostpool'])
except Exception as e:
win_popup_button(f"Error: could not parse hostpool in section {section}:\n{e!r}", "OK")
return False
for key, value in hostjson.items():
G.hosts[group]['hostpool'].append({
'host': key,
'port': int(value)
})
if 'auth_backend' in config[section]:
G.hosts[group]['backend'] = config[section]['auth_backend']
if 'user' in config[section]:
G.hosts[group]['user'] = config[section]['user']
if 'token_name' in config[section]:
G.hosts[group]['token_name'] = config[section]['token_name']
if 'token_value' in config[section]:
G.hosts[group]['token_value'] = config[section]['token_value']
if 'auth_totp' in config[section]:
G.hosts[group]['totp'] = config[section].getboolean('auth_totp')
if 'tls_verify' in config[section]:
G.hosts[group]['verify_ssl'] = config[section].getboolean('tls_verify')
if 'pwresetcmd' in config[section]:
G.hosts[group]['pwresetcmd'] = config[section]['pwresetcmd']
if 'auto_vmid' in config[section]:
G.hosts[group]['auto_vmid'] = config[section].getint('auto_vmid')
if 'knock_seq' in config[section]:
try:
G.hosts[group]['knock_seq'] = json.loads(config[section]['knock_seq'])
except Exception as e:
win_popup_button(f'Knock sequence not valid JSON, skipping!\n{e!r}', 'OK')
i += 1
if 'SpiceProxyRedirect' in config: if 'SpiceProxyRedirect' in config:
for key in config['SpiceProxyRedirect']: for key in config['SpiceProxyRedirect']:
G.spiceproxy_conv[key] = config['SpiceProxyRedirect'][key] G.spiceproxy_conv[key] = config['SpiceProxyRedirect'][key]
@ -189,22 +245,192 @@ def win_popup_button(message, button):
return return
def setmainlayout(): def setmainlayout():
readonly = False
if G.hosts[G.current_hostset]['user'] and G.hosts[G.current_hostset]['token_name'] and G.hosts[G.current_hostset]['token_value']:
readonly = True
layout = [] layout = []
if G.imagefile: if G.imagefile:
layout.append([sg.Image(G.imagefile), sg.Text(G.title, size =(18*G.scaling, 1*G.scaling), justification='c', font=["Helvetica", 18])]) layout.append(
[
sg.Image(G.imagefile),
sg.Text(
G.title,
size = (
18*G.scaling,
1*G.scaling
),
justification = 'c',
font = [
"Helvetica",
18
]
)
]
)
else: else:
layout.append([sg.Text(G.title, size =(30*G.scaling, 1*G.scaling), justification='c', font=["Helvetica", 18])]) layout.append(
layout.append([sg.Text("Username", size =(12*G.scaling, 1*G.scaling), font=["Helvetica", 12]), sg.InputText(default_text=G.user,key='-username-', font=["Helvetica", 12])]) [
layout.append([sg.Text("Password", size =(12*G.scaling, 1*G.scaling),font=["Helvetica", 12]), sg.InputText(key='-password-', password_char='*', font=["Helvetica", 12])]) sg.Text(
G.title,
size = (
30*G.scaling,
1*G.scaling
),
justification='c',
font = [
"Helvetica",
18
]
)
]
)
if G.totp: if len(G.hosts) > 1:
layout.append([sg.Text("OTP Key", size =(12*G.scaling, 1), font=["Helvetica", 12]), sg.InputText(key='-totp-', font=["Helvetica", 12])]) groups = []
for key, _ in G.hosts.items():
groups.append(key)
layout.append(
[
sg.Text(
"Server Group:",
size = (
12*G.scaling,
1*G.scaling
),
font = [
"Helvetica",
12
]
),
sg.Combo(
groups,
G.current_hostset,
key = '-group-',
font = [
"Helvetica",
12
],
readonly = True,
enable_events = True
)
]
)
layout.append(
[
sg.Text(
"Username",
size = (
12*G.scaling,
1*G.scaling
),
font = [
"Helvetica",
12
]
),
sg.InputText(
default_text = G.hosts[G.current_hostset]['user'],
key = '-username-',
font = [
"Helvetica",
12
],
readonly = readonly
)
]
)
layout.append(
[
sg.Text(
"Password",
size = (
12*G.scaling,
1*G.scaling
),
font = [
"Helvetica",
12
]
),
sg.InputText(
key='-password-',
password_char='*',
font = [
"Helvetica",
12
],
readonly = readonly
)
]
)
if G.hosts[G.current_hostset]['totp']:
layout.append(
[
sg.Text(
"OTP Key",
size = (
12*G.scaling,
1
),
font = [
"Helvetica",
12
]
),
sg.InputText(
key = '-totp-',
font = [
"Helvetica",
12
]
)
]
)
if G.kiosk: if G.kiosk:
layout.append([sg.Button("Log In", font=["Helvetica", 14], bind_return_key=True)]) layout.append(
[
sg.Button(
"Log In",
font = [
"Helvetica",
14
],
bind_return_key=True
)
]
)
else: else:
layout.append([sg.Button("Log In", font=["Helvetica", 14], bind_return_key=True), sg.Button("Cancel", font=["Helvetica", 14])]) layout.append(
if G.pwresetcmd: [
layout[-1].append(sg.Button('Password Reset', font=["Helvetica", 14])) sg.Button(
"Log In",
font = [
"Helvetica",
14
],
bind_return_key=True
),
sg.Button(
"Cancel",
font = [
"Helvetica",
14
]
)
]
)
if G.hosts[G.current_hostset]['pwresetcmd']:
layout[-1].append(
sg.Button(
'Password Reset',
font = [
"Helvetica",
14
]
)
)
return layout return layout
def getvms(listonly = False): def getvms(listonly = False):
@ -289,7 +515,7 @@ def setvmlayout(vms):
def iniwin(inistring): def iniwin(inistring):
inilayout = [ inilayout = [
[sg.Multiline(default_text=inistring, size=(800*G.scaling, 600*G.scaling))] [sg.Multiline(default_text=inistring, size=(100, 40))]
] ]
iniwindow = sg.Window('INI debug', inilayout) iniwindow = sg.Window('INI debug', inilayout)
while True: while True:
@ -454,9 +680,9 @@ def setcmd():
sys.exit() sys.exit()
def pveauth(username, passwd=None, totp=None): def pveauth(username, passwd=None, totp=None):
random.shuffle(G.hostpool) random.shuffle(G.hosts[G.current_hostset]['hostpool'])
err = None err = None
for hostinfo in G.hostpool: for hostinfo in G.hosts[G.current_hostset]['hostpool']:
host = hostinfo['host'] host = hostinfo['host']
if 'port' in hostinfo: if 'port' in hostinfo:
port = hostinfo['port'] port = hostinfo['port']
@ -466,12 +692,32 @@ def pveauth(username, passwd=None, totp=None):
authenticated = False authenticated = False
if not connected and not authenticated: if not connected and not authenticated:
try: try:
if G.token_name and G.token_value: if G.hosts[G.current_hostset]['token_name'] and G.hosts[G.current_hostset]['token_value']:
G.proxmox = proxmoxer.ProxmoxAPI(host, user=f'{username}@{G.backend}',token_name=G.token_name,token_value=G.token_value, verify_ssl=G.verify_ssl, port=port) G.proxmox = proxmoxer.ProxmoxAPI(
host,
user=f"{username}@{G.hosts[G.current_hostset]['backend']}",
token_name=G.hosts[G.current_hostset]['token_name'],
token_value=G.hosts[G.current_hostset]['token_value'],
verify_ssl=G.hosts[G.current_hostset]['verify_ssl'],
port=port
)
elif totp: elif totp:
G.proxmox = proxmoxer.ProxmoxAPI(host, user=f'{username}@{G.backend}', otp=totp, password=passwd, verify_ssl=G.verify_ssl, port=port) G.proxmox = proxmoxer.ProxmoxAPI(
host,
user=f"{username}@{G.hosts[G.current_hostset]['backend']}",
otp=totp,
password=passwd,
verify_ssl=G.hosts[G.current_hostset]['verify_ssl'],
port=port
)
else: else:
G.proxmox = proxmoxer.ProxmoxAPI(host, user=f'{username}@{G.backend}', password=passwd, verify_ssl=G.verify_ssl, port=port) G.proxmox = proxmoxer.ProxmoxAPI(
host,
user=f"{username}@{G.hosts[G.current_hostset]['backend']}",
password=passwd,
verify_ssl=G.hosts[G.current_hostset]['verify_ssl'],
port=port
)
connected = True connected = True
authenticated = True authenticated = True
return connected, authenticated, err return connected, authenticated, err
@ -486,18 +732,18 @@ def pveauth(username, passwd=None, totp=None):
def loginwindow(): def loginwindow():
layout = setmainlayout() layout = setmainlayout()
if G.user and G.token_name and G.token_value: # We need to skip the login if G.hosts[G.current_hostset]['user'] and G.hosts[G.current_hostset]['token_name'] and G.hosts[G.current_hostset]['token_value'] and len(G.hosts) == 1: # We need to skip the login
popwin = win_popup("Please wait, authenticating...") popwin = win_popup("Please wait, authenticating...")
connected, authenticated, error = pveauth(G.user) connected, authenticated, error = pveauth(G.hosts[G.current_hostset]['user'])
popwin.close() popwin.close()
if not connected: if not connected:
win_popup_button(f'Unable to connect to any VDI server, are you connected to the Internet?\nError Info: {error}', 'OK') win_popup_button(f'Unable to connect to any VDI server, are you connected to the Internet?\nError Info: {error}', 'OK')
return False return False, False
elif connected and not authenticated: elif connected and not authenticated:
win_popup_button('Invalid username and/or password, please try again!', 'OK') win_popup_button('Invalid username and/or password, please try again!', 'OK')
return False return False, False
elif connected and authenticated: elif connected and authenticated:
return True return True, False
else: else:
if G.icon: if G.icon:
window = sg.Window(G.title, layout, return_keyboard_events=True, resizable=False, no_titlebar=G.kiosk, icon=G.icon) window = sg.Window(G.title, layout, return_keyboard_events=True, resizable=False, no_titlebar=G.kiosk, icon=G.icon)
@ -505,12 +751,17 @@ def loginwindow():
window = sg.Window(G.title, layout, return_keyboard_events=True, resizable=False, no_titlebar=G.kiosk) window = sg.Window(G.title, layout, return_keyboard_events=True, resizable=False, no_titlebar=G.kiosk)
while True: while True:
event, values = window.read() event, values = window.read()
if event == '-group-' and values['-group-'] != G.current_hostset:
#Switch cluster
G.current_hostset = values['-group-']
window.close()
return False, True
if event == 'Cancel' or event == sg.WIN_CLOSED: if event == 'Cancel' or event == sg.WIN_CLOSED:
window.close() window.close()
return False return False, False
elif event == 'Password Reset': elif event == 'Password Reset':
try: try:
subprocess.check_call(G.pwresetcmd, shell=True) subprocess.check_call(G.hosts[G.current_hostset]['pwresetcmd'], shell=True)
except Exception as e: except Exception as e:
win_popup_button(f'Unable to open password reset command.\n\nError Info:\n{e}', 'OK') win_popup_button(f'Unable to open password reset command.\n\nError Info:\n{e}', 'OK')
else: else:
@ -530,7 +781,7 @@ def loginwindow():
win_popup_button('Invalid username and/or password, please try again!', 'OK') win_popup_button('Invalid username and/or password, please try again!', 'OK')
elif connected and authenticated: elif connected and authenticated:
window.close() window.close()
return True return True, False
#break #break
def showvms(): def showvms():
@ -629,26 +880,29 @@ def main():
return False return False
sg.theme(G.theme) sg.theme(G.theme)
loggedin = False loggedin = False
switching = False
while True: while True:
if not loggedin: if not loggedin:
loggedin = loginwindow() loggedin, switching = loginwindow()
if not loggedin: if not loggedin and not switching:
if G.user and G.token_name and G.token_value: # This means if we don't exit we'll be in an infinite loop if G.hosts[G.current_hostset]['user'] and G.hosts[G.current_hostset]['token_name'] and G.hosts[G.current_hostset]['token_value']: # This means if we don't exit we'll be in an infinite loop
return 1 return 1
break break
elif not loggedin and switching:
pass
else: else:
if G.auto_vmid: if G.hosts[G.current_hostset]['auto_vmid']:
vms = getvms() vms = getvms()
for row in vms: for row in vms:
if row['vmid'] == G.auto_vmid: if row['vmid'] == G.hosts[G.current_hostset]['auto_vmid']:
vmaction(row['node'], row['vmid'], row['type'], action='connect') vmaction(row['node'], row['vmid'], row['type'], action='connect')
return 0 return 0
win_popup_button(f'No VDI instance with ID {G.auto_vmid} found!', 'OK') win_popup_button(f"No VDI instance with ID {G.hosts[G.current_hostset]['auto_vmid']} found!", 'OK')
vmstat = showvms() vmstat = showvms()
if not vmstat: if not vmstat:
G.proxmox = None G.proxmox = None
loggedin = False loggedin = False
if G.user and G.token_name and G.token_value: # This means if we don't exit we'll be in an infinite loop if G.hosts[G.current_hostset]['user'] and G.hosts[G.current_hostset]['token_name'] and G.hosts[G.current_hostset]['token_value'] and len(G.hosts) == 1: # This means if we don't exit we'll be in an infinite loop
return 0 return 0
else: else:
return return