diff --git a/Sources/Sandbox/Subprocess.py b/Sources/Sandbox/Subprocess.py new file mode 100644 index 00000000..938438d5 --- /dev/null +++ b/Sources/Sandbox/Subprocess.py @@ -0,0 +1,20 @@ +import subprocess +lCMD = "for /l %x in (1, 1, 5) do echo %x && ping 127.0.0.1 -n 2" +lCMD = "git status" +proc = subprocess.Popen(f'cmd /c {lCMD}', stdout=subprocess.PIPE, stderr=subprocess.STDOUT) +# proc = subprocess.Popen('notepad', stdout=subprocess.PIPE, stderr=subprocess.STDOUT) +#proc = subprocess.Popen('cmd /c git status', stdout=subprocess.PIPE, stderr=subprocess.STDOUT) +#proc = subprocess.run(f'cmd /c {lCMD}', stdout=subprocess.PIPE, stderr=subprocess.STDOUT) +print(123) +#import pdb +#pdb.set_trace() +#tmp = proc.stdout.read() +lListenBool = True +while lListenBool: + tmp = proc.stdout.readline() + if tmp == b"": + lListenBool = False + #tmp = proc.stdout + #print(tmp) + print(tmp.decode("cp866")) +print("Happy end") \ No newline at end of file diff --git a/Sources/pyOpenRPA/Orchestrator/BackwardCompatibility.py b/Sources/pyOpenRPA/Orchestrator/BackwardCompatibility.py index 53766d8a..d05d0a61 100644 --- a/Sources/pyOpenRPA/Orchestrator/BackwardCompatibility.py +++ b/Sources/pyOpenRPA/Orchestrator/BackwardCompatibility.py @@ -351,7 +351,7 @@ def Update(inGSettings): if lL: lL.warning(f"Backward compatibility (v1.1.20 to v1.2.0): Create new attribute 'VersionStr'") # Log about compatibility if "DumpLogListRefreshIntervalSecFloat" not in inGSettings["Client"]: # Create new ProcessorDict structure inGSettings["Client"].update({ - "DumpLogListRefreshIntervalSecFloat": 5.0, # Duration between updates for the Client + "DumpLogListRefreshIntervalSecFloat": 3.0, # Duration between updates for the Client "DumpLogListCountInt": 100, # Set the max row for the dump "DumpLogList": [], # Will be filled automatically "DumpLogListHashStr": None, # Will be filled automatically diff --git a/Sources/pyOpenRPA/Orchestrator/Orchestrator.py b/Sources/pyOpenRPA/Orchestrator/Orchestrator.py index e06f47b5..b6a4f24d 100644 --- a/Sources/pyOpenRPA/Orchestrator/Orchestrator.py +++ b/Sources/pyOpenRPA/Orchestrator/Orchestrator.py @@ -35,11 +35,38 @@ def OSCredentialsVerify(inUserStr, inPasswordStr, inDomainStr=""): ## Verify cre else: return True -def OSCMD(inCMDStr): ## OS send command in shell locally - lCMDCode = "cmd /c " + inCMDStr - subprocess.Popen(lCMDCode) - lResultCMDRun = 1 # os.system(lCMDCode) - return lResultCMDRun +def OSCMD(inCMDStr, inRunAsyncBool=True, inLogger = None): ## OS send command in shell locally + lResultStr = "" + # Subdef to listen OS result + def _CMDRunAndListenLogs(inCMDStr, inLogger): + lResultStr = "" + lOSCMDKeyStr = str(uuid.uuid4())[0:4].upper() + lCMDProcess = subprocess.Popen(f'cmd /c {inCMDStr}', stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + if inLogger: + lListenBool = True + inLogger.info(f"{lOSCMDKeyStr}: # # # # CMD Process has been STARTED # # # # ") + inLogger.info(f"{lOSCMDKeyStr}: {inCMDStr}") + while lListenBool: + lOutputLineBytes = lCMDProcess.stdout.readline() + if lOutputLineBytes == b"": + lListenBool = False + lStr = lOutputLineBytes.decode('cp866') + if lStr.endswith("\n"): lStr = lStr[:-1] + inLogger.info(f"{lOSCMDKeyStr}: {lStr}") + lResultStr+=lStr + inLogger.info(f"{lOSCMDKeyStr}: # # # # CMD Process has been FINISHED # # # # ") + return lResultStr + # New call + if inRunAsyncBool: + lThread = threading.Thread(target=_CMDRunAndListenLogs, kwargs={"inCMDStr":inCMDStr, "inLogger":inLogger}) + lThread.start() + lResultStr="ActivityList has been started in async mode - no output is available here." + else: + lResultStr = _CMDRunAndListenLogs(inCMDStr=inCMDStr, inLogger=inLogger) + #lCMDCode = "cmd /c " + inCMDStr + #subprocess.Popen(lCMDCode) + #lResultCMDRun = 1 # os.system(lCMDCode) + return lResultStr def OrchestratorRestart(inGSettings=None): ## Orchestrator restart OrchestratorSessionSave(inGSettings=inGSettings) # Dump RDP List in file json @@ -541,9 +568,17 @@ def GSettingsAutocleaner(inGSettings): from .. import __version__ # Get version from the package # Main def for orchestrator def Orchestrator(inGSettings): - # Test lL = inGSettings["Logger"] + # Append Orchestrator def to ProcessorDictAlias + lModule = sys.modules[__name__] + lModuleDefList = dir(lModule) + for lItemDefNameStr in lModuleDefList: + # Dont append alias for defs Orchestrator and ___deprecated_orchestrator_start__ + if lItemDefNameStr not in ["Orchestrator", "___deprecated_orchestrator_start__"]: + lItemDef = getattr(lModule,lItemDefNameStr) + if callable(lItemDef): inGSettings["ProcessorDict"]["AliasDefDict"][lItemDefNameStr]=lItemDef + #mGlobalDict = Settings.Settings(sys.argv[1]) gSettingsDict = inGSettings # Alias for old name in alg inGSettings["VersionStr"] = __version__ diff --git a/Sources/pyOpenRPA/Orchestrator/Processor.py b/Sources/pyOpenRPA/Orchestrator/Processor.py index c34c3493..c254c0f6 100644 --- a/Sources/pyOpenRPA/Orchestrator/Processor.py +++ b/Sources/pyOpenRPA/Orchestrator/Processor.py @@ -3,7 +3,7 @@ import time, copy, threading # Run processor synchronious def ProcessorRunSync(inGSettings): """ - "ProcessorDict": { # Has been changed. New general processor (one threaded) v.1.2.0 + "ProcessorDict": { # Has been changed. New general processor (one threaded) v.1.2.0 "ActivityList": [ # List of the activities # { # "Def":"DefAliasTest", # def link or def alias (look gSettings["Processor"]["AliasDefDict"]) @@ -34,7 +34,7 @@ def ActivityListExecute(inGSettings, inActivityList): if callable(lActivityItem["Def"]): # CHeck if def is callable lDef = lActivityItem["Def"] # Get the def else: # Is not callable - check alias - lDef = inGSettings["ProcessorDict"]["AliasDefDict"].pop(lActivityItem["Def"], None) # get def if def key in Alias def storage + lDef = inGSettings["ProcessorDict"]["AliasDefDict"].get(lActivityItem["Def"], None) # get def if def key in Alias def storage #gSettings lGSettingsDictKey = lActivityItem.pop("ArgGSettings",None) # # Prepare arg dict - gSettings diff --git a/Sources/pyOpenRPA/Orchestrator/ServerSettings.py b/Sources/pyOpenRPA/Orchestrator/ServerSettings.py index bd367326..a6965ddc 100644 --- a/Sources/pyOpenRPA/Orchestrator/ServerSettings.py +++ b/Sources/pyOpenRPA/Orchestrator/ServerSettings.py @@ -11,6 +11,7 @@ import datetime # datetime functions import threading # Multi-threading from .Web import Basic from . import BackwardCompatibility # Support old up to 1.2.0 defs +from . import Processor # # # # # # # # # # # # # v 1.2.0 Functionallity # # # # # # # # # # # # @@ -139,7 +140,6 @@ def pyOpenRPA_ServerLog(inRequest,inGSDict): inResponseDict["Body"] = bytes(message, "utf8") return lResult - def pyOpenRPA_Screenshot(inRequest,inGlobalDict): # Get Screenshot def SaveScreenshot(inFilePath): @@ -157,6 +157,38 @@ def pyOpenRPA_Screenshot(inRequest,inGlobalDict): inRequest.OpenRPAResponseDict["Body"] = lFileObject.read() # Закрыть файловый объект lFileObject.close() +# Add activity item or activity list to the processor queue +# Body is Activity item or Activity List +def pyOpenRPA_Processor(inRequest, inGSettings): + # Recieve the data + lValueStr = None + if inRequest.headers.get('Content-Length') is not None: + lInputByteArrayLength = int(inRequest.headers.get('Content-Length')) + lInputByteArray = inRequest.rfile.read(lInputByteArrayLength) + # Превращение массива байт в объект + lInput = json.loads(lInputByteArray.decode('utf8')) + # If list - operator plus + if type(lInput) is list: + inGSettings["ProcessorDict"]["ActivityList"]+=lInput + else: + inGSettings["ProcessorDict"]["ActivityList"].append(lInput) +# Execute activity list +def pyOpenRPA_ActivityListExecute(inRequest, inGSettings): + # Recieve the data + lValueStr = None + if inRequest.headers.get('Content-Length') is not None: + lInputByteArrayLength = int(inRequest.headers.get('Content-Length')) + lInputByteArray = inRequest.rfile.read(lInputByteArrayLength) + # Превращение массива байт в объект + lInput = json.loads(lInputByteArray.decode('utf8')) + # If list - operator plus + if type(lInput) is list: + lResultList = Processor.ActivityListExecute(inGSettings = inGSettings, inActivityList = lInput) + inRequest.OpenRPAResponseDict["Body"] = bytes(json.dumps(lResultList), "utf8") + else: + lResultList = Processor.ActivityListExecute(inGSettings = inGSettings, inActivityList = [lInput]) + inRequest.OpenRPAResponseDict["Body"] = bytes(json.dumps(lResultList[0]), "utf8") + def SettingsUpdate(inGlobalConfiguration): import os import pyOpenRPA.Orchestrator @@ -190,6 +222,8 @@ def SettingsUpdate(inGlobalConfiguration): {"Method": "POST", "URL": "/pyOpenRPA/ServerData", "MatchType": "Equal","ResponseDefRequestGlobal": pyOpenRPA_ServerData, "ResponseContentType": "application/json"}, {"Method": "POST", "URL": "/pyOpenRPA/ServerLog", "MatchType": "Equal","ResponseDefRequestGlobal": pyOpenRPA_ServerLog, "ResponseContentType": "application/json"}, {"Method": "GET", "URL": "/pyOpenRPA/Screenshot", "MatchType": "BeginWith", "ResponseDefRequestGlobal": pyOpenRPA_Screenshot, "ResponseContentType": "image/png"}, + {"Method": "POST", "URL": "/pyOpenRPA/Processor", "MatchType": "Equal","ResponseDefRequestGlobal": pyOpenRPA_Processor, "ResponseContentType": "application/json"}, + {"Method": "POST", "URL": "/pyOpenRPA/ActivityListExecute", "MatchType": "Equal","ResponseDefRequestGlobal": pyOpenRPA_ActivityListExecute, "ResponseContentType": "application/json"}, ] inGlobalConfiguration["Server"]["URLList"]=inGlobalConfiguration["Server"]["URLList"]+lURLList return inGlobalConfiguration \ No newline at end of file diff --git a/Sources/pyOpenRPA/Orchestrator/SettingsTemplate.py b/Sources/pyOpenRPA/Orchestrator/SettingsTemplate.py index 282ca2ff..d3b4f6b5 100644 --- a/Sources/pyOpenRPA/Orchestrator/SettingsTemplate.py +++ b/Sources/pyOpenRPA/Orchestrator/SettingsTemplate.py @@ -31,7 +31,7 @@ def __Create__(): }, }, # # # # # # Client... # # # # # # # # - "DumpLogListRefreshIntervalSecFloat": 5.0, # Duration between updates for the Client + "DumpLogListRefreshIntervalSecFloat": 3.0, # Duration between updates for the Client "DumpLogListCountInt": 100, # Set the max row for the dump "DumpLogList": [], # Will be filled automatically "DumpLogListHashStr": None, # Will be filled automatically diff --git a/Sources/pyOpenRPA/Orchestrator/Web/Index.js b/Sources/pyOpenRPA/Orchestrator/Web/Index.js index 838eca83..afa4d54c 100644 --- a/Sources/pyOpenRPA/Orchestrator/Web/Index.js +++ b/Sources/pyOpenRPA/Orchestrator/Web/Index.js @@ -122,11 +122,17 @@ $(document).ready(function() { lCMDCode=$(".openrpa-controller-cmd-run-input")[0].value ///Подготовить конфигурацию lData = [ - {"Type":"CMDStart", "Command": lCMDCode } + { + "Def":"OSCMD", // def link or def alias (look gSettings["Processor"]["AliasDefDict"]) + "ArgList":[], // Args list + "ArgDict":{"inCMDStr":lCMDCode,"inRunAsyncBool":false}, // Args dictionary + "ArgGSettings": null, // Name of GSettings attribute: str (ArgDict) or index (for ArgList) + "ArgLogger": "inLogger" // Name of GSettings attribute: str (ArgDict) or index (for ArgList) + } ] $.ajax({ type: "POST", - url: 'Utils/Processor', + url: '/pyOpenRPA/ActivityListExecute', data: JSON.stringify(lData), success: function(lData,l2,l3) @@ -390,14 +396,16 @@ $(document).ready(function() { }, dataType: "text", error: function(jqXHR, textStatus, errorThrown ) { - sleep(3000) - mGlobal.pyOpenRPA.ServerDataRefreshDef() // recursive + setTimeout(mGlobal.pyOpenRPA.ServerDataRefreshDef,3000) + //sleep(3000) + //mGlobal.pyOpenRPA.ServerDataRefreshDef() // recursive } }); } catch(error) { - sleep(3000) - mGlobal.pyOpenRPA.ServerDataRefreshDef() // recursive + setTimeout(mGlobal.pyOpenRPA.ServerDataRefreshDef,3000) + //sleep(3000) + //mGlobal.pyOpenRPA.ServerDataRefreshDef() // recursive } } ///////////////////////////////////////////////////////////// @@ -437,14 +445,16 @@ $(document).ready(function() { }, dataType: "text", error: function(jqXHR, textStatus, errorThrown ) { - sleep(3000) - mGlobal.pyOpenRPA.ServerLogListRefreshDef() // recursive + setTimeout(mGlobal.pyOpenRPA.ServerLogListRefreshDef,3000) + //sleep(3000) + //mGlobal.pyOpenRPA.ServerLogListRefreshDef() // recursive } }); } catch(error) { - sleep(3000) - mGlobal.pyOpenRPA.ServerLogListRefreshDef() // recursive + setTimeout(mGlobal.pyOpenRPA.ServerLogListRefreshDef,3000) + //sleep(3000) + //mGlobal.pyOpenRPA.ServerLogListRefreshDef() // recursive } } ///////////////////////////////////////////////////////////// diff --git a/Sources/pyOpenRPA/Orchestrator/Web/Index.xhtml b/Sources/pyOpenRPA/Orchestrator/Web/Index.xhtml index e63e8c61..8b248b86 100644 --- a/Sources/pyOpenRPA/Orchestrator/Web/Index.xhtml +++ b/Sources/pyOpenRPA/Orchestrator/Web/Index.xhtml @@ -190,9 +190,9 @@ - +
+

