diff --git a/Orchestrator/Settings/SettingsOrchestratorExample.py b/Orchestrator/Settings/SettingsOrchestratorExample.py index 21c352bf..32384138 100644 --- a/Orchestrator/Settings/SettingsOrchestratorExample.py +++ b/Orchestrator/Settings/SettingsOrchestratorExample.py @@ -79,12 +79,12 @@ def Settings(): lOrchestratorFolder = "\\".join(pyOpenRPA.Orchestrator.__file__.split("\\")[:-1]) mDict = { "Autocleaner": { # Some gurbage is collecting in g settings. So you can configure autocleaner to periodically clear gSettings - "IntervalSecFloat": 10, # Sec float to periodically clear gsettings + "IntervalSecFloat": 600.0, # Sec float to periodically clear gsettings }, "Client":{ # Settings about client web orchestrator "Session":{ # Settings about web session. Session algorythms works only for special requests (URL in ServerSettings) - "LifetimeSecFloat": 10.0, # Client Session lifetime in seconds. after this time server will forget about this client session - "LifetimeRequestSecFloat": 15.0, # 1 client request lifetime in server in seconds + "LifetimeSecFloat": 600.0, # Client Session lifetime in seconds. after this time server will forget about this client session + "LifetimeRequestSecFloat": 120.0, # 1 client request lifetime in server in seconds "ControlPanelRefreshIntervalSecFloat": 1.5, # Interval to refresh control panels for session, "TechnicalSessionGUIDCache": { # TEchnical cache. Fills when web browser is requesting #"SessionGUIDStr":{ # Session with some GUID str. On client session guid stored in cookie "SessionGUIDStr" @@ -103,6 +103,8 @@ def Settings(): } }, "Server": { + "WorkingDirectoryPathStr": None , # Will be filled automatically + "RequestTimeoutSecFloat": 300, # Time to handle request in seconds "ListenPort_": "Порт, по которому можно подключиться к демону", "ListenPort": 80, "ListenURLList": [ @@ -363,12 +365,15 @@ def Settings(): lFileList = [f for f in os.listdir(lSettingsPath) if os.path.isfile(os.path.join(lSettingsPath, f)) and f.split(".")[-1] == "py" and os.path.join(lSettingsPath, f) != __file__] import importlib.util for lModuleFilePathItem in lFileList + gControlPanelPyFilePathList: # UPD 2020 04 27 Add gControlPanelPyFilePathList to import py files from Robots - lModuleName = lModuleFilePathItem[0:-3] - lFileFullPath = os.path.join(lSettingsPath, lModuleFilePathItem) - lTechSpecification = importlib.util.spec_from_file_location(lModuleName, lFileFullPath) - lTechModuleFromSpec = importlib.util.module_from_spec(lTechSpecification) - lTechSpecificationModuleLoader = lTechSpecification.loader.exec_module(lTechModuleFromSpec) - if lSubmoduleFunctionName in dir(lTechModuleFromSpec): - #Run SettingUpdate function in submodule - getattr(lTechModuleFromSpec, lSubmoduleFunctionName)(mDict) + try: # Try to init - go next if error and log in logger + lModuleName = lModuleFilePathItem[0:-3] + lFileFullPath = os.path.join(lSettingsPath, lModuleFilePathItem) + lTechSpecification = importlib.util.spec_from_file_location(lModuleName, lFileFullPath) + lTechModuleFromSpec = importlib.util.module_from_spec(lTechSpecification) + lTechSpecificationModuleLoader = lTechSpecification.loader.exec_module(lTechModuleFromSpec) + if lSubmoduleFunctionName in dir(lTechModuleFromSpec): + #Run SettingUpdate function in submodule + getattr(lTechModuleFromSpec, lSubmoduleFunctionName)(mDict) + except Exception as e: + if mRobotLogger: mRobotLogger.exception(f"Error when init .py file in orchestrator '{lModuleFilePathItem}'. Exception is below:") return mDict \ No newline at end of file diff --git a/Sources/pyOpenRPA/Orchestrator/BackwardCompatibility.py b/Sources/pyOpenRPA/Orchestrator/BackwardCompatibility.py new file mode 100644 index 00000000..fadf0d3a --- /dev/null +++ b/Sources/pyOpenRPA/Orchestrator/BackwardCompatibility.py @@ -0,0 +1,41 @@ +# Def to check inGSettings and update structure to the backward compatibility +# !!! ATTENTION: Backward compatibility has been started from v1.1.13 !!! +# So you can use config of the orchestrator 1.1.13 in new Orchestrator versions and all will be ok :) (hope it's true) +def Update(inGSettings): + lL = inGSettings["Logger"] # Alias for logger + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + # v1.1.13 to v1.1.14 + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + if "Autocleaner" not in inGSettings: # Add "Autocleaner" structure + inGSettings["Autocleaner"] = { # Some gurbage is collecting in g settings. So you can configure autocleaner to periodically clear gSettings + "IntervalSecFloat": 600.0, # Sec float to periodically clear gsettings + } + if lL: lL.warning(f"Backward compatibility (v1.1.13 to v1.1.14): Add default 'Autocleaner' structure") # Log about compatibility + if "Client" not in inGSettings: # Add "Client" structure + inGSettings["Client"] = { # Settings about client web orchestrator + "Session":{ # Settings about web session. Session algorythms works only for special requests (URL in ServerSettings) + "LifetimeSecFloat": 600.0, # Client Session lifetime in seconds. after this time server will forget about this client session + "LifetimeRequestSecFloat": 120.0, # 1 client request lifetime in server in seconds + "ControlPanelRefreshIntervalSecFloat": 1.5, # Interval to refresh control panels for session, + "TechnicalSessionGUIDCache": { # TEchnical cache. Fills when web browser is requesting + #"SessionGUIDStr":{ # Session with some GUID str. On client session guid stored in cookie "SessionGUIDStr" + # "InitDatetime": None, # Datetime when session GUID was created + # "DatasetLast": { + # "ControlPanel": { + # "Data": None, # Struct to check with new iterations. None if starts + # "ReturnBool": False # flag to return, close request and return data as json + # } + # }, + # "ClientRequestHandler": None, # Last client request handler + # "UserADStr": None, # User, who connect. None if user is not exists + # "DomainADStr": None, # Domain of the user who connect. None if user is not exists + #} + } + } + } + if lL: lL.warning( + f"Backward compatibility (v1.1.13 to v1.1.14): Add default 'Client' structure") # Log about compatibility + if "RequestTimeoutSecFloat" not in inGSettings["Server"]: # Add Server > "RequestTimeoutSecFloat" property + inGSettings["Server"]["RequestTimeoutSecFloat"] = 300 # Time to handle request in seconds + if lL: lL.warning( + f"Backward compatibility (v1.1.13 to v1.1.14): Add default 'Server' > 'RequestTimeoutSecFloat' property") # Log about compatibility \ No newline at end of file diff --git a/Sources/pyOpenRPA/Orchestrator/Orchestrator.py b/Sources/pyOpenRPA/Orchestrator/Orchestrator.py index a4378a61..23c74398 100644 --- a/Sources/pyOpenRPA/Orchestrator/Orchestrator.py +++ b/Sources/pyOpenRPA/Orchestrator/Orchestrator.py @@ -10,6 +10,7 @@ import pdb from . import Server from . import Timer from . import Processor +from . import BackwardCompatibility # Backward compatibility from v1.1.13 #from .Settings import Settings import importlib @@ -79,6 +80,10 @@ if os.path.exists("_SessionLast_RDPList.json"): lDaemonLoopSeconds=gSettingsDict["Scheduler"]["ActivityTimeCheckLoopSeconds"] lDaemonActivityLogDict={} #Словарь отработанных активностей, ключ - кортеж (, , , ) lDaemonLastDateTime=datetime.datetime.now() +gSettingsDict["Server"]["WorkingDirectoryPathStr"] = os.getcwd() # Set working directory in g settings + +# Turn on backward compatibility +BackwardCompatibility.Update(inGSettings= gSettingsDict) #Инициализация сервера lThreadServer = Server.RobotDaemonServer("ServerThread", gSettingsDict) diff --git a/Sources/pyOpenRPA/Orchestrator/Processor.py b/Sources/pyOpenRPA/Orchestrator/Processor.py index 8a6fd112..2e64edb8 100644 --- a/Sources/pyOpenRPA/Orchestrator/Processor.py +++ b/Sources/pyOpenRPA/Orchestrator/Processor.py @@ -23,6 +23,9 @@ import psutil # "Type": "OrchestratorRestart" # }, # { +# "Type": "OrchestratorSessionSave" +# }, +# { # "Type": "GlobalDictKeyListValueSet", # "KeyList": ["key1","key2",...], # "Value": @@ -117,12 +120,23 @@ def Activity(inActivity): lFile = open("_SessionLast_RDPList.json", "w", encoding="utf-8") lFile.write(json.dumps(gSettingsDict["RobotRDPActive"]["RDPList"])) # dump json to file lFile.close() # Close the file - if lL: lL.info(f"Orchestrator has dump the RDP list before the restart. The RDP List is {gSettingsDict['RobotRDPActive']['RDPList']}") + if lL: lL.info(f"Orchestrator has dump the RDP list before the restart. The RDP List is {gSettingsDict['RobotRDPActive']['RDPList']}. Do restart") # Restart session os.execl(sys.executable, os.path.abspath(__file__), *sys.argv) lItem["Result"] = True sys.exit(0) ########################################################### + # Обработка команды OrchestratorSessionSave + ########################################################### + if lItem["Type"] == "OrchestratorSessionSave": + # Dump RDP List in file json + lFile = open("_SessionLast_RDPList.json", "w", encoding="utf-8") + lFile.write(json.dumps(gSettingsDict["RobotRDPActive"]["RDPList"])) # dump json to file + lFile.close() # Close the file + if lL: lL.info( + f"Orchestrator has dump the RDP list before the restart. The RDP List is {gSettingsDict['RobotRDPActive']['RDPList']}") + lItem["Result"] = True + ########################################################### #Обработка команды GlobalDictKeyListValueSet ########################################################### if lItem["Type"]=="GlobalDictKeyListValueSet": diff --git a/Sources/pyOpenRPA/Orchestrator/Server.py b/Sources/pyOpenRPA/Orchestrator/Server.py index 6780730c..44af44f3 100644 --- a/Sources/pyOpenRPA/Orchestrator/Server.py +++ b/Sources/pyOpenRPA/Orchestrator/Server.py @@ -467,7 +467,7 @@ class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): daemon_threads = True """Handle requests in a separate thread.""" def finish_request(self, request, client_address): - request.settimeout(30) + request.settimeout(gSettingsDict["Server"]["RequestTimeoutSecFloat"]) # "super" can not be used because BaseServer is not created from object HTTPServer.finish_request(self, request, client_address) #inGlobalDict diff --git a/Sources/pyOpenRPA/Orchestrator/ServerSettings.py b/Sources/pyOpenRPA/Orchestrator/ServerSettings.py index 5c332583..5663a4de 100644 --- a/Sources/pyOpenRPA/Orchestrator/ServerSettings.py +++ b/Sources/pyOpenRPA/Orchestrator/ServerSettings.py @@ -50,8 +50,8 @@ def Monitor_ControlPanelDictGet_SessionCheckInit(inRequest,inGlobalDict): lItemValue["DatasetLast"]["ControlPanel"]["ReturnBool"] = True # Set flag to return the data # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Technicaldef - Create new session struct - def TechnicalSessionNew(): - lCookieSessionGUIDStr = str(uuid.uuid4()) # Generate the new GUID + def TechnicalSessionNew(inSessionGUIDStr): + lCookieSessionGUIDStr = inSessionGUIDStr # Generate the new GUID lSessionNew = { # Session with some GUID str. On client session guid stored in cookie "SessionGUIDStr" "InitDatetime": datetime.datetime.now(), # Datetime when session GUID was created "DatasetLast": { @@ -71,13 +71,15 @@ def Monitor_ControlPanelDictGet_SessionCheckInit(inRequest,inGlobalDict): # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # lCreateNewSessionBool = False # Flag to create new session structure # step 1 - get cookie SessionGUIDStr - lCookies = cookies.SimpleCookie(inRequest.headers.get("Cookie", "")) - if "SessionGUIDStr" in lCookies: - lCookieSessionGUIDStr = lCookies.get("SessionGUIDStr", None).value # Get the cookie - if lCookieSessionGUIDStr not in inGlobalDict["Client"]["Session"]["TechnicalSessionGUIDCache"]: - lCookieSessionGUIDStr= TechnicalSessionNew() # Create new session + lSessionGUIDStr = inRequest.headers.get("SessionGUIDStr", None) + if lSessionGUIDStr is not None: # Check if GUID session is ok + lCookieSessionGUIDStr = lSessionGUIDStr # Get the existing GUID + if lSessionGUIDStr not in inGlobalDict["Client"]["Session"]["TechnicalSessionGUIDCache"]: + lCookieSessionGUIDStr= TechnicalSessionNew(inSessionGUIDStr = lSessionGUIDStr) # Create new session + else: # Update the datetime of the request session + inGlobalDict["Client"]["Session"]["TechnicalSessionGUIDCache"][lCookieSessionGUIDStr]["InitDatetime"]=datetime.datetime.now() else: - lCookieSessionGUIDStr = TechnicalSessionNew() # Create new session + lCookieSessionGUIDStr = TechnicalSessionNew(inSessionGUIDStr = lSessionGUIDStr) # Create new session # Init the RobotRDPActive in another thread #lThreadCheckCPInterval = threading.Thread(target=TechnicalIntervalCheck) #lThreadCheckCPInterval.daemon = True # Run the thread in daemon mode. @@ -103,7 +105,7 @@ def Monitor_ControlPanelDictGet_SessionCheckInit(inRequest,inGlobalDict): lItemValue["DatasetLast"]["ControlPanel"]["ReturnBool"] = False # Set flag that data was returned lDoWhileBool = False # Stop the iterations else: - lCookieSessionGUIDStr = TechnicalSessionNew() # Create new session + lCookieSessionGUIDStr = TechnicalSessionNew(inSessionGUIDStr = lCookieSessionGUIDStr) # Create new session if lDoWhileBool: # Sleep if we wait hte next iteration time.sleep(lControlPanelRefreshIntervalSecFloat) # Sleep to the next iteration diff --git a/Sources/pyOpenRPA/Orchestrator/Web/Index.js b/Sources/pyOpenRPA/Orchestrator/Web/Index.js index 1ca47658..8b71f6e0 100644 --- a/Sources/pyOpenRPA/Orchestrator/Web/Index.js +++ b/Sources/pyOpenRPA/Orchestrator/Web/Index.js @@ -1,5 +1,12 @@ var mGlobal={} +window.onload=function() { + //document.cookie = "SessionGUIDStr=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; + //Render existing data + //mGlobal.Monitor.fControlPanelRefresh_TechnicalRender() +} $(document).ready(function() { + document.cookie = "SessionGUIDStr=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; + console.log("Cookie is deleted") // fix main menu to page on passing $('.main.menu').visibility({ type: 'fixed' @@ -55,7 +62,7 @@ $(document).ready(function() { //For data storage key mGlobal["DataStorage"] = {} // Clear the session cookie - document.cookie = "SessionGUIDStr=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; + String.prototype.replaceAll = function(search, replace){ return this.split(search).join(replace); } @@ -93,6 +100,7 @@ $(document).ready(function() { /////Controller JS module ////////////////////////// mGlobal.Controller={}; + mGlobal.Controller.CMDRunText=function(inCMDText) { ///Подготовить конфигурацию lData = [ @@ -159,6 +167,28 @@ $(document).ready(function() { dataType: "text" }); } + ///Restart PC + mGlobal.Controller.PCRestart = function () { + mGlobal.Controller.CMDRunText("shutdown -r") + } + ///Orchestrator save session + mGlobal.Controller.OrchestratorSessionSave=function() { + ///Подготовить конфигурацию + lData = [ + {"Type":"OrchestratorSessionSave"} + ] + $.ajax({ + type: "POST", + url: 'Utils/Processor', + data: JSON.stringify(lData), + success: + function(lData,l2,l3) + { + var lResponseJSON=JSON.parse(lData) + }, + dataType: "text" + }); + } ///Перезагрузить Orchestrator mGlobal.Controller.OrchestratorRestart=function() { ///Подготовить конфигурацию @@ -177,6 +207,10 @@ $(document).ready(function() { dataType: "text" }); } + mGlobal.Controller.OrchestratorGITPullRestart = function() { + mGlobal.Controller.OrchestratorSessionSave() //Save current RDP list session + mGlobal.Controller.CMDRunText("timeout 3 & taskkill /f /im OpenRPA_Orchestrator.exe & timeout 2 & cd "+mGlobal.WorkingDirectoryPathStr+" & git reset --hard & git pull & pyOpenRPA.Orchestrator_x64_administrator_startup.cmd"); + } ////////////////////////// /////Monitor JS module ////////////////////////// @@ -230,83 +264,102 @@ $(document).ready(function() { ms += new Date().getTime(); while (new Date() < ms){} } + function uuidv4() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + } + mGlobal.SessionGUIDStr = uuidv4() // Generate uuid4 of the session + //console.log(uuidv4()); + mGlobal.Monitor.fControlPanelRefresh_TechnicalRender = function() + { + lResponseJSON = mGlobal.Monitor.mDatasetLast + if (lResponseJSON!= null) { + ///Escape onclick + /// RenderRobotList + lResponseJSON["RenderRobotList"].forEach( + function(lItem){ + if ('FooterButtonX2List' in lItem) { + /// FooterButtonX2List + lItem["FooterButtonX2List"].forEach( + function(lItem2){ + if ('OnClick' in lItem) { + lOnClickEscaped = lItem["OnClick"]; + lOnClickEscaped = lOnClickEscaped.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); + lItem["OnClick"] = lOnClickEscaped; + } + } + ); + /// FooterButtonX1List + lItem["FooterButtonX1List"].forEach( + function(lItem2){ + if ('OnClick' in lItem) { + lOnClickEscaped = lItem["OnClick"]; + lOnClickEscaped = lOnClickEscaped.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); + lItem["OnClick"] = lOnClickEscaped; + } + } + ); + } + } + ); + ////////////////////////////////////////////////////////// + ///Сформировать HTML код новой таблицы - контрольная панель + lHTMLCode=mGlobal.GeneralGenerateHTMLCodeHandlebars(".openrpa-hidden-control-panel",lResponseJSON) + //Присвоить ответ в mGlobal.Monitor.mResponseList + mGlobal.Monitor.mResponseList = lResponseJSON + ///Set result in mGlobal.DataStorage + lResponseJSON["RenderRobotList"].forEach( + function(lItem){ + if ('DataStorageKey' in lItem) { + mGlobal["DataStorage"][lItem['DataStorageKey']]=lItem + } + } + ) + ///Прогрузить новую таблицу + $(".openrpa-control-panel").html(lHTMLCode) + //////////////////////////////////////////////////// + ///Сформировать HTML код новой таблицы - список RDP + lHTMLCode=mGlobal.GeneralGenerateHTMLCodeHandlebars(".openrpa-hidden-robotrdpactive-control-panel",lResponseJSON) + //Присвоить ответ в mGlobal.RobotRDPActive.mResponseList + mGlobal.RobotRDPActive.mResponseList = lResponseJSON + ///Прогрузить новую таблицу + $(".openrpa-robotrdpactive-control-panel").html(lHTMLCode) + ///Очистить дерево + //mGlobal.ElementTree.fClear(); + } + } + mGlobal.Monitor.mDatasetLast = null mGlobal.Monitor.fControlPanelRefresh=function() { try { + //var XHR = new XMLHttpRequest(); + //XHR.setRequestHeader("Cookies",document.cookie) ///Загрузка данных + //console.log("Request is sent") + //console.log(document.cookie) $.ajax({ - type: "GET", - url: 'Monitor/ControlPanelDictGet', - data: '', - xhrFields: { - withCredentials: true - }, - success: function(lData,l2,l3) - { + type: "GET", + headers: {"SessionGUIDStr":mGlobal.SessionGUIDStr}, + url: 'Monitor/ControlPanelDictGet', + data: '', + //cache: false, + //xhr: XHR, + success: function(lData,l2,l3) { try { var lResponseJSON=JSON.parse(lData) - ///Escape onclick - /// RenderRobotList - lResponseJSON["RenderRobotList"].forEach( - function(lItem){ - if ('FooterButtonX2List' in lItem) { - /// FooterButtonX2List - lItem["FooterButtonX2List"].forEach( - function(lItem2){ - if ('OnClick' in lItem) { - lOnClickEscaped = lItem["OnClick"]; - lOnClickEscaped = lOnClickEscaped.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); - lItem["OnClick"] = lOnClickEscaped; - } - } - ); - /// FooterButtonX1List - lItem["FooterButtonX1List"].forEach( - function(lItem2){ - if ('OnClick' in lItem) { - lOnClickEscaped = lItem["OnClick"]; - lOnClickEscaped = lOnClickEscaped.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); - lItem["OnClick"] = lOnClickEscaped; - } - } - ); - } - } - ); - ////////////////////////////////////////////////////////// - ///Сформировать HTML код новой таблицы - контрольная панель - lHTMLCode=mGlobal.GeneralGenerateHTMLCodeHandlebars(".openrpa-hidden-control-panel",lResponseJSON) - //Присвоить ответ в mGlobal.Monitor.mResponseList - mGlobal.Monitor.mResponseList = lResponseJSON - ///Set result in mGlobal.DataStorage - lResponseJSON["RenderRobotList"].forEach( - function(lItem){ - if ('DataStorageKey' in lItem) { - mGlobal["DataStorage"][lItem['DataStorageKey']]=lItem - } - } - ) - ///Прогрузить новую таблицу - $(".openrpa-control-panel").html(lHTMLCode) - //////////////////////////////////////////////////// - ///Сформировать HTML код новой таблицы - список RDP - lHTMLCode=mGlobal.GeneralGenerateHTMLCodeHandlebars(".openrpa-hidden-robotrdpactive-control-panel",lResponseJSON) - //Присвоить ответ в mGlobal.RobotRDPActive.mResponseList - mGlobal.RobotRDPActive.mResponseList = lResponseJSON - ///Прогрузить новую таблицу - $(".openrpa-robotrdpactive-control-panel").html(lHTMLCode) - ///Очистить дерево - //mGlobal.ElementTree.fClear(); - + mGlobal.Monitor.mDatasetLast = lResponseJSON + mGlobal.Monitor.fControlPanelRefresh_TechnicalRender() } catch(error) { } mGlobal.Monitor.fControlPanelRefresh() // recursive }, - dataType: "text", - error: function(jqXHR, textStatus, errorThrown ) { - sleep(3000) - mGlobal.Monitor.fControlPanelRefresh() // recursive - } + dataType: "text", + error: function(jqXHR, textStatus, errorThrown ) { + sleep(3000) + mGlobal.Monitor.fControlPanelRefresh() // recursive + } }); } catch(error) { @@ -615,4 +668,28 @@ $(document).ready(function() { }); } mGlobal.UserRoleUpdate() // Cal the update User Roles function + // Orchestrator model + mGlobal.WorkingDirectoryPathStr = null + mGlobal.OrchestratorModelUpdate=function() { + lData = [ + { + "Type": "GlobalDictKeyListValueGet", + "KeyList": ["Server","WorkingDirectoryPathStr"] + } + ] + $.ajax({ + type: "POST", + url: 'Utils/Processor', + data: JSON.stringify(lData), + success: + function(lData,l2,l3) + { + var lUACAsk = mGlobal.UserRoleAsk // Alias + var lResponseList=JSON.parse(lData) + mGlobal.WorkingDirectoryPathStr = lResponseList[0]["Result"] + }, + dataType: "text" + }); + } + mGlobal.OrchestratorModelUpdate() // Cal the update orchestrator model }); \ No newline at end of file diff --git a/Sources/pyOpenRPA/Orchestrator/Web/Index.xhtml b/Sources/pyOpenRPA/Orchestrator/Web/Index.xhtml index 341e90d1..cba1613c 100644 --- a/Sources/pyOpenRPA/Orchestrator/Web/Index.xhtml +++ b/Sources/pyOpenRPA/Orchestrator/Web/Index.xhtml @@ -2,7 +2,7 @@ - OpenRPA + pyOpenRPA Orchestrator