1
0
mirror of https://github.com/sismics/docs.git synced 2025-12-21 05:31:42 +00:00

Closes #83: Edit ACLs for tags in UI + batch for old DB

This commit is contained in:
jendib
2016-05-08 00:46:32 +02:00
parent b851fd0ecc
commit a55c55bbdb
23 changed files with 531 additions and 398 deletions

View File

@@ -13,36 +13,55 @@ angular.module('docs',
* Configuring modules.
*/
.config(function($stateProvider, $httpProvider, RestangularProvider) {
// Configuring UI Router
$stateProvider
.state('main', {
url: '',
views: {
'page': {
templateUrl: 'partial/docs/main.html',
controller: 'Main'
.state('main', {
url: '',
views: {
'page': {
templateUrl: 'partial/docs/main.html',
controller: 'Main'
}
}
}
})
.state('tag', {
url: '/tag',
views: {
'page': {
templateUrl: 'partial/docs/tag.html',
controller: 'Tag'
})
.state('tag', {
url: '/tag',
abstract: true,
views: {
'page': {
templateUrl: 'partial/docs/tag.html',
controller: 'Tag'
}
}
}
})
.state('settings', {
url: '/settings',
abstract: true,
views: {
'page': {
templateUrl: 'partial/docs/settings.html',
controller: 'Settings'
})
.state('tag.default', {
url: '',
views: {
'tag': {
templateUrl: 'partial/docs/tag.default.html'
}
}
}
})
})
.state('tag.edit', {
url: '/:id',
views: {
'tag': {
templateUrl: 'partial/docs/tag.edit.html',
controller: 'TagEdit'
}
}
})
.state('settings', {
url: '/settings',
abstract: true,
views: {
'page': {
templateUrl: 'partial/docs/settings.html',
controller: 'Settings'
}
}
})
.state('settings.default', {
url: '',
views: {
@@ -106,70 +125,70 @@ angular.module('docs',
}
}
})
.state('settings.user.edit', {
url: '/edit/:username',
views: {
'user': {
templateUrl: 'partial/docs/settings.user.edit.html',
controller: 'SettingsUserEdit'
}
.state('settings.user.edit', {
url: '/edit/:username',
views: {
'user': {
templateUrl: 'partial/docs/settings.user.edit.html',
controller: 'SettingsUserEdit'
}
})
.state('settings.user.add', {
url: '/add',
views: {
'user': {
templateUrl: 'partial/docs/settings.user.edit.html',
controller: 'SettingsUserEdit'
}
}
})
.state('settings.user.add', {
url: '/add',
views: {
'user': {
templateUrl: 'partial/docs/settings.user.edit.html',
controller: 'SettingsUserEdit'
}
})
}
})
.state('settings.group', {
url: '/group',
views: {
'settings': {
templateUrl: 'partial/docs/settings.group.html',
controller: 'SettingsGroup'
}
url: '/group',
views: {
'settings': {
templateUrl: 'partial/docs/settings.group.html',
controller: 'SettingsGroup'
}
})
.state('settings.group.edit', {
url: '/edit/:name',
views: {
'group': {
templateUrl: 'partial/docs/settings.group.edit.html',
controller: 'SettingsGroupEdit'
}
}
})
.state('settings.group.add', {
url: '/add',
views: {
'group': {
templateUrl: 'partial/docs/settings.group.edit.html',
controller: 'SettingsGroupEdit'
}
}
})
.state('settings.vocabulary', {
url: '/vocabulary',
views: {
'settings': {
templateUrl: 'partial/docs/settings.vocabulary.html',
controller: 'SettingsVocabulary'
}
}
})
.state('document', {
url: '/document',
abstract: true,
views: {
'page': {
templateUrl: 'partial/docs/document.html',
controller: 'Document'
})
.state('settings.group.edit', {
url: '/edit/:name',
views: {
'group': {
templateUrl: 'partial/docs/settings.group.edit.html',
controller: 'SettingsGroupEdit'
}
}
}
})
})
.state('settings.group.add', {
url: '/add',
views: {
'group': {
templateUrl: 'partial/docs/settings.group.edit.html',
controller: 'SettingsGroupEdit'
}
}
})
.state('settings.vocabulary', {
url: '/vocabulary',
views: {
'settings': {
templateUrl: 'partial/docs/settings.vocabulary.html',
controller: 'SettingsVocabulary'
}
}
})
.state('document', {
url: '/document',
abstract: true,
views: {
'page': {
templateUrl: 'partial/docs/document.html',
controller: 'Document'
}
}
})
.state('document.default', {
url: '',
views: {
@@ -179,17 +198,17 @@ angular.module('docs',
}
}
})
.state('document.default.search', {
url: '/search/:search'
})
.state('document.default.file', {
url: '/file/:fileId',
views: {
'file': {
controller: 'FileView'
}
.state('document.default.search', {
url: '/search/:search'
})
.state('document.default.file', {
url: '/file/:fileId',
views: {
'file': {
controller: 'FileView'
}
})
}
})
.state('document.add', {
url: '/add?files',
views: {
@@ -218,59 +237,68 @@ angular.module('docs',
}
}
})
.state('document.view.content', {
url: '/content',
views: {
'tab': {
templateUrl: 'partial/docs/document.view.content.html',
controller: 'DocumentViewContent'
}
.state('document.view.content', {
url: '/content',
views: {
'tab': {
templateUrl: 'partial/docs/document.view.content.html',
controller: 'DocumentViewContent'
}
})
.state('document.view.content.file', {
url: '/file/:fileId',
views: {
'file': {
controller: 'FileView'
}
}
})
.state('document.view.permissions', {
url: '/permissions',
views: {
'tab': {
templateUrl: 'partial/docs/document.view.permissions.html',
controller: 'DocumentViewPermissions'
}
}
})
.state('document.view.activity', {
url: '/activity',
views: {
'tab': {
templateUrl: 'partial/docs/document.view.activity.html',
controller: 'DocumentViewActivity'
}
}
})
.state('login', {
url: '/login',
views: {
'page': {
templateUrl: 'partial/docs/login.html',
controller: 'Login'
}
}
})
.state('user', {
url: '/user',
views: {
'page': {
templateUrl: 'partial/docs/usergroup.html',
controller: 'UserGroup'
})
.state('document.view.content.file', {
url: '/file/:fileId',
views: {
'file': {
controller: 'FileView'
}
}
}
})
})
.state('document.view.permissions', {
url: '/permissions',
views: {
'tab': {
templateUrl: 'partial/docs/document.view.permissions.html',
controller: 'DocumentViewPermissions'
}
}
})
.state('document.view.activity', {
url: '/activity',
views: {
'tab': {
templateUrl: 'partial/docs/document.view.activity.html',
controller: 'DocumentViewActivity'
}
}
})
.state('login', {
url: '/login',
views: {
'page': {
templateUrl: 'partial/docs/login.html',
controller: 'Login'
}
}
})
.state('user', {
url: '/user',
abstract: true,
views: {
'page': {
templateUrl: 'partial/docs/usergroup.html',
controller: 'UserGroup'
}
}
})
.state('user.default', {
url: '',
views: {
'sub': {
templateUrl: 'partial/docs/usergroup.default.html'
}
}
})
.state('user.profile', {
url: '/:username',
views: {
@@ -280,15 +308,24 @@ angular.module('docs',
}
}
})
.state('group', {
url: '/group',
views: {
'page': {
templateUrl: 'partial/docs/usergroup.html',
controller: 'UserGroup'
.state('group', {
url: '/group',
abstract: true,
views: {
'page': {
templateUrl: 'partial/docs/usergroup.html',
controller: 'UserGroup'
}
}
}
})
})
.state('group.default', {
url: '',
views: {
'sub': {
templateUrl: 'partial/docs/usergroup.default.html'
}
}
})
.state('group.profile', {
url: '/:name',
views: {
@@ -298,7 +335,6 @@ angular.module('docs',
}
}
});
// Configuring Restangular
RestangularProvider.setBaseUrl('../api');

View File

@@ -3,95 +3,5 @@
/**
* Document view permissions controller.
*/
angular.module('docs').controller('DocumentViewPermissions', function ($scope, $stateParams, Restangular, $q) {
// Watch for ACLs change and group them for easy displaying
$scope.$watch('document.acls', function(acls) {
$scope.acls = _.groupBy(acls, function(acl) {
return acl.id;
});
});
// Initialize add ACL
$scope.acl = { perm: 'READ' };
/**
* Delete an ACL.
*/
$scope.deleteAcl = function(acl) {
Restangular.one('acl/' + $stateParams.id + '/' + acl.perm + '/' + acl.id, null).remove().then(function () {
$scope.document.acls = _.reject($scope.document.acls, function(s) {
return angular.equals(acl, s);
});
});
};
/**
* Add an ACL.
*/
$scope.addAcl = function() {
// Compute ACLs to add
$scope.acl.source = $stateParams.id;
var acls = [];
if ($scope.acl.perm == 'READWRITE') {
acls = [{
source: $stateParams.id,
target: $scope.acl.target.name,
perm: 'READ',
type: $scope.acl.target.type
}, {
source: $stateParams.id,
target: $scope.acl.target.name,
perm: 'WRITE',
type: $scope.acl.target.type
}];
} else {
acls = [{
source: $stateParams.id,
target: $scope.acl.target.name,
perm: $scope.acl.perm,
type: $scope.acl.target.type
}];
}
// Add ACLs
_.each(acls, function(acl) {
Restangular.one('acl').put(acl).then(function(acl) {
if (_.isUndefined(acl.id)) {
return;
}
$scope.document.acls.push(acl);
$scope.document.acls = angular.copy($scope.document.acls);
});
});
// Reset form
$scope.acl = { perm: 'READ' };
};
/**
* Auto-complete on ACL target.
*/
$scope.getTargetAclTypeahead = function($viewValue) {
var deferred = $q.defer();
Restangular.one('acl/target/search')
.get({
search: $viewValue
}).then(function(data) {
var output = [];
// Add the type to use later
output.push.apply(output, _.map(data.users, function(user) {
user.type = 'USER';
return user;
}));
output.push.apply(output, _.map(data.groups, function(group) {
group.type = 'GROUP';
return group;
}));
// Send the data to the typeahead directive
deferred.resolve(output, true);
});
return deferred.promise;
};
angular.module('docs').controller('DocumentViewPermissions', function() {
});

View File

@@ -3,37 +3,14 @@
/**
* Tag controller.
*/
angular.module('docs').controller('Tag', function($scope, $dialog, Tag, Restangular) {
angular.module('docs').controller('Tag', function($scope, $dialog, Restangular) {
$scope.tag = { name: '', color: '#3a87ad' };
// Retrieve tags
Tag.tags().then(function(data) {
Restangular.one('tag/list').get().then(function(data) {
$scope.tags = data.tags;
});
// Retrieve tag stats
Restangular.one('tag/stats').get().then(function(data) {
$scope.stats = data.stats;
});
/**
* Returns total number of document from tag stats.
*/
$scope.getStatCount = function() {
return _.reduce($scope.stats, function(memo, stat) {
return memo + stat.count
}, 0);
};
/**
* Validate a tag name for duplicate.
*/
$scope.validateDuplicate = function(name) {
return !_.find($scope.tags, function(tag) {
return tag.name == name;
});
};
/**
* Add a tag.
*/
@@ -71,15 +48,6 @@ angular.module('docs').controller('Tag', function($scope, $dialog, Tag, Restangu
*/
$scope.updateTag = function(tag) {
// Update the server
return Restangular.one('tag', tag.id).post('', tag).then(function () {
// Update the stat object
var stat = _.find($scope.stats, function (t) {
return tag.id == t.id;
});
if (stat) {
_.extend(stat, tag);
}
});
return Restangular.one('tag', tag.id).post('', tag);
};
});

View File

@@ -0,0 +1,10 @@
'use strict';
/**
* Tag edit controller.
*/
angular.module('docs').controller('TagEdit', function($scope, $stateParams, Restangular) {
Restangular.one('tag', $stateParams.id).get().then(function(data) {
$scope.tag = data;
})
});

View File

@@ -0,0 +1,112 @@
'use strict';
/**
* ACL edit directive.
*/
angular.module('docs').directive('aclEdit', function() {
return {
restrict: 'E',
templateUrl: 'partial/docs/directive.acledit.html',
replace: true,
scope: {
source: '=',
acls: '=',
writable: '=',
creator: '='
},
controller: function($scope, Restangular, $q) {
// Watch for ACLs change and group them for easy displaying
$scope.$watch('acls', function(acls) {
$scope.groupedAcls = _.groupBy(acls, function(acl) {
return acl.id;
});
});
// Initialize add ACL
$scope.acl = { perm: 'READ' };
/**
* Delete an ACL.
*/
$scope.deleteAcl = function(acl) {
Restangular.one('acl/' + $scope.source + '/' + acl.perm + '/' + acl.id, null).remove().then(function () {
$scope.acls = _.reject($scope.acls, function(s) {
return angular.equals(acl, s);
});
});
};
/**
* Add an ACL.
*/
$scope.addAcl = function() {
// Compute ACLs to add
$scope.acl.source = $scope.source;
var acls = [];
if ($scope.acl.perm == 'READWRITE') {
acls = [{
source: $scope.source,
target: $scope.acl.target.name,
perm: 'READ',
type: $scope.acl.target.type
}, {
source: $scope.source,
target: $scope.acl.target.name,
perm: 'WRITE',
type: $scope.acl.target.type
}];
} else {
acls = [{
source: $scope.source,
target: $scope.acl.target.name,
perm: $scope.acl.perm,
type: $scope.acl.target.type
}];
}
// Add ACLs
_.each(acls, function(acl) {
Restangular.one('acl').put(acl).then(function(acl) {
if (_.isUndefined(acl.id)) {
return;
}
$scope.acls.push(acl);
$scope.acls = angular.copy($scope.acls);
});
});
// Reset form
$scope.acl = { perm: 'READ' };
};
/**
* Auto-complete on ACL target.
*/
$scope.getTargetAclTypeahead = function($viewValue) {
var deferred = $q.defer();
Restangular.one('acl/target/search')
.get({
search: $viewValue
}).then(function(data) {
var output = [];
// Add the type to use later
output.push.apply(output, _.map(data.users, function(user) {
user.type = 'USER';
return user;
}));
output.push.apply(output, _.map(data.groups, function(group) {
group.type = 'GROUP';
return group;
}));
// Send the data to the typeahead directive
deferred.resolve(output, true);
});
return deferred.promise;
};
},
link: function(scope, element, attr, ctrl) {
}
}
});

View File

@@ -13,9 +13,9 @@ angular.module('docs').directive('selectTag', function() {
ref: '@',
ngDisabled: '='
},
controller: function($scope, Tag) {
controller: function($scope, Restangular) {
// Retrieve tags
Tag.tags().then(function(data) {
Restangular.one('tag/list').get().then(function(data) {
$scope.allTags = data.tags;
});
@@ -48,7 +48,7 @@ angular.module('docs').directive('selectTag', function() {
if ($event) {
$event.preventDefault();
}
}
};
/**
* Remove a tag.

View File

@@ -1,18 +0,0 @@
'use strict';
/**
* Tag service.
*/
angular.module('docs').factory('Tag', function(Restangular) {
var tags = null;
return {
/**
* Returns tags.
* @param force If true, force reloading data
*/
tags: function(force) {
return Restangular.one('tag/list').get();
}
}
});

View File

@@ -55,6 +55,7 @@
<script src="app/docs/controller/document/FileModalView.js" type="text/javascript"></script>
<script src="app/docs/controller/Login.js" type="text/javascript"></script>
<script src="app/docs/controller/tag/Tag.js" type="text/javascript"></script>
<script src="app/docs/controller/tag/TagEdit.js" type="text/javascript"></script>
<script src="app/docs/controller/Navigation.js" type="text/javascript"></script>
<script src="app/docs/controller/settings/Settings.js" type="text/javascript"></script>
<script src="app/docs/controller/settings/SettingsDefault.js" type="text/javascript"></script>
@@ -73,7 +74,6 @@
<script src="app/docs/controller/usergroup/UserProfile.js" type="text/javascript"></script>
<script src="app/docs/controller/usergroup/GroupProfile.js" type="text/javascript"></script>
<script src="app/docs/service/User.js" type="text/javascript"></script>
<script src="app/docs/service/Tag.js" type="text/javascript"></script>
<script src="app/docs/filter/Newline.js" type="text/javascript"></script>
<script src="app/docs/filter/Shorten.js" type="text/javascript"></script>
<script src="app/docs/filter/Filesize.js" type="text/javascript"></script>
@@ -84,6 +84,7 @@
<script src="app/docs/directive/InlineEdit.js" type="text/javascript"></script>
<script src="app/docs/directive/ImgError.js" type="text/javascript"></script>
<script src="app/docs/directive/Acl.js" type="text/javascript"></script>
<script src="app/docs/directive/AclEdit.js" type="text/javascript"></script>
<!-- endref -->
</head>
<body>

View File

@@ -0,0 +1,61 @@
<div>
<table class="table">
<tr>
<th style="width: 40%">For</th>
<th style="width: 40%">Permission</th>
</tr>
<tr ng-repeat="(id, acl) in groupedAcls">
<td><acl data="acl[0]"></acl></td>
<td>
<span class="label label-default" style="margin-right: 6px;" ng-repeat="a in acl | orderBy: 'perm'">
{{ a.perm }}
<span ng-show="(creator != a.name && a.type == 'USER' || a.type != 'USER') && writable"
class="glyphicon glyphicon-remove pointer"
ng-click="deleteAcl(a)"></span>
</span>
</td>
</tr>
</table>
<div ng-show="writable">
<h4>Add a permission</h4>
<form name="aclForm" class="form-horizontal">
<div class="form-group">
<label class="col-sm-2 control-label" for="inputTarget">For</label>
<div class="col-sm-3">
<input required ng-maxlength="50" class="form-control" type="text" id="inputTarget"
placeholder="Search a user or group" name="target" ng-model="acl.target" autocomplete="off"
typeahead="target as target.name for target in getTargetAclTypeahead($viewValue) | filter: $viewValue"
typeahead-template-url="partial/docs/directive.typeahead.acl.html"
typeahead-wait-ms="200" />
</div>
<div class="col-sm-4">
<span class="btn btn-primary" ng-if="acl.target.type" ng-click="acl.target = null">
<acl data="acl.target"></acl>
</span>
</div>
</div>
<div class="form-group">
<label class=" col-sm-2 control-label" for="inputPermission">Permission</label>
<div class="col-sm-3">
<select class="form-control" ng-model="acl.perm" id="inputPermission">
<option value="READ">Can read</option>
<option value="READWRITE">Can edit</option>
</select>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary" ng-disabled="!acl.target.type" ng-click="addAcl()">
<span class="glyphicon glyphicon-plus"></span>
Add
</button>
</div>
</div>
</form>
</div>
</div>

View File

@@ -1,59 +1,4 @@
<table class="table">
<tr>
<th style="width: 40%">For</th>
<th style="width: 40%">Permission</th>
</tr>
<tr ng-repeat="(id, acl) in acls">
<td><acl data="acl[0]"></acl></td>
<td>
<span class="label label-default" style="margin-right: 6px;" ng-repeat="a in acl | orderBy: 'perm'">
{{ a.perm }}
<span ng-show="(document.creator != a.name && a.type == 'USER' || a.type != 'USER') && document.writable"
class="glyphicon glyphicon-remove pointer"
ng-click="deleteAcl(a)"></span>
</span>
</td>
</tr>
</table>
<div ng-show="document.writable">
<h4>Add a permission</h4>
<form name="aclForm" class="form-horizontal">
<div class="form-group">
<label class="col-sm-2 control-label" for="inputTarget">For</label>
<div class="col-sm-3">
<input required ng-maxlength="50" class="form-control" type="text" id="inputTarget"
placeholder="Search a user or group" name="target" ng-model="acl.target" autocomplete="off"
typeahead="target as target.name for target in getTargetAclTypeahead($viewValue) | filter: $viewValue"
typeahead-template-url="partial/docs/directive.typeahead.acl.html"
typeahead-wait-ms="200" />
</div>
<div class="col-sm-4">
<span class="btn btn-primary" ng-if="acl.target.type" ng-click="acl.target = null">
<acl data="acl.target"></acl>
</span>
</div>
</div>
<div class="form-group">
<label class=" col-sm-2 control-label" for="inputPermission">Permission</label>
<div class="col-sm-3">
<select class="form-control" ng-model="acl.perm" id="inputPermission">
<option value="READ">Can read</option>
<option value="READWRITE">Can edit</option>
</select>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary" ng-disabled="!acl.target.type" ng-click="addAcl()">
<span class="glyphicon glyphicon-plus"></span>
Add
</button>
</div>
</div>
</form>
</div>
<acl-edit source="document.id"
acls="document.acls"
writable="document.writable"
creator="document.creator"></acl-edit>

View File

@@ -0,0 +1,6 @@
<h1>Tags</h1>
<p><strong>Tags</strong> are labels associated to documents.</p>
<p>A document can be tagged by multiple tags, and a tag can be applied to multiple documents.</p>
<p>Using the <span class="glyphicon glyphicon-user"></span> button, you can edit permissions on a tag.</p>
<p>If a tag can be read by another user or group, associated documents can also be read by those people.</p>
<p>For example, tag your company documents with a tag <span class="label label-info">MyCompany</span> and add the permission <strong>Read</strong> to a group <span class="btn btn-default">employees</span></p>

View File

@@ -0,0 +1,4 @@
<acl-edit source="tag.id"
acls="tag.acls"
writable="tag.writable"
creator="tag.creator"></acl-edit>

View File

@@ -5,10 +5,9 @@
<p class="input-group" ng-class="{ 'has-error': !tagForm.name.$valid }">
<span colorpicker class="input-group-addon btn btn-default" data-color="#3a87ad" ng-model="tag.color" ng-style="{ 'background': tag.color }">&nbsp;</span>
<input type="text" name="name" placeholder="New tag" class="form-control"
ng-maxlength="36" required ng-model="tag.name" ui-validate="{duplicate: 'validateDuplicate($value)', space: '!$value || $value.indexOf(\' \') == -1' }">
ng-maxlength="36" required ng-model="tag.name" ui-validate="{ space: '!$value || $value.indexOf(\' \') == -1' }">
<span class="input-group-addon btn btn-primary" ng-disabled="!tagForm.$valid" ng-click="addTag()">Add</span>
</p>
<span class="help-block" ng-show="tagForm.name.$error.duplicate">This tag already exists</span>
<span class="help-block" ng-show="tagForm.name.$error.space">Space are not allowed</span>
</form>
@@ -31,22 +30,15 @@
</select>
</td>
<td class="col-xs-1"><span colorpicker class="btn" on-hide="updateTag(tag)" data-color="" ng-model="tag.color" ng-style="{ 'background': tag.color }">&nbsp;</span></td>
<td class="col-xs-1"><button class="btn btn-danger pull-right" ng-click="deleteTag(tag)"><span class="glyphicon glyphicon-trash"></span></button></td>
<td class="col-xs-1"><a href="#/tag/{{ tag.id }}" class="btn btn-default pull-right" title="Edit permissions"><span class="glyphicon glyphicon-user"></span></a></td>
<td class="col-xs-1"><button class="btn btn-danger pull-right" ng-click="deleteTag(tag)" title="Delete this tag"><span class="glyphicon glyphicon-trash"></span></button></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="col-md-8" ng-if="stats.length >= 0">
<h1>{{ tags.length }} <small>tag{{ tags.length > 1 ? 's' : '' }}</small></h1>
<dl class="dl-horizontal" ng-repeat="stat in stats | orderBy: '-count'">
<dt>{{ stat.name }} <span class="badge badge-info" ng-style="{ 'background': stat.color }">{{ stat.count }}</span></dt>
<dd><progressbar value="stat.count / getStatCount() * 100" class="progress-info"></progressbar></dd>
</dl>
</div>
<div class="col-md-8" ng-if="!stats">
<img src="img/loader.gif" />
<div class="col-md-8">
<div ui-view="tag"></div>
</div>
</div>

View File

@@ -0,0 +1,2 @@
<h1>Users & Groups</h1>
<p>Here you can view informations about users and groups.</p>