diff --git a/plugins/workbench-resources/src/components/SelectWorkspaceMenu.svelte b/plugins/workbench-resources/src/components/SelectWorkspaceMenu.svelte index 9bdf4e00f2..5733c7d405 100644 --- a/plugins/workbench-resources/src/components/SelectWorkspaceMenu.svelte +++ b/plugins/workbench-resources/src/components/SelectWorkspaceMenu.svelte @@ -134,8 +134,13 @@
{#if isAdmin} -
+
+ {#if isAdminUser()} +
+ {$workspacesStore.length} +
+ {/if}
{/if}
diff --git a/plugins/workbench-resources/src/components/ServerManager.svelte b/plugins/workbench-resources/src/components/ServerManager.svelte index f9cc6d215a..07da789086 100644 --- a/plugins/workbench-resources/src/components/ServerManager.svelte +++ b/plugins/workbench-resources/src/components/ServerManager.svelte @@ -1,11 +1,12 @@ @@ -180,73 +183,84 @@
{/if} {:else if selectedTab === 'users'} +
+ +
Show only users
+
{#each Object.entries(activeSessions) as act} {@const wsInstance = $workspacesStore.find((it) => it.workspaceId === act[0])} {@const totalFind = act[1].reduce((it, itm) => itm.current.find + it, 0)} {@const totalTx = act[1].reduce((it, itm) => itm.current.tx + it, 0)} - {@const employeeGroups = Array.from(new Set(act[1].map((it) => it.userId)))} - - - -
- Workspace: {wsInstance?.workspaceName ?? act[0]}: {act[1].length} current 5 mins => {totalFind}/{totalTx} -
-
-
- {#each employeeGroups as employeeId} - {@const employee = employees.get(employeeId)} - {@const connections = act[1].filter((it) => it.userId === employeeId)} - - {@const find = connections.reduce((it, itm) => itm.current.find + it, 0)} - {@const txes = connections.reduce((it, itm) => itm.current.tx + it, 0)} -
- - -
- {#if employee} - - {:else} - {employeeId} - {/if} - : {connections.length} -
-
{find}/{txes}
-
-
-
- {#each connections as user, i} -
- #{i} - {user.userId} -
- Total: {user.total.find}/{user.total.tx} -
-
- Previous 5 mins: {user.mins5.find}/{user.mins5.tx} -
-
- Current 5 mins: {user.current.find}/{user.current.tx} -
-
-
- {#each Object.entries(user.data ?? {}) as [k, v]} -
- {k}: {JSON.stringify(v)} -
- {/each} -
- {/each} -
+ {@const employeeGroups = Array.from(new Set(act[1].map((it) => it.userId))).filter( + (it) => systemAccountEmail !== it || !realUsers + )} + {@const realGroup = Array.from(new Set(act[1].map((it) => it.userId))).filter( + (it) => systemAccountEmail !== it + )} + {#if employeeGroups.length > 0} + + + +
+ Workspace: {wsInstance?.workspaceName ?? act[0]}: {employeeGroups.length} current 5 mins => {totalFind}/{totalTx}
- {/each} -
- - + +
+ {#each employeeGroups as employeeId} + {@const employee = employees.get(employeeId)} + {@const connections = act[1].filter((it) => it.userId === employeeId)} + + {@const find = connections.reduce((it, itm) => itm.current.find + it, 0)} + {@const txes = connections.reduce((it, itm) => itm.current.tx + it, 0)} +
+ + +
+ {#if employee} + + {:else} + {employeeId} + {/if} + : {connections.length} +
+
{find}/{txes}
+
+
+
+ {#each connections as user, i} +
+ #{i} + {user.userId} +
+ Total: {user.total.find}/{user.total.tx} +
+
+ Previous 5 mins: {user.mins5.find}/{user.mins5.tx} +
+
+ Current 5 mins: {user.current.find}/{user.current.tx} +
+
+
+ {#each Object.entries(user.data ?? {}) as [k, v]} +
+ {k}: {JSON.stringify(v)} +
+ {/each} +
+ {/each} +
+
+ {/each} +
+ + + {/if} {/each}
{:else if selectedTab === 'statistics'} @@ -266,3 +280,9 @@ {/if} + + diff --git a/server/mongo/src/__tests__/storage.test.ts b/server/mongo/src/__tests__/storage.test.ts index 5e5b2fc0a8..ba49bd2b62 100644 --- a/server/mongo/src/__tests__/storage.test.ts +++ b/server/mongo/src/__tests__/storage.test.ts @@ -44,9 +44,8 @@ import { DummyFullTextAdapter, type FullTextAdapter } from '@hcengineering/server-core' -import { type MongoClient } from 'mongodb' import { createMongoAdapter, createMongoTxAdapter } from '..' -import { getMongoClient, shutdown } from '../utils' +import { getMongoClient, type MongoClientReference, shutdown } from '../utils' import { genMinModel } from './minmodel' import { createTaskModel, type Task, type TaskComment, taskPlugin } from './tasks' @@ -80,7 +79,7 @@ async function createNullContentTextAdapter (): Promise { describe('mongo operations', () => { const mongodbUri: string = process.env.MONGO_URL ?? 'mongodb://localhost:27017' - let mongoClient!: MongoClient + let mongoClient!: MongoClientReference let dbId: string = generateId() let hierarchy: Hierarchy let model: ModelDb @@ -88,7 +87,7 @@ describe('mongo operations', () => { let operations: TxOperations beforeAll(async () => { - mongoClient = await getMongoClient(mongodbUri) + mongoClient = getMongoClient(mongodbUri) }) afterAll(async () => { @@ -101,7 +100,7 @@ describe('mongo operations', () => { afterEach(async () => { try { - await mongoClient.db(dbId).dropDatabase() + await (await mongoClient.getClient()).db(dbId).dropDatabase() } catch (eee) {} }) diff --git a/server/mongo/src/storage.ts b/server/mongo/src/storage.ts index 4a8370871d..84e63b18c9 100644 --- a/server/mongo/src/storage.ts +++ b/server/mongo/src/storage.ts @@ -67,11 +67,10 @@ import { type Db, type Document, type Filter, - type MongoClient, type Sort, type UpdateFilter } from 'mongodb' -import { getMongoClient, getWorkspaceDB } from './utils' +import { getMongoClient, getWorkspaceDB, type MongoClientReference } from './utils' function translateDoc (doc: Doc): Document { return { ...doc, '%hash%': null } @@ -106,7 +105,7 @@ abstract class MongoAdapterBase implements DbAdapter { protected readonly db: Db, protected readonly hierarchy: Hierarchy, protected readonly modelDb: ModelDb, - protected readonly client: MongoClient + protected readonly client: MongoClientReference ) {} async init (): Promise {} @@ -1408,8 +1407,8 @@ export async function createMongoAdapter ( workspaceId: WorkspaceId, modelDb: ModelDb ): Promise { - const client = await getMongoClient(url) - const db = getWorkspaceDB(client, workspaceId) + const client = getMongoClient(url) + const db = getWorkspaceDB(await client.getClient(), workspaceId) return new MongoAdapter(db, hierarchy, modelDb, client) } @@ -1423,7 +1422,7 @@ export async function createMongoTxAdapter ( workspaceId: WorkspaceId, modelDb: ModelDb ): Promise { - const client = await getMongoClient(url) - const db = getWorkspaceDB(client, workspaceId) + const client = getMongoClient(url) + const db = getWorkspaceDB(await client.getClient(), workspaceId) return new MongoTxAdapter(db, hierarchy, modelDb, client) } diff --git a/server/mongo/src/utils.ts b/server/mongo/src/utils.ts index c90d6d770b..e541c3c3c7 100644 --- a/server/mongo/src/utils.ts +++ b/server/mongo/src/utils.ts @@ -16,7 +16,7 @@ import { toWorkspaceString, type WorkspaceId } from '@hcengineering/core' import { type Db, MongoClient, type MongoClientOptions } from 'mongodb' -let connections: MongoClient[] = [] +const connections = new Map() // Register mongo close on process exit. process.on('exit', () => { @@ -30,24 +30,66 @@ process.on('exit', () => { */ export async function shutdown (): Promise { for (const c of connections.values()) { - await c.close() + await c.close(true) } - connections = [] + connections.clear() } + +export class MongoClientReference { + count: number + client: MongoClient | Promise + + constructor (client: MongoClient | Promise) { + this.count = 1 + this.client = client + } + + async getClient (): Promise { + if (this.client instanceof Promise) { + this.client = await this.client + } + return this.client + } + + async close (force: boolean = false): Promise { + this.count-- + if (this.count === 0 || force) { + if (force) { + this.count = 0 + } + await (await this.client).close() + } + } + + addRef (): void { + this.count++ + } +} + /** * Initialize a workspace connection to DB * @public */ -export async function getMongoClient (uri: string, options?: MongoClientOptions): Promise { +export function getMongoClient (uri: string, options?: MongoClientOptions): MongoClientReference { const extraOptions = JSON.parse(process.env.MONGO_OPTIONS ?? '{}') - const client = await MongoClient.connect(uri, { - ...options, - enableUtf8Validation: false, - maxConnecting: 1024, - ...extraOptions - }) - connections.push(client) - return client + const key = `${uri}${process.env.MONGO_OPTIONS}` + let existing = connections.get(key) + + // If not created or closed + if (existing === undefined || existing.count === 0) { + existing = new MongoClientReference( + MongoClient.connect(uri, { + ...options, + enableUtf8Validation: false, + maxConnecting: 1024, + ...extraOptions + }) + ) + connections.set(key, existing) + } else { + existing.addRef() + } + return existing } /**