diff --git a/plugins/login-resources/src/components/AdminWorkspaces.svelte b/plugins/login-resources/src/components/AdminWorkspaces.svelte index fa672951bc..4bfe4f1e8d 100644 --- a/plugins/login-resources/src/components/AdminWorkspaces.svelte +++ b/plugins/login-resources/src/components/AdminWorkspaces.svelte @@ -9,11 +9,18 @@ isRestoringMode, isUpgradingMode, reduceCalls, + systemAccountUuid, versionToString, type WorkspaceInfoWithStatus } from '@hcengineering/core' - import { getEmbeddedLabel } from '@hcengineering/platform' - import { copyTextToClipboard, isAdminUser, MessageBox } from '@hcengineering/presentation' + import { getEmbeddedLabel, getMetadata } from '@hcengineering/platform' + import presentation, { + copyTextToClipboard, + isAdminUser, + MessageBox, + type OverviewStatistics, + type WorkspaceStatistics + } from '@hcengineering/presentation' import { Button, ButtonMenu, @@ -51,15 +58,17 @@ let workspaces: WorkspaceInfo[] = [] enum SortingRule { - Name = '1', - BackupDate = '2', - BackupSize = '3', - LastVisit = '4' + Activity = '1', + Name = '2', + BackupDate = '3', + BackupSize = '4', + LastVisit = '5' } - let sortingRule = SortingRule.BackupDate + let sortingRule = SortingRule.Activity const sortRules = { + [SortingRule.Activity]: 'Active users', [SortingRule.Name]: 'Name', [SortingRule.BackupDate]: 'Backup date', [SortingRule.BackupSize]: 'Backup size', @@ -81,6 +90,31 @@ let showOther: boolean = true let showGrAttempts: boolean = true let showSelectedRegionOnly: boolean = false + let showInactive = false + + function isWorkspaceInactive (it: WorkspaceInfo, stats: WorkspaceStatistics | undefined): boolean { + if (stats === undefined) { + return true + } + const ops = (stats.sessions ?? []).reduceRight( + (p, it) => p + (it.mins5.tx + it.mins5.find) + (it.current.tx + it.current.find), + 0 + ) + if (ops === 0) { + return true + } + if (stats.sessions.filter((it) => (it.userId as any) !== systemAccountUuid).length === 0) { + return true + } + return false + } + + function getBackupSize (workspace: WorkspaceInfo): number { + return Math.max( + workspace.backupInfo?.backupSize ?? 0, + (workspace.backupInfo?.dataSize ?? 0) + (workspace.backupInfo?.blobsSize ?? 0) + ) + } $: sortedWorkspaces = workspaces .filter( @@ -89,7 +123,8 @@ (it.url?.includes(search) ?? false) || it.uuid?.includes(search) || it.createdBy?.includes(search)) && - (showSelectedRegionOnly ? it.region === selectedRegionId : true) && + (showSelectedRegionOnly ? it.region === filterRegionId : true) && + (showInactive ? isWorkspaceInactive(it, statsByWorkspace.get(it.uuid)) : true) && ((showActive && isActiveMode(it.mode)) || (showArchived && isArchivingMode(it.mode)) || (showDeleted && isDeletingMode(it.mode)) || @@ -102,11 +137,16 @@ ) .sort((a, b) => { switch (sortingRule) { + case SortingRule.Activity: { + const aStats = statsByWorkspace.get(a.uuid ?? '') + const bStats = statsByWorkspace.get(b.uuid ?? '') + return (bStats?.sessions?.length ?? 0) - (aStats?.sessions?.length ?? 0) + } case SortingRule.BackupDate: { return (a.backupInfo?.lastBackup ?? 0) - (b.backupInfo?.lastBackup ?? 0) } case SortingRule.BackupSize: - return (b.backupInfo?.backupSize ?? 0) - (a.backupInfo?.backupSize ?? 0) + return getBackupSize(b) - getBackupSize(a) case SortingRule.LastVisit: return (b.lastVisit ?? 0) - (a.lastVisit ?? 0) } @@ -119,6 +159,24 @@ let backupable: WorkspaceInfo[] = [] + const token: string = getMetadata(presentation.metadata.Token) ?? '' + + const endpoint = getMetadata(presentation.metadata.StatsUrl) + + async function fetchStats (time: number): Promise { + await fetch(endpoint + `/api/v1/overview?token=${token}`, {}) + .then(async (json) => { + data = await json.json() + }) + .catch((err) => { + console.error(err) + }) + } + let data: OverviewStatistics | undefined + $: void fetchStats($ticker) + + $: statsByWorkspace = new Map((data?.workspaces ?? []).map((it) => [it.wsId, it])) + $: { // Assign backup idx const backupSorting = [...workspaces].filter((it) => { @@ -180,7 +238,9 @@ } const dayRanges = { - Today: [-1, 1], + Hour: [-1, 0.1], + HalfDay: [0.1, 0.5], + Day: [0.5, 1], Week: [1, 7], Weeks: [7, 14], Month: [14, 30], @@ -195,8 +255,8 @@ let limit = 50 $: groupped = groupByArray(sortedWorkspaces, (it) => { - const lastUsageDays = Math.round((now - (it.lastVisit ?? 0)) / (1000 * 3600 * 24)) - return Object.entries(dayRanges).find(([_k, v]) => v[0] < lastUsageDays && lastUsageDays < v[1])?.[0] ?? 'Other' + const lastUsageDays = Math.round((10 * (now - (it.lastVisit ?? 0))) / (1000 * 3600 * 24)) / 10 + return Object.entries(dayRanges).find(([_k, v]) => v[0] < lastUsageDays && lastUsageDays <= v[1])?.[0] ?? 'Years' }) let regionInfo: RegionInfo[] = [] @@ -204,6 +264,9 @@ let regionTitles: Record = {} let selectedRegionId: string = '' + + let filterRegionId: string = '' + void getRegionInfo().then((_regionInfo) => { regionInfo = _regionInfo ?? [] regionTitles = Object.fromEntries( @@ -212,6 +275,9 @@ if (selectedRegionId === '' && regionInfo.length > 0) { selectedRegionId = regionInfo[0].region } + if (filterRegionId === '' && regionInfo.length > 0) { + filterRegionId = regionInfo[0].region + } }) $: selectedRegionRef = regionInfo.find((it) => it.region === selectedRegionId) @@ -222,6 +288,14 @@ : selectedRegionRef.region : '' + $: filteredRegionRef = regionInfo.find((it) => it.region === filterRegionId) + $: filteredRegionName = + filteredRegionRef !== undefined + ? filteredRegionRef.name.length > 0 + ? filteredRegionRef.name + : filteredRegionRef.region + : '' + $: byVersion = groupByArray( workspaces.filter((it) => { const lastUsed = Math.round((now - (it.lastVisit ?? 0)) / (1000 * 3600 * 24)) @@ -249,10 +323,14 @@
Workspaces: {workspaces.length} active: {workspaces.filter((it) => isActiveMode(it.mode)).length} - upgrading: {workspaces.filter((it) => isUpgradingMode(it.mode)).length} - +
Backupable: {backupable.length} new: {backupable.reduce((p, it) => p + (it.backupInfo == null ? 1 : 0), 0)} + Active: {data?.workspaces.length ?? -1} +
+ + Users: {data?.usersTotal}/{data?.connectionsTotal} +
{#each byVersion.entries() as [k, v]} @@ -299,6 +377,10 @@ Show selected region only:
+
+ Show inactive workspaces: + +
@@ -329,13 +411,32 @@ }} />
+ +
+
+ +
+ Filtere region selector: + ({ + id: it.region === '' ? '#' : it.region, + label: getEmbeddedLabel(it.name.length > 0 ? it.name : it.region + ' (hidden)') + }))} + on:selected={(it) => { + filterRegionId = it.detail === '#' ? '' : it.detail + }} + /> +
{#each Object.keys(dayRanges) as k} {@const v = groupped.get(k) ?? []} {@const hasMore = (groupped.get(k) ?? []).length > limit} - {@const activeV = v.filter((it) => isActiveMode(it.mode) && it.region !== selectedRegionId)} + {@const activeV = v.filter((it) => isActiveMode(it.mode) && it.region !== selectedRegionId).slice(0, limit)} {@const activeAll = v.filter((it) => isActiveMode(it.mode))} {@const archivedV = v.filter((it) => isArchivingMode(it.mode))} {@const deletedV = v.filter((it) => isDeletingMode(it.mode))} @@ -422,11 +523,23 @@ {@const wsName = workspace.name} {@const lastUsageDays = Math.round((now - (workspace.lastVisit ?? 0)) / (1000 * 3600 * 24))} {@const bIdx = backupIdx.get(workspace.uuid)} + {@const stats = statsByWorkspace.get(workspace.uuid ?? '')}
{wsName} + {#if stats} + - +
+ {stats.sessions?.length ?? 0} + + {(stats.sessions ?? []).reduceRight( + (p, it) => p + (it.mins5.tx + it.mins5.find) + (it.current.tx + it.current.find), + 0 + )} +
+ {/if}