10 Commits

Author SHA1 Message Date
jpattWPC
b382d69126 Fix #51 2023-03-10 09:22:10 -06:00
jpattWPC
715dfef72d Add python3-tk to apt install 2023-03-08 09:14:43 -06:00
jpattWPC
fbe00ec782 Bump release version 2023-02-15 08:54:29 -06:00
joshpatten
7bc5966cd6 Merge pull request #47 from bekema/main
Prevent exceptions when a node is offline
2023-02-15 08:50:14 -06:00
Hessel Bekema
9942361877 only list vms for nodes that are online; prevents KeyError crashes 2023-02-15 23:12:51 +11:00
Hessel Bekema
7148fc85ed bind enter to the log in button 2023-02-15 23:10:09 +11:00
jpattWPC
c2221da4c2 Fix typo 2023-02-03 15:57:12 -06:00
jpattWPC
de4088773b Update install instructions 2023-02-03 15:54:49 -06:00
jpattWPC
de3ab9ea68 Enhance auto-refresh logic 2023-02-03 15:47:10 -06:00
jpattWPC
57ffa48257 Add VM list refresh
- Add ability to query new VMs every 10 seconds, and redraw the window if a different list is found.
- Remove vestigial DPI checking code.
2023-02-03 15:13:27 -06:00
4 changed files with 62 additions and 46 deletions

View File

@@ -24,7 +24,9 @@ you will need to download the latest 3.10 python release, and run the following
Run the following commands on a Debian/Ubuntu Linux system to install the appropriate prerequisites Run the following commands on a Debian/Ubuntu Linux system to install the appropriate prerequisites
apt install python3-pip virt-viewer apt install python3-pip python3-tk virt-viewer git
git clone https://github.com/joshpatten/PVE-VDIClient.git
cd ./PVE-VDIClient/
chmod +x requirements.sh chmod +x requirements.sh
./requirements.sh ./requirements.sh
cp vdiclient.py /usr/local/bin cp vdiclient.py /usr/local/bin

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.1.0", "version" : "1.1.03",
"product_name" : "VDI Client", "product_name" : "VDI Client",
"manufacturer" : "Josh Patten", "manufacturer" : "Josh Patten",
"name" : "VDI Client", "name" : "VDI Client",

View File

@@ -1,7 +1,7 @@
[General] [General]
# This is the title that is diplayed to the user # This is the title that is diplayed to the user
title = VDI Login title = VDI Login
# This is the PySimpleGui Theme that is used. Run pvevdi.py with flag `--list_themes` for a list of themes # This is the PySimpleGui Theme that is used. Run vdiclient.py with flag `--list_themes` for a list of themes
theme = LightBlue theme = LightBlue
# Program Icon # Program Icon
icon = vdiicon.ico icon = vdiicon.ico

View File

