Merge pull request #4107 from okriuchykhin/ok_SCI_6821

Update the exiting permissions helpers so they will check new permissions [SCI-6821]
This commit is contained in:
Alex Kriuchykhin 2022-05-24 10:51:12 +02:00 committed by GitHub
commit 4436809115
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 70 additions and 132 deletions

View file

@ -78,7 +78,7 @@ module ClientApi
def check_update_team_permission def check_update_team_permission
@team = Team.find_by_id(params[:team_id]) @team = Team.find_by_id(params[:team_id])
unless can_update_team?(@team) unless can_manage_team?(@team)
respond_422(t('client_api.teams.update_permission_error')) respond_422(t('client_api.teams.update_permission_error'))
end end
end end

View file

@ -107,7 +107,7 @@ class ProjectFoldersController < ApplicationController
project_folders = current_team.project_folders.where(id: params[:project_folders_ids]) project_folders = current_team.project_folders.where(id: params[:project_folders_ids])
counter = 0 counter = 0
project_folders.each do |folder| project_folders.each do |folder|
next if folder.projects.exists? || folder.project_folders.exists? || !can_update_team?(current_team) next if folder.projects.exists? || folder.project_folders.exists? || !can_manage_team?(current_team)
folder.transaction do folder.transaction do
log_activity(:delete_project_folder, folder, project_folder: folder.id) log_activity(:delete_project_folder, folder, project_folder: folder.id)
@ -158,7 +158,7 @@ class ProjectFoldersController < ApplicationController
end end
def check_manage_permissions def check_manage_permissions
render_403 unless can_update_team?(current_team) render_403 unless can_manage_team?(current_team)
end end
def move_projects(destination_folder) def move_projects(destination_folder)

View file

