diff --git a/models/server/metrics.js b/models/server/metrics.js new file mode 100644 index 000000000..5e9404e87 --- /dev/null +++ b/models/server/metrics.js @@ -0,0 +1,145 @@ +import { Meteor } from 'meteor/meteor'; +import Users from '../users'; + +function acceptedIpAdress(ipAdress) { + //return true if a given ipAdress was setted by an admin user + console.log('idpAdress', ipAdress); + + //Check if ipAdress is accepted + console.log( + 'process.env.WEKAN_METRICS_ACCEPTED_IP_ADRESS', + process.env.WEKAN_METRICS_ACCEPTED_IP_ADRESS, + ); + //console.log("process.env", process.env); + const trustedIpAdress = process.env.WEKAN_METRICS_ACCEPTED_IP_ADRESS; + //console.log("trustedIpAdress", trustedIpAdress); + //console.log("trustedIpAdress !== undefined && trustedIpAdress.split(",").includes(ipAdress)", trustedIpAdress !== undefined && trustedIpAdress.split(",").includes(ipAdress)); + return ( + trustedIpAdress !== undefined && + trustedIpAdress.split(',').includes(ipAdress) + ); +} + +Meteor.startup(() => { + WebApp.connectHandlers.use('/metrics', (req, res, next) => { + try { + const ipAdress = + req.headers['x-forwarded-for'] || req.socket.remoteAddress; + // if(process.env.TRUST_PROXY_FORXARD) + // { + // const ipAdress = req.headers['x-forwarded-for'] || req.socket.remoteAddress + // }else{ + // const ipAdress = req.socket.remoteAddress + // } + + // List of trusted ip adress will be found in environment variable "WEKAN_METRICS_ACCEPTED_IP_ADRESS" (separeted with commas) + if (acceptedIpAdress(ipAdress)) { + let metricsRes = ''; + let resCount = 0; + //connected users + metricsRes += '# Number of connected users\n'; + + // To Do: Get number of connected user by using meteor socketJs + const allOpenedSockets = Meteor.server.stream_server.open_sockets; + let connectedUserIds = []; + allOpenedSockets.forEach( + (socket) => + //console.log('meteor session', socket._meteorSession.userId) + socket._meteorSession.userId !== null && + connectedUserIds.push(socket._meteorSession.userId), + ); + resCount = connectedUserIds.length; // KPI 1 + metricsRes += 'connectedUsers ' + resCount + '\n'; + + //registered users + metricsRes += '# Number of registered users\n'; + + // To Do: Get number of registered user + resCount = Users.find({}).count(); // KPI 2 + metricsRes += 'registeredUsers ' + resCount + '\n'; + resCount = 0; + + //board numbers + metricsRes += '# Number of registered boards\n'; + + // To Do: Get number of registered boards + resCount = Boards.find({ archived: false, type: 'board' }).count(); // KPI 3 + metricsRes += 'registeredboards ' + resCount + '\n'; + resCount = 0; + + //board numbers by registered users + metricsRes += '# Number of registered boards by registered users\n'; + + // To Do: Get number of registered boards by registered users + resCount = + Boards.find({ archived: false, type: 'board' }).count() / + Users.find({}).count(); // KPI 4 + metricsRes += 'registeredboardsBysRegisteredUsers ' + resCount + '\n'; + resCount = 0; + + //board numbers with only one member + metricsRes += '# Number of registered boards\n'; + + // To Do: Get board numbers with only one member + resCount = Boards.find({ + archived: false, + type: 'board', + members: { $size: 1 }, + }).count(); // KPI 5 + metricsRes += 'registeredboardsWithOnlyOneMember ' + resCount + '\n'; + resCount = 0; + + // KPI 6 : - stocker la date de dernière connexion + // KPI 6 = count where date de dernière connexion > x jours + // Découpe en label since 5 jours / 10 jours / 20 Jours / 30 jours + + //Number of users with last connection dated 5 days ago + metricsRes += + '# Number of users with last connection dated 5 days ago\n'; + + // To Do: Get number of users with last connection dated 5 days ago + let xdays = 5; + let dateWithXdaysAgo = new Date( + new Date() - xdays * 24 * 60 * 60 * 1000, + ); + console.log({ dateWithXdaysAgo }); + resCount = Users.find({ + lastConnectionDate: { $gte: dateWithXdaysAgo }, + }).count(); // KPI 5 + metricsRes += 'usersWithLastConnectionDated5DaysAgo ' + resCount + '\n'; + resCount = 0; + + metricsRes += + '# Number of users with last connection dated 10 days ago\n'; + + // To Do: Get number of users with last connection dated 10 days ago + xdays = 10; + dateWithXdaysAgo = new Date(new Date() - xdays * 24 * 60 * 60 * 1000); + console.log({ dateWithXdaysAgo }); + resCount = Users.find({ + lastConnectionDate: { $gte: dateWithXdaysAgo }, + }).count(); // KPI 5 + metricsRes += + 'usersWithLastConnectionDated10DaysAgo ' + resCount + '\n'; + resCount = 0; + + // TO DO: + // moyenne de connexion : ((date de déconnexion - date de dernière connexion) + (dernière moyenne)) / 2 + // KPI 7 : somme des moyenne de connexion / nombre d'utilisateur (à ignore les utilisateur avec 0 moyenne) + + res.writeHead(200); // HTTP status + res.end(metricsRes); + } else { + res.writeHead(401); // HTTP status + res.end( + 'IpAdress: ' + + ipAdress + + ' is not authorized to perform this action !!\n', + ); + } + } catch (e) { + res.writeHead(500); // HTTP status + res.end(e.toString()); + } + }); +}); diff --git a/models/users.js b/models/users.js index bb0f949dd..7889a48b5 100644 --- a/models/users.js +++ b/models/users.js @@ -2,7 +2,7 @@ import { SyncedCron } from 'meteor/percolate:synced-cron'; import { TAPi18n } from '/imports/i18n'; import ImpersonatedUsers from './impersonatedUsers'; -import { Index, MongoDBEngine } from 'meteor/easy:search' +import { Index, MongoDBEngine } from 'meteor/easy:search'; // Sandstorm context is detected using the METEOR_SETTINGS environment variable // in the package definition. @@ -45,39 +45,39 @@ Users.attachSchema( /** * the list of organizations that a user belongs to */ - type: [Object], - optional: true, + type: [Object], + optional: true, }, - 'orgs.$.orgId':{ + 'orgs.$.orgId': { /** * The uniq ID of the organization */ - type: String, + type: String, }, - 'orgs.$.orgDisplayName':{ + 'orgs.$.orgDisplayName': { /** * The display name of the organization */ - type: String, + type: String, }, teams: { /** * the list of teams that a user belongs to */ - type: [Object], - optional: true, + type: [Object], + optional: true, }, - 'teams.$.teamId':{ + 'teams.$.teamId': { /** * The uniq ID of the team */ - type: String, + type: String, }, - 'teams.$.teamDisplayName':{ + 'teams.$.teamDisplayName': { /** * The display name of the team */ - type: String, + type: String, }, emails: { /** @@ -228,7 +228,7 @@ Users.attachSchema( type: String, optional: true, }, - 'profile.moveAndCopyDialog' : { + 'profile.moveAndCopyDialog': { /** * move and copy card dialog */ @@ -254,7 +254,7 @@ Users.attachSchema( */ type: String, }, - 'profile.moveChecklistDialog' : { + 'profile.moveChecklistDialog': { /** * move checklist dialog */ @@ -286,7 +286,7 @@ Users.attachSchema( */ type: String, }, - 'profile.copyChecklistDialog' : { + 'profile.copyChecklistDialog': { /** * copy checklist dialog */ @@ -494,6 +494,10 @@ Users.attachSchema( type: [String], optional: true, }, + lastConnectionDate: { + type: Date, + optional: true, + }, }), ); @@ -542,13 +546,13 @@ UserSearchIndex = new Index({ fields: ['username', 'profile.fullname', 'profile.avatarUrl'], allowedFields: ['username', 'profile.fullname', 'profile.avatarUrl'], engine: new MongoDBEngine({ - fields: function(searchObject, options) { + fields: function (searchObject, options) { return { - 'username': 1, + username: 1, 'profile.fullname': 1, - 'profile.avatarUrl': 1 + 'profile.avatarUrl': 1, }; - } + }, }), }); @@ -561,6 +565,7 @@ Users.safeFields = { orgs: 1, teams: 1, authenticationMethod: 1, + lastConnectionDate: 1, }; if (Meteor.isClient) { @@ -630,43 +635,65 @@ Users.helpers({ teamIds() { if (this.teams) { // TODO: Should the Team collection be queried to determine if the team isActive? - return this.teams.map(team => { return team.teamId }); + return this.teams.map((team) => { + return team.teamId; + }); } return []; }, orgIds() { if (this.orgs) { // TODO: Should the Org collection be queried to determine if the organization isActive? - return this.orgs.map(org => { return org.orgId }); + return this.orgs.map((org) => { + return org.orgId; + }); } return []; }, orgsUserBelongs() { if (this.orgs) { - return this.orgs.map(function(org){return org.orgDisplayName}).sort().join(','); + return this.orgs + .map(function (org) { + return org.orgDisplayName; + }) + .sort() + .join(','); } return ''; }, orgIdsUserBelongs() { if (this.orgs) { - return this.orgs.map(function(org){return org.orgId}).join(','); + return this.orgs + .map(function (org) { + return org.orgId; + }) + .join(','); } return ''; }, teamsUserBelongs() { if (this.teams) { - return this.teams.map(function(team){ return team.teamDisplayName}).sort().join(','); + return this.teams + .map(function (team) { + return team.teamDisplayName; + }) + .sort() + .join(','); } return ''; }, teamIdsUserBelongs() { if (this.teams) { - return this.teams.map(function(team){ return team.teamId}).join(','); + return this.teams + .map(function (team) { + return team.teamId; + }) + .join(','); } return ''; }, boards() { - return Boards.userBoards(this._id, null, {}, { sort: { sort: 1 } }) + return Boards.userBoards(this._id, null, {}, { sort: { sort: 1 } }); }, starredBoards() { @@ -675,7 +702,7 @@ Users.helpers({ this._id, false, { _id: { $in: starredBoards } }, - { sort: { sort: 1 } } + { sort: { sort: 1 } }, ); }, @@ -690,7 +717,7 @@ Users.helpers({ this._id, false, { _id: { $in: invitedBoards } }, - { sort: { sort: 1 } } + { sort: { sort: 1 } }, ); }, @@ -728,7 +755,7 @@ Users.helpers({ *