Skip to content

Commit

Permalink
🐛 fixed Error when trying to add team member, and you do not have acc…
Browse files Browse the repository at this point in the history
…ess #207

✨ added search and statistics to the administration > users page
  • Loading branch information
faburem committed Mar 22, 2024
1 parent e3b602c commit 10e0e4b
Show file tree
Hide file tree
Showing 13 changed files with 145 additions and 23 deletions.
2 changes: 1 addition & 1 deletion imports/api/projects/server/methods.js
Expand Up @@ -359,7 +359,7 @@ const addTeamMember = new ValidatedMethod({
const targetProject = await Projects.findOneAsync({ _id: projectId })
if (!targetProject
|| !(targetProject.userId === this.userId
|| targetProject.admins.indexOf(this.userId) >= 0)) {
|| targetProject.admins?.indexOf(this.userId) >= 0)) {
throw new Meteor.Error('notifications.only_owner_can_add_team_members')
}
const targetUser = await Meteor.users.findOneAsync({ 'emails.0.address': eMail, inactive: { $ne: true } })
Expand Down
31 changes: 22 additions & 9 deletions imports/api/users/server/methods.js
Expand Up @@ -120,20 +120,12 @@ const updateTimeUnit = new ValidatedMethod({
}) {
await Meteor.users.updateAsync({ _id: this.userId }, {
$set: {
'profile.timeunit': timeunit
'profile.timeunit': timeunit,
},
})
},
})









/**
* Resets a user's settings.
* @throws {Meteor.Error} If user is not authenticated.
Expand Down Expand Up @@ -393,6 +385,26 @@ const setTimer = new ValidatedMethod({
}
},
})
/**
* Get user statistics for the admininistration > users page
* @throws {Meteor.Error} If user is not authenticated.
* @returns {Object} The user statistics
*/
const adminUserStats = new ValidatedMethod({
name: 'adminUserStats',
validate: null,
mixins: [adminAuthenticationMixin],
async run() {
return {
totalUsers: await Meteor.users.find({}).countAsync(),
newUsers: await Meteor.users
.find({ createdAt: { $gt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) } })
.countAsync(),
adminUsers: await Meteor.users.find({ isAdmin: true }).countAsync(),
inactiveUsers: await Meteor.users.find({ inactive: true }).countAsync(),
}
},
})

