├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── Dockerfile ├── LICENSE ├── README.md ├── app.py ├── console-frontend ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── LICENSE ├── README.md ├── build │ ├── Hydra.ico │ ├── asset-manifest.json │ ├── favicon.ico │ ├── index.html │ ├── manifest.json │ ├── precache-manifest.f5a86fea91e522c0a3265c700c9a2818.js │ ├── service-worker.js │ └── static │ │ ├── css │ │ ├── main.a1a1ee78.chunk.css │ │ └── main.a1a1ee78.chunk.css.map │ │ └── js │ │ ├── 2.29454c3a.chunk.js │ │ ├── 2.29454c3a.chunk.js.map │ │ ├── main.a636c623.chunk.js │ │ ├── main.a636c623.chunk.js.map │ │ ├── runtime~main.a8a9905a.js │ │ └── runtime~main.a8a9905a.js.map ├── package-lock.json ├── package.json ├── public │ ├── Hydra.ico │ ├── favicon.ico │ ├── index.html │ └── manifest.json ├── src │ ├── app │ │ ├── app.js │ │ ├── app.scss │ │ ├── app.test.js │ │ └── gui-theme.js │ ├── assets │ │ ├── images │ │ │ ├── GitHub.png │ │ │ ├── agent_gui.png │ │ │ ├── hydra_eco_logo.png │ │ │ └── logo.svg │ │ └── sass │ │ │ ├── global.scss │ │ │ ├── mixin.scss │ │ │ └── spin.scss │ ├── components │ │ ├── hydra-console │ │ │ ├── HydraConsole.js │ │ │ ├── endpoints-buttons │ │ │ │ └── EndpointsButtons.js │ │ │ ├── operations-buttons │ │ │ │ └── OperationsButtons.js │ │ │ ├── pagination │ │ │ │ └── Pagination.js │ │ │ └── properties-editor │ │ │ │ └── PropertiesEditor.js │ │ ├── hydra-graph │ │ │ ├── HydraGraph.js │ │ │ └── HydraGraph.test.js │ │ ├── loader │ │ │ ├── Loader.js │ │ │ └── Loader.test.js │ │ └── navbar │ │ │ ├── NavBar.js │ │ │ └── NavBar.test.js │ ├── index.css │ ├── index.tsx │ ├── react-app-env.d.ts │ ├── routes │ │ └── home │ │ │ ├── home.js │ │ │ └── index.js │ ├── service-worker.js │ ├── services │ │ ├── api-doc-graph-service.js │ │ ├── hydra-doc-service.js │ │ ├── send-command.js │ │ └── start-agent-service.js │ └── utils │ │ └── utils.js └── tsconfig.json ├── docker-compose.yaml ├── redis_setup.sh └── requirements.txt /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 4 | 5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. 6 | 7 | Examples of unacceptable behavior by participants include: 8 | 9 | * The use of sexualized language or imagery 10 | * Personal attacks 11 | * Trolling or insulting/derogatory comments 12 | * Public or private harassment 13 | * Publishing other's private information, such as physical or electronic addresses, without explicit permission 14 | * Other unethical or unprofessional conduct. 15 | 16 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. 17 | 18 | This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. 19 | 20 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 21 | 22 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) 23 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ### Contribution Best Practices 2 | 3 | * Read this [how-to about Github workflow here](https://guides.github.com/introduction/flow/) if you are not familiar with. 4 | 5 | * Read all the texts related to [contributing for an OS community](https://github.com/HTTP-APIs/hydrus/tree/master/.github). 6 | 7 | * Read this [how-to about writing a PR](https://github.com/blog/1943-how-to-write-the-perfect-pull-request) and this [other how-to about writing a issue](https://wiredcraft.com/blog/how-we-write-our-github-issues/) 8 | 9 | * **first ask in chat**: if you find a problem, first ask for [help in the chat](https://hydraecosystem.slack.com/), then consider opening a issue. 10 | 11 | * **read history**: before opening a PR be sure that all the tests pass successfully. If any is failing for non-related reasons, annotate the test failure in the PR comment. 12 | 13 | * **PRs on develop**: any change should be PRed first in `develop`, `master` can only receive merge from develop. 14 | 15 | * **testing**: everything should work and be tested for Python 3.5.2 and above. 16 | 17 | * **free PR**: no permission is needed to work on the code. Fork `master`, submit a PR and ask for reviewing. PR is the natural place for code comparison and corrections. If many contributors have something ready in a PR, we can consider opening a branch in which different people working on the same part of the application can collaborate. 18 | 19 | * **pylint**: code in PRs should be accurately compliant with [PEP-8](https://www.python.org/dev/peps/pep-0008/), checking code with `pylint` is fine. 20 | 21 | * **ESLint**: every module is and should in future should be compliant with ESLint. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### I'm submitting a 2 | - [ ] bug report. 3 | - [ ] feature request. 4 | 5 | ### Current Behaviour: 6 | 7 | 8 | ### Expected Behaviour: 9 | 10 | 11 | ### Steps to reproduce: 12 | 13 | 14 | ### Do you want to work on this issue? 15 | 16 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Fixes # 5 | 6 | ### Checklist 7 | 8 | - [ ] My branch is up-to-date with upstream/develop branch. 9 | - [ ] Everything works and tested for Python 3.8 and above. 10 | 11 | ### Description 12 | 13 | 14 | 15 | ### Change logs 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/] 3 | .vscode/ 4 | *.py[cod] 5 | *$py.class 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | env/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *.cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # Jupyter Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # SageMath parsed files 79 | *.sage.py 80 | 81 | # dotenv 82 | .env 83 | 84 | # virtualenv 85 | .venv 86 | venv/ 87 | ENV/ 88 | 89 | # Spyder project settings 90 | .spyderproject 91 | .spyproject 92 | 93 | # Rope project settings 94 | .ropeproject 95 | 96 | # mkdocs documentation 97 | /site 98 | 99 | # mypy 100 | .mypy_cache/ 101 | 102 | # build packages 103 | /src 104 | 105 | # Graphviz generated graph 106 | hydra_graph.gv 107 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | include: 3 | - language: node_js 4 | node: "node" 5 | 6 | script: 7 | - cd console-frontend 8 | - npm install 9 | - npm run lint 10 | 11 | - language: python 12 | python: 13 | - "3.8" 14 | - "3.8-dev" # 3.8 development branch 15 | 16 | services: 17 | - docker 18 | 19 | before_script: 20 | - docker run -d -p 6379:6379 -it --rm --name redisgraph redislabs/redisgraph:2.0-edge 21 | 22 | install: 23 | - pip install -r requirements.txt --no-cache 24 | 25 | script: 26 | - echo "skipping tests" 27 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3 2 | 3 | WORKDIR /app 4 | 5 | COPY ./requirements.txt requirements.txt 6 | 7 | RUN pip install -U pip && pip install --upgrade pip setuptools \ 8 | && pip install -r requirements.txt 9 | 10 | COPY . /app 11 | 12 | ENV PYTHONPATH "${PYTHONPATH}:/app" 13 | 14 | ENV MESSAGE "Hail Hydra" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 HYDRA W3C Group, Github.com/HTTP-APIs contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Hydra Agent GUI 2 | This is a GUI for the [Hydra Ecosystem's Agent](https://github.com/HTTP-APIs/hydra-python-agent). It's divided in two parts: the left side shows the Hydra API as a linked graph and at the right you have a generic console built based on the API Documentation that you can use to query the API. The Frontend was built with React, a middle layer to use the Agent was built with Flask. 3 | 4 | ![Agent GUI Picture](/console-frontend/src/assets/images/agent_gui.png) 5 | 6 | ### Installing Requirements 7 | It's recommended that you use venv(virtual environment): 8 | 9 | ``` 10 | sudo apt-get install python3-venv # If not installed 11 | python3 -m venv venv 12 | source venv/bin/activate 13 | pip3 install -r requirements.txt 14 | ``` 15 | 16 | ### Running Redis Graph 17 | The Agent uses a Redis local server as a caching layer. That said, it's necessary that you run Redis Graph locally: 18 | 19 | ``` 20 | sudo ./redis_setup.sh # <- Might be necessary to uso sudo 21 | ``` 22 | ### Running hydrus server 23 | Since this is an API Client, we need an appropriate hydrus server to query to. To setup a localhost follow the instructions at https://github.com/HTTP-APIs/hydrus#demo. 24 | 25 | 26 | ### Running the GUI 27 | If you've installed the Requirements and have the proper Redis running you can simply: 28 | ``` 29 | python3 app.py 30 | ``` 31 | 32 | **Now open your browser and enjoy at:** [http://localhost:3000/](http://localhost:3000/ "http://localhost:3000/") 33 | 34 | ### Contributing to the GUI 35 | 36 | This repository is divided in two parts, the Middle-Layer/Backend with Flask under ```app.py``` and the React project under the folder ```console-frontend```. 37 | 38 | **The Flask Backend** is built to communicate with the Python Agent package. It declares five endpoints which are used for this: 39 | 40 | - **send-command** - Send Commands to Agent and returns the server response 41 | - **/hydra-doc** - Simply serves the Hydra Doc 42 | - **apidoc-graph** - Fetches Hydra Doc from the Agent, process it in Vis.js Network format and returns it to the GUI 43 | - **/start-agent** - Simply starts/restarts the Agent with the URL parameter 44 | - **/** - Serves the React build under /console-frontend/build 45 | 46 | **The React Frontend** 47 | 48 | Go the the README.md inside ```console-frontend``` for further information. If you make modifications inside ```console-frontend```, make sure to run ```npm run build``` to create a updated production build. 49 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request, send_from_directory 2 | from flask_cors import CORS 3 | from hydra_agent.agent import Agent 4 | import sys 5 | import json 6 | import os 7 | 8 | app = Flask(__name__, static_folder="console-frontend/build/") 9 | 10 | # Setting CORS so it allows requests from our React app in localhost:3000 11 | CORS(app, resources={r"*": {"origins": "http://localhost:3000"}}) 12 | 13 | # Remove to deploy 14 | url = "http://localhost:8080/serverapi" 15 | agent = Agent(url) 16 | 17 | 18 | @app.route("/", defaults={"path": ""}) 19 | @app.route("/") 20 | def serve(path): 21 | """Default endpoint, it serves the built static React App 22 | :return: Served file 23 | """ 24 | file_path = os.path.join(app.static_folder, path) 25 | if path != "" and os.path.exists(file_path): 26 | return send_from_directory(app.static_folder, path) 27 | else: 28 | return send_from_directory(app.static_folder, "index.html") 29 | 30 | 31 | @app.route("/start-agent", methods=["POST"]) 32 | def start_agent(): 33 | """Receive hydrus server URL and start the Agent 34 | :param body['url']: Entrypoint URL for the hydrus server 35 | :return: Success message 36 | """ 37 | global agent 38 | global url 39 | body = request.get_data() 40 | body = body.decode("utf8").replace("'", '"') 41 | body = json.loads(body) 42 | url = body["url"] 43 | agent = Agent(url) 44 | return "Server started successfully" 45 | 46 | 47 | @app.route("/hydra-doc", methods=["GET"]) 48 | def hydra_doc(): 49 | """Serve Hydra Doc 50 | :return: Hydra Doc loaded on the agent with url for current connected 51 | """ 52 | apidoc = agent.fetch_apidoc() 53 | generatedApiDoc = apidoc.generate() 54 | generatedApiDoc["serverURL"] = url 55 | return generatedApiDoc 56 | 57 | 58 | @app.route("/apidoc-graph", methods=["GET"]) 59 | def apidoc_graph(): 60 | """Sends Formatted ApiDoc Graph in Vis.js network format to Frontend 61 | :return: Dict containing Nodes and Edges 62 | """ 63 | global agent 64 | if agent is None: 65 | return "No agent connected." 66 | # Add the entrypoint node 67 | nodes = [{"id": 1, "shape": "hexagon", "size": 15, "label": "Entrypoint"}] 68 | edges = list() 69 | id = 1 70 | api_doc = agent.fetch_apidoc() 71 | for resource_endpoint in api_doc.entrypoint.entrypoint.supportedProperty: 72 | id += 1 73 | endpoint_id = id 74 | endpoint_path = resource_endpoint.id_.replace("vocab:EntryPoint/", "") 75 | endpoint_node = create_node(endpoint_id, "box", 12, endpoint_path) 76 | nodes.append(endpoint_node) 77 | edge = create_edge(1, endpoint_id) 78 | edges.append(edge) 79 | for supportedOp in resource_endpoint.supportedOperation: 80 | id += 1 81 | op_id = id 82 | operation_node = create_node(op_id, "circle", 10, supportedOp.method) 83 | nodes.append(operation_node) 84 | supportedOp_edge = create_edge(endpoint_id, op_id, "supportedOp") 85 | edges.append(supportedOp_edge) 86 | if supportedOp.expects: 87 | expects = supportedOp.expects 88 | else: 89 | expects = "null" 90 | if supportedOp.returns: 91 | returns = supportedOp.returns 92 | else: 93 | returns = "null" 94 | # Extract class name 95 | if "vocab:" in expects: 96 | expects = expects.replace("vocab:", "") 97 | if "vocab:" in returns: 98 | returns = returns.replace("vocab:", "") 99 | id += 1 100 | expected_class_node = create_node(id, "circle", 8, expects) 101 | nodes.append(expected_class_node) 102 | expects_edge = create_edge(op_id, id, "expects") 103 | edges.append(expects_edge) 104 | if expects in api_doc.parsed_classes: 105 | class_id = id 106 | for supportedProp in api_doc.parsed_classes[expects][ 107 | "class" 108 | ].supportedProperty: 109 | id += 1 110 | property_node = create_node(id, "box", 7, supportedProp.title) 111 | nodes.append(property_node) 112 | property_edge = create_edge(class_id, id, "supportedProp") 113 | edges.append(property_edge) 114 | id += 1 115 | returned_class_node = create_node(id, "circle", 8, returns) 116 | nodes.append(returned_class_node) 117 | returns_edge = create_edge(op_id, id, "returns") 118 | edges.append(returns_edge) 119 | if returns in api_doc.parsed_classes: 120 | class_id = id 121 | for supportedProp in api_doc.parsed_classes[returns][ 122 | "class" 123 | ].supportedProperty: 124 | id += 1 125 | property_node = create_node(id, "box", 7, supportedProp.title) 126 | nodes.append(property_node) 127 | property_edge = create_edge(class_id, id, "supportedProp") 128 | edges.append(property_edge) 129 | graph = {"nodes": nodes, "edges": edges} 130 | return graph 131 | 132 | 133 | def create_node(id, shape, size, label): 134 | """Auxiliary function that creates a Node in Vis.js format 135 | :return: Dict with Node attributes 136 | """ 137 | node = { 138 | "id": id, 139 | "shape": shape, 140 | "size": size, 141 | "label": label, 142 | "color": {"background": "#FBD20B"}, 143 | } 144 | return node 145 | 146 | 147 | def create_edge(from_, to, label=None): 148 | """Auxiliary function that creates a Edge in Vis.js format 149 | :return: Dict with Edge attributes 150 | """ 151 | edge = {"from": from_, "to": to} 152 | if label is not None: 153 | edge["label"] = label 154 | return edge 155 | 156 | 157 | @app.route("/send-command", methods=["POST"]) 158 | def send_command(): 159 | """Send Command to Agent and returns hydrus response 160 | :param: All parameters enabled by the Agent. 161 | :param: Please check Agent.py in Agent's main repository. 162 | :return: hydrus response to the request 163 | """ 164 | global agent 165 | if agent is None: 166 | return "No agent connected." 167 | body = request.get_data() 168 | body = body.decode("utf8") 169 | body = json.loads(body) 170 | if "method" not in body: 171 | return "Request must have a method." 172 | if body["method"] == "get": 173 | # Get optional parameters 174 | filters = body.get("filters", {}) 175 | cached_limit = body.get("cached_limit", sys.maxsize) 176 | if "url" in body: 177 | return json.dumps( 178 | agent.get(url=body["url"], filters=filters, cached_limit=cached_limit) 179 | ) 180 | elif "resource_type" in body: 181 | return json.dumps( 182 | agent.get( 183 | resource_type=body["resource_type"], 184 | filters=filters, 185 | cached_limit=cached_limit, 186 | ) 187 | ) 188 | else: 189 | return "Must contain url or the resource type" 190 | elif body["method"] == "put": 191 | if "url" in body: 192 | url = body["url"] 193 | else: 194 | return "Put request must contain a url" 195 | if "new_object" in body: 196 | new_object = body["new_object"] 197 | else: 198 | return "Put request must contain the new_object." 199 | return json.dumps(agent.put(url, new_object)) 200 | elif body["method"] == "post": 201 | if "url" in body: 202 | url = body["url"] 203 | else: 204 | return "Post request must contain a url" 205 | if "updated_object" in body: 206 | updated_object = body["updated_object"] 207 | else: 208 | return "Put request must contain the updated_object." 209 | return json.dumps(agent.post(url, updated_object)) 210 | elif body["method"] == "delete": 211 | if "url" in body: 212 | url = body["url"] 213 | else: 214 | return "Delete request must contain a url" 215 | return json.dumps(agent.delete(url)) 216 | else: 217 | return "Method not supported." 218 | 219 | 220 | if __name__ == "__main__": 221 | app.run(use_reloader=True, port=3000, threaded=True) 222 | -------------------------------------------------------------------------------- /console-frontend/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | src/service-worker.js 4 | *.test.js 5 | *.test.jsx -------------------------------------------------------------------------------- /console-frontend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | // Converted to JS file, Now we can add comments 2 | module.exports = { 3 | settings: { 4 | react: { 5 | version: "latest", 6 | }, 7 | }, 8 | env: { 9 | browser: true, 10 | es6: true, 11 | node: true, 12 | }, 13 | extends: ["eslint:recommended", "plugin:react/recommended"], 14 | globals: { 15 | Atomics: "readonly", 16 | SharedArrayBuffer: "readonly", 17 | }, 18 | parserOptions: { 19 | parser: "@babel/eslint-parser", 20 | requireConfigFile: false, 21 | ecmaFeatures: { 22 | jsx: true, 23 | arrowFunctions: true, 24 | }, 25 | ecmaVersion: 2018, 26 | sourceType: "module", 27 | }, 28 | plugins: ["react"], 29 | rules: { 30 | "require-jsdoc": 0, 31 | "max-len": [ 32 | 1, 33 | { 34 | ignoreComments: true, 35 | code: 120, 36 | }, 37 | ], 38 | camelcase: 0, 39 | "react/prop-types": 0, 40 | "no-invalid-this": 0, 41 | "guard-for-in": 0, 42 | "prefer-const": [ 43 | "error", 44 | { 45 | destructuring: "any", 46 | ignoreReadBeforeAssign: false, 47 | }, 48 | ], 49 | "no-unused-vars": ["error", { args: "none" }], 50 | "no-console": 0, 51 | }, 52 | }; 53 | -------------------------------------------------------------------------------- /console-frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # We're using npm as our package manager 2 | yarn.lock 3 | 4 | # Output folders 5 | /coverage 6 | /node_modules 7 | /.DS_Store 8 | /.env.local 9 | /.env.development.local 10 | /.env.production.local 11 | /.env.test.local 12 | /npm-debug.log 13 | /yarn-debug.log 14 | /yarn-error.log 15 | node_modules/ -------------------------------------------------------------------------------- /console-frontend/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Charles Stover 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /console-frontend/README.md: -------------------------------------------------------------------------------- 1 | ## Hydra Agent Console GUI 2 | 3 | The React Frontend is divided in three main components. 4 | 5 | - Hydra Console has the state for the whole component and its subcomponents are all controlled components. 6 | - Hydra Graph only loads vis.js module and show what's returned by the ```/apidoc-graph``` endpoint 7 | - App.js only has internally the fetched ApiDoc and controls the state of the views of maximizing/minimizing the Console. 8 | 9 | The overall core structure of the Application is the following: 10 | ``` 11 | /app 12 | # This is the main component, declares and 13 | app.js 14 | /components 15 | # HydraConsole component and its Subcomponents 16 | /hydra-console 17 | /endpoints-buttons 18 | /operations-buttons 19 | /properties-editor 20 | # HydraGraph component 21 | /hydra-graph 22 | ``` 23 | 24 | ### Contributing 25 | There are multiple issues open to make the GUI better. When you make changes, make sure you run ```npm run build``` to create an updated production build for the Flask Backend. 26 | 27 | ## React Default Available Scripts 28 | 29 | In the project directory, you can run: 30 | 31 | ### `npm install` 32 | 33 | Install the dependencies in the local node_modules folder. 34 | By default, npm install will install all modules listed as dependencies in package.json
35 | 36 | ### `npm start` 37 | 38 | Runs the app in the development mode.
39 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 40 | 41 | The page will reload if you make edits.
42 | You will also see any lint errors in the console. 43 | 44 | ### `npm test` 45 | 46 | Launches the test runner in the interactive watch mode.
47 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 48 | 49 | ### `npm run build` 50 | 51 | Builds the app for production to the `build` folder.
52 | It correctly bundles React in production mode and optimizes the build for the best performance. 53 | 54 | The build is minified and the filenames include the hashes.
55 | Your app is ready to be deployed! 56 | 57 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 58 | 59 | ### `npm run eject` 60 | 61 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 62 | 63 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 64 | 65 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 66 | 67 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 68 | 69 | ## Learn More 70 | 71 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 72 | 73 | To learn React, check out the [React documentation](https://reactjs.org/). 74 | 75 | ### Code Splitting 76 | 77 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting 78 | 79 | ### Analyzing the Bundle Size 80 | 81 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size 82 | 83 | ### Making a Progressive Web App 84 | 85 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app 86 | 87 | ### Advanced Configuration 88 | 89 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration 90 | 91 | ### Deployment 92 | 93 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment 94 | 95 | ### `npm run build` fails to minify 96 | 97 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify 98 | -------------------------------------------------------------------------------- /console-frontend/build/Hydra.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HTTP-APIs/hydra-python-agent-gui/fd56dec857d500041b41fa65caa91d4d7e447d70/console-frontend/build/Hydra.ico -------------------------------------------------------------------------------- /console-frontend/build/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "main.css": "/static/css/main.a1a1ee78.chunk.css", 4 | "main.js": "/static/js/main.a636c623.chunk.js", 5 | "main.js.map": "/static/js/main.a636c623.chunk.js.map", 6 | "runtime~main.js": "/static/js/runtime~main.a8a9905a.js", 7 | "runtime~main.js.map": "/static/js/runtime~main.a8a9905a.js.map", 8 | "static/js/2.29454c3a.chunk.js": "/static/js/2.29454c3a.chunk.js", 9 | "static/js/2.29454c3a.chunk.js.map": "/static/js/2.29454c3a.chunk.js.map", 10 | "index.html": "/index.html", 11 | "precache-manifest.f5a86fea91e522c0a3265c700c9a2818.js": "/precache-manifest.f5a86fea91e522c0a3265c700c9a2818.js", 12 | "service-worker.js": "/service-worker.js", 13 | "static/css/main.a1a1ee78.chunk.css.map": "/static/css/main.a1a1ee78.chunk.css.map" 14 | } 15 | } -------------------------------------------------------------------------------- /console-frontend/build/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HTTP-APIs/hydra-python-agent-gui/fd56dec857d500041b41fa65caa91d4d7e447d70/console-frontend/build/favicon.ico -------------------------------------------------------------------------------- /console-frontend/build/index.html: -------------------------------------------------------------------------------- 1 | Hydra Agent
-------------------------------------------------------------------------------- /console-frontend/build/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Hydra Agent", 3 | "name": "Hydra Ecosystem Python Agent", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /console-frontend/build/precache-manifest.f5a86fea91e522c0a3265c700c9a2818.js: -------------------------------------------------------------------------------- 1 | self.__precacheManifest = (self.__precacheManifest || []).concat([ 2 | { 3 | "revision": "3a6947729a5be9196ff544bc65beb576", 4 | "url": "/index.html" 5 | }, 6 | { 7 | "revision": "d72dc7a0d787aed5ce28", 8 | "url": "/static/css/main.a1a1ee78.chunk.css" 9 | }, 10 | { 11 | "revision": "a8e7a7bce3e80d1a7e47", 12 | "url": "/static/js/2.29454c3a.chunk.js" 13 | }, 14 | { 15 | "revision": "d72dc7a0d787aed5ce28", 16 | "url": "/static/js/main.a636c623.chunk.js" 17 | }, 18 | { 19 | "revision": "42ac5946195a7306e2a5", 20 | "url": "/static/js/runtime~main.a8a9905a.js" 21 | } 22 | ]); -------------------------------------------------------------------------------- /console-frontend/build/service-worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Welcome to your Workbox-powered service worker! 3 | * 4 | * You'll need to register this file in your web app and you should 5 | * disable HTTP caching for this file too. 6 | * See https://goo.gl/nhQhGp 7 | * 8 | * The rest of the code is auto-generated. Please don't update this file 9 | * directly; instead, make changes to your Workbox build configuration 10 | * and re-run your build process. 11 | * See https://goo.gl/2aRDsh 12 | */ 13 | 14 | importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js"); 15 | 16 | importScripts( 17 | "/precache-manifest.f5a86fea91e522c0a3265c700c9a2818.js" 18 | ); 19 | 20 | self.addEventListener('message', (event) => { 21 | if (event.data && event.data.type === 'SKIP_WAITING') { 22 | self.skipWaiting(); 23 | } 24 | }); 25 | 26 | workbox.core.clientsClaim(); 27 | 28 | /** 29 | * The workboxSW.precacheAndRoute() method efficiently caches and responds to 30 | * requests for URLs in the manifest. 31 | * See https://goo.gl/S9QRab 32 | */ 33 | self.__precacheManifest = [].concat(self.__precacheManifest || []); 34 | workbox.precaching.precacheAndRoute(self.__precacheManifest, {}); 35 | 36 | workbox.routing.registerNavigationRoute(workbox.precaching.getCacheKeyForURL("/index.html"), { 37 | 38 | blacklist: [/^\/_/,/\/[^/]+\.[^/]+$/], 39 | }); 40 | -------------------------------------------------------------------------------- /console-frontend/build/static/css/main.a1a1ee78.chunk.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css2?family=Roboto&display=swap);@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.app-header{display:flex;flex-direction:column;align-items:center;justify-content:center;font-size:calc(10px + 2vmin);color:#fff}.app-header .app-link{color:#fbd20b}.app-header .app-logo{-webkit-animation:spin 20s linear infinite;animation:spin 20s linear infinite;height:40vmin;pointer-events:none}body{margin:0;padding:0;font-family:Roboto,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace} 2 | /*# sourceMappingURL=main.a1a1ee78.chunk.css.map */ -------------------------------------------------------------------------------- /console-frontend/build/static/css/main.a1a1ee78.chunk.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["main.a1a1ee78.chunk.css","E:\\Projetc\\hydra-python-agent-gui\\console-frontend/src\\assets\\sass\\spin.scss","E:\\Projetc\\hydra-python-agent-gui\\console-frontend/src\\app\\app.scss","index.css"],"names":[],"mappings":"AAAA,yEAAyE,CCAzE,wBACE,GACE,8BAAuB,CAAvB,sBAAuB,CAEzB,GACE,+BAAyB,CAAzB,uBAAyB,CAAA,CAL7B,gBACE,GACE,8BAAuB,CAAvB,sBAAuB,CAEzB,GACE,+BAAyB,CAAzB,uBAAyB,CAAA,CCH7B,YAEE,YAAa,CACb,qBAAsB,CACtB,kBAAmB,CACnB,sBAAuB,CACvB,4BAA6B,CAC7B,UAAY,CAPd,sBAUI,aAAc,CAVlB,sBAcI,0CAAmC,CAAnC,kCAAmC,CACnC,aAAc,CACd,mBAAoB,CCjBxB,KACE,QAAS,CACT,SAAU,CACV,6BAAiC,CACjC,kCAAmC,CACnC,iCACF,CAEA,KACE,uEAEF","file":"main.a1a1ee78.chunk.css","sourcesContent":["@import url(https://fonts.googleapis.com/css2?family=Roboto&display=swap);\n@-webkit-keyframes spin {\n from {\n -webkit-transform: rotate(0deg);\n transform: rotate(0deg); }\n to {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg); } }\n\n@keyframes spin {\n from {\n -webkit-transform: rotate(0deg);\n transform: rotate(0deg); }\n to {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg); } }\n\n.app-header {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n font-size: calc(10px + 2vmin);\n color: white; }\n .app-header .app-link {\n color: #FBD20B; }\n .app-header .app-logo {\n -webkit-animation: spin infinite 20s linear;\n animation: spin infinite 20s linear;\n height: 40vmin;\n pointer-events: none; }\n\nbody {\r\n margin: 0;\r\n padding: 0;\r\n font-family: \"Roboto\", sans-serif;\r\n -webkit-font-smoothing: antialiased;\r\n -moz-osx-font-smoothing: grayscale;\r\n}\r\n\r\ncode {\r\n font-family: source-code-pro, Menlo, Monaco, Consolas, \"Courier New\",\r\n monospace;\r\n}\r\n\n","@keyframes spin {\r\n from {\r\n transform: rotate(0deg);\r\n }\r\n to {\r\n transform: rotate(360deg);\r\n }\r\n}\r\n","@import '../assets/sass/spin';\r\n\r\n.app-header {\r\n // background-color: #eeeeee;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n font-size: calc(10px + 2vmin);\r\n color: white;\r\n\r\n .app-link {\r\n color: #FBD20B;\r\n }\r\n\r\n .app-logo {\r\n animation: spin infinite 20s linear;\r\n height: 40vmin;\r\n pointer-events: none;\r\n }\r\n\r\n}\r\n","@import url(\"https://fonts.googleapis.com/css2?family=Roboto&display=swap\");\r\nbody {\r\n margin: 0;\r\n padding: 0;\r\n font-family: \"Roboto\", sans-serif;\r\n -webkit-font-smoothing: antialiased;\r\n -moz-osx-font-smoothing: grayscale;\r\n}\r\n\r\ncode {\r\n font-family: source-code-pro, Menlo, Monaco, Consolas, \"Courier New\",\r\n monospace;\r\n}\r\n"]} -------------------------------------------------------------------------------- /console-frontend/build/static/js/main.a636c623.chunk.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[0],{13:function(e,t){e.exports={isArray:function(e){return e&&"object"===typeof e&&e.constructor===Array},isString:function(e){return"string"===typeof e||e instanceof String},isObject:function(e){return e&&"object"===typeof e&&e.constructor===Object},setInLocalStorage:function(e,t){localStorage.setItem(e,t)},getFromLocalStorage:function(e){return localStorage.getItem(e)},jsonStringifyReplacer:function(e,t){if(""!==t)return t},extractPageNumberFromString:function(e){var t=e.indexOf("page=");return e[t+5]}}},130:function(e,t,n){},131:function(e,t,n){},132:function(e,t,n){"use strict";n.r(t);var r=n(0),a=n.n(r),o=n(7),s=n.n(o),i=n(27),c=n.n(i),l=n(34),p=n(16),d=n(12),u=n(19),h=n(17),m=n(20),g=n(11),f=n(172),b=n(173),v=n(174),y=n(175),E=n(70),C=n.n(E),x=n(170),k=n(66),O=n.n(k),w=n(67),j=n.n(w),I=Object(x.a)({hydraEcoLogo:{maxWidth:"30px",cursor:"pointer",marginRight:"6px"},AppBar:{},Typography:{fontSize:"18px",flexGrow:1,paddingLeft:"1em"},centeringSpace:{flexGrow:1.21},fab:{boxShadow:"none"}}),A=function(e){var t=I();return a.a.createElement("div",null,a.a.createElement(f.a,{position:"static",className:t.AppBar,color:e.color},a.a.createElement(b.a,null,e.onClick&&a.a.createElement("img",{src:O.a,onClick:function(){return window.open("http://www.hydraecosystem.org/")},className:t.hydraEcoLogo,alt:"logo"}),a.a.createElement(v.a,{className:t.Typography,color:e.fontColor},e.text),e.onClick&&a.a.createElement(y.a,{color:"primary",onClick:e.onClick,"aria-label":"add",className:t.fab},a.a.createElement(C.a,null)),a.a.createElement("div",{className:t.centeringSpace}),e.onClick&&a.a.createElement("img",{src:j.a,onClick:function(){return window.open("https://github.com/HTTP-APIs/hydra-python-agent-gui")},className:t.hydraEcoLogo,alt:"logo"}))))},N=n(134),R=Object(N.a)(function(e){return{image:{position:"absolute",top:"50%",left:"50%",width:"120px",height:"120px",margin:"-60px 0 0 -60px",animation:"spin 4s linear infinite",borderRadius:"30%"},"@keyframes spin":{"100%":{transform:"rotate(360deg)"}}}})(function(e){var t=e.classes;return a.a.createElement("div",{className:t.loader},a.a.createElement("img",{className:t.image,src:"https://avatars1.githubusercontent.com/u/24541949?s=100&v=4",alt:"Loader"}))}),D=n(179),S=n(183),B=n(177),L=n(178),F=n(181),P=n(74),J=n(176),T=Object(P.a)({palette:{primary:{main:"#212121",dark:"#404040",light:"#eeeeee",contrastText:"#fff"},secondary:{main:"#FBD20B",dark:"#c3a100",light:"#ffff54",contrastText:"#000"},contrastThreshold:3,tonalOffset:.2,companyBlue:"#FF0000",companyRed:{backgroundColor:"#E44D69",color:"#000"},accent:{backgroundColor:J.a[500],color:"#000"},text:{primary:"#000000",secondary:"#585858"}}}),U=n(48),Z=n.n(U),G=(n(96),function(e){function t(e){var n;Object(p.a)(this,t),n=Object(u.a)(this,Object(h.a)(t).call(this,e));var r=[];Object.keys(n.props.endpoints).forEach(function(e){r[e]=!1});return r[0]=!0,n.state={buttons:r,selectedButton:0},n}return Object(m.a)(t,e),Object(d.a)(t,[{key:"selectButton",value:function(e){var t=this.state.buttons.slice();t[this.state.selectedButton]=!1,t[e]=!0,this.setState({buttons:t,selectedButton:e})}},{key:"generateButtons",value:function(){var e=this,t=Object.keys(this.props.endpoints),n=this.props.classes;return t.map(function(t,r){var o=e.props.endpoints[t].property.label;e.state.buttons[t]?n.active:n.endpointButton;return a.a.createElement(B.a,{key:t,className:"".concat(n.endpointButton," ").concat(e.state.buttons[t]?n.active:""),onClick:function(n){e.selectButton(t),e.props.selectEndpoint(t)}},o,a.a.createElement("span",{className:n.rightArrow},">"))})}},{key:"render",value:function(){return this.generateButtons()}}]),t}(a.a.Component)),W=Object(N.a)(function(e){return{endpointButton:Object(g.a)({minWidth:"auto",padding:"1em",borderRight:"1px solid #E4E4E4",marginBottom:"0px",backgroundColor:"#F9F9F9"},"@media (min-width:780px)",Object(g.a)({width:"95%",textAlight:"start",justifyContent:"start",borderBottom:"1px solid #E4E4E4",display:"flex"},"justifyContent","space-between")),endpointSelectedButton:{backgroundColor:"grey"},active:{backgroundColor:"white",borderRight:"none"},rightArrow:Object(g.a)({display:"none"},"@media (min-width:780px)",{display:"flex"})}})(G),Y=function(e){function t(e){var n;Object(p.a)(this,t),n=Object(u.a)(this,Object(h.a)(t).call(this,e));var r=[],a=0;return Object.keys(n.props.operations).forEach(function(e,t){r[e]=!1,"GET"===n.props.operations[e].method&&(a=t)}),r[a]=!0,n.state={buttons:r,selectedButton:a},n}return Object(m.a)(t,e),Object(d.a)(t,[{key:"selectButton",value:function(e){var t=this.state.buttons.slice();t[this.state.selectedButton]=!1,t[e]=!0,this.setState({buttons:t,selectedButton:e})}},{key:"generateButtons",value:function(){var e=this,t=Object.keys(this.props.operations),n=this.props.classes;return t.map(function(t,r){var o=e.props.operations[t].method;return a.a.createElement(B.a,{key:t,color:e.state.buttons[t]?"secondary":"default",className:"".concat(n.operationButton," ").concat(e.state.buttons[r]?n.operationButtonActive:""),onClick:function(n){e.selectButton(t),e.props.selectOperation(t)}},o)})}},{key:"componentDidUpdate",value:function(){if(this.state.selectedButton!==this.props.selectedOperationIndex){var e=this.state.buttons.slice();e[this.state.selectedButton]=!1,e[this.props.selectedOperationIndex]=!0,this.setState({buttons:e,selectedButton:this.props.selectedOperationIndex})}}},{key:"render",value:function(){return this.generateButtons()}}]),t}(a.a.Component),Q=Object(N.a)(function(e){return{operationButton:{borderRadius:"16px",backgroundColor:"#F5F5F5",margin:"0.6em"},operationButtonActive:{backgroundColor:"#F2C94C",color:"black"}}})(Y),X=function(e){function t(){return Object(p.a)(this,t),Object(u.a)(this,Object(h.a)(t).apply(this,arguments))}return Object(m.a)(t,e),Object(d.a)(t,[{key:"generateField",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,n=arguments.length>2?arguments[2]:void 0,r=arguments.length>3?arguments[3]:void 0,o=this.props.classes,s=n[r].find(function(t){return t.property===e});return a.a.createElement(L.a,{className:o.propertyContainer,container:!0,direction:"row",justify:"flex-start",alignItems:"center"},a.a.createElement("label",{className:o.propertyInput},e,":"),a.a.createElement(D.a,{placeholder:t,name:e,value:this.props.properties[e],onChange:this.props.onChange,className:o.input,inputProps:{"aria-label":"description"}}),a.a.createElement("small",{className:o.required},1==s.required?"(required)":"(optional)"))}},{key:"generateProperties",value:function(){var e=[];for(var t in this.props.properties)e.push(this.generateField(t,null,this.props.metaProps,this.props.endpoint));return e}},{key:"render",value:function(){return this.generateProperties()}}]),t}(a.a.Component),H=Object(N.a)(function(e){return{propertyInput:{color:T.palette.primary.dark,marginTop:"1em"},propertyContainer:{marginTop:"2px",marginBottom:"2px"},input:{display:"block",width:"100%"},required:{color:"rgba(0, 0, 0, 0.5)"}}})(X),V=Object(x.a)({page:{padding:"0.5em",backgroundColor:"white",margin:"0.5em","&:hover":{cursor:"pointer"},borderRadius:"5px"}});var z=function(e){var t=e.last_page,n=e.paginate,r=V(),o=[];if(t>1)for(var s=function(e){o.push(a.a.createElement("span",{className:r.page,onClick:function(t){return n(t,e)}},e))},i=1;i<=t;i++)s(i);return o},M=n(13),K=n(24),q=n.n(K),_=function(e){return q.a.post("".concat("","/send-command"),e).then(function(e){return e}).catch(function(e){return console.error(e)})},$=Object(N.a)({root:{}})(F.a),ee=function(e){function t(e){var n;Object(p.a)(this,t),(n=Object(u.a)(this,Object(h.a)(t).call(this,e))).child=a.a.createRef();var r=null,o=[];for(var s in n.agentEndpoint="",n.temporaryEndpoint=null,n.previousEndpointIndex=0,n.selectedEndpoint=null,n.selectedOperation=null,n.getURL=!0,n.props.hydraClasses)o[n.props.hydraClasses[s]["@id"]]=n.props.hydraClasses[s],"vocab:EntryPoint"===n.props.hydraClasses[s]["@id"]&&(r=n.props.hydraClasses[s].supportedProperty);var i={},c={},l={};for(var d in o)for(var m in i[o[d]["@id"]]={},l[d]=[],c[o[d]["@id"]]={},c[o[d]["@id"]].ResourceID="",o[d].supportedProperty)i[o[d]["@id"]][o[d].supportedProperty[m].title]="",l[d].push({property:o[d].supportedProperty[m].title,required:o[d].supportedProperty[m].required});return null===Object(M.getFromLocalStorage)("properties")?Object(M.setInLocalStorage)("properties",JSON.stringify(i)):i=JSON.parse(Object(M.getFromLocalStorage)("properties")),null===Object(M.getFromLocalStorage)("resourceIDs")?Object(M.setInLocalStorage)("resourceIDs",JSON.stringify(c)):c=JSON.parse(Object(M.getFromLocalStorage)("resourceIDs")),n.state={hydraClasses:o,classesPropertiesWithMetaData:l,endpoints:r,properties:i,resourcesIDs:c,selectedEndpointIndex:0,selectedOperationIndex:1,getPage:1,outputText:" Your request output will be displayed here..."},n}return Object(m.a)(t,e),Object(d.a)(t,[{key:"componentDidUpdate",value:function(){this.restorePropertiesAndResourceIDs()}},{key:"changePage",value:function(e,t){this.sendCommand(t)}},{key:"restorePropertiesAndResourceIDs",value:function(){if(this.previousEndpointIndex!==this.state.selectedEndpointIndex){var e=JSON.parse(Object(M.getFromLocalStorage)("properties")),t=JSON.parse(Object(M.getFromLocalStorage)("resourceIDs"));this.setState({properties:e,resourcesIDs:t}),this.previousEndpointIndex=this.state.selectedEndpointIndex}}},{key:"selectEndpoint",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"GET",n=this.state.endpoints[e];this.selectedEndpoint=n,this.child.current.selectButton(e);var r=n.property.range.replace("Collection","");this.temporaryEndpoint=r;var a=this.state.hydraClasses[r].supportedOperation,o=0;a.forEach(function(e,n){e.method===t&&(o=n)}),this.setState({selectedEndpointIndex:e,selectedOperationIndex:o})}},{key:"selectOperation",value:function(e){this.setState({selectedOperationIndex:e})}},{key:"handleChange",value:function(e){this.getURL=!1;var t=Object.assign({},this.state.properties);t[this.temporaryEndpoint][e.target.name]=e.target.value,Object(M.setInLocalStorage)("properties",JSON.stringify(t)),this.setState({properties:t})}},{key:"handleChangeResourceID",value:function(e){this.getURL=!0;var t=Object.assign({},this.state.resourcesIDs);t[e.target.name].ResourceID=e.target.value,Object(M.setInLocalStorage)("resourceIDs",JSON.stringify(t)),this.setState({resourcesIDs:t})}},{key:"clearAllInputs",value:function(e){var t=this,n=Object.assign({},this.state.properties);Object.keys(n[this.temporaryEndpoint]).forEach(function(e){n[t.temporaryEndpoint][e]=""});var r=Object.assign({},this.state.resourcesIDs);Object.keys(r).forEach(function(e){r[e].ResourceID=""}),Object(M.setInLocalStorage)("properties",JSON.stringify(n)),Object(M.setInLocalStorage)("resourceIDs",JSON.stringify(r)),this.setState({properties:n,resourcesIDs:r})}},{key:"setResourceID",value:function(e,t){this.getURL=!0;var n=Object.assign({},this.state.resourcesIDs);n[e].ResourceID=t.split("/").pop(),Object(M.setInLocalStorage)("resourceIDs",JSON.stringify(n)),this.setState({resourcesIDs:n})}},{key:"sendCommand",value:function(){var e=Object(l.a)(c.a.mark(function e(t){var n,r,a,o,s,i,l,p,d,u,h,m,g,f,b,v,y,E,C;return c.a.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:for(a in n=this.state.properties[this.temporaryEndpoint],r={},n)""!==n[a]&&(r[a]=n[a]);if(o=this.selectedEndpoint.property.label.replace("Collection",""),"get"!==this.selectedOperation.method.toLowerCase()){e.next=19;break}return r.page=t,s=null,i="",this.getURL?(i=this.state.resourcesIDs[this.temporaryEndpoint].ResourceID?this.props.serverUrl+this.selectedEndpoint.property.label+"/"+this.state.resourcesIDs[this.temporaryEndpoint].ResourceID:this.props.serverUrl+this.selectedEndpoint.property.label+"/",s={method:"get",url:i,filters:r}):s={method:"get",resource_type:o,filters:r},e.next=11,_(s);case 11:l=e.sent,p=l.data.members||l.data,d=l.data.view,u=1,d&&(u=Object(M.extractPageNumberFromString)(d.last)),this.setState({outputText:p,lastPage:u}),e.next=49;break;case 19:if("put"!==this.selectedOperation.method.toLowerCase()){e.next=30;break}return null,h={method:"put",url:this.props.serverUrl+this.selectedEndpoint.property.label+"/"+this.state.resourcesIDs[this.temporaryEndpoint].ResourceID,new_object:r},r["@type"]=o,e.next=25,_(h);case 25:m=e.sent,g=m.data,this.setState({outputText:g}),e.next=49;break;case 30:if("post"!==this.selectedOperation.method.toLowerCase()){e.next=41;break}return null,f={method:"post",url:this.props.serverUrl+this.selectedEndpoint.property.label+"/"+this.state.resourcesIDs[this.temporaryEndpoint].ResourceID,updated_object:r},r["@type"]=o,e.next=36,_(f);case 36:b=e.sent,v=b.data,this.setState({outputText:v}),e.next=49;break;case 41:if("delete"!==this.selectedOperation.method.toLowerCase()){e.next=49;break}return null,y={method:"delete",url:this.props.serverUrl+this.selectedEndpoint.property.label+"/"+this.state.resourcesIDs[this.temporaryEndpoint].ResourceID},e.next=46,_(y);case 46:E=e.sent,C=E.data,this.setState({outputText:C});case 49:case"end":return e.stop()}},e,this)}));return function(t){return e.apply(this,arguments)}}()},{key:"render",value:function(){var e=this,t=this.props.classes,n=this.state.endpoints[this.state.selectedEndpointIndex];this.selectedEndpoint=n;var r=n.property.range.replace("Collection","");this.temporaryEndpoint=r;var o=this.state.hydraClasses[r],s=o.supportedOperation[this.state.selectedOperationIndex];this.selectedOperation=s;var i=JSON.stringify(this.state.properties[r],M.jsonStringifyReplacer),c="";return c=this.getURL?"agent."+this.selectedOperation.method.toLowerCase()+'("'+this.props.serverUrl+this.selectedEndpoint.property.label+"/"+this.state.resourcesIDs[this.temporaryEndpoint].ResourceID+'")':"agent."+this.selectedOperation.method.toLowerCase()+'("/'+n.property.label+'", '+i+")",a.a.createElement(L.a,{container:!0,md:12},a.a.createElement(L.a,{item:!0,xs:12,md:5},a.a.createElement("div",{className:t.endpointButtonContainerOuter},a.a.createElement("div",{className:t.endpointButtonContainerInner},a.a.createElement(W,{ref:this.child,selectEndpoint:function(t){e.selectEndpoint(t)},endpoints:this.state.endpoints})))),a.a.createElement(L.a,{item:!0,xs:12,md:7,direction:"column",justify:"center",alignItems:"center",className:t.consoleGrid},a.a.createElement(L.a,{item:!0,justify:"center",alignItems:"center",className:t.operationsButtonContainer},a.a.createElement("div",{className:t.classDescription},o.description),a.a.createElement("div",null,a.a.createElement(Q,{operations:o.supportedOperation,selectedOperationIndex:this.state.selectedOperationIndex,selectOperation:function(t){e.selectOperation(t)}}))),a.a.createElement(L.a,{className:t.propertiesContainer,item:!0,direction:"row",justify:"flex-start",alignItems:"center"},a.a.createElement(L.a,{className:t.propertyContainer,item:!0,direction:"row",justify:"flex-start",alignItems:"center"},a.a.createElement("label",{className:t.propertyInput},"ResourceID:"),a.a.createElement(D.a,{name:r,value:this.state.resourcesIDs[r].ResourceID,onChange:function(t){return e.handleChangeResourceID(t)},onFocus:function(t){return e.handleChangeResourceID(t)},className:t.input,inputProps:{"aria-label":"description"}})),"DELETE"!==this.selectedOperation.method&&a.a.createElement(H,{activatedMethod:this.selectedOperation.method,endpoint:this.temporaryEndpoint,properties:this.state.properties[r],metaProps:this.state.classesPropertiesWithMetaData,onChange:function(t){e.handleChange(t)}})),a.a.createElement(B.a,{"aria-label":"delete",size:"medium",variant:"outlined",className:t.deleteIconButton,onClick:function(t){return e.clearAllInputs(t)}},"CLEAR"),a.a.createElement(B.a,{className:t.sendRequest,onClick:function(){return e.sendCommand(1)}},"Send Request")),a.a.createElement(L.a,{item:!0,xs:12,container:!0,direction:"row",justify:"center",alignItems:"center",className:t.rawCommandGrid},a.a.createElement($,{id:"outlined-name",label:"Raw Command",className:t.textField,onChange:function(){},margin:"normal",variant:"outlined",value:c})),a.a.createElement(L.a,{item:!0,xs:12,md:11,direction:"column",justify:"center",alignItems:"center",className:t.responseGrid},a.a.createElement("span",{className:t.outputContainerHeader}," RESPONSE"),a.a.createElement("div",{className:t.outputContainer},"string"===typeof this.state.outputText?a.a.createElement(Z.a,{src:{msg:"Your Output will be displayed here"},name:null}):a.a.createElement(Z.a,{src:this.state.outputText,name:null})),a.a.createElement("div",{className:t.pages},a.a.createElement(z,{last_page:this.state.lastPage,paginate:this.changePage.bind(this)}))))}}]),t}(r.Component),te=Object(N.a)(function(e){return{propertiesContainer:{maxHeight:"30vh",width:"100%",padding:"1em",overflowY:"auto",backgroundColor:"white"},propertyContainer:{marginTop:"2px",marginBottom:"2px"},propertyInput:{color:T.palette.primary.dark,marginTop:"1em"},input:{display:"block",width:"100%"},outputContainer:Object(g.a)({width:"100%",padding:"1em",overflowY:"auto",marginBottom:"1em",backgroundColor:"white",marginTop:"1em",maxHeight:"50vh"},"@media (min-width:780px)",{width:"100%",fontSize:"0.8em"}),rawCommandGrid:{backgroundColor:"white",marginBottom:"1em"},outputContainerHeader:{fontSize:"1.0em",padding:"1em",letterSpacing:"1px"},textField:{width:"100%",margin:"1em",color:"#000",borderColor:"#0f0",fontFamily:"source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace"},deleteIconButton:{marginLeft:"1em",color:T.palette.primary.dark},endpointButtonContainerOuter:Object(g.a)({backgroundColor:"white",overflow:"auto",marginLeft:"1em",maxHeight:"50vh"},"@media (min-width:780px)",{backgroundColor:"white",marginLeft:"0em",width:"100%"}),endpointButtonContainerInner:Object(g.a)({display:"flex",width:"100%",borderBottom:"1px solid #E4E4E4"},"@media (min-width:780px)",{flexDirection:"column",borderBottom:"none"}),operationsButtonContainer:Object(g.a)({width:"100%",paddingTop:"1em",display:"flex",justifyContent:"center",alignItems:"center",backgroundColor:"white",flexDirection:"column"},"@media (min-width:780px)",{}),description:{marginBottom:"1em",color:"grey",letterSpacing:"1px"},sendRequest:{backgroundColor:"#F2C94C",float:"right",marginRight:"1em"},consoleGrid:{backgroundColor:"white"},responseGrid:Object(g.a)({},"@media (min-width:780px)",{}),classDescription:{width:"100%",margin:"0 auto",marginBottom:"0.5em",textAlign:"center"},pages:{width:"100%",display:"flex",justifyContent:"center"}}})(ee),ne=n(46),re=function(e){function t(){return Object(p.a)(this,t),Object(u.a)(this,Object(h.a)(t).apply(this,arguments))}return Object(m.a)(t,e),Object(d.a)(t,[{key:"componentDidMount",value:function(){var e,t=this,n=new ne.DataSet(this.props.apidocGraph.nodes),r=new ne.DataSet(this.props.apidocGraph.edges),a=document.getElementById("mynetwork"),o={nodes:n,edges:r},s={interaction:{hover:!0},autoResize:!0,nodes:{color:{hover:{border:"#5BDE79",background:"#5BDE79"}}},layout:{improvedLayout:!0}},i=0,c=null;for(var l in this.props.hydraClasses)"vocab:EntryPoint"===this.props.hydraClasses[l]["@id"]&&(c=this.props.hydraClasses[l].supportedProperty);var p=new ne.Network(a,o,s);this.selectedNode=function(e){this.props.selectNode(e)},p.on("hoverNode",function(t){i=0;var n=t.node,r=Object.keys(o.nodes._data).map(function(e){return o.nodes._data[e]});(r.forEach(function(t){t.id===n&&(e=t,c.forEach(function(t){t.property.label===e.label&&(i=1)}))}),1!==i)&&Object.keys(o.edges._data).map(function(e){return o.edges._data[e]}).forEach(function(t){t.to===n&&"supportedOp"===t.label&&r.forEach(function(n){n.id===t.from&&(e=n,c.forEach(function(t){t.property.label===e.label&&(i=1)}))})});1===i?(s.nodes.color.hover.background="#5BDE79",s.nodes.color.hover.border="#5BDE79",p.setOptions(s)):(s.nodes.color.hover.background="#FBD20B",s.nodes.color.hover.border="#FBD20B",p.setOptions(s))}),p.on("select",function(n){var r;i=0;var a=n.nodes,s=Object.keys(o.nodes._data).map(function(e){return o.nodes._data[e]});s.forEach(function(t){t.id===a[0]&&(e=t)});var l=0;if(c.forEach(function(n){n.property.label===e.label&&(i=1,r={Index:l,operation:"GET"},t.selectedNode(r)),l+=1}),1!==i){var p=e.label;Object.keys(o.edges._data).map(function(e){return o.edges._data[e]}).forEach(function(n){n.to===a[0]&&"supportedOp"===n.label&&s.forEach(function(a){a.id===n.from&&(e=a,l=0,c.forEach(function(n){n.property.label===e.label&&(i=1,r={Index:l,operation:p},t.selectedNode(r)),l+=1}))})})}}),p.moveTo({scale:1.3,animation:{duration:"0.5s",easingFunction:"linear"}})}},{key:"render",value:function(){var e=this.props.classes;return a.a.createElement("header",{className:"app-header"},a.a.createElement("div",{className:e.graphContainer,id:"mynetwork"}))}}]),t}(a.a.Component),ae=Object(N.a)(function(e){return{graphContainer:{height:"500px",backgroundColor:"#EEEEEE",margin:"0 1em",borderRadius:"8px",width:"90%"},graphName:{color:"black",border:"1px solid white",borderRadius:"8px",width:"100%",letterSpacing:"2px"}}})(re),oe=(n(130),n(180)),se=function(){return q.a.get("".concat("","/hydra-doc")).then(function(e){return e.data})},ie=function(){return q.a.get("".concat("","/apidoc-graph")).then(function(e){return e.data})},ce=function(e){return q.a.post("".concat("","/start-agent"),{url:e}).then(function(e){return e})},le=function(e){function t(e){var n;return Object(p.a)(this,t),(n=Object(u.a)(this,Object(h.a)(t).call(this,e))).selectNode=function(e){n.child.current.selectEndpoint(e.Index,e.operation)},n.child=a.a.createRef(),n.state={consoleWidth:6,hidden:!1,classes:null,apidocGraph:{edges:null,nodes:null},serverURL:"http://localhost:8080/serverapi/",selectedNodeIndex:null},n.agentEndpoint="",n}return Object(m.a)(t,e),Object(d.a)(t,[{key:"componentDidMount",value:function(){var e=Object(l.a)(c.a.mark(function e(){var t,n;return c.a.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,se();case 2:return t=e.sent,this.setState({classes:t.supportedClass,serverURL:t.serverURL.replace(/\/$/,"")+"/"}),e.next=6,ie();case 6:n=e.sent,this.setState({apidocGraph:n});case 8:case"end":return e.stop()}},e,this)}));return function(){return e.apply(this,arguments)}}()},{key:"toggleGraph",value:function(){this.state.hidden?this.setState({consoleWidth:6,hidden:!1}):this.setState({consoleWidth:12,hidden:!0})}},{key:"handleChangeServerURL",value:function(e){this.setState({serverURL:e.target.value})}},{key:"submitServerURL",value:function(){var e=Object(l.a)(c.a.mark(function e(t){var n;return c.a.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,ce(this.state.serverURL);case 2:return e.next=4,se();case 4:n=e.sent,this.setState({classes:n.supportedClass}),window.location.reload();case 7:case"end":return e.stop()}},e,this)}));return function(t){return e.apply(this,arguments)}}()},{key:"render",value:function(){var e=this,t=this.props.classes;return this.state.classes&&this.state.apidocGraph.nodes?a.a.createElement(oe.a,{theme:T},a.a.createElement(A,{text:"Hydra Agent GUI",backgroundColor:T.palette.primary.main,color:"primary",onClick:function(){return e.toggleGraph()}}),a.a.createElement(L.a,{container:!0,className:t.appContainer},a.a.createElement(L.a,{item:!0,className:t.serverInputContainer},a.a.createElement("div",{className:t.inputContainer},a.a.createElement(S.a,{className:t.inputLabel,htmlFor:"server_url_input"},"Server URL:"),a.a.createElement(D.a,{id:"server_url_input",placeholder:"Server URL - Default: https://localhost:8080/serverapi/",onKeyPress:function(t){"Enter"===t.key&&e.submitServerURL(t)},value:this.state.serverURL,onChange:function(t){return e.handleChangeServerURL(t)},className:t.serverInput,disableUnderline:!0,inputProps:{"aria-label":"hydrus-url"}})),a.a.createElement(B.a,{variant:"contained",className:t.goBtn,onClick:function(t){return e.submitServerURL(t)},disableElevation:!0},"GO"," ")),a.a.createElement(L.a,{container:!0,display:"flex"},a.a.createElement(L.a,{item:!0,order:2,md:this.state.consoleWidth,xs:12,color:"primary",className:t.consoleGrid},a.a.createElement(te,{ref:this.child,serverUrl:this.state.serverURL,hydraClasses:this.state.classes,color:"primary"})),!this.state.hidden&&a.a.createElement(L.a,{item:!0,md:6,xs:12,className:t.graphGrid},a.a.createElement(ae,{apidocGraph:this.state.apidocGraph,serverUrl:this.state.serverURL,hydraClasses:this.state.classes,selectNode:this.selectNode}))))):a.a.createElement(R,null)}}]),t}(a.a.Component),pe=Object(N.a)(function(e){return{serverInputContainer:Object(g.a)({display:"flex",flexDirection:"column",width:"90%",margin:"0 auto",borderRadius:"4px 4px 0px 0px"},"@media (min-width:780px)",{width:"100%",flexDirection:"row",alignItems:"center",justifyContent:"center",margin:"0 auto"}),inputContainer:Object(g.a)({backgroundColor:"white",padding:"0 10px"},"@media (min-width:780px)",{width:"35%"}),serverInput:Object(g.a)({width:"90%",padding:"5px",borderBottom:"1px solid black",paddingBottom:"0px",borderColor:"#000",borderRadius:"4px 4px 0px 0px"},"@media (min-width:780px)",{width:"100%"}),inputLabel:Object(g.a)({},"@media (min-width:780px)",{display:"block",paddingTop:"5px"}),goBtn:Object(g.a)({display:"block",margin:"1em 0em",width:"90%",backgroundColor:"#F2C94C",boxShadow:"0px"},"@media (min-width:780px)",{display:"inline-block",width:"5%",marginLeft:"1em",padding:"1em"}),consoleGrid:Object(g.a)({borderRadius:"8px"},"@media (min-width:780px)",{order:2}),graphGrid:Object(g.a)({},"@media (min-width:780px)",{order:1}),appContainer:{minHeight:"100vh",backgroundColor:"#F9F9F9",padding:"1em"}}})(le);n(131),Boolean("localhost"===window.location.hostname||"[::1]"===window.location.hostname||window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/));s.a.render(a.a.createElement(pe,null),document.getElementById("root")),"serviceWorker"in navigator&&navigator.serviceWorker.ready.then(function(e){e.unregister()})},66:function(e,t){e.exports="data:image/png;base64,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"},67:function(e,t){e.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NDkxMSwgMjAxMy8xMC8yOS0xMTo0NzoxNiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RERCMUIwOUY4NkNFMTFFM0FBNTJFRTMzNTJEMUJDNDYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RERCMUIwOUU4NkNFMTFFM0FBNTJFRTMzNTJEMUJDNDYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkU1MTc4QTJBOTlBMDExRTI5QTE1QkMxMDQ2QTg5MDREIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkU1MTc4QTJCOTlBMDExRTI5QTE1QkMxMDQ2QTg5MDREIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+jUqS1wAAApVJREFUeNq0l89rE1EQx3e3gVJoSPzZeNEWPKgHoa0HBak0iHiy/4C3WvDmoZ56qJ7txVsPQu8qlqqHIhRKJZceesmhioQEfxTEtsoSpdJg1u/ABJ7Pmc1m8zLwgWTmzcw3L+/te+tHUeQltONgCkyCi2AEDHLsJ6iBMlgHL8FeoqokoA2j4CloRMmtwTmj7erHBXPgCWhG6a3JNXKdCiDl1cidVbXZkJoXQRi5t5BrxwoY71FzU8S4JuAIqFkJ2+BFSlEh525b/hr3+k/AklDkNsf6wTT4yv46KIMNpsy+iMdMc47HNWxbsgVcUn7FmLAzzoFAWDsBx+wVP6bUpp5ewI+DOeUx0Wd9D8F70BTGNjkWtqnhmT1JQAHcUgZd8Lo3rQb1LAT8eJVUfgGvHQigGp+V2Z0iAUUl8QH47kAA1XioxIo+bRN8OG8F/oBjwv+Z1nJgX5jpdzQDw0LCjsPmrcW7I/iHScCAEDj03FtD8A0EyuChHgg4KTlJQF3wZ7WELppnBX+dBFSVpJsOBWi1qiRgSwnOgoyD5hmuJdkWCVhTgnTvW3AgYIFrSbZGh0UW/Io5Vp+DQoK7o80pztWMemZbgxeNwCNwDbw1fIfgGZjhU6xPaJgBV8BdsMw5cbZoHsenwYFxkZzl83xTSKTiviCAfCsJLysH3POfC8m8NegyGAGfLP/VmGmfSChgXroR0RSWjEFv2J/nG84cuKFMf4sTCZqXuJd4KaXFVjEG3+tw4eXbNK/YC9oXXs3O8NY8y99L4BXY5cvLY/Bb2VZ58EOJVcB18DHJq9lRsKr8inyKGVjlmh29mtHs3AHfuhCwy1vXT/Nu2GKQt+UHsGdctyX6eQyNvc+5sfX9Dl7Pe2J/BRgAl2CpwmrsHR0AAAAASUVORK5CYII="},85:function(e,t,n){e.exports=n(132)}},[[85,1,2]]]); 2 | //# sourceMappingURL=main.a636c623.chunk.js.map -------------------------------------------------------------------------------- /console-frontend/build/static/js/runtime~main.a8a9905a.js: -------------------------------------------------------------------------------- 1 | !function(e){function r(r){for(var n,f,i=r[0],l=r[1],a=r[2],c=0,s=[];c0.2%", 30 | "not dead", 31 | "not op_mini all" 32 | ], 33 | "development": [ 34 | "last 1 chrome version", 35 | "last 1 firefox version", 36 | "last 1 safari version" 37 | ] 38 | }, 39 | "description": "The React Frontend is divided in three main components.", 40 | "main": "index.js", 41 | "devDependencies": { 42 | "@types/jest": "^26.0.20", 43 | "@types/node": "^14.14.35", 44 | "@types/react": "^17.0.3", 45 | "@types/react-dom": "^17.0.2", 46 | "typescript": "^4.2.3" 47 | }, 48 | "author": "", 49 | "license": "ISC" 50 | } 51 | -------------------------------------------------------------------------------- /console-frontend/public/Hydra.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HTTP-APIs/hydra-python-agent-gui/fd56dec857d500041b41fa65caa91d4d7e447d70/console-frontend/public/Hydra.ico -------------------------------------------------------------------------------- /console-frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HTTP-APIs/hydra-python-agent-gui/fd56dec857d500041b41fa65caa91d4d7e447d70/console-frontend/public/favicon.ico -------------------------------------------------------------------------------- /console-frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | Hydra Agent 23 | 24 | 25 | 26 | 27 |
28 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /console-frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Hydra Agent", 3 | "name": "Hydra Ecosystem Python Agent", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /console-frontend/src/app/app.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import NavBar from "../components/navbar/NavBar"; 3 | import Loader from "../components/loader/Loader"; 4 | import Input from "@material-ui/core/Input"; 5 | import InputLabel from "@material-ui/core/InputLabel"; 6 | import Button from "@material-ui/core/Button"; 7 | import { withStyles } from "@material-ui/styles"; 8 | import Grid from "@material-ui/core/Grid"; 9 | import HydraConsole from "../components/hydra-console/HydraConsole"; 10 | import HydraGraph from "../components/hydra-graph/HydraGraph"; 11 | import "./app.scss"; 12 | import GuiTheme from "./gui-theme"; 13 | import { ThemeProvider } from "@material-ui/styles"; 14 | import getHydraDoc from "../services/hydra-doc-service"; 15 | import getApiDocGraph from "../services/api-doc-graph-service"; 16 | import startAgent from "../services/start-agent-service"; 17 | const styles = () => ( { 18 | serverInputContainer: { 19 | display: "flex", 20 | flexDirection: "column", 21 | width: "90%", 22 | margin: "0 auto", 23 | borderRadius: "4px 4px 0px 0px", 24 | ["@media (min-width:780px)"]: { 25 | width: "100%", 26 | flexDirection: "row", 27 | alignItems: "center", 28 | justifyContent: "center", 29 | margin: "0 auto", 30 | }, 31 | }, 32 | inputContainer: { 33 | backgroundColor: "white", 34 | padding: "0 10px", 35 | ["@media (min-width:780px)"]: { 36 | width: "35%", 37 | }, 38 | }, 39 | serverInput: { 40 | width: "90%", 41 | padding: "5px", 42 | borderBottom: "1px solid black", 43 | paddingBottom: "0px", 44 | borderColor: "#000", 45 | borderRadius: "4px 4px 0px 0px", 46 | ["@media (min-width:780px)"]: { 47 | width: "100%", 48 | }, 49 | }, 50 | inputLabel: { 51 | ["@media (min-width:780px)"]: { 52 | display: "block", 53 | paddingTop: "5px", 54 | }, 55 | }, 56 | goBtn: { 57 | display: "block", 58 | margin: "1em 0em", 59 | width: "90%", 60 | backgroundColor: "#F2C94C", 61 | boxShadow: "0px", 62 | ["@media (min-width:780px)"]: { 63 | display: "inline-block", 64 | width: "5%", 65 | marginLeft: "1em", 66 | padding: "1em", 67 | }, 68 | }, 69 | consoleGrid: { 70 | borderRadius: "8px", 71 | ["@media (min-width:780px)"]: { 72 | order: 2, 73 | }, 74 | }, 75 | graphGrid: { 76 | ["@media (min-width:780px)"]: { 77 | order: 1, 78 | }, 79 | }, 80 | appContainer: { 81 | minHeight: "100vh", 82 | backgroundColor: "#F9F9F9", 83 | padding: "1em", 84 | }, 85 | } ); 86 | 87 | const AgentGUI = ( props ) => { 88 | var child = React.createRef(); 89 | const [state, setState] = useState( { 90 | consoleWidth: 6, 91 | hidden: false, 92 | classes: null, 93 | apidocGraph: { edges: null, nodes: null }, 94 | serverURL: "http://localhost:8080/serverapi/", 95 | selectedNodeIndex: null, 96 | } ); 97 | // Empty when hosted using flask 98 | // var agentEndpoint = ""; 99 | 100 | useEffect( () => { 101 | const getData = async () => { 102 | 103 | const data = await getHydraDoc(); 104 | //for supportedClass > if @id="vocab:EntryPoint" then supportedProperty.property.label 105 | setState( { 106 | classes: data.supportedClass, 107 | serverURL: data.serverURL.replace( /\/$/, "" ) + "/", 108 | } ); 109 | const apidocGraph = await getApiDocGraph(); 110 | //for supportedClass > if @id="vocab:EntryPoint" then supportedProperty.property.label 111 | setState( { 112 | apidocGraph, 113 | } ); 114 | } 115 | getData() 116 | 117 | }, [] ) 118 | 119 | const selectNode = ( selectedRequest ) => { 120 | child.current.selectEndpoint( 121 | selectedRequest.Index, 122 | selectedRequest.operation 123 | ); 124 | }; 125 | const toggleGraph = () => { 126 | if ( state.hidden ) { 127 | setState( { 128 | consoleWidth: 6, 129 | hidden: false, 130 | } ); 131 | } else { 132 | setState( { 133 | consoleWidth: 12, 134 | hidden: true, 135 | } ); 136 | } 137 | } 138 | 139 | const handleChangeServerURL = e => { 140 | setState( { 141 | serverURL: e.target.value, 142 | } ); 143 | } 144 | 145 | const submitServerURL = async ( e ) => { 146 | await startAgent( state.serverURL ); 147 | const hydradoc = await getHydraDoc(); 148 | setState( { 149 | classes: hydradoc.supportedClass, 150 | } ); 151 | window.location.reload(); 152 | } 153 | 154 | const { classes } = props; 155 | if ( state.classes && state.apidocGraph.nodes ) { 156 | return ( 157 | 158 | toggleGraph()} 163 | > 164 | 165 | 166 |
167 | 171 | Server URL: 172 | 173 | { 177 | if ( e.key === "Enter" ) { 178 | submitServerURL( e ); 179 | } 180 | }} 181 | value={state.serverURL} 182 | onChange={( e ) => handleChangeServerURL( e )} 183 | className={classes.serverInput} 184 | disableUnderline={true} 185 | inputProps={{ 186 | "aria-label": "hydrus-url", 187 | }} 188 | /> 189 |
190 | 198 |
199 | 200 | 208 | 214 | 215 | {!state.hidden && ( 216 | 217 | 223 | 224 | )} 225 | 226 |
227 |
228 | ); 229 | } else { 230 | // This should return a loading screen 231 | return ; 232 | } 233 | 234 | } 235 | export default withStyles( styles )( AgentGUI ); 236 | -------------------------------------------------------------------------------- /console-frontend/src/app/app.scss: -------------------------------------------------------------------------------- 1 | @import '../assets/sass/spin'; 2 | 3 | .app-header { 4 | // background-color: #eeeeee; 5 | display: flex; 6 | flex-direction: column; 7 | align-items: center; 8 | justify-content: center; 9 | font-size: calc(10px + 2vmin); 10 | color: white; 11 | 12 | .app-link { 13 | color: #FBD20B; 14 | } 15 | 16 | .app-logo { 17 | animation: spin infinite 20s linear; 18 | height: 40vmin; 19 | pointer-events: none; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /console-frontend/src/app/app.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './app'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /console-frontend/src/app/gui-theme.js: -------------------------------------------------------------------------------- 1 | import { createMuiTheme } from '@material-ui/core/styles'; 2 | import { purple } from '@material-ui/core/colors'; 3 | 4 | export default createMuiTheme({ 5 | palette: { 6 | primary: { 7 | main: '#212121', 8 | dark: '#404040', 9 | light: '#eeeeee', 10 | contrastText: '#fff', 11 | }, 12 | secondary: { 13 | main: '#FBD20B', 14 | dark: '#c3a100', 15 | light: '#ffff54', 16 | contrastText: '#000', 17 | }, 18 | contrastThreshold: 3, 19 | tonalOffset: 0.2, 20 | companyBlue: '#FF0000', 21 | companyRed: { 22 | backgroundColor: '#E44D69', 23 | color: '#000', 24 | }, 25 | accent: { 26 | backgroundColor: purple[500], 27 | color: '#000', 28 | }, 29 | text: { 30 | primary: '#000000', 31 | secondary: '#585858', 32 | }, 33 | }, 34 | }); 35 | -------------------------------------------------------------------------------- /console-frontend/src/assets/images/GitHub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HTTP-APIs/hydra-python-agent-gui/fd56dec857d500041b41fa65caa91d4d7e447d70/console-frontend/src/assets/images/GitHub.png -------------------------------------------------------------------------------- /console-frontend/src/assets/images/agent_gui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HTTP-APIs/hydra-python-agent-gui/fd56dec857d500041b41fa65caa91d4d7e447d70/console-frontend/src/assets/images/agent_gui.png -------------------------------------------------------------------------------- /console-frontend/src/assets/images/hydra_eco_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HTTP-APIs/hydra-python-agent-gui/fd56dec857d500041b41fa65caa91d4d7e447d70/console-frontend/src/assets/images/hydra_eco_logo.png -------------------------------------------------------------------------------- /console-frontend/src/assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /console-frontend/src/assets/sass/global.scss: -------------------------------------------------------------------------------- 1 | $hello: 'world'; 2 | -------------------------------------------------------------------------------- /console-frontend/src/assets/sass/mixin.scss: -------------------------------------------------------------------------------- 1 | @mixin hello-world { 2 | } 3 | -------------------------------------------------------------------------------- /console-frontend/src/assets/sass/spin.scss: -------------------------------------------------------------------------------- 1 | @keyframes spin { 2 | from { 3 | transform: rotate(0deg); 4 | } 5 | to { 6 | transform: rotate(360deg); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /console-frontend/src/components/hydra-console/HydraConsole.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import Button from "@material-ui/core/Button"; 3 | import TextField from "@material-ui/core/TextField"; 4 | import Input from "@material-ui/core/Input"; 5 | import Grid from "@material-ui/core/Grid"; 6 | import GuiTheme from "../../app/gui-theme"; 7 | import { withStyles } from "@material-ui/styles"; 8 | import ReactJson from "react-json-view"; 9 | // Custom imports 10 | // import { Scrollbars } from "react-custom-scrollbars"; 11 | 12 | // Local components 13 | import EndpointsButtons from "./endpoints-buttons/EndpointsButtons"; 14 | import OperationsButtons from "./operations-buttons/OperationsButtons"; 15 | import PropertiesEditor from "./properties-editor/PropertiesEditor"; 16 | import Pagination from "./pagination/Pagination"; 17 | // utils imports 18 | import { 19 | setInLocalStorage, 20 | getFromLocalStorage, 21 | jsonStringifyReplacer, 22 | } from "../../utils/utils"; 23 | import {extractPageNumberFromString} from "../../utils/utils"; 24 | // Service Import 25 | import getRawOutput from "../../services/send-command.js"; 26 | // Custom Css modification to Raw Command Input field 27 | const CssTextField = withStyles( { 28 | root: { 29 | // "& label.Mui-focused": { 30 | // color: GuiTheme.palette.primary.light, 31 | // }, 32 | // "& .MuiInput-underline:after": { 33 | // borderBottomColor: GuiTheme.palette.secondary.main, 34 | // }, 35 | // "& .MuiOutlinedInput-root": { 36 | // "& fieldset": { 37 | // borderColor: GuiTheme.palette.primary.light, 38 | // height: "55px", 39 | // }, 40 | // "&:hover fieldset": { 41 | // borderColor: GuiTheme.palette.secondary.main, 42 | // }, 43 | // "&.Mui-focused fieldset": { 44 | // borderColor: GuiTheme.palette.primary.light, 45 | // }, 46 | // }, 47 | }, 48 | } )( TextField ); 49 | 50 | // Css Styles to the Components 51 | const styles = ( theme ) => ( { 52 | propertiesContainer: { 53 | maxHeight: "30vh", 54 | width: "100%", 55 | padding: "1em", 56 | overflowY: "auto", 57 | backgroundColor: "white", 58 | }, 59 | propertyContainer: { 60 | marginTop: "2px", 61 | marginBottom: "2px", 62 | }, 63 | propertyInput: { 64 | color: GuiTheme.palette.primary.dark, 65 | marginTop: "1em", 66 | }, 67 | input: { 68 | display: "block", 69 | width: "100%", 70 | }, 71 | outputContainer: { 72 | width: "100%", 73 | padding: "1em", 74 | overflowY: "auto", 75 | marginBottom: "1em", 76 | backgroundColor: "white", 77 | marginTop: "1em", 78 | maxHeight: "50vh", 79 | ["@media (min-width:780px)"]: { 80 | width: "100%", 81 | fontSize: "0.8em", 82 | }, 83 | }, 84 | rawCommandGrid: { 85 | backgroundColor: "white", 86 | marginBottom: "1em", 87 | }, 88 | outputContainerHeader: { 89 | fontSize: "1.0em", 90 | padding: "1em", 91 | letterSpacing: "1px", 92 | }, 93 | textField: { 94 | width: "100%", 95 | margin: "1em", 96 | color: "#000", 97 | borderColor: "#0f0", 98 | fontFamily: 99 | "source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace", 100 | }, 101 | deleteIconButton: { 102 | marginLeft: "1em", 103 | color: GuiTheme.palette.primary.dark, 104 | }, 105 | 106 | endpointButtonContainerOuter: { 107 | backgroundColor: "white", 108 | overflow: "auto", 109 | marginLeft: "1em", 110 | maxHeight: "50vh", 111 | ["@media (min-width:780px)"]: { 112 | backgroundColor: "white", 113 | marginLeft: "0em", 114 | width: "100%", 115 | }, 116 | }, 117 | endpointButtonContainerInner: { 118 | display: "flex", 119 | width: "100%", 120 | borderBottom: "1px solid #E4E4E4", 121 | ["@media (min-width:780px)"]: { 122 | flexDirection: "column", 123 | borderBottom: "none", 124 | }, 125 | }, 126 | operationsButtonContainer: { 127 | width: "100%", 128 | paddingTop: "1em", 129 | display: "flex", 130 | justifyContent: "center", 131 | alignItems: "center", 132 | backgroundColor: "white", 133 | flexDirection: "column", 134 | ["@media (min-width:780px)"]: {}, 135 | }, 136 | description: { 137 | marginBottom: "1em", 138 | color: "grey", 139 | letterSpacing: "1px", 140 | }, 141 | sendRequest: { 142 | backgroundColor: "#F2C94C", 143 | float: "right", 144 | marginRight: "1em", 145 | }, 146 | consoleGrid: { 147 | backgroundColor: "white", 148 | }, 149 | responseGrid: { 150 | ["@media (min-width:780px)"]: {}, 151 | }, 152 | classDescription: { 153 | width: "100%", 154 | margin: "0 auto", 155 | marginBottom: "0.5em", 156 | textAlign: "center", 157 | }, 158 | pages: { 159 | width: "100%", 160 | display: "flex", 161 | justifyContent: "center", 162 | }, 163 | } ); 164 | 165 | const HydraConsole = ( props ) => { 166 | var child = React.createRef(); 167 | let endpoints = null; 168 | const classesMapping = []; 169 | // var agentEndpoint = ""; 170 | 171 | // util variables 172 | let previousEndpointIndex = 0; // for managing the state and local storage 173 | let getURL = true; 174 | 175 | // Modifying reference from indexed array[0, 1, 2] to name ["vocab:Drone", "vocab:.."] 176 | for ( const index in props.hydraClasses ) { 177 | classesMapping[ 178 | props.hydraClasses[index]["@id"] 179 | ] = props.hydraClasses[index]; 180 | if ( props.hydraClasses[index]["@id"] === "vocab:EntryPoint" ) { 181 | endpoints = props.hydraClasses[index].supportedProperty; 182 | } 183 | } 184 | 185 | // Initializing empty array with all properties in the ApiDoc 186 | let classesProperties = {}; 187 | let resourcesIDs = {}; 188 | const classesPropertiesWithMetaData = {}; 189 | for ( const auxClass in classesMapping ) { 190 | classesProperties[classesMapping[auxClass]["@id"]] = {}; 191 | classesPropertiesWithMetaData[auxClass] = []; 192 | // Creating the array that will maintain the Resources IDs 193 | resourcesIDs[classesMapping[auxClass]["@id"]] = {}; 194 | resourcesIDs[classesMapping[auxClass]["@id"]]["ResourceID"] = ""; 195 | for ( const auxProperty in classesMapping[auxClass].supportedProperty ) { 196 | classesProperties[classesMapping[auxClass]["@id"]][ 197 | classesMapping[auxClass].supportedProperty[auxProperty].title 198 | ] = ""; 199 | classesPropertiesWithMetaData[auxClass].push( { 200 | property: 201 | classesMapping[auxClass].supportedProperty[auxProperty].title, 202 | required: 203 | classesMapping[auxClass].supportedProperty[auxProperty].required, 204 | } ); 205 | } 206 | } 207 | 208 | // Initialize the local storage with the empty values 209 | if ( getFromLocalStorage( "properties" ) === null ) { 210 | setInLocalStorage( "properties", JSON.stringify( classesProperties ) ); 211 | } else { 212 | classesProperties = JSON.parse( getFromLocalStorage( "properties" ) ); 213 | } 214 | 215 | if ( getFromLocalStorage( "resourceIDs" ) === null ) { 216 | setInLocalStorage( "resourceIDs", JSON.stringify( resourcesIDs ) ); 217 | } else { 218 | resourcesIDs = JSON.parse( getFromLocalStorage( "resourceIDs" ) ); 219 | } 220 | const [state, setState] = useState( { 221 | hydraClasses: classesMapping, 222 | classesPropertiesWithMetaData, 223 | endpoints: endpoints, 224 | properties: classesProperties, 225 | resourcesIDs: resourcesIDs, 226 | selectedEndpointIndex: 0, 227 | selectedOperationIndex: 1, 228 | getPage: 1, 229 | outputText: " Your request output will be displayed here...", 230 | } ); 231 | useEffect( () => { 232 | restorePropertiesAndResourceIDs(); 233 | }, [] ) 234 | function changePage( e, page ) { 235 | sendCommand( page ); 236 | } 237 | function restorePropertiesAndResourceIDs() { 238 | if ( previousEndpointIndex !== state.selectedEndpointIndex ) { 239 | const storedProperties = JSON.parse( getFromLocalStorage( "properties" ) ); 240 | const storedResourceIDs = JSON.parse( getFromLocalStorage( "resourceIDs" ) ); 241 | 242 | setState( { 243 | properties: storedProperties, 244 | resourcesIDs: storedResourceIDs, 245 | } ); 246 | 247 | // updating for next time 248 | previousEndpointIndex = state.selectedEndpointIndex; 249 | } 250 | } 251 | 252 | function selectEndpoint( endpointIndex, op = "GET" ) { 253 | var selectedEndpoint = state.endpoints[endpointIndex]; 254 | child.current.selectButton( endpointIndex ); 255 | const temporaryEndpoint = selectedEndpoint.property.range.replace( 256 | "Collection", 257 | "" 258 | ); 259 | 260 | const selectedHydraClass = state.hydraClasses[temporaryEndpoint]; 261 | const operations = selectedHydraClass.supportedOperation; 262 | let selectedOperationIndex = 0; 263 | operations.forEach( ( operation, index ) => { 264 | if ( operation.method === op ) selectedOperationIndex = index; 265 | } ); 266 | 267 | setState( { 268 | selectedEndpointIndex: endpointIndex, 269 | selectedOperationIndex: selectedOperationIndex, 270 | } ); 271 | } 272 | 273 | function selectOperation( operationIndex ) { 274 | setState( { selectedOperationIndex: operationIndex } ); 275 | } 276 | 277 | const handleChange = ( e ) => { 278 | // Boolean variable that says we will fetch by resource type 279 | getURL = false; 280 | 281 | const auxProperties = Object.assign( {}, state.properties ); 282 | auxProperties[temporaryEndpoint][e.target.name] = e.target.value; 283 | 284 | setInLocalStorage( "properties", JSON.stringify( auxProperties ) ); 285 | 286 | setState( { 287 | properties: auxProperties, 288 | } ); 289 | } 290 | 291 | const handleChangeResourceID = ( e ) => { 292 | // Fetch will work by URL 293 | getURL = true; 294 | 295 | const resourcesIDs = Object.assign( {}, state.resourcesIDs ); 296 | resourcesIDs[e.target.name]["ResourceID"] = e.target.value; 297 | 298 | setInLocalStorage( "resourceIDs", JSON.stringify( resourcesIDs ) ); 299 | 300 | setState( { 301 | resourcesIDs: resourcesIDs, 302 | } ); 303 | } 304 | 305 | const clearAllInputs = ( e ) => { 306 | // Will clear the current endpoints input 307 | const auxProperties = Object.assign( {}, state.properties ); 308 | Object.keys( auxProperties[temporaryEndpoint] ).forEach( ( name ) => { 309 | auxProperties[temporaryEndpoint][name] = ""; 310 | } ); 311 | 312 | const resourcesIDs = Object.assign( {}, state.resourcesIDs ); 313 | Object.keys( resourcesIDs ).forEach( ( name ) => { 314 | resourcesIDs[name]["ResourceID"] = ""; 315 | } ); 316 | 317 | setInLocalStorage( "properties", JSON.stringify( auxProperties ) ); 318 | setInLocalStorage( "resourceIDs", JSON.stringify( resourcesIDs ) ); 319 | 320 | setState( { 321 | properties: auxProperties, 322 | resourcesIDs: resourcesIDs, 323 | } ); 324 | } 325 | 326 | // var setResourceID = ( name, value ) => { 327 | // // This is a utility method to set the Resource Field id from clicking on the output link in output console 328 | // getURL = true; 329 | // const resourcesIDs = Object.assign( {}, state.resourcesIDs ); 330 | // resourcesIDs[name]["ResourceID"] = value.split( "/" ).pop(); 331 | // setInLocalStorage( "resourceIDs", JSON.stringify( resourcesIDs ) ); 332 | // setState( { 333 | // resourcesIDs: resourcesIDs, 334 | // } ); 335 | // } 336 | const sendCommand = async ( page ) => { 337 | const properties = state.properties[temporaryEndpoint]; 338 | const filteredProperties = {}; 339 | for ( const property in properties ) { 340 | if ( properties[property] !== "" ) { 341 | filteredProperties[property] = properties[property]; 342 | } 343 | } 344 | 345 | const resourceType = selectedEndpoint.property.label.replace( 346 | "Collection", 347 | "" 348 | ); 349 | 350 | if ( selectedOperation.method.toLowerCase() === "get" ) { 351 | filteredProperties["page"] = page; 352 | let getBody = null; 353 | let url = ""; 354 | if ( getURL ) { 355 | if ( state.resourcesIDs[temporaryEndpoint]["ResourceID"] ) { 356 | url = 357 | props.serverUrl + 358 | selectedEndpoint.property.label + 359 | "/" + 360 | state.resourcesIDs[temporaryEndpoint]["ResourceID"]; 361 | } else { 362 | url = 363 | props.serverUrl + selectedEndpoint.property.label + "/"; 364 | } 365 | getBody = { 366 | method: "get", 367 | url: url, 368 | filters: filteredProperties, 369 | }; 370 | } else { 371 | getBody = { 372 | method: "get", 373 | resource_type: resourceType, 374 | filters: filteredProperties, 375 | }; 376 | } 377 | // Call 1 378 | const rawOutput = await getRawOutput( getBody ); 379 | const outputText = rawOutput.data.members || rawOutput.data; 380 | const pagination = rawOutput.data.view; 381 | let lastPage = 1; 382 | if ( pagination ) { 383 | lastPage = extractPageNumberFromString( pagination["last"] ); 384 | } 385 | 386 | setState( { 387 | outputText, 388 | lastPage, 389 | } ); 390 | } else if ( selectedOperation.method.toLowerCase() === "put" ) { 391 | let putBody = null; 392 | putBody = { 393 | method: "put", 394 | url: 395 | props.serverUrl + 396 | selectedEndpoint.property.label + 397 | "/" + 398 | state.resourcesIDs[temporaryEndpoint]["ResourceID"], 399 | new_object: filteredProperties, 400 | }; 401 | filteredProperties["@type"] = resourceType; 402 | // Call 2 403 | const rawOutput = await getRawOutput( putBody ); 404 | const outputText = rawOutput.data; 405 | setState( { 406 | outputText, 407 | } ); 408 | } else if ( selectedOperation.method.toLowerCase() === "post" ) { 409 | let postBody = null; 410 | postBody = { 411 | method: "post", 412 | url: 413 | props.serverUrl + 414 | selectedEndpoint.property.label + 415 | "/" + 416 | state.resourcesIDs[temporaryEndpoint]["ResourceID"], 417 | updated_object: filteredProperties, 418 | }; 419 | filteredProperties["@type"] = resourceType; 420 | const rawOutput = await getRawOutput( postBody ); 421 | const outputText = rawOutput.data; 422 | setState( { 423 | outputText, 424 | } ); 425 | } else if ( selectedOperation.method.toLowerCase() === "delete" ) { 426 | let deleteBody = null; 427 | deleteBody = { 428 | method: "delete", 429 | url: 430 | props.serverUrl + 431 | selectedEndpoint.property.label + 432 | "/" + 433 | state.resourcesIDs[temporaryEndpoint]["ResourceID"], 434 | }; 435 | const rawOutput = await getRawOutput( deleteBody ); 436 | const outputText = rawOutput.data; 437 | setState( { 438 | outputText, 439 | } ); 440 | } 441 | } 442 | // Block of values that need to be re assigned every rendering update 443 | // They are used below along the html 444 | const { classes } = props; 445 | 446 | const selectedEndpoint = state.endpoints[ 447 | state.selectedEndpointIndex 448 | ]; 449 | 450 | const temporaryEndpoint = selectedEndpoint.property.range.replace( 451 | "Collection", 452 | "" 453 | ); 454 | 455 | const selectedHydraClass = state.hydraClasses[temporaryEndpoint]; 456 | 457 | const selectedOperation = 458 | selectedHydraClass.supportedOperation[state.selectedOperationIndex]; 459 | 460 | const stringProps = JSON.stringify( 461 | state.properties[temporaryEndpoint], 462 | jsonStringifyReplacer 463 | ); 464 | 465 | let rawCommand = ""; 466 | if ( getURL ) { 467 | rawCommand = 468 | "agent." + 469 | selectedOperation.method.toLowerCase() + 470 | '("' + 471 | props.serverUrl + 472 | selectedEndpoint.property.label + 473 | "/" + 474 | state.resourcesIDs[temporaryEndpoint]["ResourceID"] + 475 | '")'; 476 | } else { 477 | rawCommand = 478 | "agent." + 479 | selectedOperation.method.toLowerCase() + 480 | '("/' + 481 | selectedEndpoint.property.label + 482 | '", ' + 483 | stringProps + 484 | ")"; 485 | } 486 | 487 | return ( 488 | 489 | 490 |
491 |
492 | { 495 | selectEndpoint( currProperty ); 496 | }} 497 | endpoints={state.endpoints} 498 | > 499 |
500 |
501 |
502 | 511 | 517 |
518 | {selectedHydraClass.description} 519 |
520 |
521 | { 525 | selectOperation( currProperty ); 526 | }} 527 | > 528 |
529 |
530 | 537 | 544 | 545 | handleChangeResourceID( e )} 549 | onFocus={( e ) => handleChangeResourceID( e )} 550 | className={classes.input} 551 | inputProps={{ 552 | "aria-label": "description", 553 | }} 554 | /> 555 | 556 | {selectedOperation.method !== "DELETE" && ( 557 | { 563 | handleChange( updatedField ); 564 | }} 565 | > 566 | )} 567 | 568 | 577 | 583 |
584 | 593 | { }} 598 | margin="normal" 599 | variant="outlined" 600 | value={rawCommand} 601 | /> 602 | 603 | 612 | RESPONSE 613 |
614 | {typeof state.outputText === "string" ? ( 615 | 619 | ) : ( 620 | 621 | )} 622 |
623 |
624 | 628 |
629 |
630 |
631 | ); 632 | 633 | } 634 | 635 | export default withStyles( styles )( HydraConsole ); 636 | -------------------------------------------------------------------------------- /console-frontend/src/components/hydra-console/endpoints-buttons/EndpointsButtons.js: -------------------------------------------------------------------------------- 1 | import React,{useState} from "react"; 2 | import Button from "@material-ui/core/Button"; 3 | import { withStyles } from "@material-ui/styles"; 4 | 5 | const styles = (theme) => ({ 6 | endpointButton: { 7 | minWidth: "auto", 8 | padding: "1em", 9 | borderRight: "1px solid #E4E4E4", 10 | marginBottom: "0px", 11 | backgroundColor: "#F9F9F9", 12 | 13 | ["@media (min-width:780px)"]: { 14 | width: "95%", 15 | textAlight: "start", 16 | justifyContent: "start", 17 | borderBottom: "1px solid #E4E4E4", 18 | display: "flex", 19 | // justifyContent: "space-between", 20 | }, 21 | }, 22 | endpointSelectedButton: { 23 | backgroundColor: "grey", 24 | }, 25 | active: { 26 | backgroundColor: "white", 27 | borderRight: "none", 28 | }, 29 | rightArrow: { 30 | display: "none", 31 | ["@media (min-width:780px)"]: { 32 | display: "flex", 33 | }, 34 | }, 35 | }); 36 | 37 | function EndpointsButtons(props) { 38 | const [selectedButton, setSelectedButton] = useState(0); 39 | const [buttons, setButtons] = useState([]); 40 | props.endpoints.map().forEach((endpoint) => { 41 | buttons[endpoint] = false; 42 | }); 43 | 44 | const selectButton = (clickedButton) => { 45 | const updatedButtons = buttons.slice(); 46 | updatedButtons[selectedButton] = false; 47 | updatedButtons[clickedButton] = true; 48 | setButtons(updatedButtons); 49 | setSelectedButton(clickedButton); 50 | } 51 | 52 | const generateButtons = () => { 53 | const endpointsArray = props.endpoints.map() 54 | const { classes } = props; 55 | 56 | const buttons = endpointsArray.map((currProperty, index) => { 57 | const labelEndpoint = props.endpoints[currProperty].property.label; 58 | //color={this.state.buttons[currProperty] ? "secondary" : "default"} 59 | // const selectedClass = buttons[currProperty] 60 | // ? classes.active 61 | // : classes.endpointButton; 62 | return ( 63 | 76 | ); 77 | }); 78 | return buttons; 79 | } 80 | return generateButtons(); 81 | } 82 | 83 | export default withStyles(styles)(EndpointsButtons); 84 | -------------------------------------------------------------------------------- /console-frontend/src/components/hydra-console/operations-buttons/OperationsButtons.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Button from "@material-ui/core/Button"; 3 | import { withStyles } from "@material-ui/styles"; 4 | 5 | const styles = (theme) => ({ 6 | operationButton: { 7 | borderRadius: "16px", 8 | backgroundColor: "#F5F5F5", 9 | margin: "0.6em", 10 | }, 11 | operationButtonActive: { 12 | backgroundColor: "#F2C94C", 13 | color: "black", 14 | }, 15 | }); 16 | 17 | class OperationsButtons extends React.Component { 18 | constructor(props) { 19 | super(props); 20 | const buttons = []; 21 | 22 | let selectedButton = 0; 23 | 24 | const operationsArray = Object.keys(this.props.operations); 25 | operationsArray.forEach((operation, index) => { 26 | buttons[operation] = false; 27 | if (this.props.operations[operation].method === "GET") 28 | selectedButton = index; 29 | }); 30 | 31 | buttons[selectedButton] = true; 32 | 33 | this.state = { 34 | buttons: buttons, 35 | selectedButton: selectedButton, 36 | }; 37 | } 38 | 39 | selectButton(clickedButton) { 40 | const updatedButtons = this.state.buttons.slice(); 41 | updatedButtons[this.state.selectedButton] = false; 42 | updatedButtons[clickedButton] = true; 43 | this.setState({ 44 | buttons: updatedButtons, 45 | selectedButton: clickedButton, 46 | }); 47 | } 48 | 49 | generateButtons() { 50 | const operationsArray = Object.keys(this.props.operations); 51 | 52 | const { classes } = this.props; 53 | 54 | const buttons = operationsArray.map((currProperty, index) => { 55 | const operation = this.props.operations[currProperty].method; 56 | return ( 57 | 70 | ); 71 | }); 72 | return buttons; 73 | } 74 | 75 | componentDidUpdate() { 76 | if (this.state.selectedButton !== this.props.selectedOperationIndex) { 77 | const updatedButtons = this.state.buttons.slice(); 78 | updatedButtons[this.state.selectedButton] = false; 79 | updatedButtons[this.props.selectedOperationIndex] = true; 80 | this.setState({ 81 | buttons: updatedButtons, 82 | selectedButton: this.props.selectedOperationIndex, 83 | }); 84 | } 85 | } 86 | 87 | render() { 88 | return this.generateButtons(); 89 | } 90 | } 91 | 92 | export default withStyles(styles)(OperationsButtons); 93 | -------------------------------------------------------------------------------- /console-frontend/src/components/hydra-console/pagination/Pagination.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { makeStyles } from "@material-ui/core"; 3 | const useStyles = makeStyles({ 4 | page: { 5 | padding: "0.5em", 6 | backgroundColor: "white", 7 | margin: "0.5em", 8 | "&:hover": { 9 | cursor: "pointer", 10 | }, 11 | borderRadius: "5px", 12 | }, 13 | }); 14 | 15 | const Pagination = ({ last_page, paginate }) => { 16 | const classes = useStyles(); 17 | const paged = []; 18 | if (last_page > 1) { 19 | for (let i = 1; i <= last_page; i++) { 20 | paged.push( 21 | paginate(e, i)}> 22 | {i} 23 | 24 | ); 25 | } 26 | } 27 | 28 | return paged; 29 | }; 30 | 31 | export default Pagination; 32 | -------------------------------------------------------------------------------- /console-frontend/src/components/hydra-console/properties-editor/PropertiesEditor.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { withStyles } from "@material-ui/styles"; 3 | import Input from "@material-ui/core/Input"; 4 | import Grid from "@material-ui/core/Grid"; 5 | import GuiTheme from "../../../app/gui-theme"; 6 | 7 | const styles = (theme) => ({ 8 | propertyInput: { 9 | color: GuiTheme.palette.primary.dark, 10 | marginTop: "1em", 11 | }, 12 | propertyContainer: { 13 | marginTop: "2px", 14 | marginBottom: "2px", 15 | }, 16 | input: { 17 | display: "block", 18 | width: "100%", 19 | }, 20 | required: { 21 | color: "rgba(0, 0, 0, 0.5)", 22 | }, 23 | }); 24 | 25 | function PropertiesEditor(props) { 26 | const generateField = ( 27 | propertyName, 28 | placeholder = null, 29 | metaProps, 30 | endpoint 31 | ) => { 32 | const { classes } = props; 33 | //this.filledProperties[fieldName]; 34 | const prop = metaProps[endpoint].find( 35 | (prop) => prop.property === propertyName 36 | ); 37 | return ( 38 | 45 | 46 | 56 | 57 | {prop.required === true ? "(required)" : "(optional)"} 58 | 59 | 60 | ); 61 | }; 62 | 63 | const generateProperties = () => { 64 | const fields = []; 65 | 66 | for (const property in props.properties) { 67 | fields.push( 68 | generateField(property, null, props.metaProps, props.endpoint) 69 | ); 70 | } 71 | return fields; 72 | }; 73 | return generateProperties(); 74 | } 75 | 76 | export default withStyles(styles)(PropertiesEditor); 77 | -------------------------------------------------------------------------------- /console-frontend/src/components/hydra-graph/HydraGraph.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { withStyles } from "@material-ui/styles"; 3 | import { DataSet, Network } from "visjs-network"; 4 | 5 | const styles = (theme) => ({ 6 | graphContainer: { 7 | height: "500px", 8 | backgroundColor: '#EEEEEE', 9 | margin: "0 1em", 10 | borderRadius: "8px", 11 | width: "90%", 12 | }, 13 | graphName: { 14 | color: "black", 15 | border: "1px solid white", 16 | borderRadius: "8px", 17 | width: "100%", 18 | letterSpacing: "2px", 19 | }, 20 | }); 21 | 22 | class HydraGraph extends React.Component { 23 | componentDidMount() { 24 | const self = this; 25 | // Create Node and Edge Datasets 26 | const nodes = new DataSet(this.props.apidocGraph.nodes); 27 | const edges = new DataSet(this.props.apidocGraph.edges); 28 | // Get reference to the mynetwork div 29 | const container = document.getElementById("mynetwork"); 30 | 31 | const data = { 32 | nodes: nodes, 33 | edges: edges, 34 | }; 35 | 36 | // See vis.js network options for more details on how to use this 37 | const options = { 38 | interaction: { hover: true }, 39 | autoResize: true, 40 | nodes: { 41 | color: { 42 | hover: { 43 | border: "#5BDE79", 44 | background: "#5BDE79", 45 | }, 46 | }, 47 | }, 48 | layout: { 49 | improvedLayout: true, 50 | }, 51 | }; 52 | // Create a network 53 | // eslint-disable-next-line 54 | let endpoint; 55 | let check = 0; 56 | let endpoints = null; 57 | 58 | for (const index in this.props.hydraClasses) { 59 | if (this.props.hydraClasses[index]["@id"] === "vocab:EntryPoint") { 60 | endpoints = this.props.hydraClasses[index].supportedProperty; 61 | } 62 | } 63 | 64 | const network = new Network(container, data, options); 65 | this.selectedNode = function (e) { 66 | this.props.selectNode(e); 67 | }; 68 | network.on("hoverNode", function (event) { 69 | check = 0; 70 | const node = event.node; 71 | const element_array = Object.keys(data.nodes._data).map(function (key) { 72 | return data.nodes._data[key]; 73 | }); 74 | 75 | element_array.forEach((element) => { 76 | if (element.id === node) { 77 | endpoint = element; 78 | endpoints.forEach((ept) => { 79 | if (ept.property.label === endpoint.label) { 80 | check = 1; 81 | } 82 | }); 83 | } 84 | }); 85 | 86 | if (check !== 1) { 87 | const edges_array = Object.keys(data.edges._data).map(function (key) { 88 | return data.edges._data[key]; 89 | }); 90 | 91 | edges_array.forEach((edge) => { 92 | if (edge.to === node && edge.label === "supportedOp") { 93 | element_array.forEach((element) => { 94 | if (element.id === edge.from) { 95 | endpoint = element; 96 | endpoints.forEach((ept) => { 97 | if (ept.property.label === endpoint.label) { 98 | check = 1; 99 | } 100 | }); 101 | } 102 | }); 103 | } 104 | }); 105 | } 106 | 107 | if (check === 1) { 108 | options.nodes.color.hover.background = "#5BDE79"; 109 | options.nodes.color.hover.border = "#5BDE79"; 110 | network.setOptions(options); 111 | } else { 112 | options.nodes.color.hover.background = "#FBD20B"; 113 | options.nodes.color.hover.border = "#FBD20B"; 114 | network.setOptions(options); 115 | } 116 | }); 117 | 118 | network.on("select", function (event) { 119 | check = 0; 120 | let selectedRequest; 121 | const { nodes } = event; 122 | const element_array = Object.keys(data.nodes._data).map(function (key) { 123 | return data.nodes._data[key]; 124 | }); 125 | 126 | element_array.forEach((element) => { 127 | if (element.id === nodes[0]) { 128 | endpoint = element; 129 | } 130 | }); 131 | 132 | let i = 0; 133 | endpoints.forEach((endpoints) => { 134 | if (endpoints.property.label === endpoint.label) { 135 | check = 1; 136 | selectedRequest = { Index: i, operation: "GET" }; 137 | self.selectedNode(selectedRequest); 138 | } 139 | i += 1; 140 | }); 141 | if (check !== 1) { 142 | const operation = endpoint.label; 143 | const edges_array = Object.keys(data.edges._data).map(function (key) { 144 | return data.edges._data[key]; 145 | }); 146 | 147 | edges_array.forEach((edge) => { 148 | if (edge.to === nodes[0] && edge.label === "supportedOp") { 149 | element_array.forEach((element) => { 150 | if (element.id === edge.from) { 151 | endpoint = element; 152 | i = 0; 153 | endpoints.forEach((endpoints) => { 154 | if (endpoints.property.label === endpoint.label) { 155 | check = 1; 156 | selectedRequest = { Index: i, operation: operation }; 157 | self.selectedNode(selectedRequest); 158 | } 159 | i += 1; 160 | }); 161 | } 162 | }); 163 | } 164 | }); 165 | } 166 | }); 167 | network.moveTo({ 168 | scale: 1.3, 169 | animation: { 170 | duration: "0.5s", 171 | easingFunction: "linear", 172 | }, 173 | }); 174 | } 175 | 176 | render() { 177 | const { classes } = this.props; 178 | return ( 179 |
180 |
181 |
182 | ); 183 | } 184 | } 185 | 186 | export default withStyles(styles)(HydraGraph); 187 | -------------------------------------------------------------------------------- /console-frontend/src/components/hydra-graph/HydraGraph.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import HydraGraph from './HydraGraph'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /console-frontend/src/components/loader/Loader.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { withStyles } from "@material-ui/styles"; 3 | 4 | const styles = (theme) => ({ 5 | image: { 6 | position: "absolute", 7 | top: "50%", 8 | left: "50%", 9 | width: "120px", 10 | height: "120px", 11 | margin: `-60px 0 0 -60px`, 12 | animation: `spin 4s linear infinite`, 13 | borderRadius: `30%`, 14 | }, 15 | "@keyframes spin": { 16 | "100%": { 17 | transform: "rotate(360deg)", 18 | }, 19 | }, 20 | }); 21 | 22 | const Loader = (props) => { 23 | const classes = props.classes; 24 | return ( 25 |
26 | Loader 31 |
32 | ); 33 | }; 34 | 35 | export default withStyles(styles)(Loader); 36 | -------------------------------------------------------------------------------- /console-frontend/src/components/loader/Loader.test.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HTTP-APIs/hydra-python-agent-gui/fd56dec857d500041b41fa65caa91d4d7e447d70/console-frontend/src/components/loader/Loader.test.js -------------------------------------------------------------------------------- /console-frontend/src/components/navbar/NavBar.js: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from "react"; 2 | import AppBar from "@material-ui/core/AppBar"; 3 | import Toolbar from "@material-ui/core/Toolbar"; 4 | import Typography from "@material-ui/core/Typography"; 5 | import Fab from "@material-ui/core/Fab"; 6 | import AspectRatioOutlinedIcon from "@material-ui/icons/AspectRatioOutlined"; 7 | import { makeStyles } from "@material-ui/core/styles"; 8 | 9 | import logo from "../../assets/images/hydra_eco_logo.png"; 10 | import github_logo from "../../assets/images/GitHub.png"; 11 | const useStyles = makeStyles( { 12 | hydraEcoLogo: { 13 | maxWidth: "30px", 14 | cursor: "pointer", 15 | marginRight: "6px", 16 | }, 17 | AppBar: {}, 18 | Typography: { 19 | fontSize: "18px", 20 | flexGrow: 1, 21 | paddingLeft: "1em", 22 | }, 23 | centeringSpace: { 24 | flexGrow: 1.21, 25 | }, 26 | fab: { 27 | boxShadow: "none", 28 | }, 29 | } ); 30 | 31 | const NavBar = ( props ) => { 32 | const classes = useStyles(); 33 | // Optimized as it is a static function 34 | const handleOnClick = useCallback( () => { 35 | window.open( 36 | "https://github.com/HTTP-APIs/hydra-python-agent-gui" 37 | ) 38 | } ) 39 | return ( 40 |
41 | 42 | 43 | {props.onClick && ( 44 | window.open( "http://www.hydraecosystem.org/" )} 47 | className={classes.hydraEcoLogo} 48 | alt="logo" 49 | /> 50 | )} 51 | 52 | {props.text} 53 | 54 | 55 | {props.onClick && ( 56 | 62 | 63 | 64 | )} 65 |
66 | {props.onClick && ( 67 | logo 73 | )} 74 |
75 |
76 |
77 | ); 78 | }; 79 | 80 | export default NavBar; 81 | -------------------------------------------------------------------------------- /console-frontend/src/components/navbar/NavBar.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import NavBar from './NavBar'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /console-frontend/src/index.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Roboto&display=swap"); 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | font-family: "Roboto", sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /console-frontend/src/index.tsx: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | import React from 'react' 4 | import ReactDOM from 'react-dom' 5 | import AgentGUI from './app/app.js' 6 | import './index.css' 7 | import * as serviceWorker from './service-worker' 8 | 9 | ReactDOM.render(, document.getElementById('root')) 10 | 11 | // If you want your app to work offline and load faster, you can change 12 | // unregister() to register() below. Note this comes with some pitfalls. 13 | // Learn more about service workers: https://bit.ly/CRA-PWA 14 | serviceWorker.unregister() 15 | -------------------------------------------------------------------------------- /console-frontend/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /console-frontend/src/routes/home/home.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function Home() { 4 | return <>; 5 | } 6 | -------------------------------------------------------------------------------- /console-frontend/src/routes/home/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './home'; 2 | -------------------------------------------------------------------------------- /console-frontend/src/service-worker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /console-frontend/src/services/api-doc-graph-service.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | const agentEndpoint = ""; 3 | 4 | const getApiDocGraph = () => { 5 | return axios.get(`${agentEndpoint}/apidoc-graph`).then(res => { 6 | return res.data; 7 | }) 8 | } 9 | 10 | export default getApiDocGraph; -------------------------------------------------------------------------------- /console-frontend/src/services/hydra-doc-service.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | const agentEndPoint = ""; 3 | const getHydraDoc = () => { 4 | return axios.get(`${agentEndPoint}/hydra-doc`).then((res) => { 5 | return res.data; 6 | }); 7 | }; 8 | 9 | export default getHydraDoc; 10 | -------------------------------------------------------------------------------- /console-frontend/src/services/send-command.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const agentEndpoint = ""; 4 | 5 | const getRawOutput = async (body) => { 6 | try { 7 | const response = await axios 8 | .post( `${ agentEndpoint }/send-command`, body ); 9 | return response; 10 | } catch ( err ) { 11 | return console.error( err ); 12 | } 13 | }; 14 | 15 | export default getRawOutput; 16 | -------------------------------------------------------------------------------- /console-frontend/src/services/start-agent-service.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | const agentEndpoint = ""; 3 | 4 | const startAgent = async (serverURL) => { 5 | const res = await axios 6 | .post( `${ agentEndpoint }/start-agent`, { url: serverURL } ); 7 | return res; 8 | }; 9 | 10 | export default startAgent; 11 | -------------------------------------------------------------------------------- /console-frontend/src/utils/utils.js: -------------------------------------------------------------------------------- 1 | const isArray = (value) => { 2 | // utility method that returns boolean value 3 | return value && typeof value === "object" && value.constructor === Array; 4 | } 5 | 6 | const isString = (value) => { 7 | // utility method that returns boolean value 8 | return typeof value === "string" || value instanceof String; 9 | } 10 | const isObject = (value) => { 11 | // utility method that returns boolean value 12 | return value && typeof value === "object" && value.constructor === Object; 13 | } 14 | 15 | const setInLocalStorage = (name, value) => { 16 | localStorage.setItem(name, value); 17 | } 18 | 19 | const getFromLocalStorage = (name) => { 20 | return localStorage.getItem(name); 21 | } 22 | 23 | const jsonStringifyReplacer = (key, value) => { 24 | // Filtering out properties 25 | if (value === "") { 26 | return undefined; 27 | } 28 | return value; 29 | } 30 | const extractPageNumberFromString = (str) => { 31 | const indexPage = str.indexOf("page="); 32 | return str[indexPage + 5]; 33 | } 34 | 35 | export { 36 | isArray, 37 | isString, 38 | isObject, 39 | setInLocalStorage, 40 | getFromLocalStorage, 41 | jsonStringifyReplacer, 42 | extractPageNumberFromString, 43 | }; 44 | -------------------------------------------------------------------------------- /console-frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "noEmit": true, 20 | "jsx": "preserve" 21 | }, 22 | "include": [ 23 | "src" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | services: 4 | 5 | client: 6 | stdin_open: true 7 | tty: true 8 | restart: always 9 | build: ./ 10 | depends_on: 11 | - db 12 | links: 13 | - "db:redis_db" 14 | environment: 15 | - REDIS_HOST=redis_db 16 | db: 17 | image: redislabs/redisgraph:2.0-edge 18 | ports: 19 | - "6379:6379" 20 | -------------------------------------------------------------------------------- /redis_setup.sh: -------------------------------------------------------------------------------- 1 | 2 | # It will check if docker is not installed, if not it will install it. 3 | docker -v 4 | if [ "$?" = "127" ] 5 | then 6 | sudo apt-get update 7 | sudo apt-get install docker 8 | sudo apt-get install docker-compose 9 | 10 | else 11 | echo "Docker is already installed" 12 | fi 13 | 14 | # after getting the docker-ce, check if `redislabs/redisgraph` docker image is not installed then install ii. 15 | if [ -z "$(docker images -q redislabs/redisgraph:2.0-edge)" ] 16 | then 17 | echo "Docker already have a redislabs/redisgraph:2.0-edge image" 18 | 19 | else 20 | sudo docker run -p 6379:6379 -it --rm redislabs/redisgraph:2.0-edge 21 | fi 22 | 23 | # Command to run the Redis directly 24 | # sudo docker run -p 6379:6379 -it --rm redislabs/redisgraph:2.0-edge -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==2.0.1 2 | Flask-Cors==3.0.10 3 | hydra-python-core==0.3.1 4 | -e git://github.com/HTTP-APIs/hydra-python-agent.git@develop#egg=hydra_python_agent 5 | -e git+https://github.com/RedisGraph/redisgraph-py@d790e35b7017510c4baad85e3c572249a9ed7279#egg=redisgraph 6 | 7 | --------------------------------------------------------------------------------