@ -389,7 +389,7 @@ class ProjectsController < ApplicationController
end end
def set_folder_inline_name_editing def set_folder_inline_name_editing
return if !can_update_team?(current_team) || @current_folder.nil? return if !can_manage_team?(current_team) || @current_folder.nil?
@inline_editable_title_config = { @inline_editable_title_config = {
name: 'title', name: 'title',

View file

@ -143,7 +143,7 @@ module Users
def load_team def load_team
@team = Team.find_by_id(params[:id]) @team = Team.find_by_id(params[:id])
render_403 unless can_update_team?(@team) render_403 unless can_manage_team?(@team)
end end
def create_params def create_params

View file

@ -1,7 +1,7 @@
module TeamsHelper module TeamsHelper
# resets the current team if needed # resets the current team if needed
def current_team_switch(team) def current_team_switch(team)
if team != current_team && current_user.is_member_of_team?(team) if team != current_team && current_user.member_of_team?(team)
current_user.current_team_id = team.id current_user.current_team_id = team.id
current_user.save current_user.save
update_current_team update_current_team

View file

@ -10,7 +10,7 @@
If you need to specific model you have to specify it in the connect method If you need to specific model you have to specify it in the connect method
like the example below: like the example below:
> >
> Permissions.connect(MyComponent, ["can_update_team", "can_read_team"], "Team"); > Permissions.connect(MyComponent, ["can_manage_team", "can_read_team"], "Team");
> >
In case your component is connected to Redux or some other HOC you can simply In case your component is connected to Redux or some other HOC you can simply

View file

@ -1,44 +0,0 @@
require 'aspector'
module User::TeamRoles
extend ActiveSupport::Concern
aspector do
# Check if user is member of team
around %i(
is_member_of_team?
is_admin_of_team?
is_normal_user_of_team?
is_normal_user_or_admin_of_team?
is_guest_of_team?
) do |proxy, *args, &block|
if args[0]
@user_team = args[0]&.user_teams&.find { |ut| ut.user == self }
@user_team ? proxy.call(*args, &block) : false
else
false
end
end
end
def is_member_of_team?(team)
# This is already checked by aspector, so just return true
true
end
def is_admin_of_team?(team)
@user_team.admin?
end
def is_normal_user_of_team?(team)
@user_team.normal_user?
end
def is_normal_user_or_admin_of_team?(team)
@user_team.normal_user? or @user_team.admin?
end
def is_guest_of_team?(team)
@user_team.guest?
end
end

View file

@ -58,7 +58,7 @@ class Project < ApplicationRecord
reject_if: :all_blank reject_if: :all_blank
scope :visible_to, (lambda do |user, team| scope :visible_to, (lambda do |user, team|
unless user.is_admin_of_team?(team) unless can_manage_team?(team)
left_outer_joins(user_assignments: :user_role) left_outer_joins(user_assignments: :user_role)
.where(user_assignments: { user: user }) .where(user_assignments: { user: user })
.where('? = ANY(user_roles.permissions)', ProjectPermissions::READ) .where('? = ANY(user_roles.permissions)', ProjectPermissions::READ)

View file

@ -4,7 +4,6 @@ class User < ApplicationRecord
include SearchableModel include SearchableModel
include SettingsModel include SettingsModel
include VariablesModel include VariablesModel
include User::TeamRoles
include TeamBySubjectModel include TeamBySubjectModel
include InputSanitizeHelper include InputSanitizeHelper
include ActiveStorageConcerns include ActiveStorageConcerns
@ -389,6 +388,10 @@ class User < ApplicationRecord
.take .take
end end
def member_of_team?(team)
team.user_assignments.exists?(user: self)
end
# Search all active users for username & email. Can # Search all active users for username & email. Can
# also specify which team to ignore. # also specify which team to ignore.
def self.search( def self.search(

View file

@ -20,7 +20,7 @@ Canaid::Permissions.register_for(Project) do
export_project) export_project)
.each do |perm| .each do |perm|
can perm do |user, project| can perm do |user, project|
user.is_admin_of_team?(project.team) || project.permission_granted?(user, ProjectPermissions::READ) project.permission_granted?(user, ProjectPermissions::READ)
end end
end end
@ -46,7 +46,7 @@ Canaid::Permissions.register_for(Project) do
end end
can :manage_project_users do |user, project| can :manage_project_users do |user, project|
user.is_admin_of_team?(project.team) || project.permission_granted?(user, ProjectPermissions::USERS_MANAGE) project.permission_granted?(user, ProjectPermissions::USERS_MANAGE)
end end
can :archive_project do |user, project| can :archive_project do |user, project|

View file

@ -16,7 +16,6 @@ Canaid::Permissions.register_for(Repository) do
%i(manage_repository %i(manage_repository
create_repository_rows create_repository_rows
manage_repository_rows manage_repository_rows
update_repository_rows
delete_repository_rows delete_repository_rows
create_repository_columns) create_repository_columns)
.each do |perm| .each do |perm|
@ -27,14 +26,14 @@ Canaid::Permissions.register_for(Repository) do
# repository: update, delete # repository: update, delete
can :manage_repository do |user, repository| can :manage_repository do |user, repository|
user.is_admin_of_team?(repository.team) unless repository.shared_with?(user.current_team) !repository.shared_with?(user.current_team) && repository.permission_granted?(user, RepositoryPermissions::MANAGE)
end end
# repository: archive, restore # repository: archive, restore
can :archive_repository do |user, repository| can :archive_repository do |user, repository|
next false if repository.is_a?(BmtRepository) next false if repository.is_a?(BmtRepository)
!repository.shared_with?(user.current_team) && user.is_admin_of_team?(repository.team) !repository.shared_with?(user.current_team) && repository.permission_granted?(user, RepositoryPermissions::MANAGE)
end end
# repository: destroy # repository: destroy
@ -44,17 +43,17 @@ Canaid::Permissions.register_for(Repository) do
# repository: share # repository: share
can :share_repository do |user, repository| can :share_repository do |user, repository|
user.is_admin_of_team?(repository.team) unless repository.shared_with?(user.current_team) can_manage_repository?(user, repository)
end end
# repository: make a snapshot with assigned rows # repository: make a snapshot with assigned rows
can :create_repository_snapshot do |user, repository| can :create_repository_snapshot do |user, repository|
user.is_normal_user_or_admin_of_team?(repository.team) can_read_repository?(user, repository)
end end
# repository: delete a snapshot with assigned rows # repository: delete a snapshot with assigned rows
can :delete_repository_snapshot do |user, repository| can :delete_repository_snapshot do |user, repository|
user.is_normal_user_or_admin_of_team?(repository.team) can_manage_repository?(user, repository)
end end
# repository: create/import record # repository: create/import record
@ -63,38 +62,36 @@ Canaid::Permissions.register_for(Repository) do
next false if repository.archived? next false if repository.archived?
if repository.shared_with?(user.current_team) if repository.shared_with?(user.current_team)
repository.shared_with_write?(user.current_team) && user.is_normal_user_or_admin_of_team?(user.current_team) repository.shared_with_write?(user.current_team) &&
elsif user.teams.include?(repository.team) repository.permission_granted?(user, RepositoryPermissions::ROWS_CREATE)
user.is_normal_user_or_admin_of_team?(repository.team) else
repository.permission_granted?(user, RepositoryPermissions::ROWS_CREATE)
end end
end end
can :manage_repository_assets do |user, repository| can :manage_repository_assets do |user, repository|
can_create_repository_rows?(user, repository) repository.permission_granted?(user, RepositoryPermissions::ROWS_UPDATE)
end end
# repository: update/delete records # repository: update/delete records
can :manage_repository_rows do |user, repository| can :manage_repository_rows do |user, repository|
can_create_repository_rows?(user, repository) repository.permission_granted?(user, RepositoryPermissions::ROWS_UPDATE)
end
can :update_repository_rows do |user, repository|
can_manage_repository_rows?(user, repository)
end end
can :delete_repository_rows do |user, repository| can :delete_repository_rows do |user, repository|
can_manage_repository_rows?(user, repository) repository.permission_granted?(user, RepositoryPermissions::ROWS_DELETE)
end end
# repository: create field # repository: create field
can :create_repository_columns do |user, repository| can :create_repository_columns do |user, repository|
can_create_repository_rows?(user, repository) unless repository.shared_with?(user.current_team) !repository.shared_with?(user.current_team) &&
repository.permission_granted?(user, RepositoryPermissions::COLUMNS_CREATE)
end end
# repository: create/update/delete filters # repository: create/update/delete filters
can :manage_repository_filters do |user, repository| can :manage_repository_filters do |user, repository|
((repository.team == user.current_team) && user.is_normal_user_or_admin_of_team?(repository.team)) || ((repository.team == user.current_team) && can_manage_team?(user, repository.team)) ||
(repository.shared_with_write?(user.current_team) && user.is_normal_user_or_admin_of_team?(user.current_team)) (repository.shared_with_write?(user.current_team) && can_manage_team?(user, user.current_team))
end end
can :manage_repository_stock do |user, repository| can :manage_repository_stock do |user, repository|

View file

@ -3,65 +3,64 @@ Canaid::Permissions.register_for(Team) do
# read protocols # read protocols
# #
can :read_team do |user, team| can :read_team do |user, team|
user.is_member_of_team?(team) user.member_of_team?(team)
end end
# team: update # team: update
can :update_team do |user, team| can :manage_team do |user, team|
user.is_admin_of_team?(team) team.permission_granted?(user, TeamPermissions::MANAGE)
end end
# team: assign/unassing user, change user role # team: assign/unassing user, change user role
can :manage_team_users do |user, team| can :manage_team_users do |user, team|
user.is_admin_of_team?(team) team.permission_granted?(user, TeamPermissions::USERS_MANAGE)
end end
# team: invite new users to the team # team: invite new users to the team
can :invite_team_users do can :invite_team_users do |user, team|
true can_manage_team_users?(user, team)
end end
# project_folder: create # project_folder: create
can :create_project_folders do |user, team| can :create_project_folders do |user, team|
user.is_admin_of_team?(team) can_manage_team?(user, team)
end end
# project: create # project: create
can :create_projects do |user, team| can :create_projects do |user, team|
user.is_normal_user_or_admin_of_team?(team) team.permission_granted?(user, TeamPermissions::PROJECTS_CREATE)
end end
# protocol in repository: create, import # protocol in repository: create, import
can :create_protocols_in_repository do |user, team| can :create_protocols_in_repository do |user, team|
user.is_normal_user_or_admin_of_team?(team) team.permission_granted?(user, TeamPermissions::PROTOCOLS_CREATE)
end end
can :manage_bmt_filters do |user, team| can :manage_bmt_filters do |user, team|
user.is_normal_user_or_admin_of_team?(team) can_manage_team?(user, team)
end end
# repository: create, copy # repository: create, copy
can :create_repositories do |user, team| can :create_repositories do |user, team|
within_limits = Repository.within_global_limits? within_limits = Repository.within_global_limits?
within_limits = Repository.within_team_limits?(team) if within_limits within_limits = Repository.within_team_limits?(team) if within_limits
within_limits && user.is_admin_of_team?(team) within_limits && team.permission_granted?(user, TeamPermissions::INVENTORIES_CREATE)
end end
# this permission is scattered around the application # this permission is scattered around the application
# if you want to make changes here keep in mind to check/change the # if you want to make changes here keep in mind to check/change the
# SQL view that lists reports in index page: # SQL view that lists reports in index page:
# - db/views/datatables_reports_v01.sql
# - check the model app/models/views/datatables/datatables_report.rb # - check the model app/models/views/datatables/datatables_report.rb
# - check visible_by method in Project model # - check visible_by method in Project model
can :manage_reports do |user, team| can :manage_reports do |user, team|
user.is_normal_user_or_admin_of_team?(team) can_manage_team?(user, team)
end end
end end
Canaid::Permissions.register_for(ProjectFolder) do Canaid::Permissions.register_for(ProjectFolder) do
# ProjectFolder: delete # ProjectFolder: delete
can :delete_project_folder do |user, project_folder| can :delete_project_folder do |user, project_folder|
user.is_admin_of_team?(project_folder.team) && can_manage_team?(user, team) &&
project_folder.projects.none? && project_folder.projects.none? &&
project_folder.project_folders.none? project_folder.project_folders.none?
end end
@ -70,7 +69,7 @@ end
Canaid::Permissions.register_for(Protocol) do Canaid::Permissions.register_for(Protocol) do
# protocol in repository: read, export, read step, read/download step asset # protocol in repository: read, export, read step, read/download step asset
can :read_protocol_in_repository do |user, protocol| can :read_protocol_in_repository do |user, protocol|
user.is_member_of_team?(protocol.team) && user.member_of_team?(protocol.team) &&
(protocol.in_repository_public? || (protocol.in_repository_public? ||
protocol.in_repository_private? && user == protocol.added_by) protocol.in_repository_private? && user == protocol.added_by)
end end
@ -78,21 +77,16 @@ Canaid::Permissions.register_for(Protocol) do
# protocol in repository: update, create/update/delete/reorder step, # protocol in repository: update, create/update/delete/reorder step,
# toggle private/public visibility, archive # toggle private/public visibility, archive
can :manage_protocol_in_repository do |user, protocol| can :manage_protocol_in_repository do |user, protocol|
protocol.in_repository_active? && protocol.in_repository_active? && protocol.permission_granted?(user, ProtocolPermissions::MANAGE)
user.is_normal_user_or_admin_of_team?(protocol.team) &&
user == protocol.added_by
end end
# protocol in repository: restore # protocol in repository: restore
can :restore_protocol_in_repository do |user, protocol| can :restore_protocol_in_repository do |user, protocol|
protocol.in_repository_archived? && protocol.in_repository_archived? && protocol.permission_granted?(user, ProtocolPermissions::MANAGE)
user.is_normal_user_or_admin_of_team?(protocol.team) &&
user == protocol.added_by
end end
# protocol in repository: copy # protocol in repository: copy
can :clone_protocol_in_repository do |user, protocol| can :clone_protocol_in_repository do |user, protocol|
can_read_protocol_in_repository?(user, protocol) && can_read_protocol_in_repository?(user, protocol) && can_create_protocols_in_repository?(user, protocol.team)
can_create_protocols_in_repository?(user, protocol.team)
end end
end end

View file

@ -83,9 +83,7 @@
<div class="widget-placeholder"> <div class="widget-placeholder">
<p class="widget-placeholder-title team"><%= I18n.t('dashboard.current_tasks.no_tasks.team_tasks.title') %></p> <p class="widget-placeholder-title team"><%= I18n.t('dashboard.current_tasks.no_tasks.team_tasks.title') %></p>
<p class="widget-placeholder-title assigned"><%= I18n.t('dashboard.current_tasks.no_tasks.assigned_tasks.title') %></p> <p class="widget-placeholder-title assigned"><%= I18n.t('dashboard.current_tasks.no_tasks.assigned_tasks.title') %></p>
<% unless current_user.is_guest_of_team?(current_team) %>
<p class="widget-placeholder-description"><%= I18n.t('dashboard.current_tasks.no_tasks.assigned_tasks.description') %></p> <p class="widget-placeholder-description"><%= I18n.t('dashboard.current_tasks.no_tasks.assigned_tasks.description') %></p>
<% end %>
</div> </div>
</template> </template>

View file

@ -1,5 +1,4 @@
<% unless current_user.is_guest_of_team?(current_team) %> <div class="quick-start-buttons">
<div class="quick-start-buttons">
<div class="new-task btn btn-secondary"><i class="fas fa-plus"></i><%= t("dashboard.quick_start.new_task") %></div> <div class="new-task btn btn-secondary"><i class="fas fa-plus"></i><%= t("dashboard.quick_start.new_task") %></div>
<%= link_to protocols_path, {class: "new-protocol btn btn-secondary"} do %> <%= link_to protocols_path, {class: "new-protocol btn btn-secondary"} do %>
<i class="fas fa-edit"></i><%= t("dashboard.quick_start.new_protocol") %> <i class="fas fa-edit"></i><%= t("dashboard.quick_start.new_protocol") %>
@ -7,6 +6,5 @@
<%= link_to new_report_path, {class: "new-report btn btn-secondary"} do %> <%= link_to new_report_path, {class: "new-report btn btn-secondary"} do %>
<i class="fas fa-clipboard-check"></i><%= t("dashboard.quick_start.new_report") %> <i class="fas fa-clipboard-check"></i><%= t("dashboard.quick_start.new_report") %>
<% end %> <% end %>
</div> </div>
<%= render "create_task_modal" %> <%= render "create_task_modal" %>
<% end %>

View file

@ -26,12 +26,7 @@
<template id="recent-work-no-results-template"> <template id="recent-work-no-results-template">
<div class="widget-placeholder"> <div class="widget-placeholder">
<% if current_user.is_guest_of_team?(current_team) %>
<p class="widget-placeholder-title"><%= t('dashboard.recent_work.no_results_guest.title') %></p>
<p class="widget-placeholder-description"><%= t('dashboard.recent_work.no_results_guest.description') %></p>
<% else %>
<p class="widget-placeholder-title"><%= t('dashboard.recent_work.no_results.title') %></p> <p class="widget-placeholder-title"><%= t('dashboard.recent_work.no_results.title') %></p>
<p class="widget-placeholder-description"><%= t('dashboard.recent_work.no_results.description') %></p> <p class="widget-placeholder-description"><%= t('dashboard.recent_work.no_results.description') %></p>
<% end %>
</div> </div>
</template> </template>

View file

@ -8,7 +8,7 @@
<div class="modal-body"></div> <div class="modal-body"></div>
<div class="modal-footer"> <div class="modal-footer">
<span class="pull-left"> <span class="pull-left">
<% if current_user.is_admin_of_team?(@experiment.project.team) %> <% if can_manage_team_users(@experiment.project.team) %>
<%= link_to t("experiments.canvas.full_zoom.modal_manage_users.invite_users_link"), team_path(@experiment.project.team.id) %> <%= link_to t("experiments.canvas.full_zoom.modal_manage_users.invite_users_link"), team_path(@experiment.project.team.id) %>
<span><%=t "experiments.canvas.full_zoom.modal_manage_users.invite_users_details", team: @experiment.project.team.name %></span> <span><%=t "experiments.canvas.full_zoom.modal_manage_users.invite_users_details", team: @experiment.project.team.name %></span>
<% else %> <% else %>

View file

@ -1,11 +1,11 @@
<div class="card folder-card" <div class="card folder-card"
data-id="<%= folder.id %>" data-id="<%= folder.id %>"
data-edit-url="<%= edit_project_folder_path(folder) %>" data-edit-url="<%= edit_project_folder_path(folder) %>"
data-editable="<%= can_update_team?(current_team) %>" data-editable="<%= can_manage_team?(current_team) %>"
data-moveable="<%= can_update_team?(current_team) %>" data-moveable="<%= can_manage_team?(current_team) %>"
data-archivable="false" data-archivable="false"
data-restorable="false" data-restorable="false"
data-deletable="<%= can_delete_project_folder?(folder) && can_update_team?(current_team) %>"> data-deletable="<%= can_delete_project_folder?(folder) && can_manage_team?(current_team) %>">
<div class="checkbox-cell table-cell"> <div class="checkbox-cell table-cell">
<div class="sci-checkbox-container"> <div class="sci-checkbox-container">
<input value="1" type="checkbox" class="sci-checkbox folder-card-selector"> <input value="1" type="checkbox" class="sci-checkbox folder-card-selector">

View file

@ -2,7 +2,7 @@
data-id="<%= project.id %>" data-id="<%= project.id %>"
data-edit-url="<%= edit_project_path(project) %>" data-edit-url="<%= edit_project_path(project) %>"
data-editable="<%= can_manage_project?(project) %>" data-editable="<%= can_manage_project?(project) %>"
data-moveable="<%= can_update_team?(current_team) %>" data-moveable="<%= can_manage_team?(current_team) %>"
data-archivable="<%= project.active? && can_archive_project?(project) %>" data-archivable="<%= project.active? && can_archive_project?(project) %>"
data-restorable="<%= project.archived? && can_restore_project?(project) %>"> data-restorable="<%= project.archived? && can_restore_project?(project) %>">
<div class="checkbox-cell table-cell"> <div class="checkbox-cell table-cell">

View file

@ -16,7 +16,7 @@
</div> </div>
<% end %> <% end %>
<div class="content-pane flexible empty-repositories" data-readonly="<%= !current_user.is_admin_of_team?(current_team) %>"> <div class="content-pane flexible empty-repositories" data-readonly="<%= !can_manage_team?(current_team) %>">
<div class="content-header"> <div class="content-header">
<h1 data-view-mode="active"><%= t('libraries.index.head_title') %></h1> <h1 data-view-mode="active"><%= t('libraries.index.head_title') %></h1>
</div> </div>

View file

@ -8,7 +8,7 @@
<%= render partial: "sidebar", locals: { repositories: @repositories, archived: params[:archived] } %> <%= render partial: "sidebar", locals: { repositories: @repositories, archived: params[:archived] } %>
<% end %> <% end %>
<div class="content-pane flexible <%= params[:archived] ? :archived : :active %> repositories-index" <div class="content-pane flexible <%= params[:archived] ? :archived : :active %> repositories-index"
data-readonly="<%= !current_user.is_admin_of_team?(current_team) %>"> data-readonly="<%= !can_manage_team?(current_team) %>">
<div class="content-header"> <div class="content-header">
<div class="title-row"> <div class="title-row">
<h1 data-view-mode="active"><%= t('libraries.index.head_title') %></h1> <h1 data-view-mode="active"><%= t('libraries.index.head_title') %></h1>

View file

@ -1,6 +1,6 @@
<% if can_manage_repository_rows?(@repository) %> <% if can_manage_repository_rows?(@repository) %>
<span id="editDeleteCopy" data-toggle="buttons" style="display:none"> <span id="editDeleteCopy" data-toggle="buttons" style="display:none">
<%if can_update_repository_rows?(@repository) %> <% if can_manage_repository_rows?(@repository) %>
<button type="button" title="<%= t('repositories.show.button_tooltip.edit') %>" <button type="button" title="<%= t('repositories.show.button_tooltip.edit') %>"
class="btn btn-light editAdd auto-shrink-button" class="btn btn-light editAdd auto-shrink-button"
id="editRepositoryRecord" disabled data-view-mode="active"> id="editRepositoryRecord" disabled data-view-mode="active">

View file

@ -16,7 +16,7 @@
<!-- TITLE --> <!-- TITLE -->
<h1 id="team-name" class="settings-team-name" data-current-team="<%= current_team == @team %>"> <h1 id="team-name" class="settings-team-name" data-current-team="<%= current_team == @team %>">
<% if can_update_team?(@team) %> <% if can_manage_team?(@team) %>
<%= render partial: "shared/inline_editing", <%= render partial: "shared/inline_editing",
locals: { locals: {
initial_value: @team.name, initial_value: @team.name,
@ -57,7 +57,7 @@
</div> </div>
<div class="team-description"> <div class="team-description">
<% if can_update_team?(@team) %> <% if can_manage_team?(@team) %>
<div <div
class="inline-init-handler" class="inline-init-handler"
data-field-to-update="description" data-field-to-update="description"

View file

@ -74,9 +74,6 @@ en:
no_results: no_results:
title: "You have not worked on anything recently." title: "You have not worked on anything recently."
description: "All tasks, projects, inventories protocols and reports, you last worked on will appear here, when you make some changes." description: "All tasks, projects, inventories protocols and reports, you last worked on will appear here, when you make some changes."
no_results_guest:
title: "No recent work."
description: "As a guest in this team you cannot make or change anything."
modes: modes:
all: "All" all: "All"
projects: "PROJECTS" projects: "PROJECTS"