p3lib 1.1.97__py3-none-any.whl → 1.1.99__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- p3lib/boot_manager.py +52 -44
- p3lib/ngt.py +95 -65
- {p3lib-1.1.97.dist-info → p3lib-1.1.99.dist-info}/METADATA +1 -1
- {p3lib-1.1.97.dist-info → p3lib-1.1.99.dist-info}/RECORD +7 -7
- {p3lib-1.1.97.dist-info → p3lib-1.1.99.dist-info}/WHEEL +1 -1
- {p3lib-1.1.97.dist-info → p3lib-1.1.99.dist-info}/LICENSE +0 -0
- {p3lib-1.1.97.dist-info → p3lib-1.1.99.dist-info}/top_level.txt +0 -0
p3lib/boot_manager.py
CHANGED
@@ -27,68 +27,68 @@ class BootManager(object):
|
|
27
27
|
parser.add_argument(BootManager.CHECK_CMD_OPT, help="Check the status of an auto started icons_gw instance.", action="store_true", default=False)
|
28
28
|
|
29
29
|
@staticmethod
|
30
|
-
def HandleOptions(uio, options, enable_syslog,
|
30
|
+
def HandleOptions(uio, options, enable_syslog, serviceName=None, restartSeconds=1):
|
31
31
|
"""@brief Handle one of the bot manager command line options if the
|
32
32
|
user passed it on the cmd line.
|
33
33
|
@param uio A UIO instance.
|
34
34
|
@param options As returned from parser.parse_args() where parser
|
35
35
|
is an instance of argparse.ArgumentParser.
|
36
36
|
@param enable_syslog True to enable systemd syslog output.
|
37
|
-
@param
|
38
|
-
|
37
|
+
@param serviceName The name of the service. If not set then the name of the initially executed
|
38
|
+
python file is used.
|
39
39
|
@param restartSeconds The number of seconds to sleep before restarting a service that has stopped (default=1).
|
40
40
|
@return True if handled , False if not."""
|
41
41
|
handled = False
|
42
42
|
if options.check_auto_start:
|
43
|
-
BootManager.CheckAutoStartStatus(uio,
|
43
|
+
BootManager.CheckAutoStartStatus(uio, serviceName)
|
44
44
|
handled = True
|
45
45
|
|
46
46
|
elif options.enable_auto_start:
|
47
|
-
BootManager.EnableAutoStart(uio, enable_syslog,
|
47
|
+
BootManager.EnableAutoStart(uio, enable_syslog, serviceName, restartSeconds)
|
48
48
|
handled = True
|
49
49
|
|
50
50
|
elif options.disable_auto_start:
|
51
|
-
BootManager.DisableAutoStart(uio,
|
51
|
+
BootManager.DisableAutoStart(uio, serviceName)
|
52
52
|
handled = True
|
53
53
|
|
54
54
|
return handled
|
55
55
|
|
56
56
|
@staticmethod
|
57
|
-
def EnableAutoStart(uio, enable_syslog,
|
57
|
+
def EnableAutoStart(uio, enable_syslog, serviceName, restartSeconds):
|
58
58
|
"""@brief Enable this program to auto start when the computer on which it is installed starts.
|
59
59
|
@param uio A UIO instance.
|
60
60
|
@param options As returned from parser.parse_args() where parser
|
61
61
|
is an instance of argparse.ArgumentParser.
|
62
62
|
@param enable_syslog True to enable systemd syslog output.
|
63
|
-
@param
|
64
|
-
|
63
|
+
@param serviceName The name of the service. If not set then the name of the initially executed
|
64
|
+
python file is used.
|
65
65
|
@param restartSeconds The number of seconds to sleep before restarting a service that has stopped (default=1)."""
|
66
|
-
bootManager = BootManager(uio=uio, ensureRootUser=True,
|
66
|
+
bootManager = BootManager(uio=uio, ensureRootUser=True, serviceName=serviceName, restartSeconds=restartSeconds)
|
67
67
|
arsString = " ".join(sys.argv)
|
68
68
|
bootManager.add(argString=arsString, enableSyslog=enable_syslog)
|
69
69
|
|
70
70
|
@staticmethod
|
71
|
-
def DisableAutoStart(uio,
|
71
|
+
def DisableAutoStart(uio, serviceName):
|
72
72
|
"""@brief Enable this program to auto start when the computer on which it is installed starts.
|
73
73
|
@param uio A UIO instance.
|
74
|
-
@param
|
75
|
-
|
76
|
-
bootManager = BootManager(uio=uio, ensureRootUser=True,
|
74
|
+
@param serviceName The name of the service. If not set then the name of the initially executed
|
75
|
+
python file is used."""
|
76
|
+
bootManager = BootManager(uio=uio, ensureRootUser=True, serviceName=serviceName)
|
77
77
|
bootManager.remove()
|
78
78
|
|
79
79
|
@staticmethod
|
80
|
-
def CheckAutoStartStatus(uio,
|
80
|
+
def CheckAutoStartStatus(uio, serviceName):
|
81
81
|
"""@brief Check the status of a process previously set to auto start.
|
82
82
|
@param uio A UIO instance.
|
83
|
-
@param
|
84
|
-
|
85
|
-
bootManager = BootManager(uio=uio,
|
83
|
+
@param serviceName The name of the service. If not set then the name of the initially executed
|
84
|
+
python file is used."""
|
85
|
+
bootManager = BootManager(uio=uio, serviceName=serviceName)
|
86
86
|
lines = bootManager.getStatus()
|
87
87
|
if lines and len(lines) > 0:
|
88
88
|
for line in lines:
|
89
89
|
uio.info(line)
|
90
90
|
|
91
|
-
def __init__(self, uio=None, allowRootUser=True, ensureRootUser=False,
|
91
|
+
def __init__(self, uio=None, allowRootUser=True, ensureRootUser=False, serviceName=None, restartSeconds=1):
|
92
92
|
"""@brief Constructor
|
93
93
|
@param uio A UIO instance to display user output. If unset then no output
|
94
94
|
is displayed to user.
|
@@ -98,15 +98,15 @@ class BootManager(object):
|
|
98
98
|
the installed program should be installed for the root
|
99
99
|
user on Linux systems.
|
100
100
|
@param ensureRootUser If True the current user must be root user (Linux systems).
|
101
|
-
@param
|
102
|
-
|
101
|
+
@param serviceName The name of the service. If not set then the name of the initially executed
|
102
|
+
python file is used.
|
103
103
|
@param restartSeconds The number of seconds to sleep before restarting a service that has stopped (default=1)."""
|
104
104
|
self._uio = uio
|
105
105
|
self._allowRootUser=allowRootUser
|
106
106
|
self._osName = platform.system()
|
107
107
|
self._platformBootManager = None
|
108
108
|
if self._osName == BootManager.LINUX_OS_NAME:
|
109
|
-
self._platformBootManager = LinuxBootManager(uio, self._allowRootUser, ensureRootUser,
|
109
|
+
self._platformBootManager = LinuxBootManager(uio, self._allowRootUser, ensureRootUser, serviceName, restartSeconds)
|
110
110
|
else:
|
111
111
|
raise Exception("{} is an unsupported OS.".format(self._osName) )
|
112
112
|
|
@@ -174,13 +174,13 @@ class LinuxBootManager(object):
|
|
174
174
|
raise Exception(f"{serviceFolder} folder not found.")
|
175
175
|
return serviceFolder
|
176
176
|
|
177
|
-
def __init__(self, uio, allowRootUser, ensureRootUser,
|
177
|
+
def __init__(self, uio, allowRootUser, ensureRootUser, serviceName, restartSeconds):
|
178
178
|
"""@brief Constructor
|
179
179
|
@param uio A UIO instance to display user output. If unset then no output is displayed to user.
|
180
180
|
@param allowRootUser If True then allow root user to to auto start programs.
|
181
181
|
@param ensureRootUser If True the current user must be root user.
|
182
|
-
@param
|
183
|
-
|
182
|
+
@param serviceName The name of the service. If not set then the name of the initially executed
|
183
|
+
python file is used.
|
184
184
|
@param restartSeconds The number of seconds to sleep before restarting a service that has stopped."""
|
185
185
|
self._uio = uio
|
186
186
|
self._logFile = None
|
@@ -202,7 +202,7 @@ class LinuxBootManager(object):
|
|
202
202
|
self._cmdLinePrefix = self._systemCtlBin
|
203
203
|
self._username = getpass.getuser()
|
204
204
|
self._serviceFolder = LinuxBootManager.GetServiceFolder(self._rootMode)
|
205
|
-
self.
|
205
|
+
self._serviceName = serviceName
|
206
206
|
self._restartSeconds = restartSeconds
|
207
207
|
|
208
208
|
def _getInstallledStartupScript(self):
|
@@ -290,12 +290,23 @@ class LinuxBootManager(object):
|
|
290
290
|
|
291
291
|
return (appName, absApp)
|
292
292
|
|
293
|
+
def _getServiceName(self):
|
294
|
+
"""@brief Get the name of the service.
|
295
|
+
@return The name of the service."""
|
296
|
+
if self._serviceName:
|
297
|
+
serviceName = self._serviceName
|
298
|
+
else:
|
299
|
+
appName, _ = self._getApp()
|
300
|
+
serviceName = appName
|
301
|
+
return "{}.service".format(serviceName)
|
302
|
+
|
293
303
|
def _getServiceFile(self, appName):
|
294
304
|
"""@brief Get the name of the service file.
|
295
305
|
@param appName The name of the app to execute.
|
296
306
|
@return The absolute path to the service file """
|
297
|
-
serviceName =
|
307
|
+
serviceName = self._getServiceName()
|
298
308
|
serviceFile = os.path.join(self._serviceFolder, serviceName)
|
309
|
+
self._uio.info(f"SERVICE FILE: {serviceFile}")
|
299
310
|
return serviceFile
|
300
311
|
|
301
312
|
def add(self, user, argString=None, enableSyslog=False):
|
@@ -314,11 +325,7 @@ class LinuxBootManager(object):
|
|
314
325
|
user = self._username
|
315
326
|
|
316
327
|
appName, absApp = self._getApp()
|
317
|
-
|
318
|
-
if self._appName:
|
319
|
-
serviceName = self._appName
|
320
|
-
else:
|
321
|
-
serviceName = appName
|
328
|
+
serviceName = self._getServiceName()
|
322
329
|
|
323
330
|
serviceFile = self._getServiceFile(serviceName)
|
324
331
|
|
@@ -370,12 +377,12 @@ class LinuxBootManager(object):
|
|
370
377
|
|
371
378
|
cmd = "{} daemon-reload".format(self._cmdLinePrefix)
|
372
379
|
self._runLocalCmd(cmd)
|
373
|
-
cmd = "{} enable {}".format(self._cmdLinePrefix,
|
374
|
-
self._info("Enabled {} on restart".format(
|
380
|
+
cmd = "{} enable {}".format(self._cmdLinePrefix, serviceName)
|
381
|
+
self._info("Enabled {} on restart".format(serviceName))
|
375
382
|
self._runLocalCmd(cmd)
|
376
|
-
cmd = "{} start {}".format(self._cmdLinePrefix,
|
383
|
+
cmd = "{} start {}".format(self._cmdLinePrefix, serviceName)
|
377
384
|
self._runLocalCmd(cmd)
|
378
|
-
self._info("Started {}".format(
|
385
|
+
self._info("Started {}".format(serviceName))
|
379
386
|
|
380
387
|
def remove(self):
|
381
388
|
"""@brief Remove the executable file to the processes started at boot time.
|
@@ -383,29 +390,30 @@ class LinuxBootManager(object):
|
|
383
390
|
named the same as the python file executed without the .py suffix."""
|
384
391
|
appName, _ = self._getApp()
|
385
392
|
|
393
|
+
serviceName = self._getServiceName()
|
386
394
|
serviceFile = self._getServiceFile(appName)
|
387
395
|
if os.path.isfile(serviceFile):
|
388
|
-
cmd = "{} disable {}".format(self._cmdLinePrefix,
|
396
|
+
cmd = "{} disable {}".format(self._cmdLinePrefix, serviceName)
|
389
397
|
self._runLocalCmd(cmd)
|
390
|
-
self._info("Disabled {} on restart".format(
|
398
|
+
self._info("Disabled {} on restart".format(serviceName))
|
391
399
|
|
392
|
-
cmd = "{} stop {}".format(self._cmdLinePrefix,
|
400
|
+
cmd = "{} stop {}".format(self._cmdLinePrefix, serviceName)
|
393
401
|
self._runLocalCmd(cmd)
|
394
|
-
self._info("Stopped {}".format(
|
402
|
+
self._info("Stopped {}".format(serviceName))
|
395
403
|
|
396
404
|
os.remove(serviceFile)
|
397
405
|
self._log("Removed {}".format(serviceFile))
|
398
406
|
else:
|
399
|
-
self._info("{} service not found".format(
|
407
|
+
self._info("{} service not found".format(serviceName))
|
400
408
|
|
401
409
|
def getStatusLines(self):
|
402
410
|
"""@brief Get a status report.
|
403
411
|
@return Lines of text indicating the status of a previously started process."""
|
404
|
-
|
412
|
+
serviceName = self._getServiceName()
|
405
413
|
if self._rootMode:
|
406
|
-
p = Popen([self._systemCtlBin, 'status',
|
414
|
+
p = Popen([self._systemCtlBin, 'status', serviceName], stdin=PIPE, stdout=PIPE, stderr=PIPE)
|
407
415
|
else:
|
408
|
-
p = Popen([self._systemCtlBin, '--user', 'status',
|
416
|
+
p = Popen([self._systemCtlBin, '--user', 'status', serviceName], stdin=PIPE, stdout=PIPE, stderr=PIPE)
|
409
417
|
output, err = p.communicate(b"input data that is passed to subprocess' stdin")
|
410
418
|
response = output.decode() + "\n" + err.decode()
|
411
419
|
lines = response.split("\n")
|
p3lib/ngt.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# !/usr/bin/env python3
|
2
2
|
|
3
3
|
"""NiceGui Tools
|
4
|
-
Responsible for providing helper classes for nicegui interfaces
|
4
|
+
Responsible for providing helper classes for nicegui interfaces
|
5
5
|
aimed at reducing coding required for a GUI.
|
6
6
|
"""
|
7
7
|
|
@@ -19,9 +19,9 @@ class TabbedNiceGui(object):
|
|
19
19
|
"""@brief Responsible for starting the providing a tabbed GUI.
|
20
20
|
This class is designed to ease the creation of a tabbed GUI interface.
|
21
21
|
The contents of each tab can be defined in the subclass.
|
22
|
-
The GUI includes a message log area below each tab. Tasks can send messages
|
22
|
+
The GUI includes a message log area below each tab. Tasks can send messages
|
23
23
|
to this log area.
|
24
|
-
If a subclass sets the self._logFile attributed then all messages sent to the
|
24
|
+
If a subclass sets the self._logFile attributed then all messages sent to the
|
25
25
|
log area are written to a log file with timestamps."""
|
26
26
|
|
27
27
|
# This can be used in the markdown text for a TAB description to give slightly larger text
|
@@ -47,7 +47,7 @@ class TabbedNiceGui(object):
|
|
47
47
|
def GetDateTimeStamp():
|
48
48
|
"""@return The log file date/time stamp """
|
49
49
|
return strftime("%Y%m%d%H%M%S", localtime()).lower()
|
50
|
-
|
50
|
+
|
51
51
|
@staticmethod
|
52
52
|
def GetInstallFolder():
|
53
53
|
"""@return The folder where the apps are installed."""
|
@@ -55,7 +55,7 @@ class TabbedNiceGui(object):
|
|
55
55
|
if not os.path.isdir(installFolder):
|
56
56
|
raise Exception(f"{installFolder} folder not found.")
|
57
57
|
return installFolder
|
58
|
-
|
58
|
+
|
59
59
|
@staticmethod
|
60
60
|
def GetLogFileName(logFilePrefix):
|
61
61
|
"""@param logFilePrefix The text in the log file name before the timestamp.
|
@@ -63,7 +63,7 @@ class TabbedNiceGui(object):
|
|
63
63
|
dateTimeStamp = TabbedNiceGui.GetDateTimeStamp()
|
64
64
|
logFileName = f"{logFilePrefix}_{dateTimeStamp}.log"
|
65
65
|
return logFileName
|
66
|
-
|
66
|
+
|
67
67
|
@staticmethod
|
68
68
|
def CheckPort(port):
|
69
69
|
"""@brief Check the server port.
|
@@ -72,7 +72,7 @@ class TabbedNiceGui(object):
|
|
72
72
|
raise Exception("The minimum TCP port that you can bind the GUI server to is 1024.")
|
73
73
|
if port > 65535:
|
74
74
|
raise Exception("The maximum TCP port that you can bind the GUI server to is 65535.")
|
75
|
-
|
75
|
+
|
76
76
|
@staticmethod
|
77
77
|
def GetProgramVersion():
|
78
78
|
"""@brief Get the program version from the poetry pyproject.toml file.
|
@@ -86,7 +86,7 @@ class TabbedNiceGui(object):
|
|
86
86
|
poetryConfigFile = os.path.join(cwd, TabbedNiceGui.POETRY_CONFIG_FILE)
|
87
87
|
if not os.path.isfile(poetryConfigFile):
|
88
88
|
raise Exception(f"{poetryConfigFile}, {poetryConfigFile2} and {poetryConfigFile} not found.")
|
89
|
-
|
89
|
+
|
90
90
|
programVersion = None
|
91
91
|
with open(poetryConfigFile, 'r') as fd:
|
92
92
|
lines = fd.readlines()
|
@@ -144,8 +144,8 @@ class TabbedNiceGui(object):
|
|
144
144
|
|
145
145
|
# Start ------------------------------
|
146
146
|
# Methods that allow the GUI to display standard UIO messages
|
147
|
-
# This allows the GUI to be used with code that was written
|
148
|
-
# to be used on the command line using UIO class instances
|
147
|
+
# This allows the GUI to be used with code that was written
|
148
|
+
# to be used on the command line using UIO class instances
|
149
149
|
#
|
150
150
|
def info(self, msg):
|
151
151
|
"""@brief Send a info message to be displayed in the GUI.
|
@@ -160,14 +160,14 @@ class TabbedNiceGui(object):
|
|
160
160
|
@param msg The message to be displayed."""
|
161
161
|
msgDict = {TabbedNiceGui.WARN_MESSAGE: str(msg)}
|
162
162
|
self.updateGUI(msgDict)
|
163
|
-
|
163
|
+
|
164
164
|
def error(self, msg):
|
165
165
|
"""@brief Send a error message to be displayed in the GUI.
|
166
166
|
This can be called from outside the GUI thread.
|
167
167
|
@param msg The message to be displayed."""
|
168
168
|
msgDict = {TabbedNiceGui.ERROR_MESSAGE: str(msg)}
|
169
169
|
self.updateGUI(msgDict)
|
170
|
-
|
170
|
+
|
171
171
|
def infoDialog(self, msg):
|
172
172
|
"""@brief Display an info level dialog.
|
173
173
|
@param msg The message dialog."""
|
@@ -198,7 +198,7 @@ class TabbedNiceGui(object):
|
|
198
198
|
else:
|
199
199
|
returnText = inputObj.value
|
200
200
|
return returnText
|
201
|
-
|
201
|
+
|
202
202
|
def reportException(self, exception):
|
203
203
|
"""@brief Report an exception.
|
204
204
|
If debug is enabled a full stack trace is displayed.
|
@@ -235,18 +235,18 @@ class TabbedNiceGui(object):
|
|
235
235
|
# Check we have a table to display
|
236
236
|
if len(table) == 0:
|
237
237
|
raise Exception("No table rows to display")
|
238
|
-
|
238
|
+
|
239
239
|
# Check all rows have the same number of columns in the table
|
240
240
|
colCount = len(table[0])
|
241
241
|
for row in table:
|
242
242
|
if len(row) != colCount:
|
243
243
|
raise Exception(f"{str(row)} column count different from first row ({colCount})")
|
244
|
-
|
244
|
+
|
245
245
|
for row in table:
|
246
246
|
for col in row:
|
247
247
|
if not isinstance(col, str):
|
248
248
|
raise Exception(f"Table column is not a string: {col} in {row}")
|
249
|
-
|
249
|
+
|
250
250
|
# Get the max width for each column
|
251
251
|
for col in range(0,colCount):
|
252
252
|
maxWidth=0
|
@@ -258,10 +258,10 @@ class TabbedNiceGui(object):
|
|
258
258
|
tableWidth = 1
|
259
259
|
for columnWidth in columnWidths:
|
260
260
|
tableWidth += columnWidth + 3 # Space each side of the column + a column divider character
|
261
|
-
|
261
|
+
|
262
262
|
# Add the top line of the table
|
263
263
|
self.info(rowSeparatorChar*tableWidth)
|
264
|
-
|
264
|
+
|
265
265
|
# The starting row index
|
266
266
|
for rowIndex in range(0, len(table)):
|
267
267
|
rowText = colSeparatorChar
|
@@ -280,7 +280,7 @@ class TabbedNiceGui(object):
|
|
280
280
|
def setLogFile(self, logFile):
|
281
281
|
pass
|
282
282
|
|
283
|
-
# End ------------------------------
|
283
|
+
# End ------------------------------
|
284
284
|
|
285
285
|
def _saveLogMsg(self, msg):
|
286
286
|
"""@brief Save the message to a log file.
|
@@ -295,7 +295,7 @@ class TabbedNiceGui(object):
|
|
295
295
|
with open(self._logFile, 'a') as fd:
|
296
296
|
dateTimeStamp = TabbedNiceGui.GetDateTimeStamp()
|
297
297
|
fd.write(dateTimeStamp + ": " + msg + '\n')
|
298
|
-
|
298
|
+
|
299
299
|
def _getDisplayMsg(self, msg, prefix):
|
300
300
|
"""@brief Get the msg to display. If the msg does not already have a msg level we add one.
|
301
301
|
@param msg The source msg.
|
@@ -315,7 +315,7 @@ class TabbedNiceGui(object):
|
|
315
315
|
self._log.push(msg)
|
316
316
|
self._saveLogMsg(msg)
|
317
317
|
self._logMessageCount += 1
|
318
|
-
# We've received a log message so update progress bar if required.
|
318
|
+
# We've received a log message so update progress bar if required.
|
319
319
|
self._updateProgressBar(msg)
|
320
320
|
|
321
321
|
def _infoGT(self, msg):
|
@@ -377,7 +377,7 @@ class TabbedNiceGui(object):
|
|
377
377
|
def initGUI(self, tabNameList, tabMethodInitList, reload=True, address="0.0.0.0", port=DEFAULT_SERVER_PORT, pageTitle="NiceGUI"):
|
378
378
|
"""@brief Init the tabbed GUI.
|
379
379
|
@param tabNameList A list of the names of each tab to be created.
|
380
|
-
@param tabMethodInitList A list of the methods to be called to init each of the above tabs.
|
380
|
+
@param tabMethodInitList A list of the methods to be called to init each of the above tabs.
|
381
381
|
The two lists must be the same size.
|
382
382
|
@param reload If reload is set False then changes to python files will not cause the server to be restarted.
|
383
383
|
@param address The address to bind the server to.
|
@@ -392,7 +392,7 @@ class TabbedNiceGui(object):
|
|
392
392
|
for tabName in tabNameList:
|
393
393
|
tabObj = ui.tab(tabName)
|
394
394
|
tabObjList.append(tabObj)
|
395
|
-
|
395
|
+
|
396
396
|
with ui.tab_panels(tabs, value=tabObjList[0]).classes('w-full'):
|
397
397
|
for tabObj in tabObjList:
|
398
398
|
with ui.tab_panel(tabObj):
|
@@ -425,7 +425,7 @@ class TabbedNiceGui(object):
|
|
425
425
|
ui.run(host=address, port=port, title=pageTitle, dark=True, uvicorn_logging_level=guiLogLevel, reload=reload)
|
426
426
|
|
427
427
|
def progressTimerCallback(self):
|
428
|
-
"""@brief Time to update the progress bar. We run the timer all the time because there appears to be a
|
428
|
+
"""@brief Time to update the progress bar. We run the timer all the time because there appears to be a
|
429
429
|
bug in the ui.timer instance. Calling cancel() does not appear to cancel the timer."""
|
430
430
|
if self._updateProgressOnTimer and self._progress.visible:
|
431
431
|
# Increment the progress bar
|
@@ -433,17 +433,17 @@ class TabbedNiceGui(object):
|
|
433
433
|
|
434
434
|
def _startProgress(self, durationSeconds=0, startMessage=None, expectedMsgList=[], expectedMsgCount=0):
|
435
435
|
"""@brief Start a timer that will update the progress bar.
|
436
|
-
The progress bar can simply update on a timer every second with durationSeconds set to the expected length
|
436
|
+
The progress bar can simply update on a timer every second with durationSeconds set to the expected length
|
437
437
|
of the task.
|
438
438
|
|
439
|
-
If startMessage is set to a text string the progress time will not start until the log message area contains
|
439
|
+
If startMessage is set to a text string the progress time will not start until the log message area contains
|
440
440
|
the start message.
|
441
|
-
|
442
|
-
Alternatively if expectedMsgList contains a list of strings we expect to receive then the progress bar is
|
441
|
+
|
442
|
+
Alternatively if expectedMsgList contains a list of strings we expect to receive then the progress bar is
|
443
443
|
updated as each message is received. The messages may be the entire line of a log message or parts of a
|
444
444
|
log message line.
|
445
445
|
|
446
|
-
Alternatively if expectedMsgCount is set to a value > 0 then the progress bar is updated as each message is
|
446
|
+
Alternatively if expectedMsgCount is set to a value > 0 then the progress bar is updated as each message is
|
447
447
|
added to the log and reaches 100% when the number of messages added to the log file reaches the expectedMsgCount.
|
448
448
|
|
449
449
|
@param startMessage The text of the log message we expect to receive to trigger the progress bar timer start.
|
@@ -495,7 +495,7 @@ class TabbedNiceGui(object):
|
|
495
495
|
if self._expectedProgressBarMsgCount > 0:
|
496
496
|
self._progressValue = self._progressValue + self._progressStepValue
|
497
497
|
self._progress.set_value( self._progressValue )
|
498
|
-
|
498
|
+
|
499
499
|
# If we have a list of log messages to update the progress bar.
|
500
500
|
elif len(self._progressBarExpectedMessageList) > 0:
|
501
501
|
if self._expectedProgressBarMessageIndex < len(self._progressBarExpectedMessageList):
|
@@ -517,7 +517,7 @@ class TabbedNiceGui(object):
|
|
517
517
|
"""@brief Should be called before a task is started."""
|
518
518
|
self._enableAllButtons(False)
|
519
519
|
self._clearMessages()
|
520
|
-
|
520
|
+
|
521
521
|
def _clearLog(self):
|
522
522
|
"""@brief Clear the log text"""
|
523
523
|
if self._log:
|
@@ -526,11 +526,11 @@ class TabbedNiceGui(object):
|
|
526
526
|
def _showLogMsgCount(self):
|
527
527
|
"""@brief Show the number of log messages"""
|
528
528
|
ui.notify(f"{self._getLogMessageCount()} messages in the log.")
|
529
|
-
|
529
|
+
|
530
530
|
def close(self):
|
531
531
|
"""@brief Close down the app server."""
|
532
532
|
ui.notify("Press 'CTRL C' at command line or close the terminal window to quit.")
|
533
|
-
# A subclass close() method can call
|
533
|
+
# A subclass close() method can call
|
534
534
|
# app.shutdown()
|
535
535
|
# if reload=False on ui.run()
|
536
536
|
|
@@ -557,7 +557,7 @@ class TabbedNiceGui(object):
|
|
557
557
|
elif TabbedNiceGui.DEBUG_MESSAGE in rxDict:
|
558
558
|
msg = rxDict[TabbedNiceGui.DEBUG_MESSAGE]
|
559
559
|
self._debugGT(msg)
|
560
|
-
|
560
|
+
|
561
561
|
elif TabbedNiceGui.ENABLE_BUTTONS in rxDict:
|
562
562
|
state = rxDict[TabbedNiceGui.ENABLE_BUTTONS]
|
563
563
|
self._enableAllButtons(state)
|
@@ -565,7 +565,7 @@ class TabbedNiceGui(object):
|
|
565
565
|
elif TabbedNiceGui.NOTIFY_DIALOG in rxDict:
|
566
566
|
message = rxDict[TabbedNiceGui.NOTIFY_DIALOG]
|
567
567
|
ui.notify(message, close_button='OK', type="positive", position="center")
|
568
|
-
|
568
|
+
|
569
569
|
else:
|
570
570
|
|
571
571
|
self._handleGUIUpdate(rxDict)
|
@@ -601,47 +601,50 @@ class TabbedNiceGui(object):
|
|
601
601
|
|
602
602
|
elif time() >= timeoutT:
|
603
603
|
raise Exception(f"{timeout} second GUI response timeout.")
|
604
|
-
|
604
|
+
|
605
605
|
else:
|
606
606
|
# Don't spin to fast
|
607
607
|
sleep(0.1)
|
608
|
-
|
608
|
+
|
609
609
|
return rxDict
|
610
|
-
|
610
|
+
|
611
611
|
def _handleGUIUpdate(self, rxDict):
|
612
612
|
"""@brief Process the dicts received from the GUI message queue
|
613
613
|
that were not handled by the parent class.
|
614
614
|
@param rxDict The dict received from the GUI message queue."""
|
615
615
|
raise NotImplementedError("_handleGUIUpdate() is not implemented. Implement this method in a subclass of TabbedNiceGUI")
|
616
|
-
|
616
|
+
|
617
617
|
|
618
618
|
class YesNoDialog(object):
|
619
619
|
"""@brief Responsible for displaying a dialog box to the user with a boolean (I.E yes/no, ok/cancel) response."""
|
620
|
-
TEXT_INPUT_FIELD_TYPE
|
621
|
-
NUMBER_INPUT_FIELD_TYPE
|
622
|
-
SWITCH_INPUT_FIELD_TYPE
|
623
|
-
DROPDOWN_INPUT_FIELD
|
624
|
-
COLOR_INPUT_FIELD
|
625
|
-
DATE_INPUT_FIELD
|
626
|
-
TIME_INPUT_FIELD
|
627
|
-
KNOB_INPUT_FIELD
|
628
|
-
|
629
|
-
|
630
|
-
|
620
|
+
TEXT_INPUT_FIELD_TYPE = 1
|
621
|
+
NUMBER_INPUT_FIELD_TYPE = 2
|
622
|
+
SWITCH_INPUT_FIELD_TYPE = 3
|
623
|
+
DROPDOWN_INPUT_FIELD = 4
|
624
|
+
COLOR_INPUT_FIELD = 5
|
625
|
+
DATE_INPUT_FIELD = 6
|
626
|
+
TIME_INPUT_FIELD = 7
|
627
|
+
KNOB_INPUT_FIELD = 8
|
628
|
+
HOUR_MIN_INPUT_FIELD_TYPE = 9
|
629
|
+
VALID_FIELD_TYPE_LIST = (TEXT_INPUT_FIELD_TYPE,
|
630
|
+
NUMBER_INPUT_FIELD_TYPE,
|
631
|
+
SWITCH_INPUT_FIELD_TYPE,
|
631
632
|
DROPDOWN_INPUT_FIELD,
|
632
633
|
COLOR_INPUT_FIELD,
|
633
634
|
DATE_INPUT_FIELD,
|
634
635
|
TIME_INPUT_FIELD,
|
635
|
-
KNOB_INPUT_FIELD
|
636
|
+
KNOB_INPUT_FIELD,
|
637
|
+
HOUR_MIN_INPUT_FIELD_TYPE)
|
636
638
|
|
637
639
|
FIELD_TYPE_KEY = "FIELD_TYPE_KEY" # The type of field to be displayed.
|
638
|
-
VALUE_KEY = "VALUE_KEY" # The value to be displayed in the field when the dialog is displayed.
|
640
|
+
VALUE_KEY = "VALUE_KEY" # The value to be displayed in the field when the dialog is displayed.
|
639
641
|
MIN_NUMBER_KEY = "MIN_NUMBER_KEY" # If the type is NUMBER_INPUT_FIELD_TYPE, the min value that can be entered.
|
640
642
|
MAX_NUMBER_KEY = "MAX_NUMBER_KEY" # If the type is NUMBER_INPUT_FIELD_TYPE, the max value that can be entered.
|
641
643
|
WIDGET_KEY = "WIDGET_KEY" # The key to the GUI widget (E.G ui.input, ui.number etc)
|
642
644
|
OPTIONS_KEY = "OPTIONS_KEY" # Some input fields require a list of options (E.G DROPDOWN_INPUT_FIELD).
|
645
|
+
STEP_KEY = "STEP_KEY" # The step size for numerical input fields
|
643
646
|
|
644
|
-
def __init__(self,
|
647
|
+
def __init__(self,
|
645
648
|
prompt,
|
646
649
|
successMethod,
|
647
650
|
failureMethod=None,
|
@@ -656,7 +659,7 @@ class YesNoDialog(object):
|
|
656
659
|
self._successMethod = None # The method to be called when the success button is selected.
|
657
660
|
self._failureMethod = None # The method to be called when the failure button is selected.
|
658
661
|
self._inputFieldDict = {} # A dict of input field details to be included in the dialog. Can be left as an empty dict if no input fields are required.
|
659
|
-
# The key in this dict is the name of the input field that the user sees.
|
662
|
+
# The key in this dict is the name of the input field that the user sees.
|
660
663
|
# The value in this dict is another dict containing details of the input field which may be
|
661
664
|
|
662
665
|
self.setPrompt(prompt)
|
@@ -666,13 +669,14 @@ class YesNoDialog(object):
|
|
666
669
|
self.setFailureButtonLabel(failureButtonText)
|
667
670
|
|
668
671
|
|
669
|
-
def addField(self, name, fieldType, value=None, minNumber=None, maxNumber=None, options=None):
|
672
|
+
def addField(self, name, fieldType, value=None, minNumber=None, maxNumber=None, options=None, step=1):
|
670
673
|
"""@brief Add a field to the dialog.
|
671
674
|
@param name The name of the field to be added.
|
672
675
|
@param fieldType The type of field to be entered.
|
673
676
|
@param value The optional initial value for the field when the dialog is displayed.
|
674
677
|
@param minNumber The optional min value if the fieldType = NUMBER_INPUT_FIELD_TYPE.
|
675
678
|
@param maxNumber The optional max value if the fieldType = NUMBER_INPUT_FIELD_TYPE.
|
679
|
+
@param step The step size for numerical input fields.
|
676
680
|
"""
|
677
681
|
if name and len(name) > 0:
|
678
682
|
if fieldType in YesNoDialog.VALID_FIELD_TYPE_LIST:
|
@@ -680,14 +684,15 @@ class YesNoDialog(object):
|
|
680
684
|
YesNoDialog.VALUE_KEY: value,
|
681
685
|
YesNoDialog.MIN_NUMBER_KEY: minNumber,
|
682
686
|
YesNoDialog.MAX_NUMBER_KEY: maxNumber,
|
683
|
-
YesNoDialog.OPTIONS_KEY: options
|
687
|
+
YesNoDialog.OPTIONS_KEY: options,
|
688
|
+
YesNoDialog.STEP_KEY: step}
|
684
689
|
|
685
690
|
else:
|
686
691
|
raise Exception(f"YesNoDialog.addField() {fieldType} is an invalid field type.")
|
687
692
|
|
688
693
|
else:
|
689
694
|
raise Exception("YesNoDialog.addField() name not set.")
|
690
|
-
|
695
|
+
|
691
696
|
def _init(self):
|
692
697
|
"""@brief Init the dialog."""
|
693
698
|
with ui.dialog() as self._dialog, ui.card():
|
@@ -701,11 +706,13 @@ class YesNoDialog(object):
|
|
701
706
|
value = self._inputFieldDict[fieldName][YesNoDialog.VALUE_KEY]
|
702
707
|
min = self._inputFieldDict[fieldName][YesNoDialog.MIN_NUMBER_KEY]
|
703
708
|
max = self._inputFieldDict[fieldName][YesNoDialog.MAX_NUMBER_KEY]
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
+
step = self._inputFieldDict[fieldName][YesNoDialog.STEP_KEY]
|
710
|
+
widget = ui.number(label=fieldName,
|
711
|
+
value=value,
|
712
|
+
min=min,
|
713
|
+
max=max,
|
714
|
+
step=step).style('width: 200px;')
|
715
|
+
|
709
716
|
elif fieldType == YesNoDialog.SWITCH_INPUT_FIELD_TYPE:
|
710
717
|
widget = ui.switch(fieldName)
|
711
718
|
|
@@ -717,7 +724,7 @@ class YesNoDialog(object):
|
|
717
724
|
widget.tooltip(fieldName)
|
718
725
|
else:
|
719
726
|
raise Exception("BUG: DROPDOWN_INPUT_FIELD defined without defining the options.")
|
720
|
-
|
727
|
+
|
721
728
|
elif fieldType == YesNoDialog.COLOR_INPUT_FIELD:
|
722
729
|
widget = ui.color_input(label=fieldName)
|
723
730
|
|
@@ -733,6 +740,10 @@ class YesNoDialog(object):
|
|
733
740
|
widget = ui.knob(show_value=True)
|
734
741
|
widget.tooltip(fieldName)
|
735
742
|
|
743
|
+
elif fieldType == YesNoDialog.HOUR_MIN_INPUT_FIELD_TYPE:
|
744
|
+
widget = self._get_input_time_field(fieldName)
|
745
|
+
widget.tooltip(fieldName)
|
746
|
+
|
736
747
|
# Save a ref to the widet in the field dict
|
737
748
|
self._inputFieldDict[fieldName][YesNoDialog.WIDGET_KEY] = widget
|
738
749
|
|
@@ -745,6 +756,25 @@ class YesNoDialog(object):
|
|
745
756
|
ui.button(self._successButtonText, on_click=self._internalSuccessMethod)
|
746
757
|
ui.button(self._failureButtonText, on_click=self._internalFailureMethod)
|
747
758
|
|
759
|
+
def _get_input_time_field(self, label):
|
760
|
+
"""@brief Add a control to allow the user to enter the time as an hour and min.
|
761
|
+
@param label The label for the time field.
|
762
|
+
@return The input field containing the hour and minute entered."""
|
763
|
+
# Put this off the bottom of the mobile screen as most times it will not be needed
|
764
|
+
# and there is not enough room on the mobile screen above the plot pane.
|
765
|
+
with ui.row().classes('w-full'):
|
766
|
+
ui.label(label)
|
767
|
+
with ui.row().classes('w-full'):
|
768
|
+
time_input = ui.input("Time (HH:MM)")
|
769
|
+
with time_input as time:
|
770
|
+
with ui.menu().props('no-parent-event') as menu:
|
771
|
+
with ui.time().bind_value(time):
|
772
|
+
with ui.row().classes('justify-end'):
|
773
|
+
ui.button('Close', on_click=menu.close).props('flat')
|
774
|
+
with time.add_slot('append'):
|
775
|
+
ui.icon('access_time').on('click', menu.open).classes('cursor-pointer')
|
776
|
+
return time_input
|
777
|
+
|
748
778
|
def setPrompt(self, prompt):
|
749
779
|
"""@brief Set the user prompt.
|
750
780
|
@param prompt The user prompt."""
|
@@ -769,7 +799,7 @@ class YesNoDialog(object):
|
|
769
799
|
"""@brief Set the text of the failure button.
|
770
800
|
@param label The failure button text."""
|
771
801
|
self._failureButtonText = label
|
772
|
-
|
802
|
+
|
773
803
|
def show(self):
|
774
804
|
"""@brief Allow the user to select yes/no, ok/cancel etc in response to a question."""
|
775
805
|
self._init()
|
@@ -796,7 +826,7 @@ class YesNoDialog(object):
|
|
796
826
|
widget = self._inputFieldDict[fieldName][YesNoDialog.WIDGET_KEY]
|
797
827
|
if hasattr(widget, 'value'):
|
798
828
|
self._inputFieldDict[fieldName][YesNoDialog.VALUE_KEY] = self._inputFieldDict[fieldName][YesNoDialog.WIDGET_KEY].value
|
799
|
-
# If defined call the method
|
829
|
+
# If defined call the method
|
800
830
|
if self._successMethod:
|
801
831
|
self._successMethod()
|
802
832
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: p3lib
|
3
|
-
Version: 1.1.
|
3
|
+
Version: 1.1.99
|
4
4
|
Summary: A group of python modules for networking, plotting data, config storage, automating boot scripts, ssh access and user input output.
|
5
5
|
Home-page: https://github.com/pjaos/p3lib
|
6
6
|
Author: Paul Austen
|
@@ -2,7 +2,7 @@ p3lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
p3lib/ate.py,sha256=_BiqMUYNAlp4O8MkP_PAUe62Bzd8dzV4Ipv62OFS6Ok,4759
|
3
3
|
p3lib/bokeh_auth.py,sha256=zi_Hty2WkCJVNQ4sINNRo5FBXujuJky9phyoF6M55ms,15180
|
4
4
|
p3lib/bokeh_gui.py,sha256=55sajP_x9O1lE0uP3w3-T5f2oMzk7jSolLqxlEdLeLg,40245
|
5
|
-
p3lib/boot_manager.py,sha256=
|
5
|
+
p3lib/boot_manager.py,sha256=l7WlaoIRt4cu2jAdCs3FnDeNO2v2jzqideNZ3qg2r_4,19652
|
6
6
|
p3lib/conduit.py,sha256=jPkjdtyCx2I6SFqcEo8y2g7rgnZ-jNY7oCuYIETzT5Q,6046
|
7
7
|
p3lib/database_if.py,sha256=XKu1w3zftGbj4Rh54wrWJnoCtqHkhCzJUPN2S70XIKg,11915
|
8
8
|
p3lib/helper.py,sha256=xTKPgpziwr4zyaoc0sjZRFr0M91fo7Tok_nSAvtiTZE,12020
|
@@ -10,13 +10,13 @@ p3lib/json_networking.py,sha256=6u4s1SmypjTYPnSxHP712OgQ3ZJaxOqIkgHQ1J7Qews,9738
|
|
10
10
|
p3lib/mqtt_rpc.py,sha256=6LmFA1kR4HSJs9eWbOJORRHNY01L_lHWjvtE2fmY8P8,10511
|
11
11
|
p3lib/netif.py,sha256=3QV5OGdHhELIf4MBj6mx5MNCtVeZ7JXoNEkeu4KzCaE,9796
|
12
12
|
p3lib/netplotly.py,sha256=PMDx-w1jtRVW6Od5u_kuKbBxNpTS_Y88mMF60puMxLM,9363
|
13
|
-
p3lib/ngt.py,sha256=
|
13
|
+
p3lib/ngt.py,sha256=Y4HsSiaBEYi7OFWkqkDDJende_yyxsvIjUremfArXgA,39204
|
14
14
|
p3lib/pconfig.py,sha256=82rX7zvJVbSFtYYHvhj9I6GTdpjf9v73fNs9WQE-5ik,35388
|
15
15
|
p3lib/ssh.py,sha256=OyoAQ_h1L2RfkjTAChDrvLFfl4Fe_gBNdX5rvK-wKiw,42125
|
16
16
|
p3lib/table_plot.py,sha256=RPncwVlGUkkx5Fw0dHQedXo0TSPlTi__VrJBDzaMsuI,32116
|
17
17
|
p3lib/uio.py,sha256=Aaxc99XiE3d2f9vLjaN-bZsckoNxay5t0ujdK6PXGrw,23265
|
18
|
-
p3lib-1.1.
|
19
|
-
p3lib-1.1.
|
20
|
-
p3lib-1.1.
|
21
|
-
p3lib-1.1.
|
22
|
-
p3lib-1.1.
|
18
|
+
p3lib-1.1.99.dist-info/LICENSE,sha256=igqTy5u0kVWM1n-NUZMvAlinY6lVjAXKoag0okkS8V8,1067
|
19
|
+
p3lib-1.1.99.dist-info/METADATA,sha256=BRMel71eg_zTF7-aETcdkRo91XDaJpwFucDaLnE2-KM,918
|
20
|
+
p3lib-1.1.99.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
21
|
+
p3lib-1.1.99.dist-info/top_level.txt,sha256=SDCpXYh-19yCFp4Z8ZK4B-3J4NvTCJElZ42NPgcR6-U,6
|
22
|
+
p3lib-1.1.99.dist-info/RECORD,,
|
File without changes
|
File without changes
|