Adding admin roles page

stable
Jesús Espino 2014-07-29 11:14:05 +02:00
parent 0369a35228
commit 62f31aeab3
13 changed files with 457 additions and 35 deletions

View File

@ -97,6 +97,9 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide) ->
$routeProvider.when("/project/:pslug/admin/memberships",
{templateUrl: "/partials/admin-memberships.html"})
$routeProvider.when("/project/:pslug/admin/roles",
{templateUrl: "/partials/admin-roles.html"})
# User settings
$routeProvider.when("/project/:pslug/user-settings/user-profile",
{templateUrl: "/partials/user-profile.html"})

View File

@ -0,0 +1,329 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program 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. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# File: modules/admin/memberships.coffee
###
taiga = @.taiga
mixOf = @.taiga.mixOf
bindOnce = @.taiga.bindOnce
module = angular.module("taigaAdmin")
#############################################################################
## Project Roles Controller
#############################################################################
class RolesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.FiltersMixin)
@.$inject = [
"$scope",
"$rootScope",
"$tgRepo",
"$tgConfirm",
"$tgResources",
"$routeParams",
"$q",
"$tgLocation"
]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location) ->
_.bindAll(@)
@scope.sectionName = "Roles" #i18n
@scope.project = {}
promise = @.loadInitialData()
promise.then null, ->
console.log "FAIL" #TODO
loadProject: ->
return @rs.projects.get(@scope.projectId).then (project) =>
@scope.project = project
return project
loadRoles: ->
return @rs.roles.list(@scope.projectId).then (data) =>
@scope.roles = data
@scope.role = @scope.roles[0]
return data
loadInitialData: ->
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
@scope.projectId = data.project
return data
return promise.then(=> @.loadProject())
.then(=> @.loadUsersAndRoles())
.then(=> @.loadRoles())
setRole: (role) ->
@scope.role = role
@scope.$broadcast("role:changed", @scope.role)
delete: ->
# TODO: i18n
title = "Delete Role"
subtitle = @scope.role.name
@confirm.ask(title, subtitle).then =>
promise = @repo.remove(@scope.role)
promise.then =>
@confirm.notify('success')
@.loadRoles()
promise.then null, =>
@confirm.notify('error')
setComputable: ->
onSuccess = (role) =>
@confirm.notify('success')
onError = =>
@confirm.notify("error")
@scope.role.computable = !@scope.role.computable
@repo.save(@scope.role).then onSuccess, onError
module.controller("RolesController", RolesController)
RolesDirective = ->
link = ($scope, $el, $attrs) ->
$ctrl = $el.controller()
$scope.$on "$destroy", ->
$el.off()
return {link:link}
module.directive("tgRoles", RolesDirective)
NewRoleDirective = ($tgrepo) ->
DEFAULT_PERMISSIONS = ["view_project", "view_milestones", "view_us", "view_tasks", "view_issues"]
link = ($scope, $el, $attrs) ->
$ctrl = $el.controller()
$scope.$on "$destroy", ->
$el.off()
$el.on "click", "a.add-button", (event) ->
event.preventDefault()
$el.find(".new").removeClass("hidden")
$el.find(".new").focus()
$el.find(".add-button").hide()
$el.on "keyup", ".new", (event) ->
event.preventDefault()
if event.keyCode == 13 # Enter key
target = angular.element(event.currentTarget)
newRole = {
project: $scope.projectId
name: target.val()
permissions: DEFAULT_PERMISSIONS
order: _.max($scope.roles, (r) -> r.order).order + 1
computable: false
}
$el.find(".new").addClass("hidden")
$el.find(".new").val('')
$tgrepo.create("roles", newRole).then (role) ->
$scope.roles.push(role)
$ctrl.setRole(role)
$el.find(".add-button").show()
else if event.keyCode == 27 # ESC key
target = angular.element(event.currentTarget)
$el.find(".new").addClass("hidden")
$el.find(".new").val('')
$el.find(".add-button").show()
return {link:link}
module.directive("tgNewRole", ["$tgRepo", NewRoleDirective])
# Use category-config.scss styles
RolePermissionsDirective = ($repo, $confirm) ->
resumeTemplate = _.template("""
<div class="resume-title"><%- category.name %></div>
<div class="count"><%- category.activePermissions %>/<%- category.permissions.length %></div>
<div class="summary-role">
<% _.each(category.permissions, function(permission) { %>
<div class="role-summary-single <% if(permission.active) { %>active<% } %>" title="<%- permission.description %>"></div>
<% }) %>
</div>
<div class="icon icon-arrow-bottom"></div>
""")
categoryTemplate = _.template("""
<div class="category-config" data-id="<%- index %>">
<div class="resume">
</div>
<div class="category-items">
<div class="items-container">
<% _.each(category.permissions, function(permission) { %>
<div class="category-item" data-id="<%- permission.key %>"> <%- permission.description %>
<div class="check">
<input type="checkbox" <% if(permission.active) { %>checked="checked"<% } %>/>
<div></div>
</div>
</div>
<% }) %>
</div>
</div>
</div>
""")
baseTemplate = _.template("""
<div class="category-config-list">
</div>
""")
link = ($scope, $el, $attrs) ->
$ctrl = $el.controller()
generateCategoriesFromRole = (role) ->
setActivePermissions = (permissions) ->
return _.map(permissions, (x) -> _.extend({}, x, {active: x["key"] in role.permissions}))
setActivePermissionsPerCategory = (category) ->
return _.map(category, (x) ->
_.extend({}, x, {
activePermissions: _.filter(x["permissions"], "active").length
})
)
categories = []
projectPermissions = [
{ key: "view_project", description: "View project" }
]
categories.push({ name: "Project", permissions: setActivePermissions(projectPermissions) })
milestonePermissions = [
{ key: "view_milestones", description: "View milestones" }
{ key: "add_milestone", description: "Add milestone" }
{ key: "modify_milestone", description: "Modify milestone" }
{ key: "delete_last_milestone", description: "Delete last milestone" }
{ key: "delete_milestone", description: "Delete milestone" }
{ key: "add_us_to_milestone", description: "Add use to milestone" }
{ key: "remove_us_from_milestone", description: "Remove us from milestone" }
{ key: "reorder_us_on_milestone", description: "Reorder us on milestone" }
]
categories.push({ name: "Milestones", permissions: setActivePermissions(milestonePermissions) })
userStoryPermissions = [
{ key: "view_us", description: "View user story" }
{ key: "add_us", description: "Add user story" }
{ key: "modify_us", description: "Modify user story" }
{ key: "delete_us", description: "Delete user story" }
]
categories.push({ name: "User Stories", permissions: setActivePermissions(userStoryPermissions) })
taskPermissions = [
{ key: "view_tasks", description: "View tasks" }
{ key: "add_task", description: "Add task" }
{ key: "modify_task", description: "Modify task" }
{ key: "delete_task", description: "Delete task" }
]
categories.push({ name: "Tasks", permissions: setActivePermissions(taskPermissions) })
issuePermissions = [
{ key: "view_issues", description: "View issues" }
{ key: "add_issue", description: "Add issue" }
{ key: "modify_issue", description: "Modify issue" }
{ key: "delete_issue", description: "Delete issue" }
{ key: "vote_issues", description: "Vote issues" }
]
categories.push({ name: "Issues", permissions: setActivePermissions(issuePermissions) })
wikiPermissions = [
{ key: "view_wiki_pages", description: "View wiki pages" }
{ key: "add_wiki_page", description: "Add wiki page" }
{ key: "modify_wiki_page", description: "Modify wiki page" }
{ key: "delete_wiki_page", description: "Delete wiki page" }
{ key: "view_wiki_links", description: "View wiki links" }
{ key: "add_wiki_link", description: "Add wiki link" }
{ key: "modify_wiki_link", description: "Modify wiki link" }
{ key: "delete_wiki_link", description: "Delete wiki link" }
]
categories.push({ name: "Wiki", permissions: setActivePermissions(wikiPermissions) })
return setActivePermissionsPerCategory(categories)
renderResume = (element, category) ->
element.find(".resume").html(resumeTemplate({category: category}))
renderCategory = (category, index) ->
html = categoryTemplate({category: category, index: index})
html = angular.element(html)
renderResume(html, category)
return html
renderPermissions = () ->
$el.off()
html = baseTemplate()
_.each generateCategoriesFromRole($scope.role), (category, index) ->
html = angular.element(html).append(renderCategory(category, index))
$el.html(html)
$el.on "click", ".resume", (event) ->
event.preventDefault()
target = angular.element(event.currentTarget)
target.next().toggleClass("open")
$el.on "change", ".category-item input", (event) ->
getActivePermissions = ->
activePermissions = _.filter($el.find(".category-item input"), (t) -> angular.element(t).is(":checked"))
activePermissions = _.sortBy(_.map(activePermissions, (t) ->
permission = angular.element(t).parents(".category-item").data("id")
))
return activePermissions
target = angular.element(event.currentTarget)
$scope.role.permissions = getActivePermissions()
onSuccess = (role) ->
$confirm.notify('success')
categories = generateCategoriesFromRole(role)
categoryId = target.parents(".category-config").data("id")
renderResume(target.parents(".category-config"), categories[categoryId])
onError = ->
$confirm.notify("error")
target.prop "checked", !target.prop("checked")
$scope.role.permissions = getActivePermissions()
$repo.save($scope.role).then onSuccess, onError
$scope.$on "$destroy", ->
$el.off()
$scope.$on "role:changed", ->
renderPermissions()
bindOnce($scope, $attrs.ngModel, renderPermissions)
return {link:link}
module.directive("tgRolePermissions", ['$tgRepo', '$tgConfirm', RolePermissionsDirective])

