Permalink
Browse files

Added raw parameter to allow raw web responses.

Changed default agent urls to the "/spade" namespace to avoid conflicts.
  • Loading branch information...
javipalanca committed Oct 24, 2018
1 parent 306e0e9 commit 083202384de8d3e718ab3fb56f069156aec45a83
View
@@ -2,6 +2,12 @@
History
=======
3.0.9 (2018-10-24)
------------------
* Added raw parameter to allow raw web responses.
* Changed default agent urls to the "/spade" namespace to avoid conflicts.
3.0.8 (2018-10-02)
------------------
View
@@ -67,4 +67,3 @@ spade.web module
:members:
:undoc-members:
:show-inheritance:
View
@@ -2,15 +2,15 @@
Web Graphical Interface
=======================
Each agent is SPADE provides a graphical interface *by default* that is accesible via web.
Each agent is SPADE provides a graphical interface *by default* that is accesible via web under the ``/spade`` path.
To activate the web interface you just have to start the web module of the agent just as follows::
agent = MyAgent("your_jid@your_xmpp_server", "your_password")
agent.start()
agent.web.start(hostname="127.0.0.1", port="10000")
Then you can open a web browser and go to the url ``http://127.0.0.1:10000`` and you'll see the
Then you can open a web browser and go to the url ``http://127.0.0.1:10000/spade`` and you'll see the
main page of your agent:
.. image:: images/spade_index.png
@@ -84,6 +84,8 @@ In this example there are some elements that must be explained:
Next we are going to explain a little more about the controller, the path and the template.
.. note:: Please, do not use the ``/spade`` path o avoid conflicts with the default agent pages (unless you want to modify them).
Controller
----------
The controller is the asyncronous method (or coroutine) that prepares the data to render the web page. It is an ``async``
@@ -142,6 +144,10 @@ Example::
self.web.add_get("/home", self.json_controller, template=None)
.. hint:: You may also use the ``raw=True`` parameter in the ``add_get`` and ``add_post`` methods to indicate that the
returned result should not be processed neither by jinja2 nor json parsing.
Path
----
The path will define where your application will respond to requests. You can use any allowed character for defining
@@ -39,7 +39,7 @@ <h5 class="description-text"><i class="fa fa-circle text-red"></i> OFFLINE</h5>
<div class="col-sm-4 border-right">
<div class="description-block">
<span class="description-header">ACTION</span>
<a href="/agent/{{ ajid }}/unsubscribe/">
<a href="/spade/agent/{{ ajid }}/unsubscribe/">
<button type="button" class="btn btn-block btn-warning btn-xs">Unsubscribe</button>
</a>
</div>
@@ -24,7 +24,7 @@
<header class="main-header">
<!-- Logo -->
<a href="/" class="logo">
<a href="/spade" class="logo">
<!-- mini logo for sidebar mini 50x50 pixels -->
<span class="logo-mini"><b>SPD</b></span>
<!-- logo for regular state and mobile devices -->
@@ -73,15 +73,15 @@ <h4>
</ul>
<!-- /.menu -->
</li>
<li class="footer"><a href="/messages/">See All Messages</a></li>
<li class="footer"><a href="/spade/messages/">See All Messages</a></li>
</ul>
</li>
<!-- /.messages-menu -->
<!-- User Account Menu -->
<li class="dropdown user user-menu">
<!-- Menu Toggle Button -->
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<a href="/spade" class="dropdown-toggle" data-toggle="dropdown">
<!-- The user image in the navbar-->
<img src="{{ agent.avatar }}" class="user-image" alt="User Image">
<!-- hidden-xs hides the username on small devices so only the image appears. -->
@@ -100,7 +100,7 @@ <h4>
<!-- Menu Footer-->
<li class="user-footer">
<div class="pull-left">
<a href="/stop" class="btn btn-default btn-flat">Stop</a>
<a href="/spade/stop" class="btn btn-default btn-flat">Stop</a>
</div>
{# <div class="pull-right">
<a href="#" class="btn btn-default btn-flat">Logout</a>
@@ -127,15 +127,15 @@ <h4>
<div class="pull-left info">
<p>{{ agent.jid.localpart }}</p>
<!-- Status -->
<a href="#"><i class="fa fa-circle text-success"></i> Online</a>
<a href="/spade"><i class="fa fa-circle text-success"></i> Online</a>
</div>
</div>
<!-- Sidebar Menu -->
<ul class="sidebar-menu" data-widget="tree">
<!-- Optionally, you can add icons to the links -->
<li class="active"><a href="/"><i class="fa fa-dashboard"></i> <span>Dashboard</span></a></li>
<li class="active"><a href="/spade"><i class="fa fa-dashboard"></i> <span>Dashboard</span></a></li>
</ul>
<!-- /.sidebar-menu -->
</section>
@@ -150,7 +150,7 @@ <h1>
{% block content_title %}Dashboard{% endblock %}
</h1>
<ol class="breadcrumb">
<li><a href="/"><i class="fa fa-dashboard"></i> Home</a></li>
<li><a href="/spade"><i class="fa fa-dashboard"></i> Home</a></li>
<li class="active">Dashboard</li>
</ol>
</section>
@@ -166,7 +166,7 @@ <h4><i class="icon fa fa-warning"></i> Alert!</h4>
</div>
<script type="application/javascript">
var xhr = new XMLHttpRequest();
xhr.open("GET", "/stop/now/", true);
xhr.open("GET", "/spade/stop/now/", true);
xhr.send(null);
</script>
{% endif %}
@@ -197,9 +197,9 @@ <h3 class="box-title">Active Behaviours</h3>
{% for behaviour in behaviours %}
<li class="item">
<div class="product-info">
<a href="/behaviour/{{behaviour}}/" class="product-title">{{ behaviour }}
<a href="/spade/behaviour/{{behaviour}}/" class="product-title">{{ behaviour }}
</a>
<a href="/behaviour/{{behaviour}}/kill/">
<a href="/spade/behaviour/{{behaviour}}/kill/">
<button type="button" class="btn btn-danger pull-right">Kill</button>
</a>
<span class="product-description">{{ behaviour.template }}</span>
@@ -289,7 +289,7 @@ <h3 class="box-title">Agent Friends</h3>
<footer class="main-footer">
<div class="pull-right hidden-xs">
<b>Version</b> 3.0.0
<b>Version</b> 3.0.9
</div>
<strong>Copyright © {% now 'local', '%Y' %} <a href="http://github.com/javipalanca/spade">SPADE</a>.</strong>
</footer>
@@ -33,10 +33,10 @@ <h3 class="box-title">Behaviours</h3>
{% endif %}
</div>
<div class="product-info">
<a href="/behaviour/{{behaviour}}/" class="product-title">{{ behaviour }}
<a href="/spade/behaviour/{{behaviour}}/" class="product-title">{{ behaviour }}
</a>
{% if not behaviour.is_killed() %}
<a href="/behaviour/{{behaviour}}/kill/">
<a href="/spade/behaviour/{{behaviour}}/kill/">
<button type="button" class="btn btn-danger pull-right">Kill</button>
</a>
{% endif %}
@@ -69,7 +69,7 @@ <h3 class="box-title">Contacts</h3>
<ul class="users-list clearfix">
{% for contact in contacts %}
<li>
<a href="/agent/{{ contact.jid }}/">
<a href="/spade/agent/{{ contact.jid }}/">
<img src="{{ contact.avatar }}" alt="User Image">
<span class="users-list-name">{{ contact.jid }}</span>
{%if not contact.available %}
@@ -109,7 +109,7 @@ <h3 class="box-title">Chat</h3>
<!-- /.box-body -->
{% if allow_send %}
<div class="box-footer">
<form action="/agent/{{ agent_jid }}/send/" method="post">
<form action="/spade/agent/{{ agent_jid }}/send/" method="post">
<div class="input-group">
<input type="text" name="message" placeholder="Type Message ..." class="form-control">
<span class="input-group-btn">
View
@@ -81,40 +81,48 @@ def _set_loaders(self):
)
def setup_routes(self):
self.app.router.add_get("/", self.index)
self.app.router.add_get("/stop", self.stop_agent)
self.app.router.add_get("/stop/now/", self.stop_now)
self.app.router.add_get("/messages/", self.get_messages)
self.app.router.add_get("/behaviour/{behaviour_type}/{behaviour_class}/", self.get_behaviour)
self.app.router.add_get("/behaviour/{behaviour_type}/{behaviour_class}/kill/", self.kill_behaviour)
self.app.router.add_get("/agent/{agentjid}/", self.get_agent)
self.app.router.add_get("/agent/{agentjid}/unsubscribe/", self.unsubscribe_agent)
self.app.router.add_post("/agent/{agentjid}/send/", self.send_agent)
def add_get(self, path, controller, template):
self.app.router.add_get("/spade", self.index)
self.app.router.add_get("/spade/stop", self.stop_agent)
self.app.router.add_get("/spade/stop/now/", self.stop_now)
self.app.router.add_get("/spade/messages/", self.get_messages)
self.app.router.add_get("/spade/behaviour/{behaviour_type}/{behaviour_class}/", self.get_behaviour)
self.app.router.add_get("/spade/behaviour/{behaviour_type}/{behaviour_class}/kill/", self.kill_behaviour)
self.app.router.add_get("/spade/agent/{agentjid}/", self.get_agent)
self.app.router.add_get("/spade/agent/{agentjid}/unsubscribe/", self.unsubscribe_agent)
self.app.router.add_post("/spade/agent/{agentjid}/send/", self.send_agent)
def add_get(self, path, controller, template, raw=False):
"""
Setup a route of type GET
Args:
path (str): URL to listen to
controller (coroutine): the coroutine to handle the request
template (str): the template to render the response or None if it is a JSON response
raw (bool): indicates if post-processing (jinja, json, etc) is needed or not
"""
fn = self._prepare_controller(controller, template)
if raw:
fn = controller
else:
fn = self._prepare_controller(controller, template)
self.app.router.add_get(path, fn)
def add_post(self, path, controller, template):
def add_post(self, path, controller, template, raw=False):
"""
Setup a route of type POST
Args:
path (str): URL to listen to
controller (coroutine): the coroutine to handle the request
template (str): the template to render the response or None if it is a JSON response
raw (bool): indicates if post-processing (jinja, json, etc) is needed or not
"""
fn = self._prepare_controller(controller, template)
if raw:
fn = controller
else:
fn = self._prepare_controller(controller, template)
self.app.router.add_post(path, fn)
def _prepare_controller(self, controller, template):
@@ -184,7 +192,7 @@ def timeago(date):
behaviour_str = request.match_info['behaviour_type'] + "/" + request.match_info['behaviour_class']
behaviour = self.find_behaviour(behaviour_str)
behaviour.kill()
raise aioweb.HTTPFound('/')
raise aioweb.HTTPFound('/spade')
@aiohttp_jinja2.template("internal_tpl_agent.html")
async def get_agent(self, request):
@@ -199,7 +207,7 @@ def timeago(date):
async def unsubscribe_agent(self, request):
agent_jid = request.match_info['agentjid']
self.agent.presence.unsubscribe(agent_jid)
raise aioweb.HTTPFound("/agent/{agentjid}/".format(agentjid=agent_jid))
raise aioweb.HTTPFound("/spade/agent/{agentjid}/".format(agentjid=agent_jid))
async def send_agent(self, request):
agent_jid = request.match_info['agentjid']
@@ -211,7 +219,7 @@ def timeago(date):
await self.agent.stream.send(aioxmpp_msg)
msg.sent = True
self.agent.traces.append(msg)
raise aioweb.HTTPFound("/agent/{agentjid}/".format(agentjid=agent_jid))
raise aioweb.HTTPFound("/spade/agent/{agentjid}/".format(agentjid=agent_jid))
def find_behaviour(self, behaviour_str):
behav = None
View
@@ -93,7 +93,7 @@ def test_check_server():
time.sleep(0.1)
assert agent.web.server is not None
response = requests.get(f"http://localhost:{port}/")
response = requests.get(f"http://localhost:{port}/spade")
sel = Selector(text=response.text)
@@ -110,7 +110,7 @@ def test_check_server():
agent.web.setup_routes()
client = await test_client(agent.web.app)
response = await client.get("/")
response = await client.get("/spade")
response = await response.text()
sel = Selector(text=response)
@@ -133,7 +133,7 @@ def test_check_server():
msg = Message(body=str(i), sender="{}@server".format(i), to="receiver@server")
agent.traces.append(msg)
response = await client.get("/messages/")
response = await client.get("/spade/messages/")
response = await response.text()
sel = Selector(text=response)
@@ -155,7 +155,7 @@ class EmptyOneShotBehaviour(OneShotBehaviour):
client = await test_client(agent.web.app)
response = await client.get("/behaviour/OneShotBehaviour/EmptyOneShotBehaviour/")
response = await client.get("/spade/behaviour/OneShotBehaviour/EmptyOneShotBehaviour/")
response = await response.text()
sel = Selector(text=response)
@@ -176,7 +176,7 @@ class EmptyCyclicBehaviour(CyclicBehaviour):
agent.web.setup_routes()
client = await test_client(agent.web.app)
await client.get("/behaviour/CyclicBehaviour/EmptyCyclicBehaviour/kill/")
await client.get("/spade/behaviour/CyclicBehaviour/EmptyCyclicBehaviour/kill/")
assert behaviour.is_killed()
@@ -193,7 +193,7 @@ class EmptyCyclicBehaviour(CyclicBehaviour):
agent.presence.roster._update_entry(item)
response = await client.get(f"/agent/{jid}/")
response = await client.get(f"/spade/agent/{jid}/")
response = await response.text()
sel = Selector(text=response)
@@ -216,9 +216,9 @@ class EmptyCyclicBehaviour(CyclicBehaviour):
agent.presence.roster._update_entry(item)
response = await client.get(f"/agent/{jid}/unsubscribe/")
response = await client.get(f"/spade/agent/{jid}/unsubscribe/")
assert str(response.url.relative()) == f"/agent/{jid}/"
assert str(response.url.relative()) == f"/spade/agent/{jid}/"
assert agent.aiothread.client.enqueue.mock_calls
arg = agent.aiothread.client.enqueue.call_args[0][0]
@@ -242,9 +242,9 @@ class EmptyCyclicBehaviour(CyclicBehaviour):
msg = "Hello World"
response = await client.post(f"/agent/{jid}/send/", data={"message": msg})
response = await client.post(f"/spade/agent/{jid}/send/", data={"message": msg})
assert str(response.url.relative()) == f"/agent/{jid}/"
assert str(response.url.relative()) == f"/spade/agent/{jid}/"
sent = agent.traces.all()[0]
@@ -321,15 +321,15 @@ def test_find_behaviour_fail():
agent.web.setup_routes()
client = await test_client(agent.web.app)
response = await client.get("/stop")
response = await client.get("/spade/stop")
response = await response.text()
sel = Selector(text=response)
assert sel.css("div.alert-warning > span::text").get().strip() == "Agent is stopping now."
with LogCapture() as log:
try:
await client.get("/stop/now/", timeout=0.0005)
await client.get("/spade/stop/now/", timeout=0.0005)
except requests.exceptions.ReadTimeout:
pass

0 comments on commit 0832023

Please sign in to comment.