[NEW] Button to download admin server info (#16059)

pull/16466/head^2
Marcos Spessatto Defendi 5 years ago committed by GitHub
parent 8014abd343
commit 4f5423ac8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 48
      app/api/server/v1/stats.js
  2. 14
      app/models/server/raw/Statistics.js
  3. 3
      app/models/server/raw/index.js
  4. 165
      app/statistics/server/functions/get.js
  5. 14
      app/statistics/server/functions/getLastStatistics.js
  6. 26
      app/statistics/server/functions/getStatistics.js
  7. 9
      app/statistics/server/functions/save.js
  8. 6
      app/statistics/server/index.js
  9. 173
      app/statistics/server/lib/statistics.js
  10. 17
      app/statistics/server/methods/getStatistics.js
  11. 1
      app/statistics/server/statisticsNamespace.js
  12. 4
      client/components/admin/info/InformationPage.js
  13. 7
      client/components/admin/info/InformationPage.stories.js
  14. 16
      client/components/admin/info/InformationRoute.js
  15. 6
      client/contexts/ServerContext.js
  16. 15
      client/helpers/download.js
  17. 16
      client/providers/ServerProvider.js
  18. 1
      packages/rocketchat-i18n/i18n/en.i18n.json
  19. 1
      packages/rocketchat-i18n/i18n/pt-BR.i18n.json
  20. 85
      tests/end-to-end/api/19-statistics.js

@ -1,48 +1,30 @@
import { Meteor } from 'meteor/meteor';
import { hasPermission } from '../../../authorization';
import { Statistics } from '../../../models';
import { API } from '../api';
import { getStatistics, getLastStatistics } from '../../../statistics/server';
API.v1.addRoute('statistics', { authRequired: true }, {
get() {
let refresh = false;
if (typeof this.queryParams.refresh !== 'undefined' && this.queryParams.refresh === 'true') {
refresh = true;
}
let stats;
Meteor.runAsUser(this.userId, () => {
stats = Meteor.call('getStatistics', refresh);
});
return API.v1.success({
statistics: stats,
});
const { refresh } = this.requestParams();
return API.v1.success(Promise.await(getLastStatistics({
userId: this.userId,
refresh: refresh && refresh === 'true',
})));
},
});
API.v1.addRoute('statistics.list', { authRequired: true }, {
get() {
if (!hasPermission(this.userId, 'view-statistics')) {
return API.v1.unauthorized();
}
const { offset, count } = this.getPaginationItems();
const { sort, fields, query } = this.parseJsonQuery();
const statistics = Statistics.find(query, {
sort: sort || { name: 1 },
skip: offset,
limit: count,
fields,
}).fetch();
return API.v1.success({
statistics,
count: statistics.length,
return API.v1.success(Promise.await(getStatistics({
userId: this.userId,
query,
pagination: {
offset,
total: Statistics.find(query).count(),
});
count,
sort,
fields,
},
})));
},
});

@ -0,0 +1,14 @@
import { BaseRaw } from './BaseRaw';
export class StatisticsRaw extends BaseRaw {
async findLast() {
const options = {
sort: {
createdAt: -1,
},
limit: 1,
};
const records = await this.find({}, options).toArray();
return records && records[0];
}
}

@ -44,6 +44,8 @@ import CustomUserStatusModel from '../models/CustomUserStatus';
import { CustomUserStatusRaw } from './CustomUserStatus';
import LivechatAgentActivityModel from '../models/LivechatAgentActivity';
import { LivechatAgentActivityRaw } from './LivechatAgentActivity';
import StatisticsModel from '../models/Statistics';
import { StatisticsRaw } from './Statistics';
export const Permissions = new PermissionsRaw(PermissionsModel.model.rawCollection());
export const Roles = new RolesRaw(RolesModel.model.rawCollection());
@ -68,3 +70,4 @@ export const OAuthApps = new OAuthAppsRaw(OAuthAppsModel.model.rawCollection());
export const CustomSounds = new CustomSoundsRaw(CustomSoundsModel.model.rawCollection());
export const CustomUserStatus = new CustomUserStatusRaw(CustomUserStatusModel.model.rawCollection());
export const LivechatAgentActivity = new LivechatAgentActivityRaw(LivechatAgentActivityModel.model.rawCollection());
export const Statistics = new StatisticsRaw(StatisticsModel.model.rawCollection());

@ -1,165 +0,0 @@
import os from 'os';
import _ from 'underscore';
import { Meteor } from 'meteor/meteor';
import { InstanceStatus } from 'meteor/konecty:multiple-instances-status';
import {
Sessions,
Settings,
Users,
Rooms,
Subscriptions,
Uploads,
Messages,
LivechatVisitors,
Integrations,
} from '../../../models/server';
import { settings } from '../../../settings/server';
import { Info, getMongoInfo } from '../../../utils/server';
import { Migrations } from '../../../migrations/server';
import { statistics } from '../statisticsNamespace';
import { Apps } from '../../../apps/server';
import { getStatistics as federationGetStatistics } from '../../../federation/server/functions/dashboard';
const wizardFields = [
'Organization_Type',
'Industry',
'Size',
'Country',
'Language',
'Server_Type',
'Register_Server',
];
statistics.get = function _getStatistics() {
const statistics = {};
// Setup Wizard
statistics.wizard = {};
wizardFields.forEach((field) => {
const record = Settings.findOne(field);
if (record) {
const wizardField = field.replace(/_/g, '').replace(field[0], field[0].toLowerCase());
statistics.wizard[wizardField] = record.value;
}
});
// Version
statistics.uniqueId = settings.get('uniqueID');
if (Settings.findOne('uniqueID')) {
statistics.installedAt = Settings.findOne('uniqueID').createdAt;
}
if (Info) {
statistics.version = Info.version;
statistics.tag = Info.tag;
statistics.branch = Info.branch;
}
// User statistics
statistics.totalUsers = Meteor.users.find().count();
statistics.activeUsers = Meteor.users.find({ active: true }).count();
statistics.nonActiveUsers = statistics.totalUsers - statistics.activeUsers;
statistics.onlineUsers = Meteor.users.find({ statusConnection: 'online' }).count();
statistics.awayUsers = Meteor.users.find({ statusConnection: 'away' }).count();
statistics.totalConnectedUsers = statistics.onlineUsers + statistics.awayUsers;
statistics.offlineUsers = statistics.totalUsers - statistics.onlineUsers - statistics.awayUsers;
// Room statistics
statistics.totalRooms = Rooms.find().count();
statistics.totalChannels = Rooms.findByType('c').count();
statistics.totalPrivateGroups = Rooms.findByType('p').count();
statistics.totalDirect = Rooms.findByType('d').count();
statistics.totalLivechat = Rooms.findByType('l').count();
statistics.totalDiscussions = Rooms.countDiscussions();
statistics.totalThreads = Messages.countThreads();
// livechat visitors
statistics.totalLivechatVisitors = LivechatVisitors.find().count();
// livechat agents
statistics.totalLivechatAgents = Users.findAgents().count();
// livechat enabled
statistics.livechatEnabled = settings.get('Livechat_enabled');
// Message statistics
statistics.totalMessages = Messages.find().count();
statistics.totalChannelMessages = _.reduce(Rooms.findByType('c', { fields: { msgs: 1 } }).fetch(), function _countChannelMessages(num, room) { return num + room.msgs; }, 0);
statistics.totalPrivateGroupMessages = _.reduce(Rooms.findByType('p', { fields: { msgs: 1 } }).fetch(), function _countPrivateGroupMessages(num, room) { return num + room.msgs; }, 0);
statistics.totalDirectMessages = _.reduce(Rooms.findByType('d', { fields: { msgs: 1 } }).fetch(), function _countDirectMessages(num, room) { return num + room.msgs; }, 0);
statistics.totalLivechatMessages = _.reduce(Rooms.findByType('l', { fields: { msgs: 1 } }).fetch(), function _countLivechatMessages(num, room) { return num + room.msgs; }, 0);
// Federation statistics
const federationOverviewData = federationGetStatistics();
statistics.federatedServers = federationOverviewData.numberOfServers;
statistics.federatedUsers = federationOverviewData.numberOfFederatedUsers;
statistics.lastLogin = Users.getLastLogin();
statistics.lastMessageSentAt = Messages.getLastTimestamp();
statistics.lastSeenSubscription = Subscriptions.getLastSeen();
statistics.os = {
type: os.type(),
platform: os.platform(),
arch: os.arch(),
release: os.release(),
uptime: os.uptime(),
loadavg: os.loadavg(),
totalmem: os.totalmem(),
freemem: os.freemem(),
cpus: os.cpus(),
};
statistics.process = {
nodeVersion: process.version,
pid: process.pid,
uptime: process.uptime(),
};
statistics.deploy = {
method: process.env.DEPLOY_METHOD || 'tar',
platform: process.env.DEPLOY_PLATFORM || 'selfinstall',
};
statistics.uploadsTotal = Uploads.find().count();
const [result] = Promise.await(Uploads.model.rawCollection().aggregate([{ $group: { _id: 'total', total: { $sum: '$size' } } }]).toArray());
statistics.uploadsTotalSize = result ? result.total : 0;
statistics.migration = Migrations._getControl();
statistics.instanceCount = InstanceStatus.getCollection().find({ _updatedAt: { $gt: new Date(Date.now() - process.uptime() * 1000 - 2000) } }).count();
const { oplogEnabled, mongoVersion, mongoStorageEngine } = getMongoInfo();
statistics.oplogEnabled = oplogEnabled;
statistics.mongoVersion = mongoVersion;
statistics.mongoStorageEngine = mongoStorageEngine;
statistics.uniqueUsersOfYesterday = Sessions.getUniqueUsersOfYesterday();
statistics.uniqueUsersOfLastMonth = Sessions.getUniqueUsersOfLastMonth();
statistics.uniqueDevicesOfYesterday = Sessions.getUniqueDevicesOfYesterday();
statistics.uniqueDevicesOfLastMonth = Sessions.getUniqueDevicesOfLastMonth();
statistics.uniqueOSOfYesterday = Sessions.getUniqueOSOfYesterday();
statistics.uniqueOSOfLastMonth = Sessions.getUniqueOSOfLastMonth();
statistics.apps = {
engineVersion: Info.marketplaceApiVersion,
enabled: Apps.isEnabled(),
totalInstalled: Apps.isInitialized() && Apps.getManager().get().length,
totalActive: Apps.isInitialized() && Apps.getManager().get({ enabled: true }).length,
};
const integrations = Integrations.find().fetch();
statistics.integrations = {
totalIntegrations: integrations.length,
totalIncoming: integrations.filter((integration) => integration.type === 'webhook-incoming').length,
totalIncomingActive: integrations.filter((integration) => integration.enabled === true && integration.type === 'webhook-incoming').length,
totalOutgoing: integrations.filter((integration) => integration.type === 'webhook-outgoing').length,
totalOutgoingActive: integrations.filter((integration) => integration.enabled === true && integration.type === 'webhook-outgoing').length,
totalWithScriptEnabled: integrations.filter((integration) => integration.scriptEnabled === true).length,
};
return statistics;
};

@ -0,0 +1,14 @@
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { statistics } from '../lib/statistics';
import { Statistics } from '../../../models/server/raw';
export async function getLastStatistics({ userId, refresh }) {
if (!await hasPermissionAsync(userId, 'view-statistics')) {
throw new Error('error-not-allowed');
}
if (refresh) {
return statistics.save();
}
return Statistics.findLast();
}

@ -0,0 +1,26 @@
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { Statistics } from '../../../models/server/raw';
export async function getStatistics({ userId, query = {}, pagination: { offset, count, sort, fields } }) {
if (!await hasPermissionAsync(userId, 'view-statistics')) {
throw new Error('error-not-allowed');
}
const cursor = Statistics.find(query, {
sort: sort || { name: 1 },
skip: offset,
limit: count,
fields,
});
const total = await cursor.count();
const statistics = await cursor.toArray();
return {
statistics,
count: statistics.length,
offset,
total,
};
}

@ -1,9 +0,0 @@
import { Statistics } from '../../../models';
import { statistics } from '../statisticsNamespace';
statistics.save = function() {
const rcStatistics = statistics.get();
rcStatistics.createdAt = new Date();
Statistics.insert(rcStatistics);
return rcStatistics;
};

@ -1,6 +1,6 @@
import './functions/get';
import './functions/save';
import './methods/getStatistics';
import './startup/monitor';
export { statistics } from './statisticsNamespace';
export { statistics } from './lib/statistics';
export { getLastStatistics } from './functions/getLastStatistics';
export { getStatistics } from './functions/getStatistics';

@ -0,0 +1,173 @@
import os from 'os';
import _ from 'underscore';
import { Meteor } from 'meteor/meteor';
import { InstanceStatus } from 'meteor/konecty:multiple-instances-status';
import {
Sessions,
Settings,
Users,
Rooms,
Subscriptions,
Uploads,
Messages,
LivechatVisitors,
Integrations,
Statistics,
} from '../../../models/server';
import { settings } from '../../../settings/server';
import { Info, getMongoInfo } from '../../../utils/server';
import { Migrations } from '../../../migrations/server';
import { Apps } from '../../../apps/server';
import { getStatistics as federationGetStatistics } from '../../../federation/server/functions/dashboard';
const wizardFields = [
'Organization_Type',
'Industry',
'Size',
'Country',
'Language',
'Server_Type',
'Register_Server',
];
export const statistics = {
get: function _getStatistics() {
const statistics = {};
// Setup Wizard
statistics.wizard = {};
wizardFields.forEach((field) => {
const record = Settings.findOne(field);
if (record) {
const wizardField = field.replace(/_/g, '').replace(field[0], field[0].toLowerCase());
statistics.wizard[wizardField] = record.value;
}
});
// Version
statistics.uniqueId = settings.get('uniqueID');
if (Settings.findOne('uniqueID')) {
statistics.installedAt = Settings.findOne('uniqueID').createdAt;
}
if (Info) {
statistics.version = Info.version;
statistics.tag = Info.tag;
statistics.branch = Info.branch;
}
// User statistics
statistics.totalUsers = Meteor.users.find().count();
statistics.activeUsers = Meteor.users.find({ active: true }).count();
statistics.nonActiveUsers = statistics.totalUsers - statistics.activeUsers;
statistics.onlineUsers = Meteor.users.find({ statusConnection: 'online' }).count();
statistics.awayUsers = Meteor.users.find({ statusConnection: 'away' }).count();
statistics.totalConnectedUsers = statistics.onlineUsers + statistics.awayUsers;
statistics.offlineUsers = statistics.totalUsers - statistics.onlineUsers - statistics.awayUsers;
// Room statistics
statistics.totalRooms = Rooms.find().count();
statistics.totalChannels = Rooms.findByType('c').count();
statistics.totalPrivateGroups = Rooms.findByType('p').count();
statistics.totalDirect = Rooms.findByType('d').count();
statistics.totalLivechat = Rooms.findByType('l').count();
statistics.totalDiscussions = Rooms.countDiscussions();
statistics.totalThreads = Messages.countThreads();
// livechat visitors
statistics.totalLivechatVisitors = LivechatVisitors.find().count();
// livechat agents
statistics.totalLivechatAgents = Users.findAgents().count();
// livechat enabled
statistics.livechatEnabled = settings.get('Livechat_enabled');
// Message statistics
statistics.totalMessages = Messages.find().count();
statistics.totalChannelMessages = _.reduce(Rooms.findByType('c', { fields: { msgs: 1 } }).fetch(), function _countChannelMessages(num, room) { return num + room.msgs; }, 0);
statistics.totalPrivateGroupMessages = _.reduce(Rooms.findByType('p', { fields: { msgs: 1 } }).fetch(), function _countPrivateGroupMessages(num, room) { return num + room.msgs; }, 0);
statistics.totalDirectMessages = _.reduce(Rooms.findByType('d', { fields: { msgs: 1 } }).fetch(), function _countDirectMessages(num, room) { return num + room.msgs; }, 0);
statistics.totalLivechatMessages = _.reduce(Rooms.findByType('l', { fields: { msgs: 1 } }).fetch(), function _countLivechatMessages(num, room) { return num + room.msgs; }, 0);
// Federation statistics
const federationOverviewData = federationGetStatistics();
statistics.federatedServers = federationOverviewData.numberOfServers;
statistics.federatedUsers = federationOverviewData.numberOfFederatedUsers;
statistics.lastLogin = Users.getLastLogin();
statistics.lastMessageSentAt = Messages.getLastTimestamp();
statistics.lastSeenSubscription = Subscriptions.getLastSeen();
statistics.os = {
type: os.type(),
platform: os.platform(),
arch: os.arch(),
release: os.release(),
uptime: os.uptime(),
loadavg: os.loadavg(),
totalmem: os.totalmem(),
freemem: os.freemem(),
cpus: os.cpus(),
};
statistics.process = {
nodeVersion: process.version,
pid: process.pid,
uptime: process.uptime(),
};
statistics.deploy = {
method: process.env.DEPLOY_METHOD || 'tar',
platform: process.env.DEPLOY_PLATFORM || 'selfinstall',
};
statistics.uploadsTotal = Uploads.find().count();
const [result] = Promise.await(Uploads.model.rawCollection().aggregate([{ $group: { _id: 'total', total: { $sum: '$size' } } }]).toArray());
statistics.uploadsTotalSize = result ? result.total : 0;
statistics.migration = Migrations._getControl();
statistics.instanceCount = InstanceStatus.getCollection().find({ _updatedAt: { $gt: new Date(Date.now() - process.uptime() * 1000 - 2000) } }).count();
const { oplogEnabled, mongoVersion, mongoStorageEngine } = getMongoInfo();
statistics.oplogEnabled = oplogEnabled;
statistics.mongoVersion = mongoVersion;
statistics.mongoStorageEngine = mongoStorageEngine;
statistics.uniqueUsersOfYesterday = Sessions.getUniqueUsersOfYesterday();
statistics.uniqueUsersOfLastMonth = Sessions.getUniqueUsersOfLastMonth();
statistics.uniqueDevicesOfYesterday = Sessions.getUniqueDevicesOfYesterday();
statistics.uniqueDevicesOfLastMonth = Sessions.getUniqueDevicesOfLastMonth();
statistics.uniqueOSOfYesterday = Sessions.getUniqueOSOfYesterday();
statistics.uniqueOSOfLastMonth = Sessions.getUniqueOSOfLastMonth();
statistics.apps = {
engineVersion: Info.marketplaceApiVersion,
enabled: Apps.isEnabled(),
totalInstalled: Apps.isInitialized() && Apps.getManager().get().length,
totalActive: Apps.isInitialized() && Apps.getManager().get({ enabled: true }).length,
};
const integrations = Integrations.find().fetch();
statistics.integrations = {
totalIntegrations: integrations.length,
totalIncoming: integrations.filter((integration) => integration.type === 'webhook-incoming').length,
totalIncomingActive: integrations.filter((integration) => integration.enabled === true && integration.type === 'webhook-incoming').length,
totalOutgoing: integrations.filter((integration) => integration.type === 'webhook-outgoing').length,
totalOutgoingActive: integrations.filter((integration) => integration.enabled === true && integration.type === 'webhook-outgoing').length,
totalWithScriptEnabled: integrations.filter((integration) => integration.scriptEnabled === true).length,
};
return statistics;
},
save() {
const rcStatistics = statistics.get();
rcStatistics.createdAt = new Date();
Statistics.insert(rcStatistics);
return rcStatistics;
},
};

@ -1,22 +1,15 @@
import { Meteor } from 'meteor/meteor';
import { hasPermission } from '../../../authorization';
import { Statistics } from '../../../models';
import { statistics } from '../statisticsNamespace';
import { getLastStatistics } from '../functions/getLastStatistics';
Meteor.methods({
getStatistics(refresh) {
if (!Meteor.userId()) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getStatistics' });
}
if (hasPermission(Meteor.userId(), 'view-statistics') !== true) {
throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'getStatistics' });
}
if (refresh) {
return statistics.save();
}
return Statistics.findLast();
return Promise.await(getLastStatistics({
userId: Meteor.userId(),
refresh,
}));
},
});