View File

@ -85,6 +85,7 @@ urls = {
"project-admin-project-values-issue-priorities": "/project/:project/admin/project-values/issue-priorities"
"project-admin-project-values-issue-severities": "/project/:project/admin/project-values/issue-severities"
"project-admin-memberships": "/project/:project/admin/memberships"
"project-admin-roles": "/project/:project/admin/roles"
"project-admin-project-profile-features": "/project/:project/admin/project-profile/features"
"project-admin-project-values-us-status": "/project/:project/admin/project-values/us-status"

View File

@ -116,6 +116,7 @@ module.run([
"$tgProjectsResourcesProvider",
"$tgMembershipsResourcesProvider",
"$tgInvitationsResourcesProvider",
"$tgRolesResourcesProvider",
"$tgSprintsResourcesProvider",
"$tgUserstoriesResourcesProvider",
"$tgTasksResourcesProvider",

View File

@ -0,0 +1,39 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program 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. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# File: modules/resources/memberships.coffee
###
taiga = @.taiga
resourceProvider = ($repo, $http, $urls) ->
service = {}
service.get = (id) ->
return $repo.queryOne("roles", id)
service.list = (projectId) ->
return $repo.queryMany("roles", {project: projectId})
return (instance) ->
instance.roles = service
module = angular.module("taigaResources")
module.factory("$tgRolesResourcesProvider", ["$tgRepo", "$tgHttp", "$tgUrls", resourceProvider])

View File

@ -1,48 +1,30 @@
extends layout
extends dummy-layout
block head
title Taiga Project management web application with scrum in mind!
block content
div.wrapper
sidebar.menu-secondary.sidebar
div.wrapper.roles(ng-controller="RolesController as ctrl",
ng-init="section='admin'", tg-roles)
sidebar.menu-secondary.sidebar(tg-admin-navigation="roles")
include views/modules/admin-menu
sidebar.menu-tertiary.sidebar
include views/modules/admin-submenu
include views/modules/admin-submenu-roles
section.main.admin-roles
header
include views/components/mainTitle
a.button.button-red.delete-role(href="", title="Delete", ng-click="ctrl.delete()") Delete
p.total
| UX
span (6 members with this role)
| {{ role.name }}
span ({{ role.members_count }} members with this role)
include views/modules/category-config
div.general-category
| Can do estimations?
div.check
input(type="checkbox", ng-model="role.computable", ng-change="ctrl.setComputable()")
div
script(type='text/javascript').
function randomIntFromInterval(min,max) {
return Math.floor(Math.random()*(max-min+1)+min);
}
(function() {
if(randomIntFromInterval(0, 4) !== 4) return true;
var inputs = document.querySelectorAll('input');
function change(input) {
var num = randomIntFromInterval(100, 600);
setTimeout(function() {
if(input.hasAttribute('checked')) {
input.removeAttribute('checked');
} else {
input.setAttribute('checked', 'checked');
}
}, num);
}
setInterval(function() {
for(var i = 0; i < inputs.length; i++) {
change(inputs[i]);
}
}, 500);
})()
div(tg-role-permissions, ng-model="role")

View File

@ -17,6 +17,6 @@ section.admin-menu
span.title Memberships
span.icon.icon-arrow-right
li#adminmenu-roles
a(href="")
a(href="" tg-nav="project-admin-roles:project=project.slug")
span.title Roles
span.icon.icon-arrow-right

View File

@ -0,0 +1,14 @@
section.admin-submenu-roles
header
h1 Roles
nav
ul
li(ng-repeat="item in roles")
a(href="" ng-click="ctrl.setRole(item)", ng-class="{active: role.id == item.id}") {{ item.name }}
span.icon.icon-arrow-right
div(tg-new-role)
a.button.button-gray.add-button(href="", title="Add New Role")
span.text + New role
input(type="text", class="hidden new")

View File

@ -1,3 +1,7 @@
/////////////////////////////////////////////////////////////////////////////
// Included in the admin-roles.jade and in the RolePermissionsDirective //
// in the modules/admin/roles.coffee file. //
/////////////////////////////////////////////////////////////////////////////
div.general-category
| Can do estimations?
div.check

View File

@ -96,6 +96,7 @@ $prefix-for-spec: true;
//modules admin
@import 'modules/admin/admin-menu';
@import 'modules/admin/admin-submenu';
@import 'modules/admin/admin-submenu-roles';
@import 'modules/admin/admin-roles';
@import 'modules/admin/admin-functionalities';
@import 'modules/admin/admin-membership-table';

View File

@ -50,4 +50,9 @@
}
}
}
.delete-role {
position: absolute;
right: 2rem;
top: 2rem;
}
}

View File

@ -0,0 +1,40 @@
.admin-submenu-roles {
h1 {
@extend %xlarge;
color: $white;
}
li {
@extend %larger;
@extend %title;
border-bottom: 1px solid #a6b2a7;
text-transform: uppercase;
&:last-child {
border-bottom: 0;
}
}
a {
color: $white;
display: block;
padding: 1rem 0 1rem 1rem;
&.active,
&:hover {
color: $blackish;
.icon {
@include transition (opacity .3s linear);
opacity: 1;
}
}
}
.icon {
color: $white;
float: right;
opacity: 0;
}
.button-gray {
padding: .5rem 0;
text-align: center;
&:hover {
background-color: darken($button-gray-hover, 15%);
}
}
}

View File

@ -38,9 +38,12 @@
text-align: right;
}
.category-items {
@include slide(400px, overflow-y);
background-color: $whitish;
padding: 2rem 1rem;
width: 100%;
.items-container {
padding: 2rem 1rem;
}
}
.category-item {
border-bottom: 1px dotted $gray;