mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-11 18:01:59 +00:00
QFIX: Admin panel show inactive workspaces (#8716)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
c31815e19f
commit
0b73f61106
@ -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<void> {
|
||||
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<string, string> = {}
|
||||
|
||||
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 @@
|
||||
</div>
|
||||
<div class="fs-title p-3">
|
||||
Workspaces: {workspaces.length} active: {workspaces.filter((it) => isActiveMode(it.mode)).length}
|
||||
|
||||
upgrading: {workspaces.filter((it) => isUpgradingMode(it.mode)).length}
|
||||
|
||||
<br />
|
||||
Backupable: {backupable.length} new: {backupable.reduce((p, it) => p + (it.backupInfo == null ? 1 : 0), 0)}
|
||||
Active: {data?.workspaces.length ?? -1}
|
||||
<br />
|
||||
<span class="mt-2">
|
||||
Users: {data?.usersTotal}/{data?.connectionsTotal}
|
||||
</span>
|
||||
|
||||
<div class="flex-row-center">
|
||||
{#each byVersion.entries() as [k, v]}
|
||||
@ -299,6 +377,10 @@
|
||||
<span class="mr-2">Show selected region only:</span>
|
||||
<CheckBox bind:checked={showSelectedRegionOnly} />
|
||||
</div>
|
||||
<div class="flex-row-center">
|
||||
<span class="mr-2">Show inactive workspaces:</span>
|
||||
<CheckBox bind:checked={showInactive} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fs-title p-3 flex-row-center">
|
||||
@ -329,13 +411,32 @@
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="fs-title p-3 flex-row-center">
|
||||
<div class="mr-2">
|
||||
<CheckBox bind:checked={showSelectedRegionOnly} />
|
||||
</div>
|
||||
<span class="mr-2"> Filtere region selector: </span>
|
||||
<ButtonMenu
|
||||
selected={filterRegionId}
|
||||
autoSelectionIfOne
|
||||
title={filteredRegionName}
|
||||
items={regionInfo.map((it) => ({
|
||||
id: it.region === '' ? '#' : it.region,
|
||||
label: getEmbeddedLabel(it.name.length > 0 ? it.name : it.region + ' (hidden)')
|
||||
}))}
|
||||
on:selected={(it) => {
|
||||
filterRegionId = it.detail === '#' ? '' : it.detail
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="fs-title p-1">
|
||||
<Scroller maxHeight={40} noStretch={true}>
|
||||
<div class="mr-4">
|
||||
{#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 ?? '')}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<tr class="flex fs-title cursor-pointer focused-button bordered" id={`${workspace.uuid}`}>
|
||||
<div class="label overflow-label p-1 flex flex-row-center" style:width={'15rem'}>
|
||||
{wsName}
|
||||
{#if stats}
|
||||
-
|
||||
<div class="ml-1">
|
||||
{stats.sessions?.length ?? 0}
|
||||
|
||||
{(stats.sessions ?? []).reduceRight(
|
||||
(p, it) => p + (it.mins5.tx + it.mins5.find) + (it.current.tx + it.current.find),
|
||||
0
|
||||
)}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="ml-1 flex flex-row-center">
|
||||
<Button
|
||||
icon={IconOpen}
|
||||
|
@ -76,12 +76,12 @@ test.describe('Workspace Archive tests', () => {
|
||||
const adminPage = new AdminPage(page2)
|
||||
await adminPage.gotoAdmin()
|
||||
|
||||
await page2.getByText('Today -').click()
|
||||
await page2.getByText('Hour -').click()
|
||||
await page2.locator('div:nth-child(3) > .checkbox-container > .checkSVG').click()
|
||||
await page2.locator('div:nth-child(4) > .checkbox-container > .checkSVG').click()
|
||||
|
||||
await page2.getByRole('button', { name: 'America', exact: true }).click()
|
||||
await page2.getByRole('button', { name: 'europe' }).click()
|
||||
await page2.getByRole('button', { name: 'America', exact: true }).first().click()
|
||||
await page2.getByRole('button', { name: 'europe' }).first().click()
|
||||
await page2.getByPlaceholder('Search').click()
|
||||
await page2.getByPlaceholder('Search').fill(workspaceInfo.workspace)
|
||||
await page2.locator(`[id="${workspaceInfo.workspace}"]`).getByRole('button', { name: 'Archive' }).click()
|
||||
|
@ -77,12 +77,12 @@ test.describe('Workspace Migration tests', () => {
|
||||
const adminPage = new AdminPage(page2)
|
||||
await adminPage.gotoAdmin()
|
||||
|
||||
await page2.getByText('Today -').click()
|
||||
await page2.getByText('Hour -').click()
|
||||
await page2.locator('div:nth-child(3) > .checkbox-container > .checkSVG').click()
|
||||
await page2.locator('div:nth-child(4) > .checkbox-container > .checkSVG').click()
|
||||
|
||||
await page2.getByRole('button', { name: 'America', exact: true }).click()
|
||||
await page2.getByRole('button', { name: 'europe' }).click()
|
||||
await page2.getByRole('button', { name: 'America', exact: true }).first().click()
|
||||
await page2.getByRole('button', { name: 'europe' }).first().click()
|
||||
await page2.getByPlaceholder('Search').click()
|
||||
await page2.getByPlaceholder('Search').fill(workspaceInfo.workspace)
|
||||
await page2.locator(`[id="${workspaceInfo.workspace}"]`).getByRole('button', { name: 'Migrate' }).click()
|
||||
|
Loading…
Reference in New Issue
Block a user