@@ -3,9 +3,11 @@ import proxmoxer # pip install proxmoxer
import PySimpleGUI as sg # pip install PySimpleGUI import PySimpleGUI as sg # pip install PySimpleGUI
gui = 'TK' gui = 'TK'
import requests import requests
from datetime import datetime
from configparser import ConfigParser from configparser import ConfigParser
import random import random
import sys import sys
import copy
import os import os
import subprocess import subprocess
from time import sleep from time import sleep
@@ -36,24 +38,6 @@ class G:
theme = 'LightBlue' theme = 'LightBlue'
guest_type = 'both' guest_type = 'both'
def get_dpi():
import ctypes
import win32api # pip install pywin32
shcore = ctypes.windll.shcore
monitors = win32api.EnumDisplayMonitors()
hresult = shcore.SetProcessDpiAwareness(2)
assert hresult == 0
dpiX = ctypes.c_uint()
dpiY = ctypes.c_uint()
for i, monitor in enumerate(monitors):
shcore.GetDpiForMonitor(
monitor[0].handle,
0,
ctypes.byref(dpiX),
ctypes.byref(dpiY)
)
return dpiX.value/96
def loadconfig(config_location = None): def loadconfig(config_location = None):
if config_location: if config_location:
config = ConfigParser(delimiters='=') config = ConfigParser(delimiters='=')
@@ -147,10 +131,12 @@ def loadconfig(config_location = None):
return True return True
def win_popup(message): def win_popup(message):
layout = [[sg.Text(message)]] layout = [
window = sg.Window('Message', layout, no_titlebar=True, keep_on_top=True, finalize=True) [sg.Text(message)]
]
window = sg.Window('Message', layout, return_keyboard_events=True, no_titlebar=True, keep_on_top=True, finalize=True)
window.bring_to_front() window.bring_to_front()
_, _ = window.read(timeout=1) # Fixes a black screen bug _, _ = window.read(timeout=10) # Fixes a black screen bug
return window return window
def win_popup_button(message, button): def win_popup_button(message, button):
@@ -178,21 +164,35 @@ def setmainlayout():
if G.totp: if G.totp:
layout.append([sg.Text("OTP Key", size =(12*G.scaling, 1), font=["Helvetica", 12]), sg.InputText(key='-totp-', font=["Helvetica", 12])]) 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])]) layout.append([sg.Button("Log In", font=["Helvetica", 14], bind_return_key=True)])
else: else:
layout.append([sg.Button("Log In", font=["Helvetica", 14]), sg.Button("Cancel", font=["Helvetica", 14])]) layout.append([sg.Button("Log In", font=["Helvetica", 14], bind_return_key=True), sg.Button("Cancel", font=["Helvetica", 14])])
return layout return layout
def getvms(): def getvms(listonly = False):
vms = [] vms = []
try: try:
nodes = []
for node in G.proxmox.cluster.resources.get(type='node'):
if node['status'] == 'online':
nodes.append(node['node'])
for vm in G.proxmox.cluster.resources.get(type='vm'): for vm in G.proxmox.cluster.resources.get(type='vm'):
if vm['node'] not in nodes:
continue
if 'template' in vm and vm['template']: if 'template' in vm and vm['template']:
continue continue
if G.guest_type == 'both': if G.guest_type == 'both' or G.guest_type == vm['type']:
vms.append(vm) if listonly:
elif G.guest_type == vm['type']: vms.append(
vms.append(vm) {
'vmid': vm['vmid'],
'name': vm['name'],
'node': vm['node']
}
)
else:
vms.append(vm)
return vms return vms
except proxmoxer.core.ResourceException as e: except proxmoxer.core.ResourceException as e:
win_popup_button(f"Unable to display list of VMs:\n {e!r}", 'OK') win_popup_button(f"Unable to display list of VMs:\n {e!r}", 'OK')
@@ -209,7 +209,7 @@ def setvmlayout(vms):
for vm in vms: for vm in vms:
if not vm["status"] == "unknown": if not vm["status"] == "unknown":
connkeyname = f'-CONN|{vm["vmid"]}-' connkeyname = f'-CONN|{vm["vmid"]}-'
layoutcolumn.append([sg.Text(vm['name'], font=["Helvetica", 14]), sg.Button('Connect', font=["Helvetica", 14], key=connkeyname)]) layoutcolumn.append([sg.Text(vm['name'], font=["Helvetica", 14], size=(22*G.scaling, 1*G.scaling)), sg.Button('Connect', font=["Helvetica", 14], key=connkeyname)])
layoutcolumn.append([sg.HorizontalSeparator()]) layoutcolumn.append([sg.HorizontalSeparator()])
if len(vms) > 5: # We need a scrollbar if len(vms) > 5: # We need a scrollbar
layout.append([sg.Column(layoutcolumn, scrollable = True, size = [450*G.scaling, None] )]) layout.append([sg.Column(layoutcolumn, scrollable = True, size = [450*G.scaling, None] )])
@@ -271,10 +271,14 @@ def vmaction(vmnode, vmid, vmtype):
if startpop: if startpop:
startpop.close() startpop.close()
return status return status
if vmtype == 'qemu': try:
spiceconfig = G.proxmox.nodes(vmnode).qemu(str(vmid)).spiceproxy.post() if vmtype == 'qemu':
else: # Not sure this is even a thing, but here it is... spiceconfig = G.proxmox.nodes(vmnode).qemu(str(vmid)).spiceproxy.post()
spiceconfig = G.proxmox.nodes(vmnode).lxc(str(vmid)).spiceproxy.post() else: # Not sure this is even a thing, but here it is...
spiceconfig = G.proxmox.nodes(vmnode).lxc(str(vmid)).spiceproxy.post()
except proxmoxer.core.ResourceException as e:
win_popup_button(f"Unable to connect to VM {vmid}:\n{e!r}\nIs SPICE display configured for your VM?", 'OK')
return False
confignode = ConfigParser() confignode = ConfigParser()
confignode['virt-viewer'] = {} confignode['virt-viewer'] = {}
for key, value in spiceconfig.items(): for key, value in spiceconfig.items():
@@ -414,18 +418,34 @@ def loginwindow():
def showvms(): def showvms():
vms = getvms() vms = getvms()
vmlist = getvms(listonly=True)
newvmlist = vmlist.copy()
if vms == False: if vms == False:
return False return False
if len(vms) < 1: if len(vms) < 1:
win_popup_button('No desktop instances found, please consult with your system administrator', 'OK') win_popup_button('No desktop instances found, please consult with your system administrator', 'OK')
return False return False
layout = setvmlayout(vms) layout = setvmlayout(vms)
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, finalize=True, resizable=False, no_titlebar=G.kiosk, icon=G.icon)
else: else:
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, finalize=True, resizable=False, no_titlebar=G.kiosk)
timer = datetime.now()
while True: while True:
event, values = window.read() if (datetime.now() - timer).total_seconds() > 10:
timer = datetime.now()
newvmlist = getvms(listonly = True)
if vmlist != newvmlist:
vmlist = newvmlist.copy()
layout = setvmlayout(getvms())
window.close()
if G.icon:
window = sg.Window(G.title, layout, return_keyboard_events=True, finalize=True, resizable=False, no_titlebar=G.kiosk, icon=G.icon)
else:
window = sg.Window(G.title, layout, return_keyboard_events=True,finalize=True, resizable=False, no_titlebar=G.kiosk)
window.bring_to_front()
event, values = window.read(timeout = 1000)
if event in ('Logout', None): if event in ('Logout', None):
window.close() window.close()
return False return False
@@ -442,13 +462,7 @@ def showvms():
return True return True
def main(): def main():
if os.name == 'nt' and gui == 'QT': G.scaling = 1 # TKinter requires integers
G.scaling = get_dpi()
else:
if gui == 'QT':
G.scaling = 1.0 #TODO FIXME: Figure out scaling on Linux
else:
G.scaling = 1 # TKinter requires integers
config_location = None config_location = None
if len(sys.argv) > 1: if len(sys.argv) > 1:
if sys.argv[1] == '--list_themes': if sys.argv[1] == '--list_themes':