diff --git a/app/__init__.py b/app/__init__.py index 5c8cc0c2..31fd5470 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -69,45 +69,13 @@ def before_request(): return login_manager.unauthorized() -if not Room.query.get("test_room"): - meetup = Task(name="Meetup", num_users=2, layout=Layout.from_json_file("meetup_task")) - admin_token = Token(room_name='test_room', - id='00000000-0000-0000-0000-000000000000' if settings.debug else None, - task=meetup, - permissions=Permissions( - user_query=True, - user_log_query=True, - user_log_event=True, - user_permissions_query=True, - user_permissions_update=True, - user_room_query=True, - user_room_join=True, - user_room_leave=True, - message_text=True, - message_image=True, - message_command=True, - message_broadcast=True, - room_query=True, - room_log_query=True, - room_create=True, - room_update=True, - room_close=True, - room_delete=True, - layout_query=True, - layout_create=True, - layout_update=True, - task_create=True, - task_query=True, - task_update=True, - token_generate=True, - token_query=True, - token_invalidate=True, - token_update=True, - )) - db.session.add(admin_token) - db.session.add(Token(room_name='test_room', - id='00000000-0000-0000-0000-000000000001' if settings.debug else None, - task=meetup, +if not Room.query.get("admin_room"): + db.session.add(Room(name="admin_room", + label="Admin Room", + layout=Layout.from_json_file("default"), + static=True)) + db.session.add(Token(room_name='admin_room', + id='00000000-0000-0000-0000-000000000000' if settings.debug else None, permissions=Permissions( user_query=True, user_log_query=True, @@ -124,54 +92,22 @@ def before_request(): room_query=True, room_log_query=True, room_create=True, + room_update=True, room_close=True, - task_create=True, - task_query=True, - task_update=True, + room_delete=True, layout_query=True, - layout_create=True, - layout_update=True, - token_generate=True, - token_query=True, - token_invalidate=True, - token_update=True, - ))) - db.session.add(Token(room_name='test_room', - id='00000000-0000-0000-0000-000000000002' if settings.debug else None, - permissions=Permissions( - user_query=True, - user_log_query=True, - user_log_event=True, - user_permissions_query=True, - user_permissions_update=True, - user_room_query=True, - user_room_join=True, - user_room_leave=True, - message_text=True, - message_image=True, - message_command=True, - message_broadcast=True, - room_query=True, - room_log_query=True, - room_create=True, - room_close=True, + layout_create=True, + layout_update=True, task_create=True, task_query=True, task_update=True, - layout_query=True, - layout_create=True, - layout_update=True, token_generate=True, token_query=True, token_invalidate=True, token_update=True, ))) - db.session.add(Room(name="test_room", - label="Test Room", - static=True, - layout=Layout.from_json_file("test_room"))) db.session.commit() - getLogger("slurk").info("generating test room and admin token...") + getLogger("slurk").info("generating admin room and token...") print("admin token:") print(Token.query.order_by(Token.date_created).first().id) sys.stdout.flush() diff --git a/app/api/__init__.py b/app/api/__init__.py index f5b08b23..dc9520db 100644 --- a/app/api/__init__.py +++ b/app/api/__init__.py @@ -225,7 +225,9 @@ def get_users(): @api.route('/user/', methods=['GET']) @auth.login_required def get_user(id): - user = User.query.get(id) + if id != g.current_user.id and not g.current_permissions.user_query: + return make_response(jsonify({'error': 'insufficient rights'}), 403) + user = User.query.get(id) if id != g.current_user.id else g.current_user if user: return jsonify(user.as_dict()) else: @@ -341,16 +343,29 @@ def get_rooms(): @api.route('/room/', methods=['GET']) @auth.login_required def get_room(name): - if not g.current_permissions.room_query: + room = Room.query.get(name) + if room not in g.current_user.current_rooms and not g.current_permissions.room_query: return make_response(jsonify({'error': 'insufficient rights'}), 403) - room = Room.query.get(name) if room: return jsonify(room.as_dict()) else: return make_response(jsonify({'error': 'room not found'}), 404) +@api.route('/room//layout', methods=['GET']) +@auth.login_required +def get_room_layout(name): + room = Room.query.get(name) + if room not in g.current_user.current_rooms and (not g.current_permissions.room_query or not g.current_permissions.layout_query): + return make_response(jsonify({'error': 'insufficient rights'}), 403) + + if room: + return jsonify(room.layout.as_dict()) + else: + return make_response(jsonify({'error': 'room not found'}), 404) + + @api.route('/room', methods=['POST']) @auth.login_required def post_rooms(): diff --git a/app/api/room.py b/app/api/room.py index 323110be..fc1e9f1c 100644 --- a/app/api/room.py +++ b/app/api/room.py @@ -49,7 +49,7 @@ def _create_room(name, label, data): if not layout: return False, "layout does not exist" else: - layout = None + layout = Layout.from_json_file("default") room = Room( name=name, diff --git a/app/models/room.py b/app/models/room.py index 532a5f34..36dc3438 100644 --- a/app/models/room.py +++ b/app/models/room.py @@ -8,7 +8,7 @@ class Room(db.Model): name = db.Column(db.String, primary_key=True) label = db.Column(db.String, nullable=False) - layout_id = db.Column(db.Integer, db.ForeignKey("Layout.id")) + layout_id = db.Column(db.Integer, db.ForeignKey("Layout.id"), nullable=False) read_only = db.Column(db.Boolean, default=False, nullable=False) show_users = db.Column(db.Boolean, default=True, nullable=False) show_latency = db.Column(db.Boolean, default=True, nullable=False) diff --git a/app/static/js/connection.js b/app/static/js/connection.js index 6ca7a107..9756903f 100644 --- a/app/static/js/connection.js +++ b/app/static/js/connection.js @@ -78,10 +78,10 @@ $(document).ready(() => { let room_request = $.get({ url: uri + "/room/" + data['room'], beforeSend: headers }); let user_request = $.get({ url: uri + "/user/" + data['user'], beforeSend: headers }); + let layout = $.get({ url: uri + "/room/" + data['room'] + "/layout", beforeSend: headers }); let history = $.get({ url: uri + "/user/" + data['user'] + "/logs", beforeSend: headers }); let room = await room_request; - let layout = $.get({ url: uri + "/layout/" + room.layout, beforeSend: headers }); apply_room_properties(room); let user = await user_request; @@ -96,8 +96,9 @@ $(document).ready(() => { updateUsers(); apply_layout(await layout); history = await history; - console.log(history[room.name]); - print_history(history[room.name]); + if (typeof print_history !== "undefined") { + print_history(history[room.name]); + } } diff --git a/app/static/layouts/default.json b/app/static/layouts/default.json index 1be67341..bb67e634 100644 --- a/app/static/layouts/default.json +++ b/app/static/layouts/default.json @@ -2,8 +2,21 @@ "subtitle": "Default Room", "html": [ { + "id": "layout-title", "layout-type": "h1", "layout-content": "This is the default layout. Please provide a layout for this room!" } - ] + ], + "css": { + "#layout-title": { + "align-content": "left", + "padding": "50px 20px 15px" + } + }, + "scripts": { + "incoming-text": "display-text", + "incoming-image": "display-image", + "submit-message": "send-message", + "print-history": "plain-history" + } } \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index aff6bd29..c35067e0 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,13 +9,12 @@ Slurk - A Lightweight Chat Server for Dialogue Experiments and Data Collection :numbered: slurk_about - slurk_installation slurk_gettingstarted slurk_multibots slurk_layouts slurk_bots slurk_api - slurk_tools + slurk_deployment slurk_amt .. @@ -28,7 +27,9 @@ Slurk - A Lightweight Chat Server for Dialogue Experiments and Data Collection meetup_main meetup_deployment -**Slurk** (think "slack for mechanical turk"...) is a lightweight and easily extensible chat server built especially for conducting multimodal dialogue experiments or data collections. See :ref:`slurk_about` for a description of the main concepts. Or jump right in with the :ref:`slurk_quickstart` (after the :ref:`slurk_installation`, of course)! +**Slurk** (think "slack for mechanical turk"...) is a lightweight and easily extensible chat server built especially +for conducting multimodal dialogue experiments or data collections. See :ref:`slurk_about` for a description of the +main concepts. Or jump right in with the :ref:`slurk_quickstart` (after the :ref:`slurk_installation`, of course)! Slurk is built in Python, on top of flask_ and flask-socketio_. diff --git a/docs/meetup_main.rst b/docs/meetup_main.rst deleted file mode 100644 index 9f2cb9cf..00000000 --- a/docs/meetup_main.rst +++ /dev/null @@ -1,20 +0,0 @@ -.. _meetup_main: - -################## -MeetUp -################## - - -**MeetUp** is a two-player coordination and reference game. - -The *basic idea* behind the MeetUp is easy to describe: two players move around -in a visually represented environment, with the objective to find each other. -They are not able to see each other, even if they are indeed in the same location. -The only way to meet their objective is by describing to each other what they see, -and determining on that basis whether they are in the same location in the environment. - -For details about production environment, please refer to :ref:`meetup_depl`. - -WILL BE WRITTEN SOON: - -Detailed instructions on how to play the MeetUp are available in our Git repository. diff --git a/docs/slurk_about.rst b/docs/slurk_about.rst index 8a38a9b2..c7a3c972 100644 --- a/docs/slurk_about.rst +++ b/docs/slurk_about.rst @@ -25,16 +25,30 @@ Some basic concepts ~~~~~~~~~~~~~~~~~~~~ Bots - Bots are little client programms (written in python, and connecting to the slurk server via `socket.io`) that can enter rooms as well. They can both interact with human clients (e.g. through text messages or images) and communicate with the server by sending commands and responding to socket events. Unlike human users, bots can be in several rooms, being responsible for handling certain dialogue *tasks*. Defining an experimental or data collection setting typically includes writing one or multiple bots. + Bots are little client programms (written in python, and connecting to the slurk server via `socket.io`) that can + enter rooms as well. They can both interact with human clients (e.g. through text messages or images) and communicate + with the server by sending commands and responding to socket events. Unlike human users, bots can be in several rooms, + being responsible for handling certain dialogue *tasks*. Defining an experimental or data collection setting typically + includes writing one or multiple bots. Rooms - Rooms are collections of users (humans and bots) that can talk to each other. Human users are always in exactly one room. - The **Waiting Room** is a special room, realised by the `pairup_bot`, where users wait until a pre-determined number of members is reached (lets say, 2), in which case all members are moved to a newly created task-specific room. + Rooms are collections of users (humans and bots) that can talk to each other. Human users are always in exactly one + room. The pairup example bot provides the **Waiting Room**, where users wait until a pre-determined number of members + is reached (lets say, 2), in which case all members are moved to a newly created task-specific room. Technical concepts - **Events**: Slurk is driven by events emitted by the server. All events have a certain type and corresponding information, which can be processed by bots and client-side scripts. The events used in Slurk are defined in the file `events.py`. + **Events**: Slurk is driven by events emitted by the server. All events have a certain type and corresponding + information, which can be processed by bots and client-side scripts. - **Commands**: Bots can communicate with the server using predefined commands, causing the server to emit corresponding events. For instance, they can be used to send room messages, connect or disconnect clients or alter the attributes of HTML elements. - **Slash-Commands** are a special kind of commands: Registered in bot files, they can be used by human users to control the bot, which in turn communicates with the server. These commands are prefixed with a slash as, for instance, in `/new_image_public`. In this example (taken from the `multi_bot`), the command triggers a change of what is shown in the display area, visible to all clients in the current room. + **API**: Bots can communicate with the server using a :ref:`slurk_api`, causing the server to emit corresponding + events. For instance, they can be used to create rooms, connect or disconnect clients or generate tokens for logging + in. - **Tokens**: To provide control over who is allowed to log into the chat (since we're not interested in running a public server here), access is regulated via tokens. Tokens need to be created in advance and link a user (who is logging in with the token) to a specific task / room type. + **Commands** are a special kind of interaction with the server: Registered via the API, they can be used by human + users to control the bot, which in turn communicates with the server. Depending on the layout, commands are usually + prefixed with a slash as, for instance, in `/new_image_public`. In this example, the command triggers a change of what + is shown in the display area, visible to all clients in the current room. + + **Tokens**: To provide control over who is allowed to log into the chat (since we're not interested in running a + public server here), access is regulated via tokens. Tokens need to be created in advance and link a user (who is + logging in with the token) to a specific task / room type. diff --git a/docs/slurk_api.rst b/docs/slurk_api.rst index 0e9f4e5a..49ce11d8 100644 --- a/docs/slurk_api.rst +++ b/docs/slurk_api.rst @@ -72,7 +72,7 @@ Room ``uri`` URI to query this room ========================= ================================================================================= -* ``GET /api/v2/rooms/`` +* ``GET /api/v2/room/`` Returns the room by name: @@ -89,7 +89,7 @@ Room ``users`` List of users who were associated with this room at least once ========================= ================================================================================= -* ``POST /api/v2/rooms/`` +* ``POST /api/v2/room/`` Creates a new room: @@ -103,7 +103,7 @@ Room ``static`` ``True`` if this room can be selected as login room ========================= ================================================================================= -* ``PUT /api/v2/rooms/`` +* ``PUT /api/v2/room/`` Updates the room by name: @@ -116,7 +116,7 @@ Room ``static`` ``True`` if this room can be selected as login room ========================= ================================================================================= -* ``DELETE /api/v2/rooms/`` +* ``DELETE /api/v2/room/`` Deletes the room by name if no associations to the room exist. Otherwise an error is returned. @@ -125,7 +125,7 @@ Logging ------- -* ``GET /api/v2/rooms//logs`` +* ``GET /api/v2/room//logs`` Returns the log of the room by name: @@ -138,7 +138,7 @@ Logging ``data`` Arbitrary data which is stored alongside the entry ========================= ================================================================================= -* ``GET /api/v2/users//logs`` +* ``GET /api/v2/user//logs`` Returns a mapping for log entries for rooms of the specified user: @@ -151,7 +151,7 @@ Logging ``data`` Arbitrary data which is stored alongside the entry ========================= ================================================================================= -* ``POST /api/v2/users//logs`` +* ``POST /api/v2/user//logs`` Creates a new log entry for the specified user diff --git a/docs/slurk_deployment.rst b/docs/slurk_deployment.rst new file mode 100644 index 00000000..0bf83ac0 --- /dev/null +++ b/docs/slurk_deployment.rst @@ -0,0 +1,203 @@ +.. _slurk_deployment: + +========================================= +Deployment of the system +========================================= + +Using docker +~~~~~~~~~~~~ + +As described in :ref:`slurk_gettingstarted`, the easiest way is to use ``docker``: :: + + sudo apt-get install docker + +In order to run the server on port 80, just run :: + + $ docker run -p 80:5000 -e SECRET_KEY=your-key -d slurk/server + +These additional environment variables can be specified: + +- ``DEBUG``: admin token is ``00000000-0000-0000-0000-000000000000`` +- ``SQLALCHEMY_DATABASE_URI``: URI to the database, defaults to ``sqlite:///:memory:`` +- ``SQLALCHEMY_TRACK_MODIFICATIONS``: Tracks modifications in the database +- ``SQLALCHEMY_ECHO``: Prints all executed commands to the database +- ``DROP_DATABASE_ON_STARTUP``: Database will be recreated on restart + +If you don't want to run it detached, you may omit ``-d``. + + +Manual setup +~~~~~~~~~~~~ + +If you don't want to use docker for whatever reason, you can also manually setup the server. + +General description and requirements +------------------------------------ + +This section includes information on how to run Slurk on Nginx server (Ubuntu 18.04) +with SSL connection. + +First, you have to be sure that Python3.6+ is installed on your machine. Please navigate to +``server`` and install required packages via executing:: + + pip install -r requirements.txt + +Also please make sure that your Ubuntu server is running on Nginx [1]_ and ``systemd`` package is installed. +Do not forget to check if UFW (Uncomplicated Firewall) is installed on your machine [2]_. + +Serving Slurk with Gunicorn +--------------------------- + +You can serve our Flask application on a permanent basis. +Such setup will allow you to have faster connections which are automatic since you are not required to manually start +the process with Python, but Nginx will configure everything for you. Most of the instructions are taken or adopted from +`this tutorial `_. + +First, isolate your Flask application on your computer. For this you have to install *virtualenv* package.:: + + sudo apt install python3-venv + +Navigate to your project directory and create a new project environment there. Also activate the environment and +install required packages:: + + cd ~/server + python3.6 -m venv serverenv + source serverenv/bin/activate + pip install -r requirements.txt + +Now, assuming that *gunicorn* and *flask* are already installed, we will create the WSGI entry point for our application.:: + + nano ~/server/wsgi.py + +The chat instance itself is created in ``~/server/app/__init__.py``, and that is why we will use this instance in our `wsgi.py` file. +Import this instance into the file, save and close it once you are finished:: + + from app import create_app + + app = create_app(debug=True) + + if __name__ == "__main__": + app.run() + +Next step would be to configure Gunicorn, but first we should check if the application is served correctly [3]_:: + + cd ~/server + gunicorn --bind host:5000 -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker wsgi:app + +Visit you server's address with the port ``:5000`` and check if you see the login page. +If it functions properly, please press ``CTRL-C`` in your terminal window and deactivate your environment. + +Now we have to create the systemd service unit file. It will allow Ubuntu to automatically start our application +once the machine boots.:: + + sudo nano /etc/systemd/system/chat.service + +Fill this service file with information about your application and adjust paths/variable names where required:: + + [Unit] + Description=Gunicorn instance to serve MeetUp + After=network.target + + [Service] + User=user + Group=www-data + WorkingDirectory=/home/user/server + Environment="PATH=/home/user/server/serverenv/bin" + ExecStart=/home/user/server/serverenv/bin/gunicorn --bind unix:myproject.sock -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -m 007 wsgi:app + + [Install] + WantedBy=multi-user.target + +Save and close this file. Now we will start the Gunicorn service and enable it so that it is active once our machine is booted:: + + sudo systemctl start chat + sudo systemctl enable chat + +To be sure that it is active, check its status:: + + sudo systemctl status chat + +The output should be similar to the following:: + + chat.service - Gunicorn instance to serve meetup + Loaded: loaded (/etc/systemd/system/chat.service; enabled; vendor preset: enabled) + Active: active (running) since Fri 2018-08-17 11:25:12 CEST; 4h 20min ago + Main PID: 18101 (gunicorn) + Tasks: 2 (limit: 4915) + CGroup: /system.slice/chat.service + ├─18101 /home/user/slurk/server/chatenv/bin/python3 /home/user/slurk/server/chatenv/bin/gunicorn --bind unix:chat.sock -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker + └─18103 /home/user/slurk/server/chatenv/bin/python3 /home/user/slurk/server/chatenv/bin/gunicorn --bind unix:chat.sock -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker + +Configuring Nginx +----------------- + +At this point our Gunicorn application server must be actively running, and now we have to enable Nginx to accept requests for our application. +First, we will create a new server block configuration file in Nginx's `sites-available` directory:: + + sudo nano /etc/nginx/sites-available/chat + +We will have to specify location of our socket file, that serves the application +and include certificates which were created earlier [4]_:: + + server { + + listen 5000 ssl default_server; + listen [::]:5000 ssl default_server; + + server_name _; + root /home/user/slurk/server/app; + + access_log /home/user/slurk/server/nginx_logs/nginx-access.log; + error_log /home/user/slurk/server/nginx_logs/nginx-error.log; + + include snippets/certs.conf; + include snippets/ssl-params.conf; + + location / { + include proxy_params; + proxy_pass http://unix:/home/user/slurk/server/chat.sock; + + } + + } + +Do not forget to link this file to the ``sites-enabled`` directory:: + + sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled + +Test it for syntax errors and restart Nginx:: + + sudo nginx -t + sudo systemctl restart nginx + +As the last step, adjust UFW setting once again, adding full access to the Nginx server:: + + sudo ufw delete allow 5000 + sudo ufw allow 'Nginx Full' + +When you navigate to your server's domain name, you should be able to see the login interface [5]_. + +Congratulations! You have managed to fully deploy Slurk! + +--------------------------------------------------------------------------- + +.. [1] There is a nice tutorial on `how to install Nginx on Ubuntu 18.04 `_. + +.. [2] A very detailed tutorial can be found here: `Initial Server Setup with Ubuntu 18.04 `_. + +.. [3] An important thing is that you specify type of the worker associated with the Gunicorn process. You should use websockets provided by `gevent` package: + + ``-k geventwebsocket.gunicorn.workers.GeventWebSocketWorker`` + + More information can be found in `official Flask documentation `_. + +.. [4] Different configurations require specification of static files' location, + which include CSS files of your applications. In order to enable it, add additional + location block where you specify location of the static files:: + + location /static/ { + alias /home/user/server/app/main/static/; + } + +.. [5] Regarding the port that you use: some ports are not accessible for the use (for example, ``7000`` is used for internal processes), so be careful when deciding which port to use. + If you choose incorrect one, the application will not be accessible from outside of your network, even if you adjust firewall settings. diff --git a/docs/slurk_gettingstarted.rst b/docs/slurk_gettingstarted.rst index 31be8037..8742f229 100644 --- a/docs/slurk_gettingstarted.rst +++ b/docs/slurk_gettingstarted.rst @@ -1,34 +1,31 @@ -.. _slurk_deployment: +.. _slurk_gettingstarted: ========================================= -Getting started +Getting Started ========================================= -Using docker -~~~~~~~~~~~~ +Building the documentation +~~~~~~~~~~~~~~~~~~~~~~~~~~ -The easiest way to deploy the system is using Docker. For this, ``docker`` is recommended: :: +In order to build the documentation yourself, you additionally need the packages *sphinx* and *sphinx_rtd_theme*. Then +you can create the documentation in the *docs* folder: - sudo apt-get install docker - -In order to run the server on port 80, just run :: +.. code-block:: sh - $ docker run -p 80:5000 -e SECRET_KEY=your-key -d slurk/server + pip install sphinx sphinx_rtd_theme + cd docs + make html -These additional environment variables can be specified: +The generated documentation can then be found at *docs/_build/* -- ``DEBUG``: admin token is ``00000000-0000-0000-0000-000000000000`` -- ``SQLALCHEMY_DATABASE_URI``: URI to the database, defaults to ``sqlite:///:memory:`` -- ``SQLALCHEMY_TRACK_MODIFICATIONS``: Tracks modifications in the database -- ``SQLALCHEMY_ECHO``: Prints all executed commands to the database -- ``DROP_DATABASE_ON_STARTUP``: Database will be recreated on restart +"Hello World": A basic test of the server +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -If you don't want to run it detached, you may omit ``-d``. +The easiest way to use system is using Docker. For this, ``docker`` is recommended: :: -Step by step minimal example -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + $ sudo apt-get install docker -Start the server and store the container id: :: +In order to run the server on port 80 and store the container id, run :: $ SLURK_SERVER_ID=$(docker run -p 80:5000 -e SECRET_KEY=your-key -d slurk/server) @@ -41,15 +38,36 @@ Verify you have a proper UUID token: :: $ echo $ADMIN_TOKEN b8b88080-b8d1-4eb2-af90-dccf7ece3d82 +Create a room as landing page for our new token: :: + + $ curl -X POST \ + -H "Authorization: Token $ADMIN_TOKEN" \ + -H "Content-Type: application/json" \ + -H "Accept: application/json" \ + -d '{"name": "test_room", "label", "Test Room"}' \ + localhost/api/v2/room + { + "current_users": {}, + "label": "Test Room", + "layout": null, + "name": "test_room", + "read_only": false, + "show_latency": true, + "show_users": true, + "static": false, + "users": {} + } + + Generate a new token for the clients (``sed`` removes quotation from JSON-string): :: - $ curl -X POST - -H "Authorization: Token $ADMIN_TOKEN" - -H "Content-Type: application/json" - -H "Accept: application/json" - -d '{"room": "test_room"}' + $ curl -X POST \ + -H "Authorization: Token $ADMIN_TOKEN" \ + -H "Content-Type: application/json" \ + -H "Accept: application/json" \ + -d '{"room": "test_room", "message_text": true}' \ localhost/api/v2/token | sed 's/^"\(.*\)"$/\1/' - 7dc2124e-f89f-4d06-9917-811df2a5bb89 + 9e6f53ae-2da7-4c53-92c7-ec43f8fa50cd for a list of possible parameters see :ref:`slurk_api` @@ -66,203 +84,63 @@ This should transport you to the chat interface, where you then can happily type A single user talking to no one in particular This has confirmed that the server is working correctly, but so far this hasn't really been very exciting. So we move on. - .. _twobrowsers: "Hello World" -- "Hello Other Browser": Testing with two browsers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Run the steps from the previous section (starting the server, creating a token and logging in), and then create an addtional token and, **from a different web browser**, log in with that token. You should now see both identities in the respective chat windows, and you should be able to talk with your two selves across these browsers. Hurray! +Run the steps from the previous section (starting the server, creating a token and logging in), and then create an +addtional token and, **from a different web browser or in a private tab**, log in with that token. You should now see +both identities in the respective chat windows, and you should be able to talk with your two selves across these +browsers. Hurray! + +(If your machine is set up in the right way [that is, the machine that is localhost is accessible from your network], +this might work across machines, and so you can have a chat with an actual different person.) -(If your machine is set up in the right way [that is, the machine that is localhost is accessible from your network], this might work across machines, and so you can have a chat with an actual different person.) +This has demonstrated the very basic capabilities -- providing a chat environment --, but so far there hasn't been any +use made of the display window. Let's change that by inviting a bot into our chat room. -This has demonstrated the very basic capabilities -- providing a chat environment --, but so far there hasn't been any use made of the display window. Let's change that by inviting a bot into our chat room. -Running the example bots -~~~~~~~~~~~~~~~~~~~~~~~~ + +Chatting with a bot +~~~~~~~~~~~~~~~~~~~ + +Without additional environment variables, the server uses an in-memory database and resets on every restart of the +server. Now let's restart the server to reset the database. Before we log onto the server in the way described above, +let us create a bot user and let it log on first. Create two tokens as described above, one for the user and one for +the bot. There are Docker containers for all example bots. To run these bots using docker, type :: - $ docker run -e TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx slurk/concierge-bot + $ docker run -e TOKEN=$BOT_TOKEN --net="host" slurk/echo +You may provide additional environment variables, too: - ``CHAT_HOST``: The host address (must include the protocol like "https://") - ``CHAT_PORT``: The port of the host Note, that you have to pass ``--net="host"`` to docker in order to make ``http://localhost`` working. +Examining the log files +~~~~~~~~~~~~~~~~~~~~~~~ -Manual setup -~~~~~~~~~~~~ - -If you don't want to use docker for whatever reason, you can also manually setup the server. - -General description and requirements ------------------------------------- - -This section includes information on how to run Slurk on Nginx server (Ubuntu 18.04) -with SSL connection. - -First, you have to be sure that Python3.6+ is installed on your machine. Please navigate to -``server`` and install required packages via executing:: - - pip install -r requirements.txt - -Also please make sure that your Ubuntu server is running on Nginx [1]_ and ``systemd`` package is installed. -Do not forget to check if UFW (Uncomplicated Firewall) is installed on your machine [2]_. - -Serving Slurk with Gunicorn ---------------------------- - -You can serve our Flask application on a permanent basis. -Such setup will allow you to have faster connections which are automatic since you are not required to manually start -the process with Python, but Nginx will configure everything for you. Most of the instructions are taken or adopted from -`this tutorial `_. - -First, isolate your Flask application on your computer. For this you have to install *virtualenv* package.:: - - sudo apt install python3-venv - -Navigate to your project directory and create a new project environment there. Also activate the environment and -install required packages:: - - cd ~/server - python3.6 -m venv serverenv - source serverenv/bin/activate - pip install -r requirements.txt - -Now, assuming that *gunicorn* and *flask* are already installed, we will create the WSGI entry point for our application.:: - - nano ~/server/wsgi.py - -The chat instance itself is created in ``~/server/app/__init__.py``, and that is why we will use this instance in our `wsgi.py` file. -Import this instance into the file, save and close it once you are finished:: - - from app import create_app - - app = create_app(debug=True) - - if __name__ == "__main__": - app.run() - -Next step would be to configure Gunicorn, but first we should check if the application is served correctly [3]_:: - - cd ~/server - gunicorn --bind host:5000 -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker wsgi:app - -Visit you server's address with the port ``:5000`` and check if you see the login page. -If it functions properly, please press ``CTRL-C`` in your terminal window and deactivate your environment. - -Now we have to create the systemd service unit file. It will allow Ubuntu to automatically start our application -once the machine boots.:: - - sudo nano /etc/systemd/system/chat.service - -Fill this service file with information about your application and adjust paths/variable names where required:: - - [Unit] - Description=Gunicorn instance to serve MeetUp - After=network.target - - [Service] - User=user - Group=www-data - WorkingDirectory=/home/user/server - Environment="PATH=/home/user/server/serverenv/bin" - ExecStart=/home/user/server/serverenv/bin/gunicorn --bind unix:myproject.sock -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -m 007 wsgi:app - - [Install] - WantedBy=multi-user.target - -Save and close this file. Now we will start the Gunicorn service and enable it so that it is active once our machine is booted:: - - sudo systemctl start chat - sudo systemctl enable chat - -To be sure that it is active, check its status:: - - sudo systemctl status chat - -The output should be similar to the following:: - - chat.service - Gunicorn instance to serve meetup - Loaded: loaded (/etc/systemd/system/chat.service; enabled; vendor preset: enabled) - Active: active (running) since Fri 2018-08-17 11:25:12 CEST; 4h 20min ago - Main PID: 18101 (gunicorn) - Tasks: 2 (limit: 4915) - CGroup: /system.slice/chat.service - ├─18101 /home/user/slurk/server/chatenv/bin/python3 /home/user/slurk/server/chatenv/bin/gunicorn --bind unix:chat.sock -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker - └─18103 /home/user/slurk/server/chatenv/bin/python3 /home/user/slurk/server/chatenv/bin/gunicorn --bind unix:chat.sock -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker - -Configuring Nginx ------------------ - -At this point our Gunicorn application server must be actively running, and now we have to enable Nginx to accept requests for our application. -First, we will create a new server block configuration file in Nginx's `sites-available` directory:: - - sudo nano /etc/nginx/sites-available/chat - -We will have to specify location of our socket file, that serves the application -and include certificates which were created earlier [4]_:: - - server { - - listen 5000 ssl default_server; - listen [::]:5000 ssl default_server; - - server_name _; - root /home/user/slurk/server/app; - - access_log /home/user/slurk/server/nginx_logs/nginx-access.log; - error_log /home/user/slurk/server/nginx_logs/nginx-error.log; - - include snippets/certs.conf; - include snippets/ssl-params.conf; - - location / { - include proxy_params; - proxy_pass http://unix:/home/user/slurk/server/chat.sock; - - } - - } - -Do not forget to link this file to the ``sites-enabled`` directory:: - - sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled - -Test it for syntax errors and restart Nginx:: - - sudo nginx -t - sudo systemctl restart nginx - -As the last step, adjust UFW setting once again, adding full access to the Nginx server:: - - sudo ufw delete allow 5000 - sudo ufw allow 'Nginx Full' - -When you navigate to your server's domain name, you should be able to see the login interface [5]_. - -Congratulations! You have managed to fully deploy Slurk! - ---------------------------------------------------------------------------- - -.. [1] There is a nice tutorial on `how to install Nginx on Ubuntu 18.04 `_. - -.. [2] A very detailed tutorial can be found here: `Initial Server Setup with Ubuntu 18.04 `_. - -.. [3] An important thing is that you specify type of the worker associated with the Gunicorn process. You should use websockets provided by `gevent` package: +The point of all this, however, is not just to make interaction *possible*, it is to *record* these interactions to be +able to later study them or train models on them. - ``-k geventwebsocket.gunicorn.workers.GeventWebSocketWorker`` +In order to read the logs, we use the API again: :: - More information can be found in `official Flask documentation `_. + $ curl -X GET \ + -H "Authorization: Token $ADMIN_TOKEN" \ + -H "Content-Type: application/json" \ + -H "Accept: application/json" \ + localhost/api/v2/room/test_room/logs -.. [4] Different configurations require specification of static files' location, - which include CSS files of your applications. In order to enable it, add additional - location block where you specify location of the static files:: +The returned data contains, as a JSON list, most of the +events that the server handled, including all the messages that were sent. This should contain the information that you +need for your particular purposes. - location /static/ { - alias /home/user/server/app/main/static/; - } -.. [5] Regarding the port that you use: some ports are not accessible for the use (for example, ``7000`` is used for internal processes), so be careful when deciding which port to use. - If you choose incorrect one, the application will not be accessible from outside of your network, even if you adjust firewall settings. +This concludes the quick start. We now can be reasonably confident that the setup is working on your machine; and you +also got a first introduction to the basic concepts. But what we have seen so far would only allow us to run a single +room at a time. That may already be all you want if you conduct experiments locally with participants that you bring +into the lab. If you want to make use of crowdsourcing, though, you will want to be able to automatically pair up + participants and create task rooms for each pair. This will be explained in the next section. diff --git a/docs/slurk_installation.rst b/docs/slurk_installation.rst index ab3f9f49..bc9c1d42 100644 --- a/docs/slurk_installation.rst +++ b/docs/slurk_installation.rst @@ -4,7 +4,8 @@ Installation ========================================= -You will need Python 3.7+ and `pip`. To test the dialogue setup, you will also need to have two different web browsers on your system. +You will need Python 3.7+ and `pip`. To test the dialogue setup, you will also need to have two different web browsers +or a browser with the ability to create private tabs/windows. To install the requirements and prepare for testing, do the following. (We assume that you have cloned the repository.) diff --git a/docs/slurk_overview.rst b/docs/slurk_overview.rst deleted file mode 100644 index 62933870..00000000 --- a/docs/slurk_overview.rst +++ /dev/null @@ -1,24 +0,0 @@ -.. _slurk_overview: - -################## -Slurk Overview -################## - -For a quickstart with Slurk, please follow steps described in :ref:`slurk_quickstart`. - -For general information about Slurk's configuration, have a look at the following pages: - -:ref:`slurk_login` - -:ref:`slurk_chat` - -:ref:`slurk_commands` - -:ref:`slurk_events` - -:ref:`slurk_deployment` - -You also can read about how to connect Slurk with Amazon Mechanical turk and collect game data. - -If you want to read about specific project, that uses Slurk, please refer to -:ref:`meetup_main`. diff --git a/docs/slurk_quickstart.rst b/docs/slurk_quickstart.rst deleted file mode 100644 index a71f836f..00000000 --- a/docs/slurk_quickstart.rst +++ /dev/null @@ -1,132 +0,0 @@ -.. _slurk_quickstart: - -========================================= -Quickstart -========================================= - -"Hello World": A basic test of the server -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Start *slurk* with: - -- `python chat.py` - -This will start the flask server in development mode. You will later learn how to start a production server. - -In the following, we will assume that you used the defaults in `config.ini` so that the app is served on localhost port 5000 (`127.0.0.1:5000`). - -If you now go to http://127.0.0.1:5000, you should see the login window, asking for two items, prefilled with "Name" and "None". The first asks for a name under which you will be identified in the chat, and the second asks for a token.. which you don't have yet. - -To get a token, you will need to generate one by going to http://127.0.0.1:5000/token. On that page, fill in the fields as follows: - -- Source: *can be anything* -- Room: Test Room -- Task: None -- Count: 1 -- Key: *the secret key you've defined in the config.ini* - -(Note: What is happening behind the curtain is that entries are created in the user database, with attributes as specified here. `Room` fixes where the user that is logging in with the created token starts, `Task` specifies the tasks that this user is allowed to do, and `Count` creates as many tokens. `Source` doesn't really have a function at the moment; any string will do here.) - -After you submit this form, you should see a long alphanumeric string as a response. Copy this string (the token) over to the login page. Enter whatever Name you like and this copied token, and click "enter chatroom". - -This should transport you to the chat interface, where you then can happily type messages which no one will see (apart from you, that is). - - -.. _screenshot_void: -.. figure:: single_user.png - :align: center - :scale: 60 % - - A single user talking to no one in particular - -This has confirmed that the server is working correctly, but so far this hasn't really been very exciting. So we move on. - -(But one additional thing before we move on: If you repeat the steps above while keeping the chat server alive, you will notice that you will end up in the same room which shows the chat history of the previously created user which now however is orphaned, as you have your new identity. The reason for that is the websocket connection that is created by the server is tied to the browser, and was kept alive. If you want to start fresh, you need to kill the chat server and restart again.) - - - -.. _twobrowsers: - -"Hello World" -- "Hello Other Browser": Testing with two browsers -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Run the steps from the previous section (starting the server, creating a token and logging in), and then create an addtional token and, **from a different web browser**, log in with that token. You should now see both identities in the respective chat windows, and you should be able to talk with your two selves across these browsers. Hurray! - -(If your machine is set up in the right way [that is, the machine that is localhost is accessible from your network], this might work across machines, and so you can have a chat with an actual different person.) - -This has demonstrated the very basic capabilities -- providing a chat environment --, but so far there hasn't been any use made of the display window. Let's change that by inviting a bot into our chat room. - - - -Chatting with a bot, and giving commands -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Kill the server (if it's still running) and start it again: - -- `python chat.py` - -Now before we log onto the server in the way described above, let us create a bot user and let it log on first. - -Bots also need tokens. Let us directly create all tokens we need. Go to http://127.0.0.1:5000/token, and fill in: - -- Source: *can be anything* -- Room: Test Room -- Task: None -- Count: 1 -- Key: *the secret key you've defined in the config.ini* - -That is, we create three tokens in one go. - -Copy the first one, and use it to do the following, in a new terminal window (`minimal.py` lives in `sample_bots`): - -- `python minimal.py` ** - -In the terminal window where you started the chat server, you should see a message stating that `minimal bot has connected`. In the window of the minimal bot, you will see some JSON structures. These structures contain information about events emitted by the server. Several kinds of information are included in this event data, for example about the room and the user. The event data can be accessed using event handlers, several of which are predefined in the bot files. For instance, the following piece of code causes the bot to print the event data: - - .. code-block:: python - - def on_message(self, data): - print("on_message", data) - -where *data*, in this case, holds information about the user that sends a text, about the rooms that are involved and of course the text message itself (see *minimal.py*). Depending on which handler you are using, information about different events can be accessed. - -Now start two browsers and log in as two different users (let's call them `A` and `B`), as described above in :ref:`twobrowsers`. The list of users in the top right corner of the window should now indicate that besides `A` and `B` there is also a user called `minimal bot`. - -So far, nothing else has changed really. (Except that you now get a glimpse of what's happening behind the scenes, as the `minimal bot` prints all messages that pass through the room to its `STDOUT` [i.e., into the terminal window where it was started].) - -But now that the bot is in the room, we can issue *slash commands* to it. Try the following in `A`'s chat (the leading `/` is important): - -- `/new_image_public` - -You should notice three things: - -- In both chat windows, an image has appeared in the display area (which up to now has been empty). -- In `A`'s chat history, the command is shown in italics. -- In `B`'s chat history, the command is not shown at all. - -This demonstrates some important features of slurk and the interaction with bots: Bots can react to commands; the interaction between a user and the bot is private by default; and bots can control the display area. - -Now type the following, as `B` for a change: - -- `/new_image_private` - -Now only the image shown for `B` should have changed and you should be seeing something similar to :numref:`screenshot_privimage`. This demonstrates another important feature: the display area can be controlled per user; that is, different users can be shown different material in that area. This makes it possible to set up tasks where the users may not in fact be seeing the same image. - - -.. _screenshot_privimage: -.. figure:: minimal_private_images.png - :align: center - :scale: 60 % - - Example interaction with the minimal bot - - -Examining the log files -~~~~~~~~~~~~~~~~~~~~~~~ - -The point of all this, however, is not just to make interaction *possible*, it is to *record* these interactions to be able to later study them or train models on them. In the directory `log/` where you started the chat server, you should now find logfiles, named with the time stamp and the name of the room. These files contain, as a JSON list, most of the events that the server handled, including all the messages that were sent. This should contain the information that you need for your particular purposes. - - - - -This concludes the quick start. We now can be reasonably confident that the setup is working on your machine; and you also got a first introduction to the basic concepts. But what we have seen so far would only allow us to run a single room at a time. That may already be all you want if you conduct experiments locally with participants that you bring into the lab. If you want to make use of crowdsourcing, though, you will want to be able to automatically pair up participants and create task rooms for each pair. This will be explained in the next section.