diff --git a/mocha-mysql.py b/mocha-mysql.py new file mode 100644 index 0000000..1e4be8f --- /dev/null +++ b/mocha-mysql.py @@ -0,0 +1,14 @@ +import mysql.connector + +from mocha.core import InstanceKey, InstanceReference +from mocha.oms import DatabaseOms + +if __name__ == "__main__": + + oms = MySQLDatabaseOms("localhost", 13306, "mocha_user", "9Q5eLsKfL5AciM4U", "mocha_test") + + inst = oms.get_instance_by_key(InstanceKey(1, 1)) + iaName = oms.get_instance_by_key(InstanceKey(4, 1)) + + val = oms.get_attribute_value(inst, iaName) + print(val) \ No newline at end of file diff --git a/mocha-web.py b/mocha-web.py new file mode 100644 index 0000000..6cf3dfb --- /dev/null +++ b/mocha-web.py @@ -0,0 +1,10 @@ +from mocha.web.WebServer import WebServer + +if __name__ == "__main__": + + port = 8081 + print("Mocha User Interface Service - running on port", port) + + server = WebServer(("127.0.0.1", port)) + server.serve_forever() + diff --git a/mocha/core/InstanceKey.py b/mocha/core/InstanceKey.py new file mode 100644 index 0000000..3654905 --- /dev/null +++ b/mocha/core/InstanceKey.py @@ -0,0 +1,26 @@ +class InstanceKey(): + + @staticmethod + def parse(val): + tup = val.split('$') + if len(tup) != 2: + return None + + return InstanceKey(tup[0], tup[1]) + + def __init__(self, class_id : int, inst_id : int): + + + self.__class_id = class_id + self.__inst_id = inst_id + + def get_class_index(self) -> int: + return self.__class_id + + def get_instance_index(self) -> int: + return self.__inst_id + + def __str__(self): + + return str(self.get_class_index()) + '$' + str(self.get_instance_index()) + \ No newline at end of file diff --git a/mocha/core/InstanceReference.py b/mocha/core/InstanceReference.py new file mode 100644 index 0000000..2d1712b --- /dev/null +++ b/mocha/core/InstanceReference.py @@ -0,0 +1,20 @@ +from .InstanceKey import InstanceKey + +class InstanceReference: + + def __init__(self, dbid, inst_key : InstanceKey, global_id): + self.dbid = dbid + self.inst_key = inst_key + self.global_id = global_id + + def get_dbid(self): + return self.dbid + + def get_instance_key(self) -> InstanceKey: + return self.inst_key + + def get_global_identifier(self): + return self.global_id.lower() + + def __str__(self): + return str(self.inst_key) + ' ' + self.global_id \ No newline at end of file diff --git a/mocha/core/TenantReference.py b/mocha/core/TenantReference.py new file mode 100644 index 0000000..ecb4709 --- /dev/null +++ b/mocha/core/TenantReference.py @@ -0,0 +1,16 @@ +from .InstanceReference import InstanceReference + +class TenantReference: + + def __init__(self, tenant_name, global_id): + self.__tenant_name = tenant_name + self.__global_id = global_id + + def get_instance(self) -> InstanceReference: + pass + + def get_name(self): + return self.__tenant_name + + def get_global_identifier(self): + return self.__global_id \ No newline at end of file diff --git a/mocha/core/__init__.py b/mocha/core/__init__.py new file mode 100644 index 0000000..ebbfc41 --- /dev/null +++ b/mocha/core/__init__.py @@ -0,0 +1,3 @@ +from .InstanceKey import InstanceKey +from .InstanceReference import InstanceReference +from .TenantReference import TenantReference diff --git a/mocha/core/__pycache__/InstanceKey.cpython-311.pyc b/mocha/core/__pycache__/InstanceKey.cpython-311.pyc new file mode 100644 index 0000000..8f2a692 Binary files /dev/null and b/mocha/core/__pycache__/InstanceKey.cpython-311.pyc differ diff --git a/mocha/core/__pycache__/InstanceReference.cpython-311.pyc b/mocha/core/__pycache__/InstanceReference.cpython-311.pyc new file mode 100644 index 0000000..a966174 Binary files /dev/null and b/mocha/core/__pycache__/InstanceReference.cpython-311.pyc differ diff --git a/mocha/core/__pycache__/Tenant.cpython-311.pyc b/mocha/core/__pycache__/Tenant.cpython-311.pyc new file mode 100644 index 0000000..85183f3 Binary files /dev/null and b/mocha/core/__pycache__/Tenant.cpython-311.pyc differ diff --git a/mocha/core/__pycache__/TenantReference.cpython-311.pyc b/mocha/core/__pycache__/TenantReference.cpython-311.pyc new file mode 100644 index 0000000..4580499 Binary files /dev/null and b/mocha/core/__pycache__/TenantReference.cpython-311.pyc differ diff --git a/mocha/core/__pycache__/__init__.cpython-311.pyc b/mocha/core/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..c360267 Binary files /dev/null and b/mocha/core/__pycache__/__init__.cpython-311.pyc differ diff --git a/mocha/oms/DatabaseOms.py b/mocha/oms/DatabaseOms.py new file mode 100644 index 0000000..b7f6896 --- /dev/null +++ b/mocha/oms/DatabaseOms.py @@ -0,0 +1,4 @@ +from .Oms import Oms + +class DatabaseOms(Oms): + pass diff --git a/mocha/oms/MySQLDatabaseOms.py b/mocha/oms/MySQLDatabaseOms.py new file mode 100644 index 0000000..dfedc62 --- /dev/null +++ b/mocha/oms/MySQLDatabaseOms.py @@ -0,0 +1,133 @@ +from .Oms import Oms +from .DatabaseOms import DatabaseOms + +from ..core import InstanceKey, InstanceReference, TenantReference + +import mysql.connector + +class MySQLDatabaseOms(DatabaseOms): + + def __init__(self, hostname, port, username, password, database): + + self.db = mysql.connector.connect( + host=hostname, + port=port, + user=username, + password=password, + database=database + ) + + def _call_proc(self, name, parms : tuple): + pass + + def get_instance_by_key(self, key : InstanceKey): + mycur = self.db.cursor() + + if key is None: + return None + + #resu = mycur.execute("CALL mocha_get_instances(NULL)", multi=True) + resu = mycur.callproc("mocha_get_instance_by_key", (key.class_id, key.inst_id)) + data = mycur.stored_results() + inst = None + + # tuple: (id, class_id, inst_id, global_identifier) + for result in data: + for row in result: + inst_key = InstanceKey(row[1], row[2]) + global_id = row[3] + inst = InstanceReference(row[0], inst_key, global_id) + return inst + + def get_instances(self, of_class : InstanceReference = None): + mycur = self.db.cursor() + + #resu = mycur.execute("CALL mocha_get_instances(NULL)", multi=True) + pclassid = None + if of_class is not None: + pclassid = of_class.get_instance_key().get_class_index() + + resu = mycur.callproc("mocha_get_instances", (pclassid,)) + data = mycur.stored_results() + + """ + sql = "INSERT INTO customers (name, address) VALUES (%s, %s)" + val = [ + ('Peter', 'Lowstreet 4'), + ('Amy', 'Apple st 652'), + ('Hannah', 'Mountain 21'), + ('Michael', 'Valley 345'), + ('Sandy', 'Ocean blvd 2'), + ('Betty', 'Green Grass 1'), + ('Richard', 'Sky st 331'), + ('Susan', 'One way 98'), + ('Vicky', 'Yellow Garden 2'), + ('Ben', 'Park Lane 38'), + ('William', 'Central st 954'), + ('Chuck', 'Main Road 989'), + ('Viola', 'Sideway 1633') + ] + + mycursor.executemany(sql, val) + """ + + insts = [] + + # tuple: (id, class_id, inst_id, global_identifier) + for result in data: + for row in result: + inst_key = row[0] + global_id = row[1] + insts.append({ "inst_key": InstanceKey.parse(inst_key), "global_identifier": global_id }) + + return insts + + def get_attribute_value(self, inst, attr_inst, eff_date = None): + mycur = self.db.cursor() + + #resu = mycur.execute("CALL mocha_get_instances(NULL)", multi=True) + resu = mycur.callproc("mocha_get_attribute_value", (inst.get_dbid(), attr_inst.get_dbid(), eff_date)) + data = mycur.stored_results() + + """ + sql = "INSERT INTO customers (name, address) VALUES (%s, %s)" + val = [ + ('Peter', 'Lowstreet 4'), + ('Amy', 'Apple st 652'), + ('Hannah', 'Mountain 21'), + ('Michael', 'Valley 345'), + ('Sandy', 'Ocean blvd 2'), + ('Betty', 'Green Grass 1'), + ('Richard', 'Sky st 331'), + ('Susan', 'One way 98'), + ('Vicky', 'Yellow Garden 2'), + ('Ben', 'Park Lane 38'), + ('William', 'Central st 954'), + ('Chuck', 'Main Road 989'), + ('Viola', 'Sideway 1633') + ] + + mycursor.executemany(sql, val) + """ + + # tuple: (id, class_id, inst_id, global_identifier) + for result in data: + rows = result.fetchall() + for row in rows: + return row[0] + + return None + + def get_tenant_by_name(self, tenant_name : str) -> TenantReference: + mycur = self.db.cursor() + + #resu = mycur.execute("CALL mocha_get_instances(NULL)", multi=True) + resu = mycur.callproc("mocha_get_tenant_by_name", (tenant_name, None)) + data = mycur.stored_results() + for result in data: + rows = result.fetchall() + for row in rows: + global_id = row[0] + return TenantReference(tenant_name, global_id) + + return None diff --git a/mocha/oms/Oms.py b/mocha/oms/Oms.py new file mode 100644 index 0000000..f3a0c09 --- /dev/null +++ b/mocha/oms/Oms.py @@ -0,0 +1,14 @@ +from ..core import InstanceKey, InstanceReference, TenantReference + +class Oms: + def __init__(self): + pass + + def get_instance_by_key(self, key : InstanceKey): + pass + + def get_instances(self, of_class : InstanceReference = None): + pass + + def get_tenant_by_name(self, tenant_name : str) -> TenantReference: + pass diff --git a/mocha/oms/__init__.py b/mocha/oms/__init__.py new file mode 100644 index 0000000..a27689b --- /dev/null +++ b/mocha/oms/__init__.py @@ -0,0 +1,3 @@ +from .Oms import Oms +from .DatabaseOms import DatabaseOms +from .MySQLDatabaseOms import MySQLDatabaseOms \ No newline at end of file diff --git a/mocha/oms/__pycache__/DatabaseOms.cpython-311.pyc b/mocha/oms/__pycache__/DatabaseOms.cpython-311.pyc new file mode 100644 index 0000000..6791233 Binary files /dev/null and b/mocha/oms/__pycache__/DatabaseOms.cpython-311.pyc differ diff --git a/mocha/oms/__pycache__/MySQLDatabaseOms.cpython-311.pyc b/mocha/oms/__pycache__/MySQLDatabaseOms.cpython-311.pyc new file mode 100644 index 0000000..492ea9a Binary files /dev/null and b/mocha/oms/__pycache__/MySQLDatabaseOms.cpython-311.pyc differ diff --git a/mocha/oms/__pycache__/Oms.cpython-311.pyc b/mocha/oms/__pycache__/Oms.cpython-311.pyc new file mode 100644 index 0000000..1cbaeae Binary files /dev/null and b/mocha/oms/__pycache__/Oms.cpython-311.pyc differ diff --git a/mocha/oms/__pycache__/__init__.cpython-311.pyc b/mocha/oms/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..5d19c6a Binary files /dev/null and b/mocha/oms/__pycache__/__init__.cpython-311.pyc differ diff --git a/mocha/web/WebControl.py b/mocha/web/WebControl.py new file mode 100644 index 0000000..ddd4714 --- /dev/null +++ b/mocha/web/WebControl.py @@ -0,0 +1,64 @@ +from .XmlTag import XmlTag + +class WebControl (XmlTag): + + def __init__(self, tag_name): + XmlTag.__init__(self, tag_name) + + def create_from_json(self, json): + if "components" in json: + for component in json["components"]: + wc = WebControl.from_json(component) + self.add(wc) + + @staticmethod + def from_json(json): + ctl = None + + if json["type"] == "box": + orientation = "vertical" + if "orientation" in json: + orientation = json["orientation"] + + from .controls import Box + ctl = Box(orientation) + if json["type"] == "grid": + from .controls import Grid, GridRow, GridCell + ctl = Grid() + + if "rows" in json: + for row in json["rows"]: + row_ctl = GridRow() + for cell in row["cells"]: + ctl1 = WebControl.from_json(cell) + if not ctl1 is None: + td = GridCell() + td.add(ctl1) + row_ctl.add(td) + ctl.add(row_ctl) + + elif json["type"] == "label": + from .controls import Label + text = "" + if "text" in json: + text = json["text"] + + ctl = Label(text) + elif json["type"] == "text": + from .controls import TextBox + ctl = TextBox() + elif json["type"] == "password": + from .controls import TextBox + ctl = TextBox("password") + + if not ctl is None: + if "components" in json: + for component in json["components"]: + wc = WebControl.from_json(component) + ctl.add(wc) + + print(json["type"] + " is " + str(ctl)) + return ctl + + def render_json(self): + return None \ No newline at end of file diff --git a/mocha/web/WebPage.py b/mocha/web/WebPage.py new file mode 100644 index 0000000..b037ad9 --- /dev/null +++ b/mocha/web/WebPage.py @@ -0,0 +1,64 @@ +from .WebControl import WebControl +from .WebStyleSheet import WebStyleSheet +from .XmlTag import XmlTag + +class WebPage (WebControl): + + def __init__(self): + WebControl.__init__(self, 'html') + self.__title = None + self.__body = XmlTag('body') + self.__head = XmlTag('head') + self.__style_sheet = WebStyleSheet() + + self.add(self.__head) + self.__head.add(self.__style_sheet) + + self.add(self.__body) + + def create_from_json(self, json): + if "components" in json: + for component in json["components"]: + wc = WebControl.from_json(component) + self.get_body().add(wc) + + if "title" in json: + self.set_title(json["title"]) + + def get_style_sheet(self): + return self.__style_sheet + def set_style_sheet(self, style_sheet): + self.__style_sheet = style_sheet + + def get_head(self): + return self.__head + def get_body(self): + return self.__body + + def get_title(self): + return self.__title + + def set_title(self, value): + self.__title = value + + def render_json(self): + return { + "title": "Not Found", + "components": None + } + + def render_before_html(self): + + title = self.get_title() + if title is None: + title = "" + + html = """ +""" + + return html + + def render_after_html(self): + html = """ +""" + return html \ No newline at end of file diff --git a/mocha/web/WebRequestHandler.py b/mocha/web/WebRequestHandler.py new file mode 100644 index 0000000..4c9c043 --- /dev/null +++ b/mocha/web/WebRequestHandler.py @@ -0,0 +1,264 @@ +from functools import cached_property +from http.cookies import SimpleCookie +from http.server import BaseHTTPRequestHandler +from urllib.parse import parse_qsl, urlparse +import json + +from ..oms import Oms + +class WebRequestHandler(BaseHTTPRequestHandler): + def __init__(self, request, client_address, server): + BaseHTTPRequestHandler.__init__(self, request, client_address, server) + + @cached_property + def url(self): + return urlparse(self.path) + + @cached_property + def query_data(self): + return dict(parse_qsl(self.url.query)) + + @cached_property + def post_data(self): + content_length = int(self.headers.get("Content-Length", 0)) + return self.rfile.read(content_length) + + @cached_property + def form_data(self): + return dict(parse_qsl(self.post_data.decode("utf-8"))) + + @cached_property + def cookies(self): + return SimpleCookie(self.headers.get("Cookie")) + + def respond_with_redirect(self, url): + self.send_response(302, "Found") + self.send_header("Location", url) + self.end_headers() + self.close_connection() + + def respond_with_content(self, response_code, response_text, content_type, content): + self.send_response(response_code, response_text) + self.send_header("Content-Type", content_type) + self.send_header("Content-Length", len(content)) + self.end_headers() + self.wfile.write(content.encode("utf-8")) + + def get_oms(self) -> Oms: + from ..oms import MySQLDatabaseOms + + if not hasattr(self, "__oms"): + self.__oms = None + + if self.__oms is None: + self.__oms = MySQLDatabaseOms("localhost", 13306, "mocha_user", "9Q5eLsKfL5AciM4U", "mocha_test") + + return self.__oms + + def do_GET(self): + oms = self.get_oms() + + default_tenant_name = "super" + + path = self.url.path.split('/') + path = path[1:] + + if (len(path) == 1 and path[0] == ""): + self.respond_with_redirect("/" + default_tenant_name) + return + + if (len(path) > 0): + + tenant_name = path[0] + + if len(path) == 1: + self.respond_with_redirect("/madi/authgwy/" + tenant_name + "/login.htmld") + return + + if len(path) > 1: + if tenant_name == "madi": + authgwy = path[1] + if authgwy == "authgwy": + if len(path) == 4: + tenant_name = path[2] + file_name = path[3] + + tenant = oms.get_tenant_by_name(tenant_name) + if tenant is None: + pass + else: + print(tenant.get_name()) + + if file_name == "login.htmld": + from .pages import LoginWebPage + page = LoginWebPage() + self.respond_with_content(200, "OK", "application/xhtml+xml", page.render_html()) + return + elif file_name == "tenant-config.xml": + content = self.get_tenant_config_xml(tenant_name) + self.respond_with_content(200, "OK", "application/xml", content) + return + + elif file_name == "images": + if len(path) >= 5: + if path[4] == "signon.xml": + return self.respond_with_header_image(tenant_name) + else: + if len(path) > 1: + if path[1] == "d": + if len(path) > 2: + if path[2] == "home.htmld": + from .pages import HomeWebPage + page = HomeWebPage() + self.respond_with_content(200, "OK", "application/xhtml+xml", page.render_html()) + + jj = self.get_json_response() + if jj["result"] == "failure" and jj["remedy"] == "login": + self.respond_with_login_page(tenant_name) + return + + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.end_headers() + self.wfile.write(self.get_response().encode("utf-8")) + + def do_POST(self): + self.do_GET() + + def respond_with_header_image(self, tenant_name): + return None + + def respond_with_login_page(self, tenant_name): + + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.end_headers() + self.wfile.write(self.get_response().encode("utf-8")) + + + def get_response(self): + jj = self.get_json_response() + if jj["result"] == "failure" and jj["remedy"] == "login": + self.respond_with_login_page() + + return json.dumps(jj) + + def get_singleline_xml(self, tag_name, content_dict): + sz = """""" + sz += "<" + tag_name + " " + for key in content_dict: + val = content_dict[key] + sz += key + "=\"" + val + "\" " + sz += "/>" + return sz + + + def get_tenant_config_xml(self, tenant_name : str): + oms = self.get_oms() + tenant = oms.get_tenant_by_name(tenant_name) + + return self.get_singleline_xml("mcu:Tenant_Config", { + "xmlns:mcu": "urn:net.alcetech.schemas.Mocha.UserInterface", + + "Allow_Attachments_And_Documents_To_Be_Shared_With_Other_Mobile_Apps": "0", + "Apns_Allowed": "0", + "Canvas_Is_Enabled": "1", + "Canvas_Hex_Code": "#005cb9,#0875e1", + "Clear_Mobile_SSO_Webview_Cookies_on_Login": "0", + "Deter_Screenshots": "0", + "Disable_Add_To_Contact": "0", + "Disable_Importing_Attachments_From_Third-Party_Cloud_Services": "0", + "Disable_Location_Service": "0", + "Disable_Mail_To": "0", + "Disable_Voice_in_Assistant_on_Mobile": "1", + "Enable_Blue_Primary_Buttons": "0", + "Enable_Certificate_Based_SSO": "0", + "Enable_DOM_Storage": "0", + "Enable_Email_Annotations": "1", + "Enable_export_to_Worksheets": "1", + "Enable_Fingerprint_Authentication": "1", + "Enable_Geospace_Visualization": "0", + "Enable_Mobile_Browser_SSO_for_Native_Apps": "0", + "Enable_New_Profile": "0", + "Error_Message_for_Browser": "This Browser is not supported by Workday. Please contact your IT department.", + "Is_FedRAMP_Tenant": "0", + "Google_Cloud_Messaging_Project_Number": "739632724832", + "Hide_Links_to_Web_from_Deep_Linking_and_Inbox": "0", + "Login_URL": "https://wd5.myworkday.com/cityoforlando/login-saml2.flex", + "Logout_URL": "https://cityoforlando.okta.com", + "OMS_Note": "PROD - Powered by Workday", + "Pin_Auth_Enabled": "0", + "Pin_Max_Attempts": "0", + "Pin_Max_Length": "0", + "Pin_Min_Length": "0", + "Reset_Password_Online": "0", + "Show_Change_Password_Link": "1", + "Show_Forgotten_Password_Link": "0", + "SignOn_Custom_Message": "<p>City of Orlando - Production</p>", + "SignOn_Message_Locale": "en_US", + "SignOn_Note": "<p>City of Orlando - Production</p>", + "SignOn_Tenant_Logo_Url": "/cityoforlando/images/signon.xml", + "System_Confidence_Level": "PROD", + "System_Note": oms.get_attribute_value(tenant.get_instance(), oms.get_instance_by_global_identifier('')), # "Your system will be unavailable for a maximum of 3 hours during the next Weekly Service Update; starting on Friday, October 27, 2023 at 11:00 PM PDT (GMT-7) until Saturday, October 28, 2023 at 2:00 AM PDT (GMT-7).", + "Use_One_Time_Use_Link": "0" + }) + + def get_json_response(self): + path = self.url.path.split('/') + path = path[1:] + + tenantName = path[0] + if tenantName == "madi": + authgwy = path[1] + if authgwy == "authgwy": + if len(path) == 4: + tenantName = path[2] + loginPage = path[3] + else: + # error out + return + else: + # error out + return + + from ..core import InstanceKey + from ..oms import MySQLDatabaseOms + + oms = MySQLDatabaseOms("localhost", 13306, "mocha_user", "9Q5eLsKfL5AciM4U", "mocha_test") + + if len(path) > 3: + if path[1] == "api": + + if path[2] == "instances": + + ik = InstanceKey.parse(path[3]) + inst = oms.get_instance_by_key(ik) + if inst is None: + jj = { + "result": "error", + "message": "invalid inst " + str(ik) + } + return jj + + jj = { + "result": "success", + "iid": str(inst.get_instance_key()), + "gid": inst.get_global_identifier() + } + return jj + + else: + + jj = { + "result": "error", + "message": "invalid request" + } + return jj + + else: + jj = { + "result": "error", + "message": "invalid request" + } + + return jj diff --git a/mocha/web/WebScript.py b/mocha/web/WebScript.py new file mode 100644 index 0000000..e18e887 --- /dev/null +++ b/mocha/web/WebScript.py @@ -0,0 +1,21 @@ +from .XmlTag import XmlTag + +class WebScript (XmlTag): + + def __init__(self, content_type : str, **kwargs): + XmlTag.__init__(self, 'script') + + self.add_attribute('type', content_type) + self.content_type = content_type + + if "content" in kwargs: + self.targetUrl = None + self.set_content(kwargs["content"]) + elif "targetUrl" in kwargs: + self.targetUrl = kwargs["targetUrl"] + self.add_attribute('src', targetUrl) + + def get_content_type(self): + return self.content_type + def get_target_url(self): + return self.targetUrl diff --git a/mocha/web/WebServer.py b/mocha/web/WebServer.py new file mode 100644 index 0000000..e9e750b --- /dev/null +++ b/mocha/web/WebServer.py @@ -0,0 +1,12 @@ +from .WebRequestHandler import WebRequestHandler + +class WebServer(): + + def __init__(self, tup): + self.__tup = tup + + def serve_forever(self): + from http.server import HTTPServer + + server = HTTPServer(self.__tup, WebRequestHandler) + server.serve_forever() \ No newline at end of file diff --git a/mocha/web/WebStyleSheet.py b/mocha/web/WebStyleSheet.py new file mode 100644 index 0000000..61de075 --- /dev/null +++ b/mocha/web/WebStyleSheet.py @@ -0,0 +1,50 @@ +from .XmlTag import XmlTag + +class WebStyleSheet (XmlTag): + + def __init__(self): + XmlTag.__init__(self, 'style') + self.__rules = [] + + def add_class(self, names): + names_list = names.split(" ") + for name in names_list: + self.__classes.append(name) + + def add_rule(self, selector, rule): + self.__rules.append((selector, rule)) + + def get_rules(self): + return self.__rules + + def get_class(self): + if len(self.__classes) == 0: + return None + + names = "" + for i in range(len(self.__classes)): + names += self.__classes[i] + if i < len(self.__classes) - 1: + names += ' ' + return names + + def render_html(self): + if len(self.get_content()) == 0: + return "" + + return super().render_html() + + def get_content(self): + html = "" + for rule in self.__rules: + selector = rule[0] + actual_rule = rule[1] + + html += selector + " { " + for k in actual_rule: + v = actual_rule[k] + html += k + ": " + v + "; " + + html += " }" + + return html \ No newline at end of file diff --git a/mocha/web/XmlAttribute.py b/mocha/web/XmlAttribute.py new file mode 100644 index 0000000..0220182 --- /dev/null +++ b/mocha/web/XmlAttribute.py @@ -0,0 +1,6 @@ +class XmlAttribute: + + def __init__(self, name, value): + self.name = name + self.value = value + \ No newline at end of file diff --git a/mocha/web/XmlTag.py b/mocha/web/XmlTag.py new file mode 100644 index 0000000..cc22dea --- /dev/null +++ b/mocha/web/XmlTag.py @@ -0,0 +1,80 @@ +from .XmlAttribute import XmlAttribute +from .XmlTagStyles import XmlTagStyles + +class XmlTag: + + def __init__(self, tag_name : str): + self.__tag_name = tag_name + self.__style = XmlTagStyles() + self.__attributes = [] + self.__controls = [] + self.__content = None + + def add_attribute(self, name, value): + self.__attributes.append(XmlAttribute(name, value)) + + def add(self, ctl): + self.__controls.append(ctl) + + def get_controls(self): + return self.__controls + + def get_tag_name(self): + return self.__tag_name + + def get_content(self): + return self.__content + def set_content(self, value): + self.__content = value + + def get_style(self) -> XmlTagStyles: + return self.__style + + def has_content_or_controls(self): + return not (self.get_content() is None and len(self.__controls) == 0) + + def render_before_html(self): + html = "<" + self.get_tag_name() + + if self.get_style().has_classes(): + classname = self.get_style().get_class() + if not (classname is None or len(classname) == 0): + html += " class=\"" + classname + "\"" + + if self.get_style().has_rules(): + rulestr = self.get_style().get_rules_css() + if not (rulestr is None or len(rulestr) == 0): + html += " style=\"" + rulestr + "\"" + + if (len(self.__attributes) > 0): + html += " " + + for i in range(len(self.__attributes)): + attr = self.__attributes[i].name + html += attr + "=\"" + self.__attributes[i].value + "\"" + if i < len(self.__attributes) - 1: + html += " " + + if self.has_content_or_controls(): + html += ">" + + return html + def render_after_html(self): + if self.has_content_or_controls(): + return "" + else: + return " />" + + def render_html(self): + html = "" + html += self.render_before_html() + if self.get_content() is None: + for ctl in self.get_controls(): + if not ctl is None: + html += ctl.render_html() + else: + html += self.get_content() + + html += self.render_after_html() + + return html \ No newline at end of file diff --git a/mocha/web/XmlTagStyles.py b/mocha/web/XmlTagStyles.py new file mode 100644 index 0000000..2e1a3e3 --- /dev/null +++ b/mocha/web/XmlTagStyles.py @@ -0,0 +1,38 @@ +class XmlTagStyles: + def __init__(self): + self.__classes = [] + self.__rules = dict() + + def add_class(self, names): + names_list = names.split(" ") + for name in names_list: + self.__classes.append(name) + + def add_rule(self, k, v): + self.__rules[k] = v + def get_rules(self): + return self.__rules + + def get_class(self): + if len(self.__classes) == 0: + return None + + names = "" + for i in range(len(self.__classes)): + names += self.__classes[i] + if i < len(self.__classes) - 1: + names += ' ' + return names + + def get_rules_css(self): + html = "" + for k in self.__rules: + v = rule[k] + html += k + ": " + v + "; " + + return html + + def has_rules(self): + return len(self.__rules) > 0 + def has_classes(self): + return len(self.__classes) > 0 \ No newline at end of file diff --git a/mocha/web/__pycache__/WebControl.cpython-311.pyc b/mocha/web/__pycache__/WebControl.cpython-311.pyc new file mode 100644 index 0000000..798646d Binary files /dev/null and b/mocha/web/__pycache__/WebControl.cpython-311.pyc differ diff --git a/mocha/web/__pycache__/WebControlAttribute.cpython-311.pyc b/mocha/web/__pycache__/WebControlAttribute.cpython-311.pyc new file mode 100644 index 0000000..9c162dc Binary files /dev/null and b/mocha/web/__pycache__/WebControlAttribute.cpython-311.pyc differ diff --git a/mocha/web/__pycache__/WebPage.cpython-311.pyc b/mocha/web/__pycache__/WebPage.cpython-311.pyc new file mode 100644 index 0000000..59fe0f9 Binary files /dev/null and b/mocha/web/__pycache__/WebPage.cpython-311.pyc differ diff --git a/mocha/web/__pycache__/WebRequestHandler.cpython-311.pyc b/mocha/web/__pycache__/WebRequestHandler.cpython-311.pyc new file mode 100644 index 0000000..3705301 Binary files /dev/null and b/mocha/web/__pycache__/WebRequestHandler.cpython-311.pyc differ diff --git a/mocha/web/__pycache__/WebScript.cpython-311.pyc b/mocha/web/__pycache__/WebScript.cpython-311.pyc new file mode 100644 index 0000000..4b456ed Binary files /dev/null and b/mocha/web/__pycache__/WebScript.cpython-311.pyc differ diff --git a/mocha/web/__pycache__/WebServer.cpython-311.pyc b/mocha/web/__pycache__/WebServer.cpython-311.pyc new file mode 100644 index 0000000..9f59898 Binary files /dev/null and b/mocha/web/__pycache__/WebServer.cpython-311.pyc differ diff --git a/mocha/web/__pycache__/WebStyleSheet.cpython-311.pyc b/mocha/web/__pycache__/WebStyleSheet.cpython-311.pyc new file mode 100644 index 0000000..a380a17 Binary files /dev/null and b/mocha/web/__pycache__/WebStyleSheet.cpython-311.pyc differ diff --git a/mocha/web/__pycache__/WebStyleSheetFragment.cpython-311.pyc b/mocha/web/__pycache__/WebStyleSheetFragment.cpython-311.pyc new file mode 100644 index 0000000..d62b7c7 Binary files /dev/null and b/mocha/web/__pycache__/WebStyleSheetFragment.cpython-311.pyc differ diff --git a/mocha/web/__pycache__/XmlAttribute.cpython-311.pyc b/mocha/web/__pycache__/XmlAttribute.cpython-311.pyc new file mode 100644 index 0000000..8740073 Binary files /dev/null and b/mocha/web/__pycache__/XmlAttribute.cpython-311.pyc differ diff --git a/mocha/web/__pycache__/XmlTag.cpython-311.pyc b/mocha/web/__pycache__/XmlTag.cpython-311.pyc new file mode 100644 index 0000000..a9dc130 Binary files /dev/null and b/mocha/web/__pycache__/XmlTag.cpython-311.pyc differ diff --git a/mocha/web/__pycache__/XmlTagStyles.cpython-311.pyc b/mocha/web/__pycache__/XmlTagStyles.cpython-311.pyc new file mode 100644 index 0000000..0ad8cc7 Binary files /dev/null and b/mocha/web/__pycache__/XmlTagStyles.cpython-311.pyc differ diff --git a/mocha/web/controls/Box.py b/mocha/web/controls/Box.py new file mode 100644 index 0000000..3ae537d --- /dev/null +++ b/mocha/web/controls/Box.py @@ -0,0 +1,12 @@ +from ..WebControl import WebControl + +class Box (WebControl): + + def __init__(self, orientation): + WebControl.__init__(self, "div") + self.get_style().add_class("uwt-box") + + if orientation == "horizontal": + self.get_style().add_class("uwt-orientation-horizontal") + elif orientation == "vertical": + self.get_style().add_class("uwt-orientation-vertical") \ No newline at end of file diff --git a/mocha/web/controls/Grid.py b/mocha/web/controls/Grid.py new file mode 100644 index 0000000..1865a6d --- /dev/null +++ b/mocha/web/controls/Grid.py @@ -0,0 +1,14 @@ +from ..WebControl import WebControl + +class Grid (WebControl): + def __init__(self): + WebControl.__init__(self, "table") + + +class GridRow (WebControl): + def __init__(self): + WebControl.__init__(self, "tr") +class GridCell (WebControl): + def __init__(self): + WebControl.__init__(self, "td") + diff --git a/mocha/web/controls/Label.py b/mocha/web/controls/Label.py new file mode 100644 index 0000000..6da2967 --- /dev/null +++ b/mocha/web/controls/Label.py @@ -0,0 +1,8 @@ +from ..WebControl import WebControl + +class Label (WebControl): + + def __init__(self, text): + WebControl.__init__(self, "label") + self.set_content(text) + \ No newline at end of file diff --git a/mocha/web/controls/TextBox.py b/mocha/web/controls/TextBox.py new file mode 100644 index 0000000..6636016 --- /dev/null +++ b/mocha/web/controls/TextBox.py @@ -0,0 +1,17 @@ +from ..WebControl import WebControl + +class TextBox (WebControl): + + def __init__(self, mode = None): + WebControl.__init__(self, "input") + if mode == "password": + self.add_attribute("type", "password") + else: + self.add_attribute("type", "text") + self.__mode = mode + + def get_mode(self): + return self.__mode + def set_mode(self, value): + self.__mode = value + \ No newline at end of file diff --git a/mocha/web/controls/__init__.py b/mocha/web/controls/__init__.py new file mode 100644 index 0000000..3e3f35c --- /dev/null +++ b/mocha/web/controls/__init__.py @@ -0,0 +1,4 @@ +from .Box import Box +from .Grid import Grid, GridRow, GridCell +from .Label import Label +from .TextBox import TextBox \ No newline at end of file diff --git a/mocha/web/controls/__pycache__/Box.cpython-311.pyc b/mocha/web/controls/__pycache__/Box.cpython-311.pyc new file mode 100644 index 0000000..d337e62 Binary files /dev/null and b/mocha/web/controls/__pycache__/Box.cpython-311.pyc differ diff --git a/mocha/web/controls/__pycache__/Grid.cpython-311.pyc b/mocha/web/controls/__pycache__/Grid.cpython-311.pyc new file mode 100644 index 0000000..0e0aac8 Binary files /dev/null and b/mocha/web/controls/__pycache__/Grid.cpython-311.pyc differ diff --git a/mocha/web/controls/__pycache__/Label.cpython-311.pyc b/mocha/web/controls/__pycache__/Label.cpython-311.pyc new file mode 100644 index 0000000..1ea1e83 Binary files /dev/null and b/mocha/web/controls/__pycache__/Label.cpython-311.pyc differ diff --git a/mocha/web/controls/__pycache__/TextBox.cpython-311.pyc b/mocha/web/controls/__pycache__/TextBox.cpython-311.pyc new file mode 100644 index 0000000..4ec48b8 Binary files /dev/null and b/mocha/web/controls/__pycache__/TextBox.cpython-311.pyc differ diff --git a/mocha/web/controls/__pycache__/__init__.cpython-311.pyc b/mocha/web/controls/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..5f953f6 Binary files /dev/null and b/mocha/web/controls/__pycache__/__init__.cpython-311.pyc differ diff --git a/mocha/web/pages/BaseWebPage.py b/mocha/web/pages/BaseWebPage.py new file mode 100644 index 0000000..258eb38 --- /dev/null +++ b/mocha/web/pages/BaseWebPage.py @@ -0,0 +1,46 @@ +from ..WebScript import WebScript +from ..WebPage import WebPage + +class BaseWebPage (WebPage): + + def __init__(self): + WebPage.__init__(self) + + self.get_style_sheet().add_rule('table', { 'width': '100%' }) + self.get_head().add(self.get_client_config_script()) + + def get_client_config_script(self): + return WebScript("text/javascript", content=""" + // Add properties to mocha + window.mocha = window.mocha || {}; + mocha.tenant = 'cityoforlando3'; + mocha.clientOrigin = 'https://wd5-impl.workday.com'; + mocha.language = 'en_US'; + mocha.clientVersion = '0'; + mocha.systemConfidenceLevel = 'PROD'; + mocha.oauthAuthorizationPending = false; + mocha.proxyLoginEnabled = false; + mocha.maintenancePageUrl = 'https://wd5-impl.workday.com/wday/drs/outage?t=cityoforlando3'; + mocha.deviceTrustDetailsUrl = ''; + mocha.pendingAuthDetailsUrl = ''; + mocha.webAuthnDetailsUrl = ''; + mocha.enableBluePrimaryButtons = 'false'; + + // Construct init params for GWT app + mocha.initParams = { + authGatewayPath: '/wday/authgwy', + baseDir: '/wday/asset/ui-html/', + systemConfidenceLevel: 'PROD', + cdn: { + endpoint: 'wd5-impl.workdaycdn.com', + enabled: true, + allowed: true + }, + proxyEnabled: false, + currentVersion: '20.0.04.045', + serviceType: 'authgwy', + loginAuthURL: '/cityoforlando3/login-auth.xml', + environment: 'Implementation - cityoforlando3', + environmentType: 'IMPL' + }; +""") \ No newline at end of file diff --git a/mocha/web/pages/HomeWebPage.py b/mocha/web/pages/HomeWebPage.py new file mode 100644 index 0000000..42b324d --- /dev/null +++ b/mocha/web/pages/HomeWebPage.py @@ -0,0 +1,10 @@ +from .BaseWebPage import BaseWebPage + +class HomeWebPage(BaseWebPage): + + def __init__(self): + BaseWebPage.__init__(self) + + self.create_from_json({ + + }) \ No newline at end of file diff --git a/mocha/web/pages/LoginWebPage.py b/mocha/web/pages/LoginWebPage.py new file mode 100644 index 0000000..85b0be1 --- /dev/null +++ b/mocha/web/pages/LoginWebPage.py @@ -0,0 +1,39 @@ +from .BaseWebPage import BaseWebPage + +class LoginWebPage(BaseWebPage): + + def __init__(self): + BaseWebPage.__init__(self) + + self.create_from_json({ + "title": "Log In", + "components": [ + { + "type": "grid", + "rows": [ + { + "cells": [ + { + "type": "label", + "text": "User _name" + }, + { + "type": "text" + } + ] + }, + { + "cells": [ + { + "type": "label", + "text": "_Password" + }, + { + "type": "password" + } + ] + } + ] + } + + ]}) \ No newline at end of file diff --git a/mocha/web/pages/__init__.py b/mocha/web/pages/__init__.py new file mode 100644 index 0000000..0b49259 --- /dev/null +++ b/mocha/web/pages/__init__.py @@ -0,0 +1,2 @@ +from .LoginWebPage import LoginWebPage +from .HomeWebPage import HomeWebPage \ No newline at end of file diff --git a/mocha/web/pages/__pycache__/BaseWebPage.cpython-311.pyc b/mocha/web/pages/__pycache__/BaseWebPage.cpython-311.pyc new file mode 100644 index 0000000..d502ddd Binary files /dev/null and b/mocha/web/pages/__pycache__/BaseWebPage.cpython-311.pyc differ diff --git a/mocha/web/pages/__pycache__/HomeWebPage.cpython-311.pyc b/mocha/web/pages/__pycache__/HomeWebPage.cpython-311.pyc new file mode 100644 index 0000000..a42e223 Binary files /dev/null and b/mocha/web/pages/__pycache__/HomeWebPage.cpython-311.pyc differ diff --git a/mocha/web/pages/__pycache__/LoginWebPage.cpython-311.pyc b/mocha/web/pages/__pycache__/LoginWebPage.cpython-311.pyc new file mode 100644 index 0000000..0cbddd9 Binary files /dev/null and b/mocha/web/pages/__pycache__/LoginWebPage.cpython-311.pyc differ diff --git a/mocha/web/pages/__pycache__/__init__.cpython-311.pyc b/mocha/web/pages/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..0933a0c Binary files /dev/null and b/mocha/web/pages/__pycache__/__init__.cpython-311.pyc differ