export {
claimAdmin,
Expand All @@ -405,4 +417,5 @@ export {
updateProfile,
updateSettings,
resetUserSettings,
adminUserStats,
}
11 changes: 10 additions & 1 deletion imports/api/users/server/publications.js
Expand Up @@ -97,9 +97,10 @@ Meteor.publish('userRoles', async function userRoles() {
return Meteor.users.find({ _id: this.userId }, { fields: { profile: 1, isAdmin: 1 } })
})

Meteor.publish('adminUserList', async function adminUserList({ limit }) {
Meteor.publish('adminUserList', async function adminUserList({ limit, search }) {
await checkAdminAuthentication(this)
check(limit, Match.Maybe(Number))
check(search, Match.Maybe(String))
const options = {}
options.fields = {
profile: 1, emails: 1, isAdmin: 1, createdAt: 1, inactive: 1,
Expand All @@ -108,6 +109,14 @@ Meteor.publish('adminUserList', async function adminUserList({ limit }) {
if (limit) {
options.limit = limit
}
if (search) {
options.filter = {
$or: [
{ 'profile.name': { $regex: `.*${search.replace(/[-[\]{}()*+?.,\\/^$|#\s]/g, '\\$&')}.*`, $options: 'i' } },
{ 'emails.address': { $regex: `.*${search.replace(/[-[\]{}()*+?.,\\/^$|#\s]/g, '\\$&')}.*`, $options: 'i' } },
],
}
}
return Meteor.users.find({}, options)
})

Expand Down
36 changes: 35 additions & 1 deletion imports/ui/pages/administration/components/userscomponent.html
@@ -1,4 +1,28 @@
<template name="userscomponent">
<div class="card mb-3">
<div class="card-block">
<div class="container">
<div class="row text-center">
<div class="col">
<h1 class="js-total-users">0</h1>
{{t "administration.total_users"}}
</div>
<div class="col">
<h1 class="js-new-users">0</h1>
{{t "administration.new_users"}}
</div>
<div class="col">
<h1 class="js-admin-users">0</h1>
{{t "administration.admin_users"}}
</div>
<div class="col">
<h1 class="js-inactive-users">0</h1>
{{t "administration.inactive_users"}}
</div>
</div>
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-header">
<a href="#" class="text-reset text-decoration-none week-row" data-bs-toggle="collapse" data-bs-target="#createUser"> {{t "administration.create_user"}} <i class="float-end fa fa-chevron-right"></i></a>
Expand Down Expand Up @@ -40,7 +64,17 @@
</div>
</div>
</div>
{{>limitpicker}}
<div class="row">
<div class="col">
<div class="input-group">
<input class="form-control" name="userSearch" id="userSearch" type="text" placeholder="{{t 'details.search'}}"/>
<button class="btn btn-outline-secondary rounded-end " type="button"><i class="fa fa-search text-body"></i></button>
</div>
</div>
<div class="col-3">
{{>limitpicker}}
</div>
</div>
<br/>
<div class="table-responsive">
<table class="table">
Expand Down
41 changes: 39 additions & 2 deletions imports/ui/pages/administration/components/userscomponent.js
Expand Up @@ -11,16 +11,43 @@ Template.userscomponent.onCreated(function userscomponentCreated() {
})
Template.userscomponent.onRendered(() => {
const templateInstance = Template.instance()
templateInstance.userSearch = new ReactiveVar('')
templateInstance.autorun(() => {
if (FlowRouter.getQueryParam('limit')) {
templateInstance.limit.set(Number(FlowRouter.getQueryParam('limit')))
templateInstance.$('#limitpicker').val(FlowRouter.getQueryParam('limit'))
}
templateInstance.subscribe('adminUserList', { limit: templateInstance.limit.get() })
templateInstance.subscribe('adminUserList', { limit: templateInstance.limit.get(), search: templateInstance.userSearch.get() })
})
Meteor.call('adminUserStats', (error, result) => {
if (error) {
console.error(error)
} else {
templateInstance.$('.js-total-users').text(result.totalUsers)
templateInstance.$('.js-new-users').text(result.newUsers)
templateInstance.$('.js-admin-users').text(result.adminUsers)
templateInstance.$('.js-inactive-users').text(result.inactiveUsers)
}
})
})
Template.userscomponent.helpers({
users: () => Meteor.users.find({}, { sort: { createdAt: -1 } }),
users: () => {
const templateInstance = Template.instance()
let query = {}
const options = { sort: { createdAt: -1 } }
if (templateInstance.userSearch?.get()) {
query = {
$or: [
{ 'profile.name': { $regex: `.*${templateInstance.userSearch?.get().replace(/[-[\]{}()*+?.,\\/^$|#\s]/g, '\\$&')}.*`, $options: 'i' } },
{ 'emails.address': { $regex: `.*${templateInstance.userSearch?.get().replace(/[-[\]{}()*+?.,\\/^$|#\s]/g, '\\$&')}.*`, $options: 'i' } },
],
}
}
if (templateInstance.limit?.get()) {
options.limit = templateInstance.limit?.get()
}
return Meteor.users.find(query, options)
},
avatar: (meteorUser) => displayUserAvatar(meteorUser),
dayjs: (date) => dayjs(date).format('DD.MM.YYYY (HH:mm)'),
})
Expand Down Expand Up @@ -112,4 +139,14 @@ Template.userscomponent.events({
}
})
},
'focusout #userSearch': (event, templateInstance) => {
event.preventDefault()
templateInstance.userSearch.set(event.currentTarget.value)
},
'keyup #userSearch': (event, templateInstance) => {
event.preventDefault()
if (event.keyCode === 13) {
templateInstance.userSearch.set(event.currentTarget.value)
}
},
})
6 changes: 5 additions & 1 deletion imports/ui/translations/de.json
Expand Up @@ -449,7 +449,11 @@
"add_update_inbound_interface": "Eingehende Schnittstelle hinzufügen/bearbeiten",
"process_data": "Script für die Datenverarbeitung",
"processdata_hint": "Dieser JavaScript Code wird immer ausgeführt, wenn Daten über die eingehende Schnittstelle abgerufen werden. Das Script muss ein JSON Objekt (oder ein Promise, die ein solches Objekt zurückgibt) mit folgender Struktur zurückgeben: [{name: 'name', description:'description'}].",
"active": "Eingehende Schnittstelle aktivieren?"
"active": "Eingehende Schnittstelle aktivieren?",
"total_users": "Benutzer insgesamt",
"new_users": "Neue Benutzer (letzte 7 Tage)",
"admin_users": "Administrative Benutzer",
"inactive_users": "Inaktive Benutzer"
},
"wekan": {
"list": "Wekan Liste",
Expand Down
6 changes: 5 additions & 1 deletion imports/ui/translations/en.json
Expand Up @@ -441,7 +441,11 @@
"add_update_inbound_interface": "Add or update inbound interface",
"process_data": "Process data script",
"processdata_hint":"This JavaScript will be executed whenever inbound interface data is requested. The script must return a JSON object (or a promise returning such an object) with the following structure: [{name: 'name', description:'description'}].",
"active": "Inbound interface active?"
"active": "Inbound interface active?",
"total_users": "Total users",
"new_users": "New users (last 7 days)",
"admin_users": "Administrative users",
"inactive_users": "Inactive users"
},
"wekan": {
"list": "Wekan list",
Expand Down
8 changes: 6 additions & 2 deletions imports/ui/translations/es.json
Expand Up @@ -412,7 +412,7 @@
"creation_date": "Fecha de Creación",
"users": "Usuarios",
"user_created": "El usuario ha sido creado.",
"user_updated": "La información del usuario ha sido actualizada.",
"user_updated": "La información del usuario ha sido actualizada.",
"user_deleted": "Los usuarios han sido eliminados",
"user_deletion_confirmation": "¿Realmente quieres eliminar a este usuario (no se puede deshacer)?",
"user_inactive": "¿Inactivo?",
Expand Down Expand Up @@ -440,7 +440,11 @@
"add_update_inbound_interface": "Agregar o actualizar interfaz de entrada",
"process_data": "Script de Procesamiento de Datos",
"processdata_hint": "Este JavaScript se ejecutará siempre que se soliciten datos de la interfaz de entrada. El script debe devolver un objeto JSON (o una promesa que devuelva dicho objeto) con la siguiente estructura: [{name: 'nombre', description:'descripción'}].",
"active": "¿Interfaz de Entrada Activa?"
"active": "¿Interfaz de Entrada Activa?",
"total_users": "Usuarios totales",
"new_users": "Nuevos usuarios (últimos 7 días)",
"admin_users": "Usuarios administrativos",
"inactive_users": "Usuarios inactivos"
},
"wekan": {
"list": "Lista de Wekan",
Expand Down
6 changes: 5 additions & 1 deletion imports/ui/translations/fr.json
Expand Up @@ -430,7 +430,11 @@
"add_update_inbound_interface": "Ajouter ou mettre à jour une interface entrante",
"process_data": "Script de traitement des données",
"processdata_hint":"Ce JavaScript sera exécuté chaque fois que des données d'interface entrantes seront demandées. Le script doit renvoyer un objet JSON (ou une promesse renvoyant un tel objet) avec la structure suivante : [{name:'name', description:'description'}].",
"active": "Interface entrante active ?"
"active": "Interface entrante active ?",
"total_users": "Utilisateurs totaux",
"new_users": "Nouveaux utilisateurs (7 derniers jours)",
"admin_users": "Utilisateurs administratifs",
"inactive_users": "Utilisateurs inactifs"
},
"wekan": {
"list": "Liste Wekan",
Expand Down
6 changes: 5 additions & 1 deletion imports/ui/translations/ru.json
Expand Up @@ -439,7 +439,11 @@
"add_update_inbound_interface": "Добавить или обновить входящий интерфейс",
"process_data": "Скрипт обработки данных",
"processdata_hint": "Этот JavaScript будет выполняться при каждом запросе данных входящего интерфейса. Скрипт должен возвращать объект JSON (или promise, возвращающий такой объект) со следующей структурой: [{name: 'name', description:'description'}].",
"active": "Активировать входящий интерфейс?"
"active": "Активировать входящий интерфейс?",
"total_users": "Всего пользователей",
"new_users": "Новые пользователи (за последние 7 дней)",
"admin_users": "Административные пользователи",
"inactive_users": "Неактивные пользователи"
},
"wekan": {
"list": "Список Wekan",
Expand Down
6 changes: 5 additions & 1 deletion imports/ui/translations/ukr.json
Expand Up @@ -439,7 +439,11 @@
"add_update_inbound_interface": "Додати або оновити вхідний інтерфейс",
"process_data": "Скрипт обробки даних",
"processdata_hint":"Цей JavaScript буде виконуватися при кожному запиті даних інтерфейсу. Скрипт повинен повертати об'єкт JSON (або promise, що повертає такий об'єкт) з наступною структурою: [{name: 'name', description:'description'}].",
"active": "Активувати вхідний інтерфейс?"
"active": "Активувати вхідний інтерфейс?",
"total_users": "Всього користувачів",
"new_users": "Нові користувачі (останні 7 днів)",
"admin_users": "Адміністративні користувачі",
"inactive_users": "Неактивні користувачі"
},
"wekan": {
"list": "Список Wekan",
Expand Down
7 changes: 6 additions & 1 deletion imports/ui/translations/zh.json
Expand Up @@ -384,7 +384,12 @@
"extension_removed": "扩展已删除",
"edit_custom_field": "编辑自定义字段",
"oidc": "OpenID Connect",
"oidc_configure": "你需要创建一个 OpenID Connect 客户端配置,并设置应用回调地址"
"oidc_configure": "你需要创建一个 OpenID Connect 客户端配置,并设置应用回调地址",
"active": "启用传入接口?",
"total_users": "总用户数",
"new_users": "新用户(过去7天)",
"admin_users": "管理员用户",
"inactive_users": "非活跃用户"
},
"wekan": {
"list": "Wekan 列表",
Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "titra",
"version": "0.97.0",
"version": "0.97.1",
"private": true,
"scripts": {
"start": "meteor run"
Expand Down

0 comments on commit 10e0e4b

Please sign in to comment.