diff --git a/beat/web/reports/serializers.py b/beat/web/reports/serializers.py
index 8d70261f2c1b1538121d6275280676d59eeb1f76..158db1c814a3dd26cbf96c0a891588660c9a6704 100644
--- a/beat/web/reports/serializers.py
+++ b/beat/web/reports/serializers.py
@@ -50,6 +50,7 @@ class BasicReportSerializer(serializers.ModelSerializer):
     content = serializers.SerializerMethodField()
     author = serializers.SerializerMethodField()
     experiments = serializers.SerializerMethodField()
+    experiment_access_map = serializers.SerializerMethodField()
     analyzer = serializers.SerializerMethodField()
     html_description = serializers.SerializerMethodField()
     add_url = serializers.SerializerMethodField()
@@ -86,6 +87,12 @@ class BasicReportSerializer(serializers.ModelSerializer):
     def get_experiments(self, obj):
         return map(lambda x: x.fullname(), obj.experiments.iterator())
 
+    def get_experiment_access_map(self, obj):
+        user = self.context['request'].user
+        access_map = list(map(lambda x: x.accessibility_for(user)[0],
+            obj.experiments.iterator()))
+        return access_map
+
     def get_analyzer(self, obj):
         if obj.analyzer is not None:
             return obj.analyzer.fullname()
@@ -118,7 +125,7 @@ class FullReportSerializer(BasicReportSerializer):
 
 
     class Meta(BasicReportSerializer.Meta):
-        fields = ['name', 'number', 'short_description', 'description', 'is_owner', 'author','status', 'creation_date', 'publication_date', 'experiments', 'analyzer', 'content', 'html_description']
+        fields = ['name', 'number', 'short_description', 'description', 'is_owner', 'author','status', 'creation_date', 'publication_date', 'experiments', 'analyzer', 'content', 'html_description', 'experiment_access_map']
 
 
 #----------------------------------------------------------
diff --git a/beat/web/reports/static/reports/app/controllers/groupsController.js b/beat/web/reports/static/reports/app/controllers/groupsController.js
index 67f6fc41475a40a60e982ee1db14599ca2945afa..0e8d7e2b8792d568e3a4cb59f122d3cb13e7fffe 100644
--- a/beat/web/reports/static/reports/app/controllers/groupsController.js
+++ b/beat/web/reports/static/reports/app/controllers/groupsController.js
@@ -25,9 +25,33 @@
  *  provides access to the groups data to Django templates,
  *  used for handling the removal of experiments from the report
  */
