diff --git a/db.sqlite3 b/db.sqlite3
index 432f204be352251f792e0f91a1b7377303918b43..e33654f60a42bd1bdaf59e30dc8587fc89bd53be 100644
Binary files a/db.sqlite3 and b/db.sqlite3 differ
diff --git a/iotamak_ihm/settings.py b/iotamak_ihm/settings.py
index fd177dfd041688a0df503d5912a7c84bdcd48738..3f506acfe0eb5c66886aaf1ac7acf80690289250 100644
--- a/iotamak_ihm/settings.py
+++ b/iotamak_ihm/settings.py
@@ -9,7 +9,7 @@ https://docs.djangoproject.com/en/4.0/topics/settings/
 For the full list of settings and their values, see
 https://docs.djangoproject.com/en/4.0/ref/settings/
 """
-
+import os
 from pathlib import Path
 
 # Build paths inside the project like this: BASE_DIR / 'subdir'.
@@ -27,6 +27,7 @@ DEBUG = True
 
 ALLOWED_HOSTS = []
 
+MEDIA_ROOT = os.path.join(BASE_DIR, 'uploads/')
 
 # Application definition
 
diff --git a/iotamak_ihm/urls.py b/iotamak_ihm/urls.py
index d502811807ace9b547f777a215ca3ecc1952087f..a05677f753dfe0e74aab042c04def288101f8d56 100644
--- a/iotamak_ihm/urls.py
+++ b/iotamak_ihm/urls.py
@@ -17,6 +17,6 @@ from django.contrib import admin
 from django.urls import path, include
 
 urlpatterns = [
-    path('ping/', include('ping.urls')),
+    path('', include('ping.urls')),
     path('admin/', admin.site.urls),
 ]
diff --git a/ping/admin.py b/ping/admin.py
index 7e490c7617f3c6324bf80425169b87d26cbfc5ff..f8dd658921a7e457d2651b11af8b8e020a498a0b 100644
--- a/ping/admin.py
+++ b/ping/admin.py
@@ -1,5 +1,7 @@
 from django.contrib import admin
 
-from .models import Client
+from .models import Client, Agent, Experiment
 
-admin.site.register(Client)
\ No newline at end of file
+admin.site.register(Client)
+admin.site.register(Agent)
+admin.site.register(Experiment)
\ No newline at end of file
diff --git a/ping/migrations/0001_initial.py b/ping/migrations/0001_initial.py
index 7eda40bfb31a9d3d6c2aa8c66bd1b1c8534e24cf..ae0c37f73e11cbb2e187439556f37a476c144143 100644
--- a/ping/migrations/0001_initial.py
+++ b/ping/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 4.0.4 on 2022-05-10 15:39
+# Generated by Django 4.0.4 on 2022-05-12 08:50
 
 from django.db import migrations, models
 
@@ -11,12 +11,21 @@ class Migration(migrations.Migration):
     ]
 
     operations = [
+        migrations.CreateModel(
+            name='Agent',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('ip', models.CharField(max_length=16)),
+                ('command', models.CharField(max_length=256)),
+            ],
+        ),
         migrations.CreateModel(
             name='Client',
             fields=[
                 ('hostname', models.CharField(max_length=16, primary_key=True, serialize=False, unique=True)),
                 ('username', models.CharField(max_length=16)),
                 ('password', models.CharField(max_length=16)),
+                ('status', models.CharField(default='Offline', max_length=8)),
             ],
         ),
     ]
diff --git a/ping/migrations/0002_experiment.py b/ping/migrations/0002_experiment.py
new file mode 100644
index 0000000000000000000000000000000000000000..c06c6d60dcf15c768fd20542a0605ce4ebb46062
--- /dev/null
+++ b/ping/migrations/0002_experiment.py
@@ -0,0 +1,23 @@
+# Generated by Django 4.0.4 on 2022-05-12 12:21
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('ping', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Experiment',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=60)),
+                ('status', models.CharField(default='Not checked', max_length=60)),
+                ('description', models.TextField()),
+                ('media', models.FileField(blank=True, null=True, upload_to='')),
+            ],
+        ),
+    ]
diff --git a/ping/models.py b/ping/models.py
index 7a1725a436281e8ce65e2c1f6fde8620e3a9a2f5..c6a47a184abdd614fc87b20e17fb3f7a3a709b3a 100644
--- a/ping/models.py
+++ b/ping/models.py
@@ -1,10 +1,32 @@
 from django.db import models
+from django.forms import ModelForm
 
 
 class Client(models.Model):
     hostname = models.CharField(max_length=16, primary_key=True, unique=True)
     username = models.CharField(max_length=16)
     password = models.CharField(max_length=16)
+    status = models.CharField(max_length=8, default="Offline")
 
     def __str__(self):
-        return "Hostname : " + self.hostname + " Username : " + self.username
\ No newline at end of file
+        return "Hostname : " + self.hostname + " Username : " + self.username + " Status : " + self.status
+
+
+class Agent(models.Model):
+    ip = models.CharField(max_length=16)
+    command = models.CharField(max_length=256)
+
+    def __str__(self):
+        return self.command
+
+class Experiment(models.Model):
+    name = models.CharField(max_length=60)
+    status = models.CharField(max_length=60, default="Not checked")
+    description = models.TextField()
+    media = models.FileField(upload_to="media", null=True, blank=True)
+
+
+class ExperimentForm(ModelForm):
+    class Meta:
+        model = Experiment
+        fields = ["name", "description", "media"]
diff --git a/ping/static/ping/index.css b/ping/static/ping/index.css
new file mode 100644
index 0000000000000000000000000000000000000000..3e4d20680a3dccf9b8ce06c5e1e8ef0aa70b7d82
--- /dev/null
+++ b/ping/static/ping/index.css
@@ -0,0 +1,41 @@
+body {
+  margin: 0px;
+  font-family: Arial, Helvetica, sans-serif;
+}
+
+.topnav {
+  overflow: hidden;
+  background-color: #333;
+}
+
+.topnav a {
+  float: left;
+  color: #f2f2f2;
+  text-align: center;
+  padding: 14px 16px;
+  text-decoration: none;
+  font-size: 17px;
+}
+
+.topnav a:hover {
+  background-color: #ddd;
+  color: black;
+}
+
+.topnav a.active {
+  background-color: #0486aa;
+  color: white;
+}
+
+th {
+  text-align: center;
+}
+tr:nth-child(even) {background-color: #f2f2f2;}
+td {
+  height: 25px;
+  vertical-align: bottom;
+}
+
+th, td {
+  border-bottom: thin solid #ddd;
+}
\ No newline at end of file
diff --git a/ping/templates/ping/entry.html b/ping/templates/ping/entry.html
new file mode 100644
index 0000000000000000000000000000000000000000..b718dd690422ca999abb822edda5e42a6a8dd29b
--- /dev/null
+++ b/ping/templates/ping/entry.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+  <meta charset="utf-8">
+  {% load static %}
+  <link rel="stylesheet" type="text/css" href="{% static 'ping/index.css' %}">
+
+  <title>Experiment - IOTAMAK</title>
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+
+</head>
+
+<body>
+
+  <div class="topnav">
+    <a href="/network">Network</a>
+    <a class="active" href="/experiment">Experiment</a>
+  </div>
+    <form action="/experiment/new/" method="POST" enctype="multipart/form-data">
+        {% csrf_token %}
+        {% for entry in form %}
+           <div>
+                {{ entry.label_tag }}
+           </div>
+           <div>
+               {{entry}}
+           </div>
+        {% endfor %}
+        <button>
+            Save!
+        </button>
+    </form>
+</body>
+</html>
\ No newline at end of file
diff --git a/ping/templates/ping/experiment.html b/ping/templates/ping/experiment.html
new file mode 100644
index 0000000000000000000000000000000000000000..86454db688d053b9a18b89d3b7fb044bf907711c
--- /dev/null
+++ b/ping/templates/ping/experiment.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+  <meta charset="utf-8">
+  {% load static %}
+  <link rel="stylesheet" type="text/css" href="{% static 'ping/index.css' %}">
+
+  <title>Experiment - IOTAMAK</title>
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+
+</head>
+
+<body>
+
+  <div class="topnav">
+    <a href="/network">Network</a>
+    <a class="active" href="/experiment">Experiment</a>
+  </div>
+
+  <form action='new' method='GET'>
+    <button type='submit'> Add experiment</button>
+  </form>
+
+  {% if experiments %}
+  <table>
+    <tr>
+      <th>Name</th>
+      <th>Status</th>
+      <th>Check</th>
+    </tr>
+    {% for experiment in experiments %}
+    <tr>
+      <td>{{ experiment.name }}</td>
+      <td>{{ experiment.status }}</td>
+      <td><a href="/experiment/{{ experiment.id }}/check">Check</td>
+    </tr>
+    {% endfor %}
+  </table>
+  {% else %}
+  <p>No agents are available.</p>
+  {% endif %}
+
+</body>
+
+</html>
\ No newline at end of file
diff --git a/ping/templates/ping/index.html b/ping/templates/ping/index.html
index 02097b415786bc2d0557e02fda3857d2b975db4e..e39a748b2828a0220baaa7799f4de10fdaef5575 100644
--- a/ping/templates/ping/index.html
+++ b/ping/templates/ping/index.html
@@ -1,19 +1,68 @@
 <!DOCTYPE html>
 <html>
-  <head>
-    <meta charset="utf-8">
-    <title>Main menu</title>
-  </head>
-  <body>
-    <form action='pressed' method='GET'>
-        <button type='submit'> Ping clients</button>
-    </form>
-
-    <ul>
-        {% for client in host_list %}
-            <li>{{client}}</li>
-        {% endfor %}
-    </ul>
-
-  </body>
+
+<head>
+  <meta charset="utf-8">
+  {% load static %}
+  <link rel="stylesheet" type="text/css" href="{% static 'ping/index.css' %}">
+
+  <title>Network - IOTAMAK</title>
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+
+</head>
+
+<body>
+  <div class="topnav">
+    <a class="active" href="/network">Network</a>
+    <a href="/experiment">Experiment</a>
+  </div>
+
+
+
+  <form action='pressed' method='GET'>
+    <button type='submit'> Ping clients</button>
+  </form>
+
+  <form action='update' method='GET'>
+    <button type='submit'> Update clients</button>
+  </form>
+
+  <form action='agents' method='GET'>
+    <button type='submit'> agents</button>
+  </form>
+
+  <table>
+    <tr>
+      <th>Hostname</th>
+      <th>Username</th>
+      <th>Status</th>
+    </tr>
+    {% for client in host_list %}
+    <tr>
+      <td>{{ client.hostname }}</td>
+      <td>{{ client.username }}</td>
+      <td>{{ client.status }}</td>
+    </tr>
+    {% endfor %}
+  </table>
+
+  {% if agents %}
+  <table>
+    <tr>
+      <th>Hostname</th>
+      <th>Agent</th>
+    </tr>
+    {% for agent in agents %}
+    <tr>
+      <td>{{ agent.ip }}</td>
+      <td>{{ agent.command }}</td>
+    </tr>
+    {% endfor %}
+  </table>
+  {% else %}
+  <p>No agents are available.</p>
+  {% endif %}
+
+</body>
+
 </html>
\ No newline at end of file
diff --git a/ping/urls.py b/ping/urls.py
index de7ace3d352acd09e9d9be367e27a2af29bf9b01..f7f30028c5c1823c88de037886b4b053f0628569 100644
--- a/ping/urls.py
+++ b/ping/urls.py
@@ -4,6 +4,11 @@ from . import views
 
 app_name = 'ping'
 urlpatterns = [
-    path('', views.index, name='index'),
-    path('pressed/', views.pressed, name='pressed'),
+    path('network/', views.index, name='index'),
+    path('network/pressed/', views.pressed, name='pressed'),
+    path('network/update/', views.update, name='update'),
+    path('network/agents/', views.agents, name='agents'),
+    path('experiment/new/', views.entry, name='entry'),
+    path('experiment/', views.experiment, name='experiment'),
+    path('experiment/<int:experiment_id>/check/', views.check, name='check'),
 ]
\ No newline at end of file
diff --git a/ping/views.py b/ping/views.py
index 948a0fa55bd848975c98721aee5172c9b1490740..9cd930554ebde50d240e380e6008034f0c3391e4 100644
--- a/ping/views.py
+++ b/ping/views.py
@@ -1,21 +1,97 @@
+import os
 import platform
+import shutil
 import subprocess
+import re
+import zipfile
+
+from django.conf import settings
 
 from django.http import HttpResponse, HttpResponseRedirect
 from django.template import loader
 from django.urls import reverse
+from iotAmak.tool.remote_client import RemoteClient
+from iotAmak.tool.ssh_client import SSHClient, Cmd
+
+from .models import Client, Agent, Experiment, ExperimentForm
+
+
+def get_remote_client():
+    res = []
+    for client in Client.objects.all():
+        if client.status == "Online":
+            res.append(RemoteClient(client.hostname, client.username, client.password))
+
+    return res
+
+
+def get_ssh_client():
+    return SSHClient(get_remote_client())
+
 
-from .models import Client
+def update(request):
+    ssh = get_ssh_client()
+    version = "0.0.1"
+    commands = [
+        Cmd(
+            cmd="cd Desktop/mqtt_goyon/iotamak-core"
+        ),
+        Cmd(
+            cmd="git pull"
+        ),
+        Cmd(
+            cmd="git checkout main"
+        ),
+        Cmd(
+            cmd="git pull"
+        ),
+        Cmd(
+            cmd="python3 -m pip install --force-reinstall dist/iotAmak-" + version + "-py3-none-any.whl"
+        ),
+        Cmd(
+            cmd="cd ../../../"
+        )  # ,
+        # Cmd(
+        #    cmd="rm -r Desktop/mqtt_goyon/example/" + self.experiment_name
+        # )
+
+    ]
+    for i_client in range(len(ssh.clients)):
+        print("Hostname :", ssh.clients[i_client].hostname, " User :", ssh.clients[i_client].user)
+        ssh.run_cmd(i_client, commands)
+
+    return HttpResponseRedirect(reverse('ping:index'))
+
+
+def agents(request):
+    ssh = get_ssh_client()
+    Agent.objects.all().delete()
+    commands = [
+        Cmd(
+            cmd="ps -ef | tr -s ' ' | cut -d ' ' -f 8-",
+            do_print=False
+        )]
+    ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
+
+    for i_client in range(len(ssh.clients)):
+        raw_string = ssh.run_cmd(i_client, commands)[0].split("\r\n")
+        raw_string = [i for i in raw_string if "python D" in i]
+        for line in raw_string:
+            new_entry = Agent(ip=ssh.clients[i_client].hostname, command=ansi_escape.sub('', line))
+            new_entry.save()
+
+    return HttpResponseRedirect(reverse('ping:index'))
 
 
 def index(request):
     template = loader.get_template('ping/index.html')
-    host_list = Client.objects.all()
     context = {
-        "host_list": host_list,
+        "host_list": Client.objects.all(),
+        "agents": Agent.objects.all()
     }
     return HttpResponse(template.render(context, request))
 
+
 def pressed(request):
     for client in Client.objects.all():
         param = '-n' if platform.system().lower() == 'windows' else '-c'
@@ -23,6 +99,84 @@ def pressed(request):
 
         response = subprocess.call(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0
 
-        print(response)
+        c = Client.objects.get(hostname=client.hostname)
+        if response:
+            c.status = "Online"
+        else:
+            c.status = "Offline"
+        c.save()
+
+    return HttpResponseRedirect(reverse('ping:index'))
 
-    return HttpResponseRedirect(reverse('ping:index'))
\ No newline at end of file
+
+def entry(request):
+    if request.method == 'POST':
+        form = ExperimentForm(request.POST, request.FILES)
+
+        if form.is_valid():
+            form.save()
+            return HttpResponseRedirect(reverse('ping:experiment'))
+
+    else:
+        form = ExperimentForm()
+        template = loader.get_template('ping/entry.html')
+        context = {"form": form}
+        return HttpResponse(template.render(context, request))
+
+def empty_tmp():
+    folder = str(settings.MEDIA_ROOT) + "/tmp"
+    for filename in os.listdir(folder):
+        file_path = os.path.join(folder, filename)
+        try:
+            if os.path.isfile(file_path) or os.path.islink(file_path):
+                os.unlink(file_path)
+            elif os.path.isdir(file_path):
+                shutil.rmtree(file_path)
+        except Exception as e:
+            print('Failed to delete %s. Reason: %s' % (file_path, e))
+
+def check(request, experiment_id):
+    # get the path
+    print(experiment_id)
+    exp = Experiment.objects.get(pk=experiment_id)
+    print(str(exp.media))
+    print(str(settings.MEDIA_ROOT) + str(exp.media))
+    # check if it's a zip -> wrong format : not zip
+    print()
+
+    if not zipfile.is_zipfile(str(settings.MEDIA_ROOT) + str(exp.media)):
+        exp.status = "Wrong format : zip file expected"
+        exp.save()
+        return HttpResponseRedirect(reverse('ping:experiment'))
+
+    with zipfile.ZipFile(str(settings.MEDIA_ROOT) + str(exp.media), 'r') as zip_ref:
+        zip_ref.extractall(str(settings.MEDIA_ROOT) + "/tmp")
+
+    folder_path = str(settings.MEDIA_ROOT) + "/tmp/" + exp.name
+
+    if not os.path.isdir(folder_path):
+        exp.status = "Wrong format : zip should contain a folder"
+        exp.save()
+        empty_tmp()
+        return HttpResponseRedirect(reverse('ping:experiment'))
+
+    required_files = ["amas.py", "agent.py", "env.py", "scheduler.py"]
+    for required_file in required_files:
+        if not os.path.exists(folder_path + "/" + required_file):
+            exp.status = "Wrong format : " + required_file + " file is expected"
+            exp.save()
+            empty_tmp()
+            return HttpResponseRedirect(reverse('ping:experiment'))
+
+    exp.status = "Checked"
+    exp.save()
+    empty_tmp()
+    return HttpResponseRedirect(reverse('ping:experiment'))
+
+
+def experiment(request):
+    template = loader.get_template('ping/experiment.html')
+    context = {
+        "experiments": Experiment.objects.all(),
+    }
+    return HttpResponse(template.render(context, request))
diff --git a/uploads/media/config.json b/uploads/media/config.json
new file mode 100644
index 0000000000000000000000000000000000000000..56c5b0c82bcd9281d321f063c9d5bcd5237c6b1f
--- /dev/null
+++ b/uploads/media/config.json
@@ -0,0 +1,25 @@
+{
+  "broker" : "192.168.30.209",
+  "clients_ssh" : [
+    {
+      "hostname" : "192.168.30.18",
+      "user" : "pi",
+      "password" : "raspberry"
+    },
+    {
+      "hostname" : "192.168.30.227",
+      "user" : "pi",
+      "password" : "raspberry"
+    },
+    {
+      "hostname" : "192.168.30.61",
+      "user" : "pi",
+      "password" : "raspberry"
+    },
+    {
+      "hostname" : "192.168.30.75",
+      "user" : "pi",
+      "password" : "raspberry"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/uploads/media/env.py b/uploads/media/env.py
new file mode 100644
index 0000000000000000000000000000000000000000..c8a767fd18a98aabce6994c1864789c28a374ee5
--- /dev/null
+++ b/uploads/media/env.py
@@ -0,0 +1,67 @@
+
+import sys
+
+
+from iotAmak.environment import Environment
+from fork import Fork
+
+
+class PhiEnv(Environment):
+
+    def __init__(self, broker_ip, nbr_phil):
+        self.nbr_phil = nbr_phil
+        super().__init__(broker_ip)
+
+    def on_initialization(self):
+        self.forks = []
+        for i in range(self.nbr_phil):
+            self.forks.append(Fork(i))
+            self.subscribe("agent/" + str(i) + "/ask_spoon", self.ask_spoon)
+            self.subscribe("agent/" + str(i) + "/done_eating", self.done_eating)
+
+    def ask_spoon(self, client, userdata, message):
+        res = str(message.payload.decode("utf-8"))
+        agent_id = int(str(message.topic).split("/")[1])
+
+        if res == "left":
+            fork_id = agent_id
+        else:
+            fork_id = (agent_id - 1) % self.nbr_phil
+
+        if self.forks[fork_id].state == 1:
+            message = {
+                "response": "False"
+            }
+        elif self.forks[fork_id].taken_by == -1:
+            message = {
+                "response": "True",
+                "side": res,
+                "state": 0
+            }
+            self.forks[fork_id].taken_by = agent_id
+        else:
+            message = {
+                "response": "True",
+                "side": res,
+                "state": 1
+            }
+            self.forks[fork_id].taken_by = agent_id
+            self.forks[fork_id].state = 1
+
+        self.client.publish("env/agent/" + str(agent_id) + "/ask_spoon", str(message))
+
+    def done_eating(self, client, userdata, message):
+        agent_id = int(str(message.topic).split("/")[1])
+
+        for fork_id in [agent_id, (agent_id - 1) % self.nbr_phil]:
+            self.forks[fork_id].state = 0
+
+    def on_cycle_begin(self) -> None:
+        for fork in self.forks:
+            self.client.publish("env/fork/" + str(fork.identifier), str(fork.to_msg()))
+            print("Fork : ", fork.identifier," taken by ", fork.taken_by, " and is :", fork.state)
+
+
+if __name__ == '__main__':
+    s = PhiEnv(str(sys.argv[1]), 5)
+    s.run()
\ No newline at end of file
diff --git a/uploads/media/philosophers.zip b/uploads/media/philosophers.zip
new file mode 100644
index 0000000000000000000000000000000000000000..7c2deb36c8adeb6b9c9f9e60519c28d58b2724f3
Binary files /dev/null and b/uploads/media/philosophers.zip differ