diff --git a/.changeset/workspace-status-admin-page.md b/.changeset/workspace-status-admin-page.md new file mode 100644 index 000000000000..b590387a01c5 --- /dev/null +++ b/.changeset/workspace-status-admin-page.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": minor +--- + +Added a new Admin page called `Workspace info` in place of Information page, to make it easier to check the license diff --git a/apps/meteor/app/api/server/lib/getServerInfo.ts b/apps/meteor/app/api/server/lib/getServerInfo.ts index 53ba3656babe..9a0e7e4e11c9 100644 --- a/apps/meteor/app/api/server/lib/getServerInfo.ts +++ b/apps/meteor/app/api/server/lib/getServerInfo.ts @@ -1,3 +1,5 @@ +import type { IWorkspaceInfo } from '@rocket.chat/core-typings'; + import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { getCachedSupportedVersionsToken, @@ -5,16 +7,9 @@ import { } from '../../../cloud/server/functions/supportedVersionsToken/supportedVersionsToken'; import { Info, minimumClientVersions } from '../../../utils/rocketchat.info'; -type ServerInfo = { - info?: typeof Info; - supportedVersions?: { signed: string }; - minimumClientVersions: typeof minimumClientVersions; - version: string; -}; - const removePatchInfo = (version: string): string => version.replace(/(\d+\.\d+).*/, '$1'); -export async function getServerInfo(userId?: string): Promise { +export async function getServerInfo(userId?: string): Promise { const hasPermissionToViewStatistics = userId && (await hasPermissionAsync(userId, 'view-statistics')); const supportedVersionsToken = await wrapPromise(getCachedSupportedVersionsToken()); diff --git a/apps/meteor/app/statistics/server/lib/statistics.ts b/apps/meteor/app/statistics/server/lib/statistics.ts index 9e63c18506ac..c0ef16b5025b 100644 --- a/apps/meteor/app/statistics/server/lib/statistics.ts +++ b/apps/meteor/app/statistics/server/lib/statistics.ts @@ -305,18 +305,30 @@ export const statistics = { ); // Message statistics - statistics.totalChannelMessages = (await Rooms.findByType('c', { projection: { msgs: 1 } }).toArray()).reduce( - function _countChannelMessages(num: number, room: IRoom) { + const channels = await Rooms.findByType('c', { projection: { msgs: 1, prid: 1 } }).toArray(); + const totalChannelDiscussionsMessages = await channels.reduce(function _countChannelDiscussionsMessages(num: number, room: IRoom) { + return num + (room.prid ? room.msgs : 0); + }, 0); + statistics.totalChannelMessages = + (await channels.reduce(function _countChannelMessages(num: number, room: IRoom) { return num + room.msgs; - }, - 0, - ); - statistics.totalPrivateGroupMessages = (await Rooms.findByType('p', { projection: { msgs: 1 } }).toArray()).reduce( - function _countPrivateGroupMessages(num: number, room: IRoom) { + }, 0)) - totalChannelDiscussionsMessages; + + const privateGroups = await Rooms.findByType('p', { projection: { msgs: 1, prid: 1 } }).toArray(); + const totalPrivateGroupsDiscussionsMessages = await privateGroups.reduce(function _countPrivateGroupsDiscussionsMessages( + num: number, + room: IRoom, + ) { + return num + (room.prid ? room.msgs : 0); + }, + 0); + statistics.totalPrivateGroupMessages = + (await privateGroups.reduce(function _countPrivateGroupMessages(num: number, room: IRoom) { return num + room.msgs; - }, - 0, - ); + }, 0)) - totalPrivateGroupsDiscussionsMessages; + + statistics.totalDiscussionsMessages = totalPrivateGroupsDiscussionsMessages + totalChannelDiscussionsMessages; + statistics.totalDirectMessages = (await Rooms.findByType('d', { projection: { msgs: 1 } }).toArray()).reduce( function _countDirectMessages(num: number, room: IRoom) { return num + room.msgs; @@ -332,6 +344,7 @@ export const statistics = { statistics.totalMessages = statistics.totalChannelMessages + statistics.totalPrivateGroupMessages + + statistics.totalDiscussionsMessages + statistics.totalDirectMessages + statistics.totalLivechatMessages; diff --git a/apps/meteor/client/hooks/useLicense.ts b/apps/meteor/client/hooks/useLicense.ts index 5c9b000d65d9..a2ea89359f87 100644 --- a/apps/meteor/client/hooks/useLicense.ts +++ b/apps/meteor/client/hooks/useLicense.ts @@ -26,11 +26,11 @@ const invalidateQueryClientLicenses = (() => { export const useLicense = (params?: LicenseParams): UseQueryResult> => { const getLicenses = useEndpoint('GET', '/v1/licenses.info'); - const queryClient = useQueryClient(); + const invalidateQueries = useInvalidateLicense(); const notify = useSingleStream('notify-all'); - useEffect(() => notify('license', () => invalidateQueryClientLicenses(queryClient)), [notify, queryClient]); + useEffect(() => notify('license', () => invalidateQueries()), [notify, invalidateQueries]); return useQuery(['licenses', 'getLicenses', params?.loadValues], () => getLicenses({ ...params }), { staleTime: Infinity, @@ -38,3 +38,9 @@ export const useLicense = (params?: LicenseParams): UseQueryResult data.license, }); }; + +export const useInvalidateLicense = () => { + const queryClient = useQueryClient(); + + return () => invalidateQueryClientLicenses(queryClient); +}; diff --git a/apps/meteor/client/hooks/useWorkspaceInfo.ts b/apps/meteor/client/hooks/useWorkspaceInfo.ts new file mode 100644 index 000000000000..472a8ed334dd --- /dev/null +++ b/apps/meteor/client/hooks/useWorkspaceInfo.ts @@ -0,0 +1,69 @@ +import type { IStats, IWorkspaceInfo, Serialized } from '@rocket.chat/core-typings'; +import type { IInstance } from '@rocket.chat/rest-typings'; +import { useEndpoint } from '@rocket.chat/ui-contexts'; +import { useMutation, useQueries, useQueryClient } from '@tanstack/react-query'; + +export const useWorkspaceInfo = () => { + const getStatistics = useEndpoint('GET', '/v1/statistics'); + const getInstances = useEndpoint('GET', '/v1/instances.get'); + const getServerInfo = useEndpoint('GET', '/info'); + + return useQueries({ + queries: [ + { + queryKey: ['info', 'serverInfo'], + queryFn: async () => { + const data = await getServerInfo(); + + if (!('minimumClientVersions' in data)) { + throw new Error('Invalid server info'); + } + if (!('info' in data)) { + throw new Error('Invalid server info'); + } + if (!('version' in data)) { + throw new Error('Invalid server info'); + } + + return data as IWorkspaceInfo; + }, + staleTime: Infinity, + keepPreviousData: true, + }, + { + queryKey: ['info', 'instances'], + queryFn: () => getInstances(), + staleTime: Infinity, + keepPreviousData: true, + select({ instances }: Serialized<{ instances: IInstance[] }>) { + return instances.map((instance) => ({ + ...instance, + ...(instance.instanceRecord && { + instanceRecord: { + ...instance.instanceRecord, + _createdAt: new Date(instance.instanceRecord._createdAt), + }, + }), + })) as IInstance[]; + }, + }, + { + queryKey: ['info', 'statistics'], + queryFn: () => getStatistics({ refresh: 'true' }), + staleTime: Infinity, + keepPreviousData: true, + select: (data: Serialized) => ({ + ...data, + lastMessageSentAt: data.lastMessageSentAt ? new Date(data.lastMessageSentAt) : undefined, + }), + }, + ], + }); +}; + +export const useRefreshStatistics = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: () => queryClient.invalidateQueries(['info', 'statistics']), + }); +}; diff --git a/apps/meteor/client/lib/utils/isOverLicenseLimits.ts b/apps/meteor/client/lib/utils/isOverLicenseLimits.ts new file mode 100644 index 000000000000..3bb8d1d50dbf --- /dev/null +++ b/apps/meteor/client/lib/utils/isOverLicenseLimits.ts @@ -0,0 +1,12 @@ +import type { LicenseLimitKind } from '@rocket.chat/license'; + +type Limits = Record< + LicenseLimitKind, + { + max: number; + value?: number; + } +>; + +export const isOverLicenseLimits = (limits: Limits): boolean => + Object.values(limits).some((limit) => limit.value !== undefined && limit.value > limit.max); diff --git a/apps/meteor/client/views/admin/info/Feature.js b/apps/meteor/client/views/admin/info/Feature.js deleted file mode 100644 index a4edaa1549c0..000000000000 --- a/apps/meteor/client/views/admin/info/Feature.js +++ /dev/null @@ -1,13 +0,0 @@ -import { Box, Icon } from '@rocket.chat/fuselage'; -import React from 'react'; - -const Feature = ({ label, enabled }) => ( - - - - - {label} - -); - -export default Feature; diff --git a/apps/meteor/client/views/admin/info/Feature.stories.tsx b/apps/meteor/client/views/admin/info/Feature.stories.tsx deleted file mode 100644 index 0f1606d730a4..000000000000 --- a/apps/meteor/client/views/admin/info/Feature.stories.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import type { ComponentMeta, ComponentStory } from '@storybook/react'; -import React from 'react'; - -import Feature from './Feature'; - -export default { - title: 'Admin/Info/Feature', - component: Feature, - parameters: { - layout: 'centered', - }, -} as ComponentMeta; - -const Template: ComponentStory = (args) => ; - -export const Enabled = Template.bind({}); -Enabled.args = { - enabled: true, - label: 'Awesome feature', -}; - -export const NotEnabled = Template.bind({}); -NotEnabled.args = { - enabled: false, - label: 'Awesome feature', -}; diff --git a/apps/meteor/client/views/admin/info/InformationPage.stories.tsx b/apps/meteor/client/views/admin/info/InformationPage.stories.tsx deleted file mode 100644 index d05c0f7372a2..000000000000 --- a/apps/meteor/client/views/admin/info/InformationPage.stories.tsx +++ /dev/null @@ -1,318 +0,0 @@ -import type { ComponentMeta, ComponentStory } from '@storybook/react'; -import React from 'react'; - -import InformationPage from './InformationPage'; - -export default { - title: 'Admin/Info/InformationPage', - component: InformationPage, - parameters: { - layout: 'fullscreen', - serverContext: { - baseURL: 'http://localhost:3000', - callEndpoint: { - 'GET /v1/licenses.get': async () => ({ - licenses: [ - { - url: 'https://example.com/license.txt', - expiry: '2020-01-01T00:00:00.000Z', - maxActiveUsers: 100, - modules: ['auditing'], - maxGuestUsers: 100, - maxRoomsPerGuest: 100, - }, - ], - }), - 'GET /v1/licenses.maxActiveUsers': async () => ({ - maxActiveUsers: 123, - activeUsers: 32, - }), - }, - callMethod: { - 'license:getTags': async () => [{ name: 'Example plan', color: 'red' }], - }, - }, - }, - decorators: [(fn) =>
{fn()}
], - argTypes: { - onClickDownloadInfo: { action: 'onClickDownloadInfo' }, - onClickRefreshButton: { action: 'onClickRefreshButton' }, - }, - args: { - canViewStatistics: true, - info: { - build: { - arch: 'x64', - cpus: 1, - platform: 'linux', - osRelease: 'Ubuntu 18.04.1 LTS', - date: '2020-01-01T00:00:00.000Z', - freeMemory: 1.3 * 1024 * 1024 * 1024, - nodeVersion: 'v12.0.0', - totalMemory: 2.4 * 1024 * 1024 * 1024, - }, - version: '1.0.0', - marketplaceApiVersion: '1.0.0', - commit: { - author: 'John Doe', - date: '2020-01-01T00:00:00.000Z', - branch: 'master', - hash: '1234567890', - subject: 'This is a commit', - tag: 'v1.0.0', - }, - }, - statistics: { - // Users - totalUsers: 123, - onlineUsers: 23, - awayUsers: 32, - busyUsers: 21, - offlineUsers: 123 - 23 - 32 - 21, - // Types and Distribution - totalConnectedUsers: 32, - activeUsers: 12, - activeGuests: 32 - 12, - nonActiveUsers: 0, - appUsers: 23, - // Uploads - uploadsTotal: 321, - uploadsTotalSize: 123 * 1024 * 1024, - // Rooms - totalRooms: 231, - totalChannels: 12, - totalPrivateGroups: 23, - totalDirect: 21, - totalDiscussions: 32, - totalLivechat: 31, - // Messages - totalMessages: 321, - totalThreads: 123, - totalChannelMessages: 213, - totalPrivateGroupMessages: 21, - totalDirectMessages: 23, - totalLivechatMessages: 31, - // - - _id: '', - wizard: {}, - uniqueId: '', - deploymentFingerprintHash: '', - deploymentFingerprintVerified: true, - installedAt: '', - version: '', - tag: '', - branch: '', - userLanguages: {}, - teams: { - totalTeams: 0, - totalRoomsInsideTeams: 0, - totalDefaultRoomsInsideTeams: 0, - }, - totalLivechatManagers: 10, - totalCustomFields: 10, - totalTriggers: 1, - isDepartmentRemovalEnabled: false, - archivedDepartments: 0, - totalLivechatVisitors: 0, - totalLivechatAgents: 0, - livechatEnabled: false, - federatedServers: 0, - federatedUsers: 0, - lastLogin: '', - lastMessageSentAt: new Date(), - lastSeenSubscription: '', - os: { - type: '', - platform: 'linux', - arch: '', - release: '', - uptime: 0, - loadavg: [0, 0, 0], - totalmem: 0, - freemem: 0, - cpus: [ - { - model: '', - speed: 0, - times: { - user: 0, - nice: 0, - sys: 0, - idle: 0, - irq: 0, - }, - }, - ], - }, - process: { - nodeVersion: '', - pid: 0, - uptime: 0, - }, - deploy: { - method: '', - platform: '', - }, - enterpriseReady: false, - migration: { - _id: '', - locked: false, - version: 0, - buildAt: '', - lockedAt: '', - }, - instanceCount: 0, - msEnabled: false, - oplogEnabled: false, - mongoVersion: '', - mongoStorageEngine: '', - pushQueue: 0, - omnichannelSources: [{}], - departments: 0, - routingAlgorithm: '', - onHoldEnabled: false, - emailInboxes: 0, - BusinessHours: {}, - lastChattedAgentPreferred: false, - assignNewConversationsToContactManager: false, - visitorAbandonment: '', - chatsOnHold: 0, - voipEnabled: false, - voipCalls: 0, - voipExtensions: 0, - voipSuccessfulCalls: 0, - voipErrorCalls: 0, - voipOnHoldCalls: 0, - webRTCEnabled: false, - webRTCEnabledForOmnichannel: false, - omnichannelWebRTCCalls: 1, - federationOverviewData: { - numberOfEvents: 0, - numberOfFederatedUsers: 0, - numberOfServers: 0, - }, - readReceiptsEnabled: false, - readReceiptsDetailed: false, - uniqueUsersOfLastWeek: { data: [], day: 0, month: 0, year: 0 }, - uniqueUsersOfLastMonth: { data: [], day: 0, month: 0, year: 0 }, - uniqueUsersOfYesterday: { data: [], day: 0, month: 0, year: 0 }, - uniqueDevicesOfYesterday: { data: [], day: 0, month: 0, year: 0 }, - uniqueDevicesOfLastWeek: { data: [], day: 0, month: 0, year: 0 }, - uniqueDevicesOfLastMonth: { data: [], day: 0, month: 0, year: 0 }, - uniqueOSOfYesterday: { data: [], day: 0, month: 0, year: 0 }, - uniqueOSOfLastWeek: { data: [], day: 0, month: 0, year: 0 }, - uniqueOSOfLastMonth: { data: [], day: 0, month: 0, year: 0 }, - omnichannelContactsBySource: { contactsCount: 0, conversationsCount: 0, sources: [] }, - uniqueContactsOfLastMonth: { contactsCount: 0, conversationsCount: 0, sources: [] }, - uniqueContactsOfLastWeek: { contactsCount: 0, conversationsCount: 0, sources: [] }, - uniqueContactsOfYesterday: { contactsCount: 0, conversationsCount: 0, sources: [] }, - apps: { - engineVersion: 'x.y.z', - enabled: false, - totalInstalled: 0, - totalActive: 0, - totalFailed: 0, - }, - services: {}, - importer: {}, - settings: {}, - integrations: { - totalIntegrations: 0, - totalIncoming: 0, - totalIncomingActive: 0, - totalOutgoing: 0, - totalOutgoingActive: 0, - totalWithScriptEnabled: 0, - }, - enterprise: { - modules: [], - tags: [], - seatRequests: 0, - livechatTags: 0, - cannedResponses: 0, - priorities: 0, - businessUnits: 0, - }, - createdAt: new Date(), - showHomeButton: false, - homeTitleChanged: false, - homeBodyChanged: false, - customCSSChanged: false, - onLogoutCustomScriptChanged: false, - loggedOutCustomScriptChanged: false, - loggedInCustomScriptChanged: false, - logoChange: false, - customCSS: 0, - customScript: 0, - tabInvites: 0, - totalEmailInvitation: 0, - totalRoomsWithStarred: 0, - totalRoomsWithPinned: 0, - totalStarred: 0, - totalPinned: 0, - totalE2ERooms: 0, - totalE2EMessages: 0, - totalUserTOTP: 0, - totalUserEmail2fa: 0, - usersCreatedADM: 0, - usersCreatedSlackImport: 0, - usersCreatedSlackUser: 0, - usersCreatedCSVImport: 0, - usersCreatedHiptext: 0, - totalOTR: 0, - totalOTRRooms: 0, - slashCommandsJitsi: 0, - messageAuditApply: 0, - messageAuditLoad: 0, - dashboardCount: 0, - joinJitsiButton: 0, - totalBroadcastRooms: 0, - totalRoomsWithActiveLivestream: 0, - totalTriggeredEmails: 0, - totalLinkInvitation: 0, - roomsInsideTeams: 0, - totalEncryptedMessages: 0, - totalLinkInvitationUses: 0, - totalManuallyAddedUsers: 0, - videoConf: { - videoConference: { - started: 0, - ended: 0, - }, - direct: { - calling: 0, - started: 0, - ended: 0, - }, - livechat: { - started: 0, - ended: 0, - }, - settings: { - provider: '', - dms: false, - channels: false, - groups: false, - teams: false, - }, - }, - totalSubscriptionRoles: 0, - totalUserRoles: 0, - totalCustomRoles: 0, - totalWebRTCCalls: 0, - uncaughtExceptionsCount: 0, - push: 0, - dailyPeakConnections: 0, - maxMonthlyPeakConnections: 0, - matrixFederation: { - enabled: false, - }, - }, - instances: [], - }, -} as ComponentMeta; - -const Template: ComponentStory = (args) => ; - -export const Default = Template.bind({}); -Default.storyName = 'InformationPage'; diff --git a/apps/meteor/client/views/admin/info/InformationRoute.tsx b/apps/meteor/client/views/admin/info/InformationRoute.tsx deleted file mode 100644 index 8bc7bf023c0c..000000000000 --- a/apps/meteor/client/views/admin/info/InformationRoute.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import type { IStats, Serialized } from '@rocket.chat/core-typings'; -import { Callout, ButtonGroup, Button } from '@rocket.chat/fuselage'; -import type { IInstance } from '@rocket.chat/rest-typings'; -import { usePermission, useServerInformation, useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; -import type { ReactElement } from 'react'; -import React, { useState, useEffect, memo } from 'react'; - -import Page from '../../../components/Page'; -import PageSkeleton from '../../../components/PageSkeleton'; -import { downloadJsonAs } from '../../../lib/download'; -import NotAuthorizedPage from '../../notAuthorized/NotAuthorizedPage'; -import InformationPage from './InformationPage'; - -type fetchStatisticsCallback = ((params: { refresh: boolean }) => void) | (() => void); - -const InformationRoute = (): ReactElement => { - const t = useTranslation(); - const canViewStatistics = usePermission('view-statistics'); - - const [isLoading, setLoading] = useState(true); - const [error, setError] = useState(false); - const [statistics, setStatistics] = useState(); - const [instances, setInstances] = useState>([]); - const [fetchStatistics, setFetchStatistics] = useState(() => (): void => undefined); - const getStatistics = useEndpoint('GET', '/v1/statistics'); - const getInstances = useEndpoint('GET', '/v1/instances.get'); - - useEffect(() => { - let didCancel = false; - - const fetchStatistics = async ({ refresh = false } = {}): Promise => { - setLoading(true); - setError(false); - - try { - const [statistics, instancesData] = await Promise.all([getStatistics({ refresh: refresh ? 'true' : 'false' }), getInstances()]); - - if (didCancel) { - return; - } - setStatistics({ - ...statistics, - lastMessageSentAt: statistics.lastMessageSentAt ? new Date(statistics.lastMessageSentAt) : undefined, - }); - setInstances(instancesData.instances); - } catch (error) { - setError(!!error); - } finally { - setLoading(false); - } - }; - - setFetchStatistics(() => fetchStatistics); - - fetchStatistics(); - - return (): void => { - didCancel = true; - }; - }, [canViewStatistics, getInstances, getStatistics]); - - const info = useServerInformation(); - - const handleClickRefreshButton = (): void => { - if (isLoading) { - return; - } - - fetchStatistics({ refresh: true }); - }; - - const handleClickDownloadInfo = (): void => { - if (isLoading) { - return; - } - downloadJsonAs(statistics, 'statistics'); - }; - - if (isLoading) { - return ; - } - - if (error || !statistics) { - return ( - - - - - - - - {t('Error_loading_pages')} - - - ); - } - - if (canViewStatistics) { - return ( - - ); - } - - return ; -}; - -export default memo(InformationRoute); diff --git a/apps/meteor/client/views/admin/info/LicenseCard.stories.tsx b/apps/meteor/client/views/admin/info/LicenseCard.stories.tsx deleted file mode 100644 index 929a3c8f04d2..000000000000 --- a/apps/meteor/client/views/admin/info/LicenseCard.stories.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import type { ComponentMeta, ComponentStory } from '@storybook/react'; -import React from 'react'; - -import LicenseCard from './LicenseCard'; - -export default { - title: 'Admin/Info/LicenseCard', - component: LicenseCard, - parameters: { - layout: 'centered', - }, -} as ComponentMeta; - -const Template: ComponentStory = () => ; - -export const Example = Template.bind({}); -Example.parameters = { - serverContext: { - callEndpoint: { - 'GET /v1/licenses.get': async () => ({ - licenses: [ - { - url: 'https://example.com/license.txt', - expiry: '2020-01-01T00:00:00.000Z', - maxActiveUsers: 100, - modules: ['auditing'], - maxGuestUsers: 100, - maxRoomsPerGuest: 100, - }, - ], - }), - }, - callMethod: { - 'license:getTags': async () => [{ name: 'Example plan', color: 'red' }], - }, - }, -}; - -export const MultipleLicenses = Template.bind({}); -MultipleLicenses.parameters = { - serverContext: { - callEndpoint: { - 'GET /v1/licenses.get': async () => ({ - licenses: [ - { - url: 'https://example.com/license.txt', - expiry: '2020-01-01T00:00:00.000Z', - maxActiveUsers: 100, - modules: ['auditing'], - maxGuestUsers: 100, - maxRoomsPerGuest: 100, - }, - { - url: 'https://example.com/license.txt', - expiry: '2020-01-01T00:00:00.000Z', - maxActiveUsers: 100, - modules: ['engagement-dashboard'], - maxGuestUsers: 100, - maxRoomsPerGuest: 100, - }, - ], - }), - }, - callMethod: { - 'license:getTags': async () => [{ name: 'Example plan', color: 'red' }], - }, - }, -}; - -export const Loading = Template.bind({}); -Loading.parameters = { - serverContext: { - callEndpoint: { - 'GET /v1/licenses.get': 'infinite', - }, - callMethod: { - 'license:getTags': 'infinite', - }, - }, -}; - -export const Errored = Template.bind({}); -Errored.parameters = { - serverContext: { - callEndpoint: { - 'GET /v1/licenses.get': 'errored', - }, - callMethod: { - 'license:getTags': 'errored', - }, - }, -}; diff --git a/apps/meteor/client/views/admin/info/LicenseCard.tsx b/apps/meteor/client/views/admin/info/LicenseCard.tsx deleted file mode 100644 index 195121932e80..000000000000 --- a/apps/meteor/client/views/admin/info/LicenseCard.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { ButtonGroup, Button, Skeleton } from '@rocket.chat/fuselage'; -import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { Card, CardBody, CardCol, CardTitle, CardColSection, CardColTitle, CardFooter } from '@rocket.chat/ui-client'; -import { useSetModal, useSetting, useTranslation } from '@rocket.chat/ui-contexts'; -import type { ReactElement } from 'react'; -import React from 'react'; - -import PlanTag from '../../../components/PlanTag'; -import { useLicense } from '../../../hooks/useLicense'; -import Feature from './Feature'; -import OfflineLicenseModal from './OfflineLicenseModal'; - -const LicenseCard = (): ReactElement => { - const t = useTranslation(); - const setModal = useSetModal(); - - const currentLicense = useSetting('Enterprise_License') as string; - const licenseStatus = useSetting('Enterprise_License_Status') as string; - - const isAirGapped = true; - - const request = useLicense(); - - const handleApplyLicense = useMutableCallback(() => - setModal( - { - setModal(); - }} - license={currentLicense} - licenseStatus={licenseStatus} - />, - ), - ); - - if (request.isLoading || request.isError) { - return ( - - {t('License')} - - - - - - - {t('Features')} - - - - - - - - - - ); - } - - const { activeModules } = request.data; - - const hasEngagement = activeModules.includes('engagement-dashboard'); - const hasOmnichannel = activeModules.includes('livechat-enterprise'); - const hasAuditing = activeModules.includes('auditing'); - const hasCannedResponses = activeModules.includes('canned-responses'); - const hasReadReceipts = activeModules.includes('message-read-receipt'); - - return ( - - {t('License')} - - - - - - - {t('Features')} - - - - - - - - - - - - {isAirGapped ? ( - - ) : ( - - )} - - - - ); -}; - -export default LicenseCard; diff --git a/apps/meteor/client/views/admin/info/OfflineLicenseModal.stories.tsx b/apps/meteor/client/views/admin/info/OfflineLicenseModal.stories.tsx deleted file mode 100644 index 74e69f7bdf76..000000000000 --- a/apps/meteor/client/views/admin/info/OfflineLicenseModal.stories.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import type { ComponentMeta, ComponentStory } from '@storybook/react'; -import { screen, userEvent } from '@storybook/testing-library'; -import React from 'react'; - -import OfflineLicenseModal from './OfflineLicenseModal'; - -export default { - title: 'Admin/Info/OfflineLicenseModal', - component: OfflineLicenseModal, - parameters: { - layout: 'fullscreen', - serverContext: { - callEndpoint: { - 'POST /v1/licenses.add': async ({ license }: { license: string }) => ({ - success: license === 'valid-license', - }), - }, - }, - }, - argTypes: { - onClose: { action: 'onClose' }, - }, -} as ComponentMeta; - -const Template: ComponentStory = (args) => ; - -export const WithValidLicense = Template.bind({}); -WithValidLicense.args = { - license: 'valid-license', - licenseStatus: 'valid', -}; - -export const WithInvalidLicense = Template.bind({}); -WithInvalidLicense.args = { - license: 'invalid-license', - licenseStatus: 'invalid', -}; - -export const ApplyingValidLicense = Template.bind({}); -ApplyingValidLicense.play = async () => { - const licenseInput = screen.getByRole('textbox'); - - await userEvent.type(licenseInput, 'valid-license', { delay: 100 }); - - const applyButton = screen.getByRole('button', { name: 'Apply license' }); - - userEvent.click(applyButton); -}; - -export const ApplyingInvalidLicense = Template.bind({}); -ApplyingInvalidLicense.play = async () => { - const licenseInput = screen.getByRole('textbox'); - - await userEvent.type(licenseInput, 'invalid-license', { delay: 100 }); - - const applyButton = screen.getByRole('button', { name: 'Apply license' }); - - userEvent.click(applyButton); -}; diff --git a/apps/meteor/client/views/admin/info/OfflineLicenseModal.tsx b/apps/meteor/client/views/admin/info/OfflineLicenseModal.tsx deleted file mode 100644 index 0ece529fc2d3..000000000000 --- a/apps/meteor/client/views/admin/info/OfflineLicenseModal.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { Modal, Box, ButtonGroup, Button, Scrollable, Callout, Margins } from '@rocket.chat/fuselage'; -import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { useEndpoint, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts'; -import type { ComponentProps, FormEvent, ReactElement } from 'react'; -import React, { useState } from 'react'; - -import { queryClient } from '../../../lib/queryClient'; - -type OfflineLicenseModalProps = { - onClose: () => void; - license: string; - licenseStatus: string; -} & ComponentProps; - -const OfflineLicenseModal = ({ onClose, license, licenseStatus, ...props }: OfflineLicenseModalProps): ReactElement => { - const t = useTranslation(); - - const dispatchToastMessage = useToastMessageDispatch(); - - const [newLicense, setNewLicense] = useState(license); - const [isUpdating, setIsUpdating] = useState(false); - const [status, setStatus] = useState(licenseStatus); - const [lastSetLicense, setLastSetLicense] = useState(license); - - const handleNewLicense = (e: FormEvent): void => { - setNewLicense(e.currentTarget.value); - }; - - const hasChanges = lastSetLicense !== newLicense; - - const handlePaste = useMutableCallback(async () => { - try { - const text = await navigator.clipboard.readText(); - setNewLicense(text); - } catch (error) { - dispatchToastMessage({ type: 'error', message: `${t('Paste_error')}: ${error}` }); - } - }); - - const addLicense = useEndpoint('POST', '/v1/licenses.add'); - - const handleApplyLicense = useMutableCallback(async (e) => { - e.preventDefault(); - setLastSetLicense(newLicense); - try { - setIsUpdating(true); - await addLicense({ license: newLicense }); - queryClient.invalidateQueries(['licenses']); - - dispatchToastMessage({ type: 'success', message: t('Cloud_License_applied_successfully') }); - onClose(); - } catch (error) { - dispatchToastMessage({ - type: 'error', - message: error && typeof error === 'object' && 'error' in error ? (error as any).error : String(error), - }); - setIsUpdating(false); - setStatus('invalid'); - } - }); - - return ( - ) => } {...props}> - - {t('Cloud_Apply_Offline_License')} - - - - -

{t('Cloud_register_offline_finish_helper')}

-
- - - - - - - - - - - {status === 'invalid' && {t('Cloud_Invalid_license')}} -
- - - - - -
- ); -}; - -export default OfflineLicenseModal; diff --git a/apps/meteor/client/views/admin/info/UsageCard.tsx b/apps/meteor/client/views/admin/info/UsageCard.tsx deleted file mode 100644 index 7a3b2123e5f2..000000000000 --- a/apps/meteor/client/views/admin/info/UsageCard.tsx +++ /dev/null @@ -1,186 +0,0 @@ -import type { IStats } from '@rocket.chat/core-typings'; -import { ButtonGroup, Button } from '@rocket.chat/fuselage'; -import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { - Card, - CardBody, - CardCol, - CardTitle, - CardColSection, - CardColTitle, - CardFooter, - TextSeparator, - CardIcon, -} from '@rocket.chat/ui-client'; -import { useRoute, useTranslation } from '@rocket.chat/ui-contexts'; -import type { ReactElement } from 'react'; -import React, { memo } from 'react'; - -import { useHasLicenseModule } from '../../../../ee/client/hooks/useHasLicenseModule'; -import { UserStatus } from '../../../components/UserStatus'; -import { useFormatMemorySize } from '../../../hooks/useFormatMemorySize'; - -type UsageCardProps = { - statistics: IStats; - vertical: boolean; -}; - -const UsageCard = ({ statistics, vertical }: UsageCardProps): ReactElement => { - const t = useTranslation(); - const formatMemorySize = useFormatMemorySize(); - - const router = useRoute('engagement-dashboard'); - - const handleEngagement = useMutableCallback(() => { - router.push(); - }); - - const canViewEngagement = useHasLicenseModule('engagement-dashboard'); - - return ( - - {t('Usage')} - - - - {t('Users')} - - {t('Total')} - - } - value={statistics.totalUsers} - /> - - - - {' '} - {t('Online')} - - } - value={statistics.onlineUsers} - /> - - - - {' '} - {t('Busy')} - - } - value={statistics.busyUsers} - /> - - - - {' '} - {t('Away')} - - } - value={statistics.awayUsers} - /> - - - - {' '} - {t('Offline')} - - } - value={statistics.offlineUsers} - /> - - - {t('Types_and_Distribution')} - - - - - - - - {t('Uploads')} - - - - - {t('Total_rooms')} - - {t('Stats_Total_Rooms')} - - } - value={statistics.totalRooms} - /> - - {t('Stats_Total_Channels')} - - } - value={statistics.totalChannels} - /> - - {t('Stats_Total_Private_Groups')} - - } - value={statistics.totalPrivateGroups} - /> - - {t('Stats_Total_Direct_Messages')} - - } - value={statistics.totalDirect} - /> - - {t('Total_Discussions')} - - } - value={statistics.totalDiscussions} - /> - - {t('Stats_Total_Livechat_Rooms')} - - } - value={statistics.totalLivechat} - /> - - - {t('Total_messages')} - - - - - - - - - - - - - - - - ); -}; - -export default memo(UsageCard); diff --git a/apps/meteor/client/views/admin/routes.tsx b/apps/meteor/client/views/admin/routes.tsx index 20b86a210f95..807329011673 100644 --- a/apps/meteor/client/views/admin/routes.tsx +++ b/apps/meteor/client/views/admin/routes.tsx @@ -13,10 +13,14 @@ declare module '@rocket.chat/ui-contexts' { pathname: `/admin/sounds${`/${string}` | ''}${`/${string}` | ''}`; pattern: '/admin/sounds/:context?/:id?'; }; - 'admin-info': { + 'info': { pathname: '/admin/info'; pattern: '/admin/info'; }; + 'workspace': { + pathname: '/admin/workspace'; + pattern: '/admin/workspace'; + }; 'admin-import': { pathname: '/admin/import'; pattern: '/admin/import'; @@ -123,9 +127,15 @@ registerAdminRoute('/sounds/:context?/:id?', { component: lazy(() => import('./customSounds/CustomSoundsRoute')), }); +/** @deprecated in favor of `/workspace` route, this is a fallback to work in Mobile app, should be removed in the next major */ registerAdminRoute('/info', { - name: 'admin-info', - component: lazy(() => import('./info/InformationRoute')), + name: 'info', + component: lazy(() => import('./workspace/WorkspaceRoute')), +}); + +registerAdminRoute('/workspace', { + name: 'workspace', + component: lazy(() => import('./workspace/WorkspaceRoute')), }); registerAdminRoute('/import', { diff --git a/apps/meteor/client/views/admin/info/UsagePieGraph.stories.tsx b/apps/meteor/client/views/admin/subscription/components/UsagePieGraph.stories.tsx similarity index 90% rename from apps/meteor/client/views/admin/info/UsagePieGraph.stories.tsx rename to apps/meteor/client/views/admin/subscription/components/UsagePieGraph.stories.tsx index d23b9f9c3f13..ef1a685a7dee 100644 --- a/apps/meteor/client/views/admin/info/UsagePieGraph.stories.tsx +++ b/apps/meteor/client/views/admin/subscription/components/UsagePieGraph.stories.tsx @@ -3,8 +3,8 @@ import type { ComponentMeta, ComponentStory, Story } from '@storybook/react'; import type { ComponentProps } from 'react'; import React from 'react'; -import { useAutoSequence } from '../../../stories/hooks/useAutoSequence'; -import UsagePieGraph from '../subscription/components/UsagePieGraph'; +import { useAutoSequence } from '../../../../stories/hooks/useAutoSequence'; +import UsagePieGraph from './UsagePieGraph'; export default { title: 'Admin/Info/UsagePieGraph', diff --git a/apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx b/apps/meteor/client/views/admin/workspace/DeploymentCard/DeploymentCard.stories.tsx similarity index 99% rename from apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx rename to apps/meteor/client/views/admin/workspace/DeploymentCard/DeploymentCard.stories.tsx index d3242e68ae2c..5fb8b4d146f2 100644 --- a/apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx +++ b/apps/meteor/client/views/admin/workspace/DeploymentCard/DeploymentCard.stories.tsx @@ -61,6 +61,7 @@ export default { totalChannelMessages: 213, totalPrivateGroupMessages: 21, totalDirectMessages: 23, + totalDiscussionsMessages: 32, totalLivechatMessages: 31, // - _id: '', diff --git a/apps/meteor/client/views/admin/info/DeploymentCard.tsx b/apps/meteor/client/views/admin/workspace/DeploymentCard/DeploymentCard.tsx similarity index 79% rename from apps/meteor/client/views/admin/info/DeploymentCard.tsx rename to apps/meteor/client/views/admin/workspace/DeploymentCard/DeploymentCard.tsx index 7a95e7aae018..2201e7c9c34c 100644 --- a/apps/meteor/client/views/admin/info/DeploymentCard.tsx +++ b/apps/meteor/client/views/admin/workspace/DeploymentCard/DeploymentCard.tsx @@ -1,29 +1,27 @@ -import type { IServerInfo, IStats, Serialized } from '@rocket.chat/core-typings'; +import type { IWorkspaceInfo, IStats } from '@rocket.chat/core-typings'; import { ButtonGroup, Button } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import type { IInstance } from '@rocket.chat/rest-typings'; -import { Card, CardBody, CardCol, CardTitle, CardColSection, CardColTitle, CardFooter } from '@rocket.chat/ui-client'; +import { Card, CardBody, CardCol, CardColSection, CardColTitle, CardFooter } from '@rocket.chat/ui-client'; import { useSetModal, useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React, { memo } from 'react'; -import { useFormatDateAndTime } from '../../../hooks/useFormatDateAndTime'; -import InstancesModal from './InstancesModal'; +import { useFormatDateAndTime } from '../../../../hooks/useFormatDateAndTime'; +import InstancesModal from './components/InstancesModal'; type DeploymentCardProps = { - info: IServerInfo; - instances: Serialized; + serverInfo: IWorkspaceInfo; + instances: IInstance[]; statistics: IStats; }; -const DeploymentCard = ({ info, statistics, instances }: DeploymentCardProps): ReactElement => { +const DeploymentCard = ({ serverInfo: { info }, statistics, instances }: DeploymentCardProps): ReactElement => { const t = useTranslation(); const formatDateAndTime = useFormatDateAndTime(); const setModal = useSetModal(); - const { commit = {} } = info; - - const appsEngineVersion = info?.marketplaceApiVersion; + const { commit = {}, marketplaceApiVersion: appsEngineVersion } = info || {}; const handleInstancesModal = useMutableCallback(() => { setModal( setModal()} />); @@ -31,9 +29,9 @@ const DeploymentCard = ({ info, statistics, instances }: DeploymentCardProps): R return ( - {t('Deployment')} + {t('Deployment')} {t('Version')} {statistics.version} diff --git a/apps/meteor/client/views/admin/info/InstancesModal/DescriptionList.stories.tsx b/apps/meteor/client/views/admin/workspace/DeploymentCard/components/InstancesModal/DescriptionList.stories.tsx similarity index 100% rename from apps/meteor/client/views/admin/info/InstancesModal/DescriptionList.stories.tsx rename to apps/meteor/client/views/admin/workspace/DeploymentCard/components/InstancesModal/DescriptionList.stories.tsx diff --git a/apps/meteor/client/views/admin/info/InstancesModal/DescriptionList.tsx b/apps/meteor/client/views/admin/workspace/DeploymentCard/components/InstancesModal/DescriptionList.tsx similarity index 100% rename from apps/meteor/client/views/admin/info/InstancesModal/DescriptionList.tsx rename to apps/meteor/client/views/admin/workspace/DeploymentCard/components/InstancesModal/DescriptionList.tsx diff --git a/apps/meteor/client/views/admin/info/InstancesModal/DescriptionListEntry.tsx b/apps/meteor/client/views/admin/workspace/DeploymentCard/components/InstancesModal/DescriptionListEntry.tsx similarity index 100% rename from apps/meteor/client/views/admin/info/InstancesModal/DescriptionListEntry.tsx rename to apps/meteor/client/views/admin/workspace/DeploymentCard/components/InstancesModal/DescriptionListEntry.tsx diff --git a/apps/meteor/client/views/admin/info/InstancesModal/InstancesModal.stories.tsx b/apps/meteor/client/views/admin/workspace/DeploymentCard/components/InstancesModal/InstancesModal.stories.tsx similarity index 95% rename from apps/meteor/client/views/admin/info/InstancesModal/InstancesModal.stories.tsx rename to apps/meteor/client/views/admin/workspace/DeploymentCard/components/InstancesModal/InstancesModal.stories.tsx index b8d66526bf78..882cbeb43c6c 100644 --- a/apps/meteor/client/views/admin/info/InstancesModal/InstancesModal.stories.tsx +++ b/apps/meteor/client/views/admin/workspace/DeploymentCard/components/InstancesModal/InstancesModal.stories.tsx @@ -27,8 +27,8 @@ Default.args = { connected: true, }, instanceRecord: { - _updatedAt: '00-00-00', - _createdAt: '00-00-00', + _updatedAt: new Date(), + _createdAt: new Date(), _id: 'instance-id', name: 'instance-name', pid: 123, diff --git a/apps/meteor/client/views/admin/info/InstancesModal/InstancesModal.tsx b/apps/meteor/client/views/admin/workspace/DeploymentCard/components/InstancesModal/InstancesModal.tsx similarity index 91% rename from apps/meteor/client/views/admin/info/InstancesModal/InstancesModal.tsx rename to apps/meteor/client/views/admin/workspace/DeploymentCard/components/InstancesModal/InstancesModal.tsx index 44d9aacc3217..e0b22e35f5f1 100644 --- a/apps/meteor/client/views/admin/info/InstancesModal/InstancesModal.tsx +++ b/apps/meteor/client/views/admin/workspace/DeploymentCard/components/InstancesModal/InstancesModal.tsx @@ -1,16 +1,15 @@ -import type { Serialized } from '@rocket.chat/core-typings'; import { Accordion } from '@rocket.chat/fuselage'; import type { IInstance } from '@rocket.chat/rest-typings'; import { useTranslation } from '@rocket.chat/ui-contexts'; import React from 'react'; -import GenericModal from '../../../../components/GenericModal'; -import { useFormatDateAndTime } from '../../../../hooks/useFormatDateAndTime'; +import GenericModal from '../../../../../../components/GenericModal'; +import { useFormatDateAndTime } from '../../../../../../hooks/useFormatDateAndTime'; import DescriptionList from './DescriptionList'; import DescriptionListEntry from './DescriptionListEntry'; type InstancesModalProps = { - instances: Serialized[]; + instances: IInstance[]; onClose: () => void; }; diff --git a/apps/meteor/client/views/admin/info/InstancesModal/index.ts b/apps/meteor/client/views/admin/workspace/DeploymentCard/components/InstancesModal/index.ts similarity index 100% rename from apps/meteor/client/views/admin/info/InstancesModal/index.ts rename to apps/meteor/client/views/admin/workspace/DeploymentCard/components/InstancesModal/index.ts diff --git a/apps/meteor/client/views/admin/info/UsageCard.stories.tsx b/apps/meteor/client/views/admin/workspace/MessagesRoomsCard/MessagesRoomsCard.stories.tsx similarity index 95% rename from apps/meteor/client/views/admin/info/UsageCard.stories.tsx rename to apps/meteor/client/views/admin/workspace/MessagesRoomsCard/MessagesRoomsCard.stories.tsx index 4c6cc871ede7..53cd89838e2a 100644 --- a/apps/meteor/client/views/admin/info/UsageCard.stories.tsx +++ b/apps/meteor/client/views/admin/workspace/MessagesRoomsCard/MessagesRoomsCard.stories.tsx @@ -1,11 +1,11 @@ import type { ComponentMeta, ComponentStory } from '@storybook/react'; import React from 'react'; -import UsageCard from './UsageCard'; +import MessagesRoomsCard from './MessagesRoomsCard'; export default { - title: 'Admin/Info/UsageCard', - component: UsageCard, + title: 'Admin/Info/MessagesRoomsCard', + component: MessagesRoomsCard, parameters: { layout: 'centered', }, @@ -39,6 +39,7 @@ export default { totalChannelMessages: 213, totalPrivateGroupMessages: 21, totalDirectMessages: 23, + totalDiscussionsMessages: 32, totalLivechatMessages: 31, // - _id: '', @@ -257,13 +258,10 @@ export default { }, }, }, -} as ComponentMeta; +} as ComponentMeta; -const Template: ComponentStory = (args) => ; +const Template: ComponentStory = (args) => ; export const Example = Template.bind({}); export const Vertical = Template.bind({}); -Vertical.args = { - vertical: true, -}; diff --git a/apps/meteor/client/views/admin/workspace/MessagesRoomsCard/MessagesRoomsCard.tsx b/apps/meteor/client/views/admin/workspace/MessagesRoomsCard/MessagesRoomsCard.tsx new file mode 100644 index 000000000000..7f6538b07b5b --- /dev/null +++ b/apps/meteor/client/views/admin/workspace/MessagesRoomsCard/MessagesRoomsCard.tsx @@ -0,0 +1,123 @@ +import type { IStats } from '@rocket.chat/core-typings'; +import { TextSeparator, Card, CardBody, CardCol, CardColSection, CardColTitle, CardIcon } from '@rocket.chat/ui-client'; +import { useTranslation } from '@rocket.chat/ui-contexts'; +import type { ReactElement } from 'react'; +import React, { memo } from 'react'; + +type MessagesRoomsCardProps = { + statistics: IStats; +}; + +const MessagesRoomsCard = ({ statistics }: MessagesRoomsCardProps): ReactElement => { + const t = useTranslation(); + + return ( + + + + + {t('Total_rooms')} + + + {t('Channels')} + + } + value={statistics.totalChannels} + /> + + + {t('Private_Groups')} + + } + value={statistics.totalPrivateGroups} + /> + + + {t('Direct_Messages')} + + } + value={statistics.totalDirect} + /> + + + {t('Discussions')} + + } + value={statistics.totalDiscussions} + /> + + + {t('Omnichannel')} + + } + value={statistics.totalLivechat} + /> + + + + + {t('Messages')} + + + {t('Stats_Total_Messages_Channel')} + + } + value={statistics.totalChannelMessages} + /> + + + {t('Stats_Total_Messages_PrivateGroup')} + + } + value={statistics.totalPrivateGroupMessages} + /> + + + {t('Stats_Total_Messages_Direct')} + + } + value={statistics.totalDirectMessages} + /> + + + {t('Stats_Total_Messages_Discussions')} + + } + value={statistics.totalDiscussionsMessages} + /> + + + {t('Stats_Total_Messages_Livechat')} + + } + value={statistics.totalLivechatMessages} + /> + + + + + + ); +}; + +export default memo(MessagesRoomsCard); diff --git a/apps/meteor/client/views/admin/workspace/UsersUploadsCard/UsersUploadsCard.tsx b/apps/meteor/client/views/admin/workspace/UsersUploadsCard/UsersUploadsCard.tsx new file mode 100644 index 000000000000..eaf1079d5d70 --- /dev/null +++ b/apps/meteor/client/views/admin/workspace/UsersUploadsCard/UsersUploadsCard.tsx @@ -0,0 +1,109 @@ +import type { IStats } from '@rocket.chat/core-typings'; +import { ButtonGroup, Button } from '@rocket.chat/fuselage'; +import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { TextSeparator, Card, CardBody, CardCol, CardColSection, CardColTitle, CardFooter, CardIcon } from '@rocket.chat/ui-client'; +import { useRouter, useTranslation } from '@rocket.chat/ui-contexts'; +import type { ReactElement } from 'react'; +import React, { memo } from 'react'; + +import { useHasLicenseModule } from '../../../../../ee/client/hooks/useHasLicenseModule'; +import { UserStatus } from '../../../../components/UserStatus'; +import { useFormatMemorySize } from '../../../../hooks/useFormatMemorySize'; + +type UsersUploadsCardProps = { + statistics: IStats; +}; + +const UsersUploadsCard = ({ statistics }: UsersUploadsCardProps): ReactElement => { + const t = useTranslation(); + const formatMemorySize = useFormatMemorySize(); + + const router = useRouter(); + + const handleEngagement = useMutableCallback(() => { + router.navigate('/admin/engagement'); + }); + + const canViewEngagement = useHasLicenseModule('engagement-dashboard'); + + return ( + + + + + {t('Users')} + + + + + {t('Online')} + + } + value={statistics.onlineUsers} + /> + + + + + {t('Busy')} + + } + value={statistics.busyUsers} + /> + + + + + {t('Away')} + + } + value={statistics.awayUsers} + /> + + + + + {t('Offline')} + + } + value={statistics.offlineUsers} + /> + + + + + {t('Types')} + + + + + + + + + {t('Uploads')} + + + + + + + + + + + + ); +}; + +export default memo(UsersUploadsCard); diff --git a/apps/meteor/client/views/admin/workspace/VersionCard/VersionCard.tsx b/apps/meteor/client/views/admin/workspace/VersionCard/VersionCard.tsx new file mode 100644 index 000000000000..a954cc4963ce --- /dev/null +++ b/apps/meteor/client/views/admin/workspace/VersionCard/VersionCard.tsx @@ -0,0 +1,234 @@ +import type { IWorkspaceInfo } from '@rocket.chat/core-typings'; +import { Box, Icon } from '@rocket.chat/fuselage'; +import { useMediaQuery } from '@rocket.chat/fuselage-hooks'; +import type { SupportedVersions } from '@rocket.chat/server-cloud-communication'; +import { Card, CardBody, CardCol, CardColSection, CardColTitle, CardFooter, ExternalLink } from '@rocket.chat/ui-client'; +import type { LocationPathname } from '@rocket.chat/ui-contexts'; +import { useModal, useMediaUrl } from '@rocket.chat/ui-contexts'; +import type { ReactElement, ReactNode } from 'react'; +import React, { useMemo } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import semver from 'semver'; + +import { useFormatDate } from '../../../../hooks/useFormatDate'; +import { useLicense } from '../../../../hooks/useLicense'; +import { useRegistrationStatus } from '../../../../hooks/useRegistrationStatus'; +import { isOverLicenseLimits } from '../../../../lib/utils/isOverLicenseLimits'; +import RegisterWorkspaceModal from '../../cloud/modals/RegisterWorkspaceModal'; +import VersionCardActionButton from './components/VersionCardActionButton'; +import type { VersionActionItem } from './components/VersionCardActionItem'; +import VersionCardActionItemList from './components/VersionCardActionItemList'; +import { VersionCardSkeleton } from './components/VersionCardSkeleton'; +import { VersionTag } from './components/VersionTag'; +import type { VersionStatus } from './components/VersionTag'; + +const SUPPORT_EXTERNAL_LINK = 'https://go.rocket.chat/i/version-support'; +const RELEASES_EXTERNAL_LINK = 'https://go.rocket.chat/i/update-product'; + +type VersionCardProps = { + serverInfo: IWorkspaceInfo; +}; + +const getVersionStatus = (serverVersion: string, versions: { version: string }[]): VersionStatus => { + const coercedServerVersion = String(semver.coerce(serverVersion)); + const highestVersion = versions.reduce((prev, current) => (prev.version > current.version ? prev : current)); + const isSupported = versions.some((v) => v.version === coercedServerVersion || v.version === serverVersion); + + if (semver.gte(coercedServerVersion, highestVersion.version)) { + return 'latest'; + } + + if (isSupported && semver.gt(highestVersion.version, coercedServerVersion)) { + return 'available_version'; + } + + return 'outdated'; +}; + +const VersionCard = ({ serverInfo }: VersionCardProps): ReactElement => { + const mediaQuery = useMediaQuery('(min-width: 1024px)'); + + const getUrl = useMediaUrl(); + const cardBackground = { + backgroundImage: `url(${getUrl('images/globe.png')})`, + backgroundRepeat: 'no-repeat', + backgroundPosition: 'right 20px center', + backgroundSize: mediaQuery ? 'auto' : 'contain', + }; + + const { setModal } = useModal(); + + const { t } = useTranslation(); + + const formatDate = useFormatDate(); + + const { data: licenseData, isLoading, refetch: refetchLicense } = useLicense(); + const { isRegistered } = useRegistrationStatus(); + + const { license, tags, trial: isTrial, limits } = licenseData || {}; + const isAirgapped = license?.information?.offline; + const licenseName = tags?.[0]?.name ?? 'Community'; + const visualExpiration = formatDate(license?.information?.visualExpiration || ''); + + const serverVersion = serverInfo.version; + + const supportedVersions = useMemo( + () => decodeBase64(serverInfo?.supportedVersions?.signed || ''), + [serverInfo?.supportedVersions?.signed], + ); + + const isOverLimits = limits && isOverLicenseLimits(limits); + + const versionStatus = useMemo(() => { + if (!supportedVersions.versions) { + return; + } + return getVersionStatus(serverVersion, supportedVersions.versions); + }, [serverVersion, supportedVersions.versions]); + + const actionButton: + | undefined + | { + path: LocationPathname; + label: ReactNode; + } + | { + action: () => void; + label: ReactNode; + } = useMemo(() => { + if (!isRegistered) { + return { + action: () => { + const handleModalClose = (): void => { + setModal(null); + refetchLicense(); + }; + setModal(); + }, + label: t('RegisterWorkspace_Button'), + }; + } + + if (versionStatus === 'outdated') { + return { + action: () => { + window.open(RELEASES_EXTERNAL_LINK, '_blank'); + }, + label: t('Update_version'), + }; + } + + if (isOverLimits) { + return { path: '/admin/subscription', label: t('Manage_subscription') }; + } + }, [isRegistered, versionStatus, isOverLimits, t, setModal, refetchLicense]); + + const actionItems = useMemo(() => { + return ( + [ + isOverLimits + ? { + type: 'danger', + icon: 'warning', + label: t('Plan_limits_reached'), + } + : { + type: 'neutral', + icon: 'check', + label: t('Operating_withing_plan_limits'), + }, + isAirgapped && { + type: 'neutral', + icon: 'warning', + label: ( + + Check + support + availability + + ), + }, + + versionStatus !== 'outdated' + ? { + type: 'neutral', + icon: 'check', + label: ( + + Version + supported + until {visualExpiration} + + ), + } + : { + type: 'danger', + icon: 'warning', + label: ( + + Version + not supported + + ), + }, + isRegistered + ? { + type: 'neutral', + icon: 'check', + label: t('Workspace_registered'), + } + : { + type: 'danger', + icon: 'warning', + label: t('Workspace_not_registered'), + }, + ].filter(Boolean) as VersionActionItem[] + ).sort((a) => (a.type === 'danger' ? -1 : 1)); + }, [isOverLimits, isAirgapped, versionStatus, isRegistered, t, visualExpiration]); + + return ( + + {!isLoading && licenseData ? ( + <> + + + + + {t('Version_version', { version: serverVersion })} + + + + + + + + + {licenseName} {isTrial && `(${t('trial')})`} + + + {actionItems.length > 0 && ( + + + + )} + + + {actionButton && ( + + + + )} + + ) : ( + + )} + + ); +}; + +export default VersionCard; + +const decodeBase64 = (b64: string): SupportedVersions => { + const [, bodyEncoded] = b64.split('.'); + return JSON.parse(atob(bodyEncoded)); +}; diff --git a/apps/meteor/client/views/admin/workspace/VersionCard/components/VersionCardActionButton.tsx b/apps/meteor/client/views/admin/workspace/VersionCard/components/VersionCardActionButton.tsx new file mode 100644 index 000000000000..4796bdee2350 --- /dev/null +++ b/apps/meteor/client/views/admin/workspace/VersionCard/components/VersionCardActionButton.tsx @@ -0,0 +1,38 @@ +import { Button } from '@rocket.chat/fuselage'; +import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import type { LocationPathname } from '@rocket.chat/ui-contexts'; +import { useRouter } from '@rocket.chat/ui-contexts'; +import type { ReactElement, ReactNode } from 'react'; +import React, { memo } from 'react'; + +type VersionCardActionButtonProps = + | { + path: LocationPathname; + label: ReactNode; + } + | { + action: () => void; + label: ReactNode; + }; + +export type VersionActionButton = {}; + +const VersionCardActionButton = (item: VersionCardActionButtonProps): ReactElement => { + const router = useRouter(); + + const handleActionButton = useMutableCallback(() => { + if ('action' in item) { + return item.action(); + } + + router.navigate(item.path); + }); + + return ( + + ); +}; + +export default memo(VersionCardActionButton); diff --git a/apps/meteor/client/views/admin/workspace/VersionCard/components/VersionCardActionItem.tsx b/apps/meteor/client/views/admin/workspace/VersionCard/components/VersionCardActionItem.tsx new file mode 100644 index 000000000000..b382aee92c33 --- /dev/null +++ b/apps/meteor/client/views/admin/workspace/VersionCard/components/VersionCardActionItem.tsx @@ -0,0 +1,32 @@ +import { Box } from '@rocket.chat/fuselage'; +import type { Keys } from '@rocket.chat/icons'; +import { FramedIcon } from '@rocket.chat/ui-client'; +import type { ReactElement, ReactNode } from 'react'; +import React from 'react'; + +export type VersionActionItem = { + type: 'danger' | 'neutral'; + icon: Keys; + label: ReactNode; +}; + +type VersionCardActionItemProps = { + actionItem: VersionActionItem; +}; + +const VersionCardActionItem = ({ actionItem }: VersionCardActionItemProps): ReactElement => { + return ( + + + {actionItem.label} + + ); +}; + +export default VersionCardActionItem; diff --git a/apps/meteor/client/views/admin/workspace/VersionCard/components/VersionCardActionItemList.tsx b/apps/meteor/client/views/admin/workspace/VersionCard/components/VersionCardActionItemList.tsx new file mode 100644 index 000000000000..9fdd1416dadc --- /dev/null +++ b/apps/meteor/client/views/admin/workspace/VersionCard/components/VersionCardActionItemList.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +import type { VersionActionItem } from './VersionCardActionItem'; +import VersionCardActionItem from './VersionCardActionItem'; + +type VersionCardActionItemListProps = { + actionItems: VersionActionItem[]; +}; + +const VersionCardActionItemList = ({ actionItems }: VersionCardActionItemListProps) => { + return ( + <> + {actionItems.map((item, index) => ( + + ))} + + ); +}; + +export default VersionCardActionItemList; diff --git a/apps/meteor/client/views/admin/workspace/VersionCard/components/VersionCardSkeleton.tsx b/apps/meteor/client/views/admin/workspace/VersionCard/components/VersionCardSkeleton.tsx new file mode 100644 index 000000000000..19dd498b1bf3 --- /dev/null +++ b/apps/meteor/client/views/admin/workspace/VersionCard/components/VersionCardSkeleton.tsx @@ -0,0 +1,24 @@ +import { Box, Skeleton } from '@rocket.chat/fuselage'; +import React from 'react'; + +export const VersionCardSkeleton = () => { + return ( + <> + + + + + + + + + + + + + + + + + ); +}; diff --git a/apps/meteor/client/views/admin/workspace/VersionCard/components/VersionTag.tsx b/apps/meteor/client/views/admin/workspace/VersionCard/components/VersionTag.tsx new file mode 100644 index 000000000000..c890ba6716e2 --- /dev/null +++ b/apps/meteor/client/views/admin/workspace/VersionCard/components/VersionTag.tsx @@ -0,0 +1,22 @@ +import { Tag } from '@rocket.chat/fuselage'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +export type VersionStatus = 'outdated' | 'latest' | 'available_version' | undefined; + +type VersionTagProps = { + versionStatus: VersionStatus; +}; + +export const VersionTag = ({ versionStatus }: VersionTagProps) => { + const { t } = useTranslation(); + if (versionStatus === 'outdated') { + return {t('Outdated')}; + } + + if (versionStatus === 'latest') { + return {t('Latest')}; + } + + return {t('New_version_available')}; +}; diff --git a/apps/meteor/client/views/admin/info/InformationPage.tsx b/apps/meteor/client/views/admin/workspace/WorkspacePage.tsx similarity index 61% rename from apps/meteor/client/views/admin/info/InformationPage.tsx rename to apps/meteor/client/views/admin/workspace/WorkspacePage.tsx index 775e7658a036..fb504ce4e063 100644 --- a/apps/meteor/client/views/admin/info/InformationPage.tsx +++ b/apps/meteor/client/views/admin/workspace/WorkspacePage.tsx @@ -1,64 +1,56 @@ -import type { IServerInfo, IStats, Serialized } from '@rocket.chat/core-typings'; +import type { IWorkspaceInfo, IStats } from '@rocket.chat/core-typings'; import { Box, Button, ButtonGroup, Callout, Grid } from '@rocket.chat/fuselage'; import type { IInstance } from '@rocket.chat/rest-typings'; import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { memo } from 'react'; -import { useSeatsCap } from '../../../../ee/client/views/admin/users/useSeatsCap'; import Page from '../../../components/Page'; import { useIsEnterprise } from '../../../hooks/useIsEnterprise'; -import SeatsCard from '../subscription/components/cards/SeatsCard'; -import DeploymentCard from './DeploymentCard'; -import LicenseCard from './LicenseCard'; -import UsageCard from './UsageCard'; +import DeploymentCard from './DeploymentCard/DeploymentCard'; +import MessagesRoomsCard from './MessagesRoomsCard/MessagesRoomsCard'; +import UsersUploadsCard from './UsersUploadsCard/UsersUploadsCard'; +import VersionCard from './VersionCard/VersionCard'; -type InformationPageProps = { +type WorkspaceStatusPageProps = { canViewStatistics: boolean; - info: IServerInfo; + serverInfo: IWorkspaceInfo; statistics: IStats; - instances: Serialized; + instances: IInstance[]; onClickRefreshButton: () => void; onClickDownloadInfo: () => void; }; -const InformationPage = memo(function InformationPage({ +const WorkspacePage = ({ canViewStatistics, - info, + serverInfo, statistics, instances, onClickRefreshButton, onClickDownloadInfo, -}: InformationPageProps) { +}: WorkspaceStatusPageProps) => { const t = useTranslation(); - const seatsCap = useSeatsCap(); - const showSeatCap = seatsCap && seatsCap.maxActiveUsers === Infinity; - const { data } = useIsEnterprise(); - if (!info) { - return null; - } - const warningMultipleInstances = !data?.isEnterprise && !statistics?.msEnabled && statistics?.instanceCount > 1; const alertOplogForMultipleInstances = warningMultipleInstances && !statistics.oplogEnabled; return ( - + {canViewStatistics && ( - )} - + {warningMultipleInstances && ( @@ -87,28 +79,24 @@ const InformationPage = memo(function InformationPage({ )} - - - + + + - - - - - {seatsCap && seatsCap.maxActiveUsers !== Infinity && ( - - - - )} + + - - + + + + + ); -}); +}; -export default InformationPage; +export default memo(WorkspacePage); diff --git a/apps/meteor/client/views/admin/workspace/WorkspaceRoute.tsx b/apps/meteor/client/views/admin/workspace/WorkspaceRoute.tsx new file mode 100644 index 000000000000..b7b22d8ff674 --- /dev/null +++ b/apps/meteor/client/views/admin/workspace/WorkspaceRoute.tsx @@ -0,0 +1,64 @@ +import { Callout, ButtonGroup, Button } from '@rocket.chat/fuselage'; +import { usePermission, useTranslation } from '@rocket.chat/ui-contexts'; +import type { ReactElement } from 'react'; +import React, { memo } from 'react'; + +import Page from '../../../components/Page'; +import PageSkeleton from '../../../components/PageSkeleton'; +import { useRefreshStatistics, useWorkspaceInfo } from '../../../hooks/useWorkspaceInfo'; +import { downloadJsonAs } from '../../../lib/download'; +import NotAuthorizedPage from '../../notAuthorized/NotAuthorizedPage'; +import WorkspacePage from './WorkspacePage'; + +const WorkspaceRoute = (): ReactElement => { + const t = useTranslation(); + const canViewStatistics = usePermission('view-statistics'); + + const [serverInfoQuery, instancesQuery, statisticsQuery] = useWorkspaceInfo(); + const refetchStatistics = useRefreshStatistics(); + + if (!canViewStatistics) { + return ; + } + + if (serverInfoQuery.isLoading || instancesQuery.isLoading || statisticsQuery.isLoading) { + return ; + } + const handleClickRefreshButton = (): void => { + refetchStatistics.mutate(); + }; + + if (serverInfoQuery.isError || instancesQuery.isError || statisticsQuery.isError) { + return ( + + + + + + + + {t('Error_loading_pages')} + + + ); + } + + const handleClickDownloadInfo = (): void => { + downloadJsonAs(statisticsQuery.data, 'statistics'); + }; + + return ( + + ); +}; + +export default memo(WorkspaceRoute); diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json index f582dae7d195..bd6bc82767b3 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json @@ -1645,7 +1645,7 @@ "Direct_message_creation_description": "You are about to create a chat with multiple users. Add the ones you would like to talk, everyone in the same place, using direct messages.", "Direct_message_someone": "Direct message someone", "Direct_message_you_have_joined": "You have joined a new direct message with", - "Direct_Messages": "Direct Messages", + "Direct_Messages": "Direct messages", "Direct_Reply": "Direct Reply", "Direct_Reply_Advice": "You can directly reply to this email. Do not modify previous emails in the thread.", "Direct_Reply_Debug": "Debug Direct Reply", @@ -1719,7 +1719,7 @@ "Dont_ask_me_again_list": "Don't ask me again list", "Download": "Download", "Download_Destkop_App": "Download Desktop App", - "Download_Info": "Download Info", + "Download_Info": "Download info", "Download_My_Data": "Download My Data (HTML)", "Download_Pending_Avatars": "Download Pending Avatars", "Download_Pending_Files": "Download Pending Files", @@ -4095,7 +4095,7 @@ "Private_Channels": "Private channels", "Private_Chats": "Private Chats", "Private_Group": "Private Group", - "Private_Groups": "Private Groups", + "Private_Groups": "Private groups", "Private_Groups_list": "List of Private Groups", "Private_Team": "Private Team", "Productivity": "Productivity", @@ -4885,17 +4885,18 @@ "Stats_Total_Active_Outgoing_Integrations": "Total Active Outgoing Integrations", "Stats_Total_Channels": "Channels", "Stats_Total_Connected_Users": "Total Connected Users", - "Stats_Total_Direct_Messages": "Direct Message Rooms", + "Stats_Total_Direct_Messages": "Direct messages", "Stats_Total_Incoming_Integrations": "Total Incoming Integrations", "Stats_Total_Installed_Apps": "Total Installed Apps", "Stats_Total_Integrations": "Total Integrations", "Stats_Total_Integrations_With_Script_Enabled": "Total Integrations With Script Enabled", "Stats_Total_Livechat_Rooms": "Omnichannel Rooms", "Stats_Total_Messages": "Messages", - "Stats_Total_Messages_Channel": "Messages in Channels", - "Stats_Total_Messages_Direct": "Messages in Direct Messages", - "Stats_Total_Messages_Livechat": "Messages in Omnichannel", - "Stats_Total_Messages_PrivateGroup": "Messages in Private Groups", + "Stats_Total_Messages_Channel": "In channels", + "Stats_Total_Messages_Direct": "In direct messages", + "Stats_Total_Messages_Livechat": "In omnichannel", + "Stats_Total_Messages_PrivateGroup": "In private groups", + "Stats_Total_Messages_Discussions": "In discussions", "Stats_Total_Outgoing_Integrations": "Total Outgoing Integrations", "Stats_Total_Private_Groups": "Private Groups", "Stats_Total_Rooms": "Rooms", @@ -6116,6 +6117,19 @@ "unread_messages_counter_plural": "{{count}} unread messages", "Premium": "Premium", "Premium_capability": "Premium capability", + "Operating_withing_plan_limits": "Operating withing plan limits", + "Plan_limits_reached": "Plan limits reached", + "Workspace_not_registered": "Workspace not registered", + "Users_Connected": "Users connected", + "Solve_issues": "Solve issues", + "Update_version": "Update version", + "Version_not_supported": "Version <1>not supported", + "Version_supported_until": "Version <1>supported until {{date}}", + "Check_support_availability": "Check <1>support availability", + "Outdated": "Outdated", + "Latest": "Latest", + "New_version_available": "New version available", + "trial": "trial", "Subscription": "Subscription", "Manage_subscription": "Manage subscription", "ActiveSessionsPeak": "Active sessions peak", diff --git a/apps/meteor/public/images/globe.png b/apps/meteor/public/images/globe.png new file mode 100644 index 000000000000..e1615da2e363 Binary files /dev/null and b/apps/meteor/public/images/globe.png differ diff --git a/apps/meteor/tests/e2e/administration-menu.spec.ts b/apps/meteor/tests/e2e/administration-menu.spec.ts index 4e630c60e16e..c30c821f2c55 100644 --- a/apps/meteor/tests/e2e/administration-menu.spec.ts +++ b/apps/meteor/tests/e2e/administration-menu.spec.ts @@ -21,7 +21,7 @@ test.describe.serial('administration-menu', () => { await expect(page).toHaveURL('admin/upgrade/go-fully-featured'); }); - test('expect open info page', async ({ page }) => { + test('expect open Workspace page', async ({ page }) => { test.skip(!IS_EE, 'Enterprise only'); await poHomeDiscussion.sidenav.openAdministrationByLabel('Workspace'); diff --git a/apps/meteor/tests/e2e/administration.spec.ts b/apps/meteor/tests/e2e/administration.spec.ts index 3006432d417d..ec9568152133 100644 --- a/apps/meteor/tests/e2e/administration.spec.ts +++ b/apps/meteor/tests/e2e/administration.spec.ts @@ -20,7 +20,7 @@ test.describe.parallel('administration', () => { }); test('expect download info as JSON', async ({ page }) => { - const [download] = await Promise.all([page.waitForEvent('download'), page.locator('button:has-text("Download Info")').click()]); + const [download] = await Promise.all([page.waitForEvent('download'), page.locator('button:has-text("Download info")').click()]); await expect(download.suggestedFilename()).toBe('statistics.json'); }); diff --git a/apps/meteor/tests/e2e/create-direct.spec.ts b/apps/meteor/tests/e2e/create-direct.spec.ts index 6f75c20101f1..4e74840619ba 100644 --- a/apps/meteor/tests/e2e/create-direct.spec.ts +++ b/apps/meteor/tests/e2e/create-direct.spec.ts @@ -14,7 +14,7 @@ test.describe.serial('channel-direct-message', () => { }); test('expect create a direct room', async ({ page }) => { - await poHomeChannel.sidenav.openNewByLabel('Direct Messages'); + await poHomeChannel.sidenav.openNewByLabel('Direct messages'); await poHomeChannel.sidenav.inputDirectUsername.click(); await page.keyboard.type('rocket.cat'); diff --git a/apps/meteor/tests/e2e/federation/page-objects/channel.ts b/apps/meteor/tests/e2e/federation/page-objects/channel.ts index 50ac5f04fc27..e22494786325 100644 --- a/apps/meteor/tests/e2e/federation/page-objects/channel.ts +++ b/apps/meteor/tests/e2e/federation/page-objects/channel.ts @@ -74,12 +74,12 @@ export class FederationChannel { } async createDirectMessagesUsingModal(usernamesToInvite: string[]) { - await this.sidenav.openNewByLabel('Direct Messages'); + await this.sidenav.openNewByLabel('Direct messages'); for await (const username of usernamesToInvite) { await this.sidenav.inviteUserToDM(username); } await this.page - .locator('//*[@id="modal-root"]//*[contains(@class, "rcx-modal__title") and contains(text(), "Direct Messages")]') + .locator('//*[@id="modal-root"]//*[contains(@class, "rcx-modal__title") and contains(text(), "Direct messages")]') .click(); await this.sidenav.btnCreateChannel.click(); } diff --git a/packages/core-typings/src/IStats.ts b/packages/core-typings/src/IStats.ts index 0df389f2dd86..6bfa761c24ce 100644 --- a/packages/core-typings/src/IStats.ts +++ b/packages/core-typings/src/IStats.ts @@ -51,6 +51,7 @@ export interface IStats { totalChannelMessages: number; totalPrivateGroupMessages: number; totalDirectMessages: number; + totalDiscussionsMessages: number; totalLivechatMessages: number; totalTriggers: number; totalMessages: number; diff --git a/packages/core-typings/src/IWorkspaceInfo.ts b/packages/core-typings/src/IWorkspaceInfo.ts new file mode 100644 index 000000000000..eede7d1011ff --- /dev/null +++ b/packages/core-typings/src/IWorkspaceInfo.ts @@ -0,0 +1,8 @@ +import type { IServerInfo } from './IServerInfo'; + +export type IWorkspaceInfo = { + info?: IServerInfo; + supportedVersions?: { signed: string }; + minimumClientVersions: { desktop: string; mobile: string }; + version: string; +}; diff --git a/packages/core-typings/src/index.ts b/packages/core-typings/src/index.ts index 6411390f0fe9..02929270c7ad 100644 --- a/packages/core-typings/src/index.ts +++ b/packages/core-typings/src/index.ts @@ -18,6 +18,7 @@ export * from './IUserAction'; export * from './IBanner'; export * from './IStats'; export * from './IServerInfo'; +export * from './IWorkspaceInfo'; export * from './IInstanceStatus'; export * from './IWebdavAccount'; export * from './IPermission'; diff --git a/packages/rest-typings/src/default/index.ts b/packages/rest-typings/src/default/index.ts index 0be60fc4413b..b3aa5d3aa535 100644 --- a/packages/rest-typings/src/default/index.ts +++ b/packages/rest-typings/src/default/index.ts @@ -1,38 +1,36 @@ // eslint-disable-next-line @typescript-eslint/naming-convention export interface DefaultEndpoints { '/info': { - GET: () => { - info: { - build: { - arch: string; - cpus: number; - date: string; - freeMemory: number; - nodeVersion: string; - osRelease: string; - platform: string; - totalMemory: number; - }; - commit: { - author?: string; - branch?: string; - date?: string; - hash?: string; - subject?: string; - tag?: string; - }; - marketplaceApiVersion: string; - version: string; - tag?: string; - branch?: string; - }; - supportedVersions?: { signed: string }; - minimumClientVersions: { - desktop: string; - mobile: string; - }; - version: string | undefined; - }; + GET: () => + | { + info: { + build: { + arch: string; + cpus: number; + date: string; + freeMemory: number; + nodeVersion: string; + osRelease: string; + platform: string; + totalMemory: number; + }; + commit: { + author?: string; + branch?: string; + date?: string; + hash?: string; + subject?: string; + tag?: string; + }; + marketplaceApiVersion: string; + version: string; + tag?: string; + branch?: string; + }; + } + | { + version: string | undefined; + }; }; '/ecdh_proxy/initEncryptedSession': { POST: () => void; diff --git a/packages/ui-client/src/components/Card/Card.tsx b/packages/ui-client/src/components/Card/Card.tsx index c078c09e80fd..41717574dd77 100644 --- a/packages/ui-client/src/components/Card/Card.tsx +++ b/packages/ui-client/src/components/Card/Card.tsx @@ -1,8 +1,32 @@ import { Box } from '@rocket.chat/fuselage'; -import type { FC } from 'react'; +import type { CSSProperties, FC, ReactNode } from 'react'; -const Card: FC = (props) => ( - +type IBackground = { + backgroundImage?: CSSProperties['backgroundImage']; + backgroundRepeat?: CSSProperties['backgroundRepeat']; + backgroundPosition?: CSSProperties['backgroundPosition']; + backgroundSize?: CSSProperties['backgroundSize']; +}; + +type CardProps = { + background?: IBackground; + children: ReactNode; +}; + +const Card: FC = ({ background, children, ...props }) => ( + + {children} + ); export default Card; diff --git a/packages/ui-client/src/components/ExternalLink.tsx b/packages/ui-client/src/components/ExternalLink.tsx index 178e308fa8b6..0e7ef7a56148 100644 --- a/packages/ui-client/src/components/ExternalLink.tsx +++ b/packages/ui-client/src/components/ExternalLink.tsx @@ -1,9 +1,9 @@ import { Box } from '@rocket.chat/fuselage'; -import type { FC } from 'react'; +import type { ComponentProps, FC } from 'react'; type ExternalLinkProps = { to: string; -}; +} & ComponentProps; export const ExternalLink: FC = ({ children, to, ...props }) => ( diff --git a/yarn.lock b/yarn.lock index 03bd8fcc0f41..46dbea7e1c21 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8036,23 +8036,14 @@ __metadata: resolution: "@rocket.chat/css-in-js@npm:0.31.26-dev.19" dependencies: "@emotion/hash": ^0.9.0 - "@rocket.chat/css-supports": ~0.31.26-dev.19 - "@rocket.chat/memo": ~0.31.26-dev.19 - "@rocket.chat/stylis-logical-props-middleware": ~0.31.26-dev.19 + "@rocket.chat/css-supports": ^0.31.25 + "@rocket.chat/memo": ^0.31.25 + "@rocket.chat/stylis-logical-props-middleware": ^0.31.25 stylis: ~4.1.3 checksum: 4d1381558188c4625051420a6760e613189abca9cf06c23beb833e582229975a0aaac9aef89a788f161ad5a99344a3d028042d90a33d5144668577aa647a78f3 languageName: node linkType: hard -"@rocket.chat/css-in-js@npm:~0.31.26-dev.19": - version: 0.31.26-dev.23 - resolution: "@rocket.chat/css-in-js@npm:0.31.26-dev.23" - dependencies: - "@rocket.chat/memo": ^0.31.25 - checksum: 6d71bd0f232c8ea3fc2711347064ddd14925b1c2b8713f6d7649b98679455029a53ee41d08b98d010da3ea4789afa21a15901a92efef61dee7b32d6965157445 - languageName: node - linkType: hard - "@rocket.chat/css-supports@npm:^0.31.25": version: 0.31.25 resolution: "@rocket.chat/css-supports@npm:0.31.25" @@ -8062,15 +8053,6 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/css-supports@npm:~0.31.26-dev.19, @rocket.chat/css-supports@npm:~0.31.26-dev.23": - version: 0.31.26-dev.23 - resolution: "@rocket.chat/css-supports@npm:0.31.26-dev.23" - dependencies: - "@rocket.chat/memo": ~0.31.26-dev.23 - checksum: a4f25562df67214b1c92c85a1cd16eb03fc2aea385f48cdde42ad0053b9e03a92ca9e3486d1387c7a31cf68f47fa888825f31acae8f4700ee2b9f03495286a12 - languageName: node - linkType: hard - "@rocket.chat/ddp-client@workspace:^, @rocket.chat/ddp-client@workspace:ee/packages/ddp-client": version: 0.0.0-use.local resolution: "@rocket.chat/ddp-client@workspace:ee/packages/ddp-client" @@ -8141,9 +8123,9 @@ __metadata: linkType: soft "@rocket.chat/emitter@npm:next": - version: 0.31.26-dev.19 - resolution: "@rocket.chat/emitter@npm:0.31.26-dev.19" - checksum: 56e89b1a2325792df59607ea4f75acba5355ccf9a0149a83a8058c1700316833c11cbce7bf58229a5118c719157840a3d7575f1b3aecea30dce90a23945577a5 + version: 0.31.26-dev.25 + resolution: "@rocket.chat/emitter@npm:0.31.26-dev.25" + checksum: 2320d757ba92bcb510a362dec49efb8a80e15c6e4877c49f428e7d757cfa08b291d6771512aef0344feadc41a564ec689673d87b4c828d8cc692a6d01726e9dd languageName: node linkType: hard @@ -8273,8 +8255,8 @@ __metadata: linkType: hard "@rocket.chat/fuselage-polyfills@npm:next": - version: 0.31.26-dev.19 - resolution: "@rocket.chat/fuselage-polyfills@npm:0.31.26-dev.19" + version: 0.31.26-dev.25 + resolution: "@rocket.chat/fuselage-polyfills@npm:0.31.26-dev.25" dependencies: "@juggle/resize-observer": ^3.4.0 clipboard-polyfill: ^3.0.3 @@ -8282,13 +8264,13 @@ __metadata: focus-visible: ^5.2.0 focus-within-polyfill: ^5.2.1 new-event-polyfill: ^1.0.1 - checksum: 2a8363bb177ee5f345bcafac856bcd5726df405f929d945fa366530c7535c125380b2818dbd0cf8b6675c69617435c40af7d143b7afe191abfb0d5652678f60f + checksum: ddee0db78b115400635d86991fe8b707f1354d2f4f1f2a4b0a6b642e7cc9eb57881942803d49804d5de811a6a37085994709980011aee848d18c6551746c0422 languageName: node linkType: hard "@rocket.chat/fuselage-toastbar@npm:next": - version: 0.32.0-dev.403 - resolution: "@rocket.chat/fuselage-toastbar@npm:0.32.0-dev.403" + version: 0.32.0-dev.409 + resolution: "@rocket.chat/fuselage-toastbar@npm:0.32.0-dev.409" peerDependencies: "@rocket.chat/fuselage": "*" "@rocket.chat/fuselage-hooks": "*" @@ -8296,7 +8278,7 @@ __metadata: "@rocket.chat/styled": "*" react: ^17.0.2 react-dom: ^17.0.2 - checksum: 674a621ccabfcb802817fcd92236376417bbac90458736f655b6500c363e76b238a3c14a5cd0df0ad42b50c7b3b5ea675d3e918f767d20688768141c72511d69 + checksum: c772456d364328484ddc72abd2b83c33a8610cd514139a6a832b77202aac1d8abc747a41d7d2ebe61e66de856d3b99446eee3c90df4b4afff86fbfe200527d56 languageName: node linkType: hard @@ -8506,14 +8488,14 @@ __metadata: linkType: soft "@rocket.chat/layout@npm:next": - version: 0.32.0-dev.312 - resolution: "@rocket.chat/layout@npm:0.32.0-dev.312" + version: 0.32.0-dev.318 + resolution: "@rocket.chat/layout@npm:0.32.0-dev.318" peerDependencies: "@rocket.chat/fuselage": "*" react: 17.0.2 react-dom: 17.0.2 react-i18next: ~11.15.4 - checksum: c3db8279b66794b349b740fa61f56a9759fe7f61856408c6c20b6cdf3c799f309de1388d2449db2b17199278fa1d17c6f41a9cfe7bf24f3fa5692a9cbbeae8a2 + checksum: fc08ca4b30373e2b7760bd4c916b8bfd233579cb604d831a52e3737774218ac50652869218499cf3122f3c7b7619fc848bd3893772e9373f6459058866e6ee6c languageName: node linkType: hard @@ -8688,19 +8670,12 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/memo@npm:~0.31.26-dev.19, @rocket.chat/memo@npm:~0.31.26-dev.23": - version: 0.31.26-dev.23 - resolution: "@rocket.chat/memo@npm:0.31.26-dev.23" - checksum: 68301161d87ba25347f1d2ab85c139ba86c5fdd1101f41678808c19ba461772814f4bff048a30e4aefd08978fe2feb952c541bddc0beb6bc3cd190bd7852393b - languageName: node - linkType: hard - "@rocket.chat/message-parser@npm:next": - version: 0.32.0-dev.377 - resolution: "@rocket.chat/message-parser@npm:0.32.0-dev.377" + version: 0.32.0-dev.383 + resolution: "@rocket.chat/message-parser@npm:0.32.0-dev.383" dependencies: tldts: ~5.7.112 - checksum: 9980ac9fbcce92a6ad521e5b48b8c6b990186046ff984a2408156d15494996d3963aa575d403dd813c78bcb5ea6c374c0a12f1a96c8342fea7782da005aab3b5 + checksum: a214e4f24caef43cb6fe4f48b86eadd9a74a9a8495aa3a6ccd51c1e7d1c7734d781ca364f36ee2a9ad994b10cbe9f440b2270c496d607987d17fa59b8d278f82 languageName: node linkType: hard @@ -9544,9 +9519,9 @@ __metadata: linkType: soft "@rocket.chat/string-helpers@npm:next": - version: 0.31.26-dev.19 - resolution: "@rocket.chat/string-helpers@npm:0.31.26-dev.19" - checksum: eb8f130f6e264483e1fc64dc3fe88bdb8e2d93ccefd168cacdc96aa8f0a4df27791045a38f33cb8db29dd063191e14c08210d6f187e5e93acde7e2d658b05128 + version: 0.31.26-dev.25 + resolution: "@rocket.chat/string-helpers@npm:0.31.26-dev.25" + checksum: cba4efb7e06cf46317fce9ba868e1c59756e1e87b8998320a95657beaacf1ead6bedbee1996e717389c7c40379392b8a6af859efcec1dd301f26adce748b6ba5 languageName: node linkType: hard @@ -9563,7 +9538,7 @@ __metadata: version: 0.31.26-dev.19 resolution: "@rocket.chat/styled@npm:0.31.26-dev.19" dependencies: - "@rocket.chat/css-in-js": ~0.31.26-dev.19 + "@rocket.chat/css-in-js": ^0.31.25 checksum: f65cd023bc99af913e2550b39ae21d51da0391699c914a5cabdf556afe1659d22bc70f2924b30084c7cf2547da952750ab96745d6162fcec74ef2b5bbfb8e01a languageName: node linkType: hard @@ -9579,17 +9554,6 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/stylis-logical-props-middleware@npm:~0.31.26-dev.19": - version: 0.31.26-dev.23 - resolution: "@rocket.chat/stylis-logical-props-middleware@npm:0.31.26-dev.23" - dependencies: - "@rocket.chat/css-supports": ~0.31.26-dev.23 - peerDependencies: - stylis: 4.0.10 - checksum: b2fbfad3b2f4dedd9023b30d4cdc51e76ae76faeeca5819cf697e896c02fd4bb2dde5bbc428b377d77f32011fd8cc82c6d98a84d66b93056ef981c13aee1dc67 - languageName: node - linkType: hard - "@rocket.chat/tools@workspace:^, @rocket.chat/tools@workspace:packages/tools": version: 0.0.0-use.local resolution: "@rocket.chat/tools@workspace:packages/tools"