-angular.module('reportApp').controller('GroupsController', ['$http', 'UrlService', function ($http, UrlService){
+angular.module('reportApp').controller('GroupsController',
+	['$scope', '$http', 'UrlService', 'GroupsService', 'ReportService', 'reportFactory', 'ErrorService', 'ExperimentsService',
+	function (scope, $http, UrlService, GroupsService, ReportService, reportFactory, ErrorService, ExperimentsService){
     let vm = this;
 
+    vm.saveReport = () => {
+	// save the serialized group data...
+	// the rest of the state is reconstructed from it and the URL
+	let saveData = {
+	    content: {
+		'groups': GroupsService.serializeGroups()
+	    }
+	};
+
+	return reportFactory.removeExperiments(ExperimentsService.cachedDeletedExperiments)
+	.then(() => reportFactory.updateReport(ReportService.author, ReportService.name, saveData, ''))
+	.then(() => {
+	    const lastEditedEl = document.querySelector('.lastEdited');
+	    lastEditedEl.classList.remove('lastEditedAnimating');
+	    void lastEditedEl.offsetWidth;
+	    lastEditedEl.classList.add('lastEditedAnimating');
+	})
+	.catch(e => {
+	    ErrorService.logError(e, `Could not save the report.`);
+	});
+    };
+
     vm.expNamesToRemove = [];
     vm.toggleExpName = (expName) => {
         let idx = vm.expNamesToRemove.indexOf(expName);
diff --git a/beat/web/reports/static/reports/app/directives/experimentsTable.js b/beat/web/reports/static/reports/app/directives/experimentsTable.js
index ae111e80d05e0b7bcd4b12abda05f2c7f1bb7d7a..e9724588ce114d3ce05809b34db2e9f4420eed82 100644
--- a/beat/web/reports/static/reports/app/directives/experimentsTable.js
+++ b/beat/web/reports/static/reports/app/directives/experimentsTable.js
@@ -61,6 +61,16 @@ angular.module('reportApp')
             scope.deleteExpFromReport = (expName) => {
                 ExperimentsService.deleteExperiment(expName);
             };
+
+		const getUnusedExperiments = (expNames, groups) => {
+			const usedExps = Array.from(new Set([].concat.apply([], groups.map(g => g.experiments))));
+			const unusedExps = expNames.filter(n => !usedExps.includes(n));
+			scope.unusedExps = unusedExps;
+		};
+
+		scope.$watchCollection('expNames', (names) => {getUnusedExperiments(names, scope.groups);});
+		scope.$watch('groups', (gs) => {getUnusedExperiments(scope.expNames, gs);}, true);
+		getUnusedExperiments(scope.expNames, scope.groups);
         },
         template: `
 <div id='{{ domId }}' class='panel panel-default'>
@@ -85,6 +95,7 @@ angular.module('reportApp')
             <table ng-if='expNames.length > 0' class="table table-striped table-hover">
                 <thead>
                     <tr>
+                        <th ng-if='!isViewmode()'></th>
                         <th ng-if='!isViewmode()'></th>
                         <th ng-if='isViewmode() && groups.length == 1'>Alias</th>
                         <th>Experiment</th>
@@ -107,6 +118,14 @@ angular.module('reportApp')
                                 </span>
                             </div>
                         </td>
+                        <td ng-if='!isViewmode()'>
+                                <span
+				    ng-if='unusedExps.includes(expName)'
+                                    style='cursor: help;'
+                                    title="Experiment needs to be added to a group">
+                                    <i class="fa fa-flag fa-lg text-warning"></i>
+                                </span>
+                        </td>
                         <td ng-if='isViewmode() && groups.length == 1'>
                             <span ng-if='groups[0].experiments.includes(expName)'>
                                 {{ groups[0].aliases[expName] }}
diff --git a/beat/web/reports/static/reports/app/directives/layout.js b/beat/web/reports/static/reports/app/directives/layout.js
index 1e9e949082401340c190d4d2c9d0b028a9896c0d..558f3e2934aa512ec50becdd8cd6ddcb00bcee9c 100644
--- a/beat/web/reports/static/reports/app/directives/layout.js
+++ b/beat/web/reports/static/reports/app/directives/layout.js
@@ -42,7 +42,7 @@ angular.module('reportApp').directive("groupsLayout", ['GroupsService', 'UrlServ
             };
         },
         template: `
-<experiments-table ng-if='!isLocked()'></experiments-table>
+<experiments-table ng-if='!isViewmode() || GroupsService.groups.length > 1'></experiments-table>
 <div ng-if='!isViewmode()' group-add-group-menu class='panel'></div>
 
 <div id='groupsLayout' class='panel-group'>
@@ -60,6 +60,9 @@ angular.module('reportApp').directive("groupsLayout", ['GroupsService', 'UrlServ
         </div>
     </div>
     <div ng-if='isViewmode() && GroupsService.groups.length == 1'>
+        <div class='panel-group'>
+            <div group-panel-experiments group='GroupsService.groups[0]' class='panel panel-default'></div>
+        </div>
         <group-panel-items group='GroupsService.groups[0]'></group-panel-items>
     </div>
 </div>
diff --git a/beat/web/reports/static/reports/app/directives/lock.js b/beat/web/reports/static/reports/app/directives/lock.js
index 73b7f687c961e04996f1f8e8b405cd608f0f2cea..d6794b1c47614f9ea38349bd6cb098fea8cae4cd 100644
--- a/beat/web/reports/static/reports/app/directives/lock.js
+++ b/beat/web/reports/static/reports/app/directives/lock.js
@@ -26,25 +26,36 @@
  *  Displays a modal for locking the current report.
  */
 angular.module('reportApp')
-.directive("reportLock", ['ReportService', 'ErrorService', function(ReportService, ErrorService){
+.directive("reportLock", ['ReportService', 'ErrorService', 'GroupsService', 'ExperimentsService', function(ReportService, ErrorService, GroupsService, ExperimentsService){
     return {
         scope: {
+		saveReport: '=saveReport',
         },
         restrict: 'E',
         link: function(scope, el){
+		scope.expNames = ExperimentsService.experimentNames;
+		scope.groups = GroupsService.groups;
+		const calcUnusedExperiments = () => {
+			const usedExps = Array.from(new Set([].concat.apply([], scope.groups.map(g => g.experiments))));
+			const unusedExps = scope.expNames.filter(n => !usedExps.includes(n));
+			scope.unusedExps = unusedExps.length !== 0;
+		};
+		scope.$watchCollection('expNames', (names) => {calcUnusedExperiments();});
+		scope.$watch('groups', () => {calcUnusedExperiments();}, true);
+
             // sends the request to lock the report
             scope.lockReport = () => {
-                return ReportService.lockReport()
-                .then(() => {
-                    window.location.reload();
-                })
+		return scope.saveReport()
+		.then(() => ReportService.lockReport())
+                .then(() => {window.location.reload();})
                 .catch(e => {
                     ErrorService.logError(e, `Could not lock the report.`);
                 });
             }
         },
         template: `
-<bootstrap-modal dom-id='lockReportModal' button-submit-text='Lock' button-submit-func='lockReport'>
+<div>
+<bootstrap-modal dom-id='lockReportModal' button-submit-text='Lock' button-submit-func='lockReport' ng-if='!unusedExps'>
     <b-title>
         Lock Report
     </b-title>
@@ -54,6 +65,16 @@ angular.module('reportApp')
         <p>In order to do lock your report, your experiments will be locked as well, if they are not already (they will not be able to be edited or deleted).</p>
     </b-content>
 </bootstrap-modal>
+<bootstrap-modal dom-id='lockReportModal' ng-if='unusedExps'>
+    <b-title>
+        Lock Report
+    </b-title>
+    <b-content>
+        <p>There are experiments that haven't been added to a group.</p>
+        <p>Please make sure every experiment is in a group, or remove experiments from the report that aren't needed.</p>
+    </b-content>
+</bootstrap-modal>
+</div>
 `
     };
 }]);
diff --git a/beat/web/reports/static/reports/app/directives/panelExperiments.js b/beat/web/reports/static/reports/app/directives/panelExperiments.js
index c831da8272ad0bfa0cbf3e34752e01d3f2b690a7..882c2d236fad589b92edc8ce791a0f77d106e52e 100644
--- a/beat/web/reports/static/reports/app/directives/panelExperiments.js
+++ b/beat/web/reports/static/reports/app/directives/panelExperiments.js
@@ -27,7 +27,7 @@
  *  a table of experiments in the group, their databases/protocols, and aliases.
  *  Also has a menu for adding (compatible) experiments to the group.
  */
-angular.module('reportApp').directive("groupPanelExperiments", ['GroupsService', 'ExperimentsService', 'UrlService', function(GroupsService, ExperimentsService, UrlService){
+angular.module('reportApp').directive("groupPanelExperiments", ['GroupsService', 'ExperimentsService', 'UrlService', 'ReportService', function(GroupsService, ExperimentsService, UrlService, ReportService){
     return {
         scope: {
             group: '='
@@ -35,6 +35,7 @@ angular.module('reportApp').directive("groupPanelExperiments", ['GroupsService',
         link: function(scope){
             scope.experiments = ExperimentsService.experiments;
             scope.dropdownId = `${scope.group.name}_exp_add_dropdown`;
+	    scope.accessMap = ReportService.accessMap;
 
             scope.getExpName = (expName) => scope.experiments[expName] ? expName : expName.split('/').pop();
             const getExp = (expName) => scope.experiments[expName] || scope.experiments[expName.split('/').pop()];
@@ -137,7 +138,10 @@ angular.module('reportApp').directive("groupPanelExperiments", ['GroupsService',
                     </td>
                     <td ng-if='!isViewmode()'><input ng-model='group.aliases[expName]' ng-model-options="{ debounce: 500 }"></input></td>
                     <td ng-if='isViewmode()'><span>{{ group.aliases[expName] }}</span></td>
-                    <td><a href='{{ getExpUrl(expName) }}'>{{ getExpName(expName) }}</a></td>
+		    <td>
+			<a ng-if='accessMap[expName]' href='{{ getExpUrl(expName) }}'>{{ getExpName(expName) }}</a>
+			<i ng-if='!accessMap[expName]'><small>experiment not accessible for current user</small></i>
+		    </td>
                     <td>
                         <span ng-repeat='db in getExpDatabases(expName)'>
                             <a href='{{ getDatabaseUrl(db.split("@")[0]) }}'>{{ db }}</a>
diff --git a/beat/web/reports/static/reports/app/directives/save.js b/beat/web/reports/static/reports/app/directives/save.js
index d4231f155b9fbf4d260a4a4fad86d7bbed6a6502..cb0cfdda65a30d2073a877fa1b6da248e1f0d6ef 100644
--- a/beat/web/reports/static/reports/app/directives/save.js
+++ b/beat/web/reports/static/reports/app/directives/save.js
@@ -29,31 +29,11 @@ angular.module('reportApp')
 .directive("reportSave", ['GroupsService', 'ReportService', 'reportFactory', 'ErrorService', 'ExperimentsService', function(GroupsService, ReportService, reportFactory, ErrorService, ExperimentsService){
     return {
         restrict: 'A',
-        link: function(scope, el){
-
-            const saveReport = () => {
-                // save the serialized group data...
-                // the rest of the state is reconstructed from it and the URL
-                let saveData = {
-                    content: {
-                        'groups': GroupsService.serializeGroups()
-                    }
-                };
-
-                return reportFactory.removeExperiments(ExperimentsService.cachedDeletedExperiments)
-                .then(() => reportFactory.updateReport(ReportService.author, ReportService.name, saveData, ''))
-                .then(() => {
-                    const lastEditedEl = document.querySelector('.lastEdited');
-                    lastEditedEl.classList.remove('lastEditedAnimating');
-                    void lastEditedEl.offsetWidth;
-                    lastEditedEl.classList.add('lastEditedAnimating');
-                })
-                .catch(e => {
-                    ErrorService.logError(e, `Could not save the report.`);
-                });
-            };
-
-            el.bind('click', saveReport);
+	scope: {
+	    saveReport: '=saveReport',
+	},
+        link: function(scope, el, attrs){
+            el.bind('click', scope.saveReport);
         },
     };
 }]);
diff --git a/beat/web/reports/static/reports/app/directives/tableItem.js b/beat/web/reports/static/reports/app/directives/tableItem.js
index 99249203d1e3e7a3c7cad01f78dc2a371c5ecc3e..1a9cae99a0efb168e4d0f57728e9d2c9c30df0ce 100644
--- a/beat/web/reports/static/reports/app/directives/tableItem.js
+++ b/beat/web/reports/static/reports/app/directives/tableItem.js
@@ -27,7 +27,7 @@
  *  manage this table's selected cols and float precision
  */
 angular.module('reportApp')
-.directive("groupTableItem", ['GroupsService', 'ExperimentsService', 'UrlService', function(GroupsService, ExperimentsService, UrlService){
+.directive("groupTableItem", ['GroupsService', 'ExperimentsService', 'UrlService', 'ReportService', function(GroupsService, ExperimentsService, UrlService, ReportService){
     return {
         scope: {
             group: '=',
@@ -35,6 +35,8 @@ angular.module('reportApp')
             content: '='
         },
         link: function(scope){
+	    // access map for experiments
+    	    scope.accessMap = ReportService.accessMap;
             // aliases
             scope.fields = scope.content.fields;
             // ids
@@ -282,10 +284,10 @@ angular.module('reportApp')
                     <tbody>
                         <tr ng-repeat="exp in group.experiments | orderBy:sortFunc:sortField.isReversed">
                             <td ng-repeat='field in fields'>
-                                <a ng-if='$index == 0 && getExperimentUrl(exp)' href='{{ getExperimentUrl(exp) }}'>
+                                <a ng-if='$index == 0 && getExperimentUrl(exp) && accessMap[exp]' href='{{ getExperimentUrl(exp) }}'>
                                     {{ getFieldVal(exp, field) }}
                                 </a>
-                                <span ng-if='!$index == 0 || !getExperimentUrl(exp)'>
+                                <span ng-if='!$index == 0 || !getExperimentUrl(exp) || !accessMap[exp]'>
                                     {{ getFieldVal(exp, field) }}
                                 </span>
                             </td>
diff --git a/beat/web/reports/static/reports/app/services/reportService.js b/beat/web/reports/static/reports/app/services/reportService.js
index 90240945348a60332096885e2afe7fcc4197d8c2..cedaa6791717058199d133d166b2753b7ad5ae57 100644
--- a/beat/web/reports/static/reports/app/services/reportService.js
+++ b/beat/web/reports/static/reports/app/services/reportService.js
@@ -50,6 +50,8 @@ angular.module('reportApp').factory('ReportService', ['GroupsService', 'plotterF
         rs.number = report.number;
         rs.author = report.author;
         rs.name = report.name.split('/').length > 1 ? report.name.split('/')[1] : null;
+        rs.accessMap = report.experiments.reduce((o, expName, i) =>
+	    ({...o, [expName]: report.experiment_access_map[i]}), {});
 
         // start up our GroupsService
         GroupsService.loadGroups(report.content.groups);
diff --git a/beat/web/reports/templates/reports/panels/actions.html b/beat/web/reports/templates/reports/panels/actions.html
index 823d722c81f164b3b71c2af04373e4465555447a..38e4be3a35b1c6c4f5311d488ee4de337742d446 100644
--- a/beat/web/reports/templates/reports/panels/actions.html
+++ b/beat/web/reports/templates/reports/panels/actions.html
@@ -22,7 +22,7 @@
 {% load report_tags %}
 
 {% with object.get_status_display as status %}
-<div class="btn-group btn-group-sm action-buttons pull-right">
+<div class="btn-group btn-group-sm action-buttons pull-right" ng-controller='GroupsController as vm'>
 
   {% if display_count %}
   <!-- Experiment count, works for all -->
@@ -39,7 +39,7 @@
   {% if display_count %}
   <a class="btn btn-default btn-edit" href="{{ object.get_author_absolute_url }}" data-toggle="tooltip" data-placement="bottom" title="Edit"><i class="fa fa-edit fa-lg"></i></a>
   {% else %}
-  <a id="save-button" class="btn btn-default btn-save" data-toggle="tooltip" data-placement="bottom" title="Save" report-save><i class="fa fa-floppy-o fa-lg"></i></a>
+  <a id="save-button" class="btn btn-default btn-save" data-toggle="tooltip" data-placement="bottom" title="Save" report-save save-report='vm.saveReport'><i class="fa fa-floppy-o fa-lg"></i></a>
   <span  class="btn btn-default" data-toggle='modal' data-target='#lockReportModal'>
 	  <a class="btn-report" data-toggle="tooltip" data-placement="bottom" title="Lock">
 		  <i class="fa fa-lock fa-lg"></i>
@@ -65,7 +65,7 @@
   <a class="btn btn-default btn-view" href="{{ object.get_absolute_url }}" data-toggle="tooltip" data-placement="bottom" title="Review"><i class="fa fa-arrow-circle-right fa-lg"></i></a>
 
   {% if not display_count and status == 'Editable' %}
-  <report-lock></report-lock>
+  <report-lock save-report='vm.saveReport'></report-lock>
   {% endif %}
 
   {% if not display_count and status == 'Locked' %}
diff --git a/beat/web/reports/tests.py b/beat/web/reports/tests.py
index d68353be41d4a80053f771f9c427e7140c79f51f..65781d8639fdd6311da6fc7a809910af4cd32437 100755
--- a/beat/web/reports/tests.py
+++ b/beat/web/reports/tests.py
@@ -891,6 +891,7 @@ class EditableReportRetrievalTestCase(ReportTestCase):
             "status": "editable",
             "creation_date": self.report.creation_date.isoformat(),
             "publication_date": None,
+            "experiment_access_map": [],
             "experiments": [],
             "content": {},
             "analyzer": None,
@@ -942,6 +943,7 @@ class LockedReportRetrievalTestCase(ReportTestCase):
             "status": "locked",
             "creation_date": self.report.creation_date.isoformat(),
             "publication_date": None,
+            "experiment_access_map": [True],
             "experiments": [self.experiment_analyzer1.fullname()],
             "content": {},
             "analyzer": None,
@@ -989,6 +991,7 @@ class PublishedReportRetrievalTestCase(ReportTestCase):
             "status": "published",
             "creation_date": self.report.creation_date.isoformat(),
             "publication_date": self.report.publication_date.isoformat(),
+            "experiment_access_map": [False],
             "experiments": [self.experiment_analyzer1.fullname()],
             "content": {},
             "analyzer": None,
@@ -1010,6 +1013,7 @@ class PublishedReportRetrievalTestCase(ReportTestCase):
             "status": "published",
             "creation_date": self.report.creation_date.isoformat(),
             "publication_date": self.report.publication_date.isoformat(),
+            "experiment_access_map": [True],
             "experiments": [self.experiment_analyzer1.fullname()],
             "content": {},
             "analyzer": None,
@@ -1031,6 +1035,7 @@ class PublishedReportRetrievalTestCase(ReportTestCase):
             "is_owner": False,
             "author": self.johndoe.username,
             "status": "published",
+            "experiment_access_map": [False],
             "creation_date": self.report.creation_date.isoformat(),
             "publication_date": self.report.publication_date.isoformat(),
             "experiments": [self.experiment_analyzer1.fullname()],
diff --git a/beat/web/ui/templates/ui/doc_editor.html b/beat/web/ui/templates/ui/doc_editor.html
index eeb862327d2e406c25134fac10b5a359522578cd..a8490fe3bf6d2cf3c12a6154461dcbd2347e2efc 100644
--- a/beat/web/ui/templates/ui/doc_editor.html
+++ b/beat/web/ui/templates/ui/doc_editor.html
@@ -1,21 +1,21 @@
 {% comment %}
  * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/
  * Contact: beat.support@idiap.ch
- * 
+ *
  * This file is part of the beat.web module of the BEAT platform.
- * 
+ *
  * Commercial License Usage
  * Licensees holding valid commercial BEAT licenses may use this file in
  * accordance with the terms contained in a written agreement between you
  * and Idiap. For further information contact tto@idiap.ch
- * 
+ *
  * Alternatively, this file may be used under the terms of the GNU Affero
  * Public License version 3 as published by the Free Software and appearing
  * in the file LICENSE.AGPL included in the packaging of this file.
  * The BEAT platform is distributed in the hope that it will be useful, but
  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  * or FITNESS FOR A PARTICULAR PURPOSE.
- * 
+ *
  * You should have received a copy of the GNU Affero Public License along
  * with the BEAT platform. If not, see http://www.gnu.org/licenses/.
 {% endcomment %}
@@ -40,6 +40,13 @@
     <div id="description-edition" style="display:none;">
       <form id="description-form" method="post" action="" class="form">
         {% csrf_token %}
+	{% if url_name == 'api_reports:object' %}
+        <div class="form-group">
+		<div class="alert alert-warning">
+			<i class="fa fa-warning"></i> Saving changes to the documentation will discard any unsaved changes on the rest of the report. Make sure to save the report before editing this documentation.
+		</div>
+        </div>
+	{% endif %}
         <div class="form-group">
           <label for="short_description">Short description</label>
           <input maxlength="100" size="80" class="form-control" id="short-description" value="{{ object.short_description }}">