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", $routeProvider.when("/project/:pslug/admin/memberships",
{templateUrl: "/partials/admin-memberships.html"}) {templateUrl: "/partials/admin-memberships.html"})
$routeProvider.when("/project/:pslug/admin/roles",
{templateUrl: "/partials/admin-roles.html"})
# User settings # User settings
$routeProvider.when("/project/:pslug/user-settings/user-profile", $routeProvider.when("/project/:pslug/user-settings/user-profile",
{templateUrl: "/partials/user-profile.html"}) {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-priorities": "/project/:project/admin/project-values/issue-priorities"
"project-admin-project-values-issue-severities": "/project/:project/admin/project-values/issue-severities" "project-admin-project-values-issue-severities": "/project/:project/admin/project-values/issue-severities"
"project-admin-memberships": "/project/:project/admin/memberships" "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-profile-features": "/project/:project/admin/project-profile/features"
"project-admin-project-values-us-status": "/project/:project/admin/project-values/us-status" "project-admin-project-values-us-status": "/project/:project/admin/project-values/us-status"

View File

@ -116,6 +116,7 @@ module.run([
"$tgProjectsResourcesProvider", "$tgProjectsResourcesProvider",
"$tgMembershipsResourcesProvider", "$tgMembershipsResourcesProvider",
"$tgInvitationsResourcesProvider", "$tgInvitationsResourcesProvider",
"$tgRolesResourcesProvider",
"$tgSprintsResourcesProvider", "$tgSprintsResourcesProvider",
"$tgUserstoriesResourcesProvider", "$tgUserstoriesResourcesProvider",
"$tgTasksResourcesProvider", "$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 block head
title Taiga Project management web application with scrum in mind! title Taiga Project management web application with scrum in mind!
block content block content
div.wrapper div.wrapper.roles(ng-controller="RolesController as ctrl",
sidebar.menu-secondary.sidebar ng-init="section='admin'", tg-roles)
sidebar.menu-secondary.sidebar(tg-admin-navigation="roles")
include views/modules/admin-menu include views/modules/admin-menu
sidebar.menu-tertiary.sidebar sidebar.menu-tertiary.sidebar
include views/modules/admin-submenu include views/modules/admin-submenu-roles
section.main.admin-roles section.main.admin-roles
header header
include views/components/mainTitle include views/components/mainTitle
a.button.button-red.delete-role(href="", title="Delete", ng-click="ctrl.delete()") Delete
p.total p.total
| UX | {{ role.name }}
span (6 members with this role) 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'). div(tg-role-permissions, ng-model="role")
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);
})()

View File

@ -17,6 +17,6 @@ section.admin-menu
span.title Memberships span.title Memberships
span.icon.icon-arrow-right span.icon.icon-arrow-right
li#adminmenu-roles li#adminmenu-roles
a(href="") a(href="" tg-nav="project-admin-roles:project=project.slug")
span.title Roles span.title Roles
span.icon.icon-arrow-right 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 div.general-category
| Can do estimations? | Can do estimations?
div.check div.check

View File

@ -96,6 +96,7 @@ $prefix-for-spec: true;
//modules admin //modules admin
@import 'modules/admin/admin-menu'; @import 'modules/admin/admin-menu';
@import 'modules/admin/admin-submenu'; @import 'modules/admin/admin-submenu';
@import 'modules/admin/admin-submenu-roles';
@import 'modules/admin/admin-roles'; @import 'modules/admin/admin-roles';
@import 'modules/admin/admin-functionalities'; @import 'modules/admin/admin-functionalities';
@import 'modules/admin/admin-membership-table'; @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; text-align: right;
} }
.category-items { .category-items {
@include slide(400px, overflow-y);
background-color: $whitish; background-color: $whitish;
padding: 2rem 1rem;
width: 100%; width: 100%;
.items-container {
padding: 2rem 1rem;
}
} }
.category-item { .category-item {
border-bottom: 1px dotted $gray; border-bottom: 1px dotted $gray;