mirror of
https://github.com/sismics/docs.git
synced 2024-11-22 22:07:56 +01:00
#20: Display logs on documents
This commit is contained in:
parent
ea4e3fd8f2
commit
6add34bb33
@ -75,6 +75,7 @@ public class AuditLogDao {
|
|||||||
if (criteria.getUserId() != null) {
|
if (criteria.getUserId() != null) {
|
||||||
StringBuilder sb0 = new StringBuilder(" (l.LOG_IDENTITY_C = :userId and l.LOG_CLASSENTITY_C = 'User' ");
|
StringBuilder sb0 = new StringBuilder(" (l.LOG_IDENTITY_C = :userId and l.LOG_CLASSENTITY_C = 'User' ");
|
||||||
sb0.append(" or l.LOG_IDENTITY_C in (select t.TAG_ID_C from T_TAG t where t.TAG_IDUSER_C = :userId) and l.LOG_CLASSENTITY_C = 'Tag' ");
|
sb0.append(" or l.LOG_IDENTITY_C in (select t.TAG_ID_C from T_TAG t where t.TAG_IDUSER_C = :userId) and l.LOG_CLASSENTITY_C = 'Tag' ");
|
||||||
|
// Show only logs from owned documents, ACL are lost on delete
|
||||||
sb0.append(" or l.LOG_IDENTITY_C in (select d.DOC_ID_C from T_DOCUMENT d where d.DOC_IDUSER_C = :userId) and l.LOG_CLASSENTITY_C = 'Document') ");
|
sb0.append(" or l.LOG_IDENTITY_C in (select d.DOC_ID_C from T_DOCUMENT d where d.DOC_IDUSER_C = :userId) and l.LOG_CLASSENTITY_C = 'Document') ");
|
||||||
criteriaList.add(sb0.toString());
|
criteriaList.add(sb0.toString());
|
||||||
parameterMap.put("userId", criteria.getUserId());
|
parameterMap.put("userId", criteria.getUserId());
|
||||||
|
@ -45,7 +45,7 @@ public class AuditLogResource extends BaseResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// On a document or a user?
|
// On a document or a user?
|
||||||
PaginatedList<AuditLogDto> paginatedList = PaginatedLists.create(100, 0);
|
PaginatedList<AuditLogDto> paginatedList = PaginatedLists.create(20, 0);
|
||||||
SortCriteria sortCriteria = new SortCriteria(1, true);
|
SortCriteria sortCriteria = new SortCriteria(1, true);
|
||||||
AuditLogCriteria criteria = new AuditLogCriteria();
|
AuditLogCriteria criteria = new AuditLogCriteria();
|
||||||
if (documentId == null) {
|
if (documentId == null) {
|
||||||
|
@ -57,6 +57,7 @@ import com.sismics.rest.exception.ServerException;
|
|||||||
import com.sismics.rest.util.ValidationUtil;
|
import com.sismics.rest.util.ValidationUtil;
|
||||||
import com.sismics.util.mime.MimeType;
|
import com.sismics.util.mime.MimeType;
|
||||||
import com.sismics.util.mime.MimeTypeUtil;
|
import com.sismics.util.mime.MimeTypeUtil;
|
||||||
|
import com.sun.jersey.api.client.ClientResponse.Status;
|
||||||
import com.sun.jersey.multipart.FormDataBodyPart;
|
import com.sun.jersey.multipart.FormDataBodyPart;
|
||||||
import com.sun.jersey.multipart.FormDataParam;
|
import com.sun.jersey.multipart.FormDataParam;
|
||||||
|
|
||||||
@ -417,7 +418,7 @@ public class FileResource extends BaseResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (NoResultException e) {
|
} catch (NoResultException e) {
|
||||||
throw new ClientException("FileNotFound", MessageFormat.format("File not found: {0}", fileId));
|
return Response.status(Status.NOT_FOUND).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -461,7 +462,7 @@ public class FileResource extends BaseResource {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new ServerException("FileError", "Error while reading the file", e);
|
return Response.status(Status.SERVICE_UNAVAILABLE).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response.ok(stream)
|
return Response.ok(stream)
|
||||||
|
@ -4,11 +4,18 @@
|
|||||||
* Document view controller.
|
* Document view controller.
|
||||||
*/
|
*/
|
||||||
angular.module('docs').controller('DocumentView', function ($scope, $state, $stateParams, $location, $dialog, $modal, Restangular, $upload, $q) {
|
angular.module('docs').controller('DocumentView', function ($scope, $state, $stateParams, $location, $dialog, $modal, Restangular, $upload, $q) {
|
||||||
// Load data from server
|
// Load document data from server
|
||||||
Restangular.one('document', $stateParams.id).get().then(function(data) {
|
Restangular.one('document', $stateParams.id).get().then(function(data) {
|
||||||
$scope.document = data;
|
$scope.document = data;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Load audit log data from server
|
||||||
|
Restangular.one('auditlog').get({
|
||||||
|
document: $stateParams.id
|
||||||
|
}).then(function(data) {
|
||||||
|
$scope.logs = data.logs;
|
||||||
|
});
|
||||||
|
|
||||||
// Watch for ACLs change and group them for easy displaying
|
// Watch for ACLs change and group them for easy displaying
|
||||||
$scope.$watch('document.acls', function(acls) {
|
$scope.$watch('document.acls', function(acls) {
|
||||||
$scope.acls = _.groupBy(acls, function(acl) {
|
$scope.acls = _.groupBy(acls, function(acl) {
|
||||||
|
15
docs-web/src/main/webapp/src/app/docs/directive/AuditLog.js
Normal file
15
docs-web/src/main/webapp/src/app/docs/directive/AuditLog.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Audit log directive.
|
||||||
|
*/
|
||||||
|
angular.module('docs').directive('auditLog', function() {
|
||||||
|
return {
|
||||||
|
restrict: 'E',
|
||||||
|
templateUrl: 'partial/docs/directive.auditlog.html',
|
||||||
|
replace: true,
|
||||||
|
scope: {
|
||||||
|
logs: '='
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
@ -62,6 +62,7 @@
|
|||||||
<script src="app/docs/filter/Shorten.js" type="text/javascript"></script>
|
<script src="app/docs/filter/Shorten.js" type="text/javascript"></script>
|
||||||
<script src="app/docs/directive/File.js" type="text/javascript"></script>
|
<script src="app/docs/directive/File.js" type="text/javascript"></script>
|
||||||
<script src="app/docs/directive/SelectTag.js" type="text/javascript"></script>
|
<script src="app/docs/directive/SelectTag.js" type="text/javascript"></script>
|
||||||
|
<script src="app/docs/directive/AuditLog.js" type="text/javascript"></script>
|
||||||
<script src="app/docs/directive/InlineEdit.js" type="text/javascript"></script>
|
<script src="app/docs/directive/InlineEdit.js" type="text/javascript"></script>
|
||||||
<!-- endref -->
|
<!-- endref -->
|
||||||
</head>
|
</head>
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
<table class="table">
|
||||||
|
<tr ng-repeat="log in logs">
|
||||||
|
<td>{{ log.create_date | date: 'yyyy-MM-dd HH:mm' }}</td>
|
||||||
|
<td>
|
||||||
|
{{ log.class }}
|
||||||
|
<span ng-switch="log.type">
|
||||||
|
<span ng-switch-when="CREATE">created</span>
|
||||||
|
<span ng-switch-when="UPDATE">updated</span>
|
||||||
|
<span ng-switch-when="DELETE">deleted</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span ng-switch="log.class">
|
||||||
|
:
|
||||||
|
<span ng-switch-when="Document">
|
||||||
|
<a ng-href="#/document/view/{{ log.target }}">{{ log.message }}</a>
|
||||||
|
</span>
|
||||||
|
<span ng-switch-when="File">
|
||||||
|
<a ng-href="#/document/view/{{ log.message }}/file/{{ log.target }}">Open</a>
|
||||||
|
</span>
|
||||||
|
<span ng-switch-when="Acl">
|
||||||
|
{{ log.message }}
|
||||||
|
</span>
|
||||||
|
<span ng-switch-when="Tag">
|
||||||
|
<a href="#/tag">{{ log.message }}</a>
|
||||||
|
</span>
|
||||||
|
<span ng-switch-when="User">
|
||||||
|
<a href="#/settings/account">{{ log.message }}</a>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
@ -1,61 +1,58 @@
|
|||||||
<img src="img/loader.gif" ng-show="!app" />
|
<img src="img/loader.gif" ng-show="!app" />
|
||||||
|
|
||||||
<div ng-show="app">
|
<div ng-show="app">
|
||||||
<div class="row upload-zone" ng-model="dropFiles" ng-file-drop drag-over-class="bg-success"
|
<div class="well">
|
||||||
ng-multiple="true" allow-dir="false" accept="image/*,application/pdf,application/zip" ng-file-change="fileDropped($files, $event, $rejectedFiles)">
|
<h3>Quick upload</h3>
|
||||||
<div class="col-xs-6 col-sm-4 col-md-3 col-lg-2 text-center" ng-repeat="file in files">
|
<div class="row upload-zone" ng-model="dropFiles" ng-file-drop drag-over-class="bg-success"
|
||||||
<div class="thumbnail" ng-class="{ 'thumbnail-checked': file.checked }" ng-if="file.id">
|
ng-multiple="true" allow-dir="false" accept="image/*,application/pdf,application/zip" ng-file-change="fileDropped($files, $event, $rejectedFiles)">
|
||||||
<a ng-click="openFile(file)">
|
<div class="col-xs-6 col-sm-4 col-md-3 col-lg-2 text-center" ng-repeat="file in files">
|
||||||
<img class="thumbnail-file" ng-src="../api/file/{{ file.id }}/data?size=thumb" tooltip="{{ file.mimetype }}" tooltip-placement="top" />
|
<div class="thumbnail" ng-class="{ 'thumbnail-checked': file.checked }" ng-if="file.id">
|
||||||
</a>
|
<a ng-click="openFile(file)">
|
||||||
<div class="caption pointer" ng-click="file.checked = !file.checked">
|
<img class="thumbnail-file" ng-src="../api/file/{{ file.id }}/data?size=thumb" tooltip="{{ file.mimetype }}" tooltip-placement="top" />
|
||||||
<div class="pull-left">
|
</a>
|
||||||
<input type="checkbox" ng-model="file.checked" />
|
<div class="caption pointer" ng-click="file.checked = !file.checked">
|
||||||
|
<div class="pull-left">
|
||||||
|
<input type="checkbox" ng-model="file.checked" />
|
||||||
|
</div>
|
||||||
|
<div class="pull-right">
|
||||||
|
<button class="btn btn-danger" ng-click="deleteFile($event, file)"><span class="glyphicon glyphicon-trash"></span></button>
|
||||||
|
</div>
|
||||||
|
<div class="clearfix"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right">
|
</div>
|
||||||
<button class="btn btn-danger" ng-click="deleteFile($event, file)"><span class="glyphicon glyphicon-trash"></span></button>
|
|
||||||
|
<div class="thumbnail" ng-if="!file.id">
|
||||||
|
<p class="text-center lead">
|
||||||
|
{{ file.status }}
|
||||||
|
</p>
|
||||||
|
<div class="caption">
|
||||||
|
<progressbar value="file.progress" class="progress-info active"></progressbar>
|
||||||
</div>
|
</div>
|
||||||
<div class="clearfix"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="thumbnail" ng-if="!file.id">
|
<p class="text-center well-lg" ng-if="files.length == 0">
|
||||||
<p class="text-center lead">
|
<span class="glyphicon glyphicon-move"></span>
|
||||||
{{ file.status }}
|
Drag & drop files here to upload
|
||||||
</p>
|
</p>
|
||||||
<div class="caption">
|
|
||||||
<progressbar value="file.progress" class="progress-info active"></progressbar>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="text-center well-lg" ng-if="files.length == 0">
|
<div class="btn-group" ng-show="checkedFiles().length > 0">
|
||||||
<span class="glyphicon glyphicon-move"></span>
|
<button class="btn btn-primary" ng-click="addDocument()"><span class="glyphicon glyphicon-plus"></span> Add to new document</button>
|
||||||
Drag & drop files here to upload
|
</div>
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="btn-group" ng-show="checkedFiles().length > 0">
|
|
||||||
<button class="btn btn-primary" ng-click="addDocument()"><span class="glyphicon glyphicon-plus"></span> Add to new document</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ui-view="file"></div>
|
<div ui-view="file"></div>
|
||||||
|
|
||||||
<table class="table">
|
<div class="well">
|
||||||
<tr>
|
<h3>Latest activity</h3>
|
||||||
<th>Date</th>
|
<audit-log logs="logs" />
|
||||||
<th>Message</th>
|
</div>
|
||||||
</tr>
|
|
||||||
<tr ng-repeat="log in logs">
|
|
||||||
<td>{{ log.create_date | date: 'yyyy-MM-dd HH:mm' }}</td>
|
|
||||||
<td>{{ log.class }} {{ log.type }} {{ log.message }}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<div class="text-muted text-right">
|
<div class="text-muted text-right">
|
||||||
<ul class="list-inline">
|
<ul class="list-inline">
|
||||||
<li><strong>Version:</strong> {{ app.current_version }}</li>
|
<li><strong>Version:</strong> {{ app.current_version }}</li>
|
||||||
<li><strong>Memory:</strong> {{ app.free_memory / 1000000 | number: 0 }}/{{ app.total_memory / 1000000 | number: 0 }} MB</li>
|
<li><strong>Memory:</strong> {{ app.free_memory / 1000000 | number: 0 }}/{{ app.total_memory / 1000000 | number: 0 }} MB</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -135,6 +135,14 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</tab>
|
</tab>
|
||||||
|
|
||||||
|
<tab>
|
||||||
|
<tab-heading class="pointer">
|
||||||
|
<span class="glyphicon glyphicon-tasks"></span> Activity
|
||||||
|
</tab-heading>
|
||||||
|
|
||||||
|
<audit-log logs="logs" />
|
||||||
|
</tab>
|
||||||
</tabset>
|
</tabset>
|
||||||
|
|
||||||
<div ui-view="file"></div>
|
<div ui-view="file"></div>
|
||||||
|
@ -178,6 +178,12 @@ public class TestFileResource extends BaseJerseyTest {
|
|||||||
json = response.getEntity(JSONObject.class);
|
json = response.getEntity(JSONObject.class);
|
||||||
Assert.assertEquals("ok", json.getString("status"));
|
Assert.assertEquals("ok", json.getString("status"));
|
||||||
|
|
||||||
|
// Get the file data (not found)
|
||||||
|
fileResource = resource().path("/file/" + file1Id + "/data");
|
||||||
|
fileResource.addFilter(new CookieAuthenticationFilter(file1AuthenticationToken));
|
||||||
|
response = fileResource.get(ClientResponse.class);
|
||||||
|
Assert.assertEquals(Status.NOT_FOUND, Status.fromStatusCode(response.getStatus()));
|
||||||
|
|
||||||
// Check that files are deleted from FS
|
// Check that files are deleted from FS
|
||||||
storedFile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file1Id).toFile();
|
storedFile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file1Id).toFile();
|
||||||
java.io.File webFile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file1Id + "_web").toFile();
|
java.io.File webFile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file1Id + "_web").toFile();
|
||||||
|
Loading…
Reference in New Issue
Block a user