|
|
|
from ... import Orchestrator
|
|
|
|
import jinja2
|
|
|
|
import os
|
|
|
|
from inspect import signature # For detect count of def args
|
|
|
|
from ..Web import Basic
|
|
|
|
import operator
|
|
|
|
import math
|
|
|
|
|
|
|
|
class ControlPanel():
|
|
|
|
"""
|
|
|
|
Manage your control panel on the orchestrator
|
|
|
|
|
|
|
|
Control panel has 3 events types:
|
|
|
|
- onRefreshHTML - run every n (see settings) second to detect changes in HTML control panel.
|
|
|
|
- onRefreshJSON - run every n (see settings) second to detect changes in JSON data container to client side.
|
|
|
|
- onInitJS - run when client reload the Orchestrator web page
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
# Usage example:
|
|
|
|
lCPManager = Orchestrator.Managers.ControlPanel(inControlPanelNameStr="TestControlPanel",
|
|
|
|
inRefreshHTMLJinja2TemplatePathStr="ControlPanel\\test.html", inJinja2TemplateRefreshBool = True)
|
|
|
|
|
|
|
|
If you use Jinja2 you can use next data context:
|
|
|
|
StorageRobotDict: Orchestrator.StorageRobotGet(inRobotNameStr=self.mRobotNameStr),
|
|
|
|
ControlPanelInstance: self,
|
|
|
|
OrchestratorModule:Orchestrator,
|
|
|
|
RequestInstance: inRequest,
|
|
|
|
UserInfoDict: Orchestrator.WebUserInfoGet(inRequest=inRequest),
|
|
|
|
UserUACDict: Orchestrator.UACUserDictGet(inRequest=inRequest),
|
|
|
|
UserUACCheckDef: inRequest.UACClientCheck,
|
|
|
|
EnumerateDef: enumerate,
|
|
|
|
OperatorModule: operator,
|
|
|
|
MathModule: math
|
|
|
|
|
|
|
|
.. code-block:: html
|
|
|
|
Hello my control panel!
|
|
|
|
You can use any def from Orchestrator module here in Jinja2 HTML template:
|
|
|
|
Example: OrchestratorModule.OSCMD(inCMDStr="notepad")
|
|
|
|
{{MathModule.pi}}
|
|
|
|
{% if UserInfoDict['UserNameUpperStr']=="ND" %}
|
|
|
|
YES - IT IS ND
|
|
|
|
{% endif %}
|
|
|
|
|
|
|
|
"""
|
|
|
|
mControlPanelNameStr = None
|
|
|
|
# Jinja2 consolidated
|
|
|
|
mJinja2TemplateRefreshBool = None
|
|
|
|
mJinja2DataUpdateDict = None
|
|
|
|
|
|
|
|
# RefreshHTML block
|
|
|
|
mRefreshHTMLJinja2TemplatePathStr = None
|
|
|
|
mRefreshHTMLJinja2TemplateFileNameStr = None
|
|
|
|
mRefreshHTMLJinja2Loader = None
|
|
|
|
mRefreshHTMLJinja2Env = None
|
|
|
|
mRefreshHTMLJinja2Template = None
|
|
|
|
|
|
|
|
# InitJS block
|
|
|
|
mInitJSJinja2TemplatePathStr = None
|
|
|
|
mInitJSJinja2TemplateFileNameStr = None
|
|
|
|
mInitJSJinja2Loader = None
|
|
|
|
mInitJSJinja2Env = None
|
|
|
|
mInitJSJinja2Template = None
|
|
|
|
|
|
|
|
mBackwardCompatibilityHTMLDef = None
|
|
|
|
mBackwardCompatibilityJSDef = None
|
|
|
|
mBackwardCompatibilityJSONDef = None
|
|
|
|
|
|
|
|
mRobotNameStr = None
|
|
|
|
|
|
|
|
def __init__(self, inControlPanelNameStr, inRefreshHTMLJinja2TemplatePathStr = None, inJinja2TemplateRefreshBool = False, inRobotNameStr = None):
|
|
|
|
"""
|
|
|
|
Constructor of the control panel manager
|
|
|
|
|
|
|
|
:param inControlPanelNameStr:
|
|
|
|
:param inJinja2TemplatePathStr:
|
|
|
|
"""
|
|
|
|
# Connect self witch pyOpenRPA via ControlPanelNameStr
|
|
|
|
if inControlPanelNameStr in Orchestrator.GSettingsGet()["ServerDict"]["ControlPanelDict"]:
|
|
|
|
raise Exception(f"Another control panel with name {inControlPanelNameStr} is already exists. Please resolve the error and restart")
|
|
|
|
Orchestrator.GSettingsGet()["ServerDict"]["ControlPanelDict"][inControlPanelNameStr] = self
|
|
|
|
self.RefreshHTMLJinja2TemplatePathSet(inJinja2TemplatePathStr = inRefreshHTMLJinja2TemplatePathStr)
|
|
|
|
self.mJinja2TemplateRefreshBool = inJinja2TemplateRefreshBool
|
|
|
|
self.mControlPanelNameStr = inControlPanelNameStr # Set the name of the control panel
|
|
|
|
self.mRobotNameStr = inRobotNameStr # Set the robot name for robot it execute
|
|
|
|
|
|
|
|
def Jinja2DataUpdateDictSet(self, inJinja2DataUpdateDict):
|
|
|
|
"""
|
|
|
|
Set the data dict from the Jinja2 context (you can add some new params)
|
|
|
|
|
|
|
|
:param inJinja2DataUpdateDict: dict, which will be appended to main data context
|
|
|
|
:return: None
|
|
|
|
"""
|
|
|
|
self.mJinja2DataUpdateDict = inJinja2DataUpdateDict
|
|
|
|
|
|
|
|
def RefreshHTMLJinja2TemplatePathSet(self, inJinja2TemplatePathStr):
|
|
|
|
"""
|
|
|
|
Create Jinja2 env and load the template html
|
|
|
|
|
|
|
|
:param inJinja2TemplatePathStr:
|
|
|
|
:return:
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
if inJinja2TemplatePathStr is not None:
|
|
|
|
lSystemLoaderPathStr = "/".join(inJinja2TemplatePathStr.split("\\")[0:-1])
|
|
|
|
lTemplateFileNameStr = inJinja2TemplatePathStr.split("\\")[-1]
|
|
|
|
self.mRefreshHTMLJinja2TemplateFileNameStr = lTemplateFileNameStr
|
|
|
|
self.mRefreshHTMLJinja2Loader = jinja2.FileSystemLoader(lSystemLoaderPathStr)
|
|
|
|
self.mRefreshHTMLJinja2Env = jinja2.Environment(loader=self.mRefreshHTMLJinja2Loader, trim_blocks=True)
|
|
|
|
self.mRefreshHTMLJinja2Template = self.mRefreshHTMLJinja2Env.get_template(lTemplateFileNameStr)
|
|
|
|
except Exception as e:
|
|
|
|
Orchestrator.OrchestratorLoggerGet().exception("EXCEPTION WHEN INIT Jinja2")
|
|
|
|
|
|
|
|
def RefreshHTMLJinja2StrGenerate(self, inDataDict):
|
|
|
|
"""
|
|
|
|
Generate the HTML str from the Jinja2. Pass the context inDataDict
|
|
|
|
:param inDataDict:
|
|
|
|
:return:
|
|
|
|
"""
|
|
|
|
if self.mJinja2TemplateRefreshBool == True:
|
|
|
|
self.mRefreshHTMLJinja2Template = self.mRefreshHTMLJinja2Env.get_template(self.mRefreshHTMLJinja2TemplateFileNameStr)
|
|
|
|
lHTMLStr = self.mRefreshHTMLJinja2Template.render(**inDataDict) # Render the template into str
|
|
|
|
return lHTMLStr
|
|
|
|
|
|
|
|
def InitJSJinja2TemplatePathSet(self, inJinja2TemplatePathStr):
|
|
|
|
"""
|
|
|
|
Create Jinja2 env and load the template html
|
|
|
|
|
|
|
|
:param inJinja2TemplatePathStr:
|
|
|
|
:return:
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
if inJinja2TemplatePathStr is not None:
|
|
|
|
lSystemLoaderPathStr = "/".join(inJinja2TemplatePathStr.split("\\")[0:-1])
|
|
|
|
lTemplateFileNameStr = inJinja2TemplatePathStr.split("\\")[-1]
|
|
|
|
self.mInitJSJinja2TemplateFileNameStr = lTemplateFileNameStr
|
|
|
|
self.mInitJSJinja2Loader = jinja2.FileSystemLoader(lSystemLoaderPathStr)
|
|
|
|
self.mInitJSJinja2Env = jinja2.Environment(loader=self.mInitJSJinja2Loader, trim_blocks=True)
|
|
|
|
self.mInitJSJinja2Template = self.mInitJSJinja2Env.get_template(lTemplateFileNameStr)
|
|
|
|
except Exception as e:
|
|
|
|
Orchestrator.OrchestratorLoggerGet().exception("EXCEPTION WHEN INIT Jinja2")
|
|
|
|
|
|
|
|
def InitJSJinja2StrGenerate(self, inDataDict):
|
|
|
|
"""
|
|
|
|
Generate the HTML str from the Jinja2. Pass the context inDataDict
|
|
|
|
:param inDataDict:
|
|
|
|
:return:
|
|
|
|
"""
|
|
|
|
if self.mJinja2TemplateRefreshBool == True:
|
|
|
|
self.mInitJSJinja2Template = self.mInitJSJinja2Env.get_template(self.mInitJSJinja2TemplateFileNameStr)
|
|
|
|
lHTMLStr = self.mInitJSJinja2Template.render(**inDataDict) # Render the template into str
|
|
|
|
return lHTMLStr
|
|
|
|
|
|
|
|
def DataDictGenerate(self, inRequest):
|
|
|
|
"""
|
|
|
|
|
|
|
|
:param inRequest: request handler (from http.server import BaseHTTPRequestHandler)
|
|
|
|
:return:
|
|
|
|
"""
|
|
|
|
lData = {
|
|
|
|
"StorageRobotDict": None,
|
|
|
|
"ControlPanelInstance":self,
|
|
|
|
"OrchestratorModule":Orchestrator,
|
|
|
|
"RequestInstance": inRequest,
|
|
|
|
"UserInfoDict": Orchestrator.WebUserInfoGet(inRequest=inRequest),
|
|
|
|
"UserUACDict": Orchestrator.UACUserDictGet(inRequest=inRequest),
|
|
|
|
"UserUACCheckDef": inRequest.UACClientCheck,
|
|
|
|
"EnumerateDef": enumerate,
|
|
|
|
"OperatorModule": operator,
|
|
|
|
"MathModule": math
|
|
|
|
}
|
|
|
|
# Get the robot storage by the robot name (if you set robot name when init)
|
|
|
|
if self.mRobotNameStr is not None:
|
|
|
|
lData["StorageRobotDict"] = Orchestrator.StorageRobotGet(inRobotNameStr=self.mRobotNameStr)
|
|
|
|
# Checkj Jinja2DataUpdateDict
|
|
|
|
if self.mJinja2DataUpdateDict is not None:
|
|
|
|
lData.update(self.mJinja2DataUpdateDict)
|
|
|
|
return lData
|
|
|
|
|
|
|
|
def OnRefreshHTMLStr(self, inRequest):
|
|
|
|
"""
|
|
|
|
Event to generate HTML code of the control panel when refresh time is over.
|
|
|
|
Support backward compatibility for previous versions.
|
|
|
|
|
|
|
|
:param inRequest: request handler (from http.server import BaseHTTPRequestHandler)
|
|
|
|
:return:
|
|
|
|
"""
|
|
|
|
lHTMLStr = None
|
|
|
|
lL = Orchestrator.OrchestratorLoggerGet()
|
|
|
|
if self.mBackwardCompatibilityHTMLDef is None:
|
|
|
|
if self.mRefreshHTMLJinja2Template is not None or (self.mJinja2TemplateRefreshBool == True and self.mRefreshHTMLJinja2TemplateFileNameStr is not None):
|
|
|
|
lDataDict = self.OnRefreshHTMLDataDict(inRequest = inRequest)
|
|
|
|
# Jinja code
|
|
|
|
lHTMLStr = self.RefreshHTMLJinja2StrGenerate(inDataDict=lDataDict)
|
|
|
|
else:
|
|
|
|
lHTMLStr = self.BackwardAdapterHTMLDef(inRequest=inRequest)
|
|
|
|
# return the str
|
|
|
|
return lHTMLStr
|
|
|
|
|
|
|
|
def OnRefreshHTMLDataDict(self, inRequest):
|
|
|
|
"""
|
|
|
|
Event to prepare data context for the futher Jinja2 HTML generation. You can override this def if you want some thing more data
|
|
|
|
|
|
|
|
:param inRequest: request handler (from http.server import BaseHTTPRequestHandler)
|
|
|
|
:return: dict
|
|
|
|
"""
|
|
|
|
return self.DataDictGenerate(inRequest=inRequest)
|
|
|
|
|
|
|
|
def OnRefreshHTMLHashStr(self, inRequest):
|
|
|
|
"""
|
|
|
|
Generate the hash the result output HTML. You can override this function if you know how to optimize HTML rendering.
|
|
|
|
TODO NEED TO MODIFY ServerSettings to work with Hash because of all defs are need do use Hash
|
|
|
|
|
|
|
|
:param inRequest: request handler (from http.server import BaseHTTPRequestHandler)
|
|
|
|
:return: None - default, hash function is not determined. Str - hash function is working on!
|
|
|
|
"""
|
|
|
|
return None
|
|
|
|
|
|
|
|
def OnRefreshJSONDict(self, inRequest):
|
|
|
|
"""
|
|
|
|
Event to transmit some data from server side to the client side in JSON format. Call when page refresh is initialized
|
|
|
|
|
|
|
|
:param inRequest: request handler (from http.server import BaseHTTPRequestHandler)
|
|
|
|
:return: Dict type
|
|
|
|
"""
|
|
|
|
lResultDict = None
|
|
|
|
if self.mBackwardCompatibilityJSONDef is None:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
lResultDict = self.BackwardAdapterJSONDef(inRequest=inRequest)
|
|
|
|
return lResultDict
|
|
|
|
|
|
|
|
def OnInitJSStr(self, inRequest):
|
|
|
|
"""
|
|
|
|
Event when orchestrator web page is init on the client side - you can transmit some java script code is str type to execute it once.
|
|
|
|
|
|
|
|
:param inRequest: request handler (from http.server import BaseHTTPRequestHandler)
|
|
|
|
:return: ""
|
|
|
|
"""
|
|
|
|
lJSStr = ""
|
|
|
|
if self.mBackwardCompatibilityJSDef is None:
|
|
|
|
if self.mInitJSJinja2Template is not None or (self.mJinja2TemplateRefreshBool == True and self.mInitJSJinja2TemplateFileNameStr is not None):
|
|
|
|
lDataDict = self.OnInitJSDataDict(inRequest = inRequest)
|
|
|
|
# Jinja code
|
|
|
|
lJSStr = self.InitJSJinja2StrGenerate(inDataDict=lDataDict)
|
|
|
|
else:
|
|
|
|
lJSStr = self.BackwardAdapterJSDef(inRequest=inRequest)
|
|
|
|
return lJSStr
|
|
|
|
|
|
|
|
def OnInitJSDataDict(self, inRequest):
|
|
|
|
"""
|
|
|
|
Event to prepare data context for the futher Jinja2 JS init generation. You can override this def if you want some thing more data
|
|
|
|
|
|
|
|
:param inRequest: request handler (from http.server import BaseHTTPRequestHandler)
|
|
|
|
:return: dict
|
|
|
|
"""
|
|
|
|
return self.DataDictGenerate(inRequest=inRequest)
|
|
|
|
|
|
|
|
def BackwardAdapterHTMLDef(self,inRequest):
|
|
|
|
lGS = Orchestrator.GSettingsGet()
|
|
|
|
lL = Orchestrator.OrchestratorLoggerGet()
|
|
|
|
# HTMLRenderDef
|
|
|
|
lItemHTMLRenderDef = self.mBackwardCompatibilityHTMLDef
|
|
|
|
lResultStr = ""
|
|
|
|
if lItemHTMLRenderDef is not None: # Call def (inRequest, inGSettings) or def (inGSettings)
|
|
|
|
lHTMLResult = None
|
|
|
|
lDEFSignature = signature(lItemHTMLRenderDef) # Get signature of the def
|
|
|
|
lDEFARGLen = len(lDEFSignature.parameters.keys()) # get count of the def args
|
|
|
|
try:
|
|
|
|
if lDEFARGLen == 1: # def (inGSettings)
|
|
|
|
lHTMLResult = lItemHTMLRenderDef(lGS)
|
|
|
|
elif lDEFARGLen == 2: # def (inRequest, inGSettings)
|
|
|
|
lHTMLResult = lItemHTMLRenderDef(inRequest, lGS)
|
|
|
|
elif lDEFARGLen == 0: # def ()
|
|
|
|
lHTMLResult = lItemHTMLRenderDef()
|
|
|
|
# RunFunction
|
|
|
|
# Backward compatibility up to 1.2.0 - call HTML generator if result has no "HTMLStr"
|
|
|
|
if type(lHTMLResult) is str:
|
|
|
|
lResultStr = lHTMLResult
|
|
|
|
elif "HTMLStr" in lHTMLResult or "JSONDict" in lHTMLResult:
|
|
|
|
lResultStr = lHTMLResult["HTMLStr"]
|
|
|
|
else:
|
|
|
|
# Call backward compatibility HTML generator
|
|
|
|
lResultStr = Basic.HTMLControlPanelBC(inCPDict=lHTMLResult)
|
|
|
|
except Exception as e:
|
|
|
|
if lL: lL.exception(f"Error in control panel HTMLRenderDef. CP Key {self.mControlPanelNameStr}. Exception are below")
|
|
|
|
return lResultStr
|
|
|
|
|
|
|
|
|
|
|
|
def BackwardAdapterJSONDef(self,inRequest):
|
|
|
|
lGS = Orchestrator.GSettingsGet()
|
|
|
|
lL = Orchestrator.OrchestratorLoggerGet()
|
|
|
|
# HTMLRenderDef
|
|
|
|
lItemJSONGeneratorDef = self.mBackwardCompatibilityJSONDef
|
|
|
|
lResultDict = {}
|
|
|
|
if lItemJSONGeneratorDef is not None: # Call def (inRequest, inGSettings) or def (inGSettings)
|
|
|
|
lJSONResult = None
|
|
|
|
lDEFSignature = signature(lItemJSONGeneratorDef) # Get signature of the def
|
|
|
|
lDEFARGLen = len(lDEFSignature.parameters.keys()) # get count of the def args
|
|
|
|
try:
|
|
|
|
if lDEFARGLen == 1: # def (inGSettings)
|
|
|
|
lJSONResult = lItemJSONGeneratorDef(lGS)
|
|
|
|
elif lDEFARGLen == 2: # def (inRequest, inGSettings)
|
|
|
|
lJSONResult = lItemJSONGeneratorDef(inRequest, lGS)
|
|
|
|
elif lDEFARGLen == 0: # def ()
|
|
|
|
lJSONResult = lItemJSONGeneratorDef()
|
|
|
|
# RunFunction
|
|
|
|
# Backward compatibility up to 1.2.0 - call HTML generator if result has no "HTMLStr"
|
|
|
|
lType = type(lJSONResult)
|
|
|
|
if lType is str or lJSONResult is None or lType is int or lType is list or lType is dict or lType is bool or lType is float:
|
|
|
|
lResultDict = lJSONResult
|
|
|
|
else:
|
|
|
|
if lL: lL.warning(f"JSONGenerator return bad type: {str(type(lJSONResult))}, CP Key {self.mControlPanelNameStr}")
|
|
|
|
except Exception as e:
|
|
|
|
if lL: lL.exception(
|
|
|
|
f"Error in control panel JSONGeneratorDef. CP Key {self.mControlPanelNameStr}. Exception are below")
|
|
|
|
return lResultDict
|
|
|
|
|
|
|
|
def BackwardAdapterJSDef(self,inRequest):
|
|
|
|
lGS = Orchestrator.GSettingsGet()
|
|
|
|
lL = Orchestrator.OrchestratorLoggerGet()
|
|
|
|
# HTMLRenderDef
|
|
|
|
lJSInitGeneratorDef = self.mBackwardCompatibilityJSDef
|
|
|
|
lResultStr = ""
|
|
|
|
if lJSInitGeneratorDef is not None: # Call def (inRequest, inGSettings) or def (inGSettings)
|
|
|
|
lJSResult = ""
|
|
|
|
lDEFSignature = signature(lJSInitGeneratorDef) # Get signature of the def
|
|
|
|
lDEFARGLen = len(lDEFSignature.parameters.keys()) # get count of the def args
|
|
|
|
try:
|
|
|
|
if lDEFARGLen == 1: # def (inGSettings)
|
|
|
|
lJSResult = lJSInitGeneratorDef(lGS)
|
|
|
|
elif lDEFARGLen == 2: # def (inRequest, inGSettings)
|
|
|
|
lJSResult = lJSInitGeneratorDef(inRequest, lGS)
|
|
|
|
elif lDEFARGLen == 0: # def ()
|
|
|
|
lJSResult = lJSInitGeneratorDef()
|
|
|
|
if type(lJSResult) is str:
|
|
|
|
lResultStr = lJSResult # Add delimiter to some cases
|
|
|
|
else:
|
|
|
|
if lL: lL.warning(f"JSInitGenerator return bad type: {str(type(lJSResult))}, CP Key {self.mControlPanelNameStr}")
|
|
|
|
except Exception as e:
|
|
|
|
if lL: lL.exception(
|
|
|
|
f"Error in control panel JSInitGeneratorDef. CP Key {self.mControlPanelNameStr}. Exception are below")
|
|
|
|
return lResultStr
|