+ +
+ Agent active list +
+

+
@@ -244,32 +252,7 @@ Administration -
- - - - -
+ @@ -280,51 +263,47 @@ Logs +
+ +
Run!
+
GUI Logout
+
+ + + +

+ + Controls +

+
+ + + +
+
@@ -397,11 +376,6 @@ textarea.scrollTop = textarea.scrollHeight;
-
- -
Run!
-
GUI Logout
-
diff --git a/changelog.md b/changelog.md index cdfd1243..7b287e6a 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,5 @@ [1.2.0] +!Orchestrator! - Consolidated processor from old orchestrator and from RDPActive processor (one threaded). Look in GSettings - - Support old orchestrator structure Processor. - - - Create BackwardCompatibility def to update structure from old to new processor @@ -39,6 +40,13 @@ - Orchestrator WEB GUI update - Administrator mode - add log view - in progress - - Add Server def /pyOpenRPA/ServerLogs - - Create logger handler for the Client DumpLog +- Create /pyOpenRPA/ActivityListExecute +- Create /pyOpenRPA/Processor +- Orchestrator.OSCMD Listen output and send to logger +- Orchestrator.OSCMD Add 2 input args inLogger + inRunAsyncBool +- WEB Update CMD Input line (tranfer to Log view). Change /Utils/Processor to /pyOpenRPA/ActivityListExecute +- Defs has been added in ProcessorAliasDict as Alias with own def name +- WEB Remove client freeze when back is dead [1.1.0] After 2 month test prefinal with new improovements (+RobotRDPActive in Orchestrator + Easy ControlPanelTemplate) Beta before 1.1.0 (new way of OpenRPA with improvements. Sorry, but no backward compatibility)/ Backward compatibility will start from 1.0.1