@ -1 +0,0 @@
export const statistics = {};

@ -18,6 +18,7 @@ export function InformationPage({
statistics,
instances,
onClickRefreshButton,
onClickDownloadInfo,
}) {
const t = useTranslation();
@ -31,6 +32,9 @@ export function InformationPage({
<Page.Header title={t('Info')}>
{canViewStatistics
&& <ButtonGroup>
<Button disabled={isLoading} external type='button' onClick={onClickDownloadInfo}>
<Icon name='download' /> {t('Download_Info')}
</Button>
<Button disabled={isLoading} primary type='button' onClick={onClickRefreshButton}>
<Icon name='reload' /> {t('Refresh')}
</Button>

@ -115,12 +115,14 @@ export const _default = () =>
statistics={object('statistics', statistics)}
instances={object('instances', exampleInstance)}
onClickRefreshButton={action('clickRefreshButton')}
onClickDownloadInfo={action('clickDownloadInfo')}
/>;
export const withoutCanViewStatisticsPermission = () =>
<InformationPage
info={info}
onClickRefreshButton={action('clickRefreshButton')}
onClickDownloadInfo={action('clickDownloadInfo')}
/>;
export const loading = () =>
@ -129,6 +131,7 @@ export const loading = () =>
isLoading
info={info}
onClickRefreshButton={action('clickRefreshButton')}
onClickDownloadInfo={action('clickDownloadInfo')}
/>;
export const withStatistics = () =>
@ -137,6 +140,7 @@ export const withStatistics = () =>
info={info}
statistics={statistics}
onClickRefreshButton={action('clickRefreshButton')}
onClickDownloadInfo={action('clickDownloadInfo')}
/>;
export const withOneInstance = () =>
@ -146,6 +150,7 @@ export const withOneInstance = () =>
statistics={statistics}
instances={[exampleInstance]}
onClickRefreshButton={action('clickRefreshButton')}
onClickDownloadInfo={action('clickDownloadInfo')}
/>;
export const withTwoInstances = () =>
@ -155,6 +160,7 @@ export const withTwoInstances = () =>
statistics={statistics}
instances={[exampleInstance, exampleInstance]}
onClickRefreshButton={action('clickRefreshButton')}
onClickDownloadInfo={action('clickDownloadInfo')}
/>;
export const withTwoInstancesAndDisabledOplog = () =>
@ -164,4 +170,5 @@ export const withTwoInstancesAndDisabledOplog = () =>
statistics={{ ...statistics, instanceCount: 2, oplogEnabled: false }}
instances={[exampleInstance, exampleInstance]}
onClickRefreshButton={action('clickRefreshButton')}
onClickDownloadInfo={action('clickDownloadInfo')}
/>;

@ -1,9 +1,10 @@
import React, { useState, useEffect } from 'react';
import { usePermission } from '../../../contexts/AuthorizationContext';
import { useMethod, useServerInformation } from '../../../contexts/ServerContext';
import { useMethod, useServerInformation, useEndpoint } from '../../../contexts/ServerContext';
import { useAdminSideNav } from '../../../hooks/useAdminSideNav';
import { InformationPage } from './InformationPage';
import { downloadJsonAsAFile } from '../../../helpers/download';
export function InformationRoute() {
useAdminSideNav();
@ -14,7 +15,7 @@ export function InformationRoute() {
const [statistics, setStatistics] = useState({});
const [instances, setInstances] = useState([]);
const [fetchStatistics, setFetchStatistics] = useState(() => () => ({}));
const getStatistics = useMethod('getStatistics');
const getStatistics = useEndpoint('GET', 'statistics');
const getInstances = useMethod('instances/get');
useEffect(() => {
@ -31,14 +32,13 @@ export function InformationRoute() {
try {
const [statistics, instances] = await Promise.all([
getStatistics(),
getStatistics({ refresh: true }),
getInstances(),
]);
if (didCancel) {
return;
}
setStatistics(statistics);
setInstances(instances);
} finally {
@ -65,6 +65,13 @@ export function InformationRoute() {
fetchStatistics();
};
const handleClickDownloadInfo = () => {
if (isLoading) {
return;
}
downloadJsonAsAFile(statistics, 'statistics');
};
return <InformationPage
canViewStatistics={canViewStatistics}
isLoading={isLoading}
@ -72,5 +79,6 @@ export function InformationRoute() {
statistics={statistics}
instances={instances}
onClickRefreshButton={handleClickRefreshButton}
onClickDownloadInfo={handleClickDownloadInfo}
/>;
}

@ -4,6 +4,7 @@ export const ServerContext = createContext({
info: {},
absoluteUrl: (path) => path,
callMethod: async () => {},
callEndpoint: async () => {},
});
export const useServerInformation = () => useContext(ServerContext).info;
@ -14,3 +15,8 @@ export const useMethod = (methodName) => {
const { callMethod } = useContext(ServerContext);
return useCallback((...args) => callMethod(methodName, ...args), [callMethod, methodName]);
};
export const useEndpoint = (httpMethod, endpoint) => {
const { callEndpoint } = useContext(ServerContext);
return useCallback((...args) => callEndpoint(httpMethod, endpoint, ...args), [callEndpoint, httpMethod, endpoint]);
};

@ -0,0 +1,15 @@
export const downloadJsonAsAFile = (jsonData, name = 'jsonfile') => {
const filename = `${ name }.json`;
const contentType = 'application/json;charset=utf-8;';
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
const blob = new Blob([decodeURIComponent(encodeURI(JSON.stringify(jsonData)))], { type: contentType });
return navigator.msSaveOrOpenBlob(blob, filename);
}
const aElement = document.createElement('a');
aElement.download = filename;
aElement.href = `data:${ contentType },${ encodeURIComponent(JSON.stringify(jsonData)) }`;
aElement.target = '_blank';
document.body.appendChild(aElement);
aElement.click();
document.body.removeChild(aElement);
};

@ -3,6 +3,7 @@ import { Meteor } from 'meteor/meteor';
import { Info as info } from '../../app/utils';
import { ServerContext } from '../contexts/ServerContext';
import { APIClient } from '../../app/utils/client';
const absoluteUrl = (path) => Meteor.absoluteUrl(path);
@ -17,10 +18,25 @@ const callMethod = (methodName, ...args) => new Promise((resolve, reject) => {
});
});
const callEndpoint = (httpMethod, endpoint, ...args) => {
const allowedHttpMethods = ['get', 'post', 'delete'];
if (!httpMethod || !allowedHttpMethods.includes(httpMethod.toLowerCase())) {
throw new Error('Invalid http method provided to "useEndpoint"');
}
if (!endpoint) {
throw new Error('Invalid endpoint provided to "useEndpoint"');
}
if (endpoint.startsWith('/')) {
endpoint = endpoint.replace('/', '');
}
return APIClient.v1[httpMethod.toLowerCase()](endpoint, ...args);
};
const contextValue = {
info,
absoluteUrl,
callMethod,
callEndpoint,
};
export function ServerProvider({ children }) {

@ -1168,6 +1168,7 @@
"Domains": "Domains",
"Domains_allowed_to_embed_the_livechat_widget": "Comma-separated list of domains allowed to embed the livechat widget. Leave blank to allow all domains.",
"Downloading_file_from_external_URL": "Downloading file from external URL",
"Download_Info": "Download Info",
"Download_My_Data": "Download My Data (HTML)",
"Download_Pending_Files": "Download Pending Files",
"Download_Snippet": "Download",

@ -1093,6 +1093,7 @@
"Domains": "Domínios",
"Domains_allowed_to_embed_the_livechat_widget": "A lista de domínios separados por vírgulas permitiu incorporar o widget do Livechat. Deixe em branco para permitir todos os domínios.",
"Downloading_file_from_external_URL": "Baixar arquivo de URL externa",
"Download_Info": "Baixar informações",
"Download_My_Data": "Baixar meus dados (HTML)",
"Download_Snippet": "Baixar",
"Drop_to_upload_file": "Largue para enviar arquivos",

@ -0,0 +1,85 @@
import {
getCredentials,
api,
request,
credentials,
} from '../../data/api-data.js';
import { updatePermission } from '../../data/permissions.helper.js';
describe('[Statistics]', function() {
this.retries(0);
before((done) => getCredentials(done));
describe('[/statistics]', () => {
let lastUptime;
it('should return an error when the user does not have the necessary permission', (done) => {
updatePermission('view-statistics', []).then(() => {
request.get(api('statistics'))
.set(credentials)
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body.error).to.be.equal('error-not-allowed');
})
.end(done);
});
});
it('should return an object with the statistics', (done) => {
updatePermission('view-statistics', ['admin']).then(() => {
request.get(api('statistics'))
.set(credentials)
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('process');
expect(res.body.process).to.have.property('uptime');
lastUptime = res.body.process.uptime;
})
.end(done);
});
});
it('should update the statistics when is provided the "refresh:true" query parameter', (done) => {
request.get(api('statistics?refresh=true'))
.set(credentials)
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('process');
expect(res.body.process).to.have.property('uptime');
expect(lastUptime).to.not.be.equal(res.body.process.uptime);
})
.end(done);
});
});
describe('[/statistics.list]', () => {
it('should return an error when the user does not have the necessary permission', (done) => {
updatePermission('view-statistics', []).then(() => {
request.get(api('statistics.list'))
.set(credentials)
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body.error).to.be.equal('error-not-allowed');
})
.end(done);
});
});
it('should return an array with the statistics', (done) => {
updatePermission('view-statistics', ['admin']).then(() => {
request.get(api('statistics.list'))
.set(credentials)
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('statistics').and.to.be.an('array');
expect(res.body).to.have.property('offset');
expect(res.body).to.have.property('total');
expect(res.body).to.have.property('count');
})
.end(done);
});
});
});
});
Loading…
Cancel
Save