Skip to content

Commit

Permalink
Merge pull request #334 from MerginMaps/server_usage_in_admin
Browse files Browse the repository at this point in the history
Server usage in admin
  • Loading branch information
MarcelGeo authored Nov 26, 2024
2 parents 3dc84cb + c090a07 commit 339b67f
Show file tree
Hide file tree
Showing 18 changed files with 365 additions and 15 deletions.
47 changes: 47 additions & 0 deletions server/mergin/auth/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,28 @@ paths:
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFoundResp"
/app/admin/usage:
get:
tags:
- user
- admin
- project
summary: Get server usage statistics
description: List server storage, projects, active contributors for admin
operationId: mergin.auth.controller.get_server_usage
responses:
"200":
description: Server usage details
content:
application/json:
schema:
$ref: "#/components/schemas/ServerUsage"
"400":
$ref: "#/components/responses/BadStatusResp"
"401":
$ref: "#/components/responses/UnauthorizedError"
"403":
$ref: "#/components/responses/Forbidden"
/app/auth/login:
post:
summary: Login
Expand Down Expand Up @@ -845,3 +867,28 @@ components:
- reader
- guest
example: reader
ServerUsage:
type: object
properties:
active_monthly_contributors:
type: array
description: count of users who made a project change last months
items:
type: integer
example: 2
projects:
type: integer
description: total number of projects
example: 12
storage:
type: string
description: projest files size in bytes
example: 1024 kB
users:
type: integer
description: count of registered accounts
example: 6
workspaces:
type: integer
description: number of workspaces on the server
example: 3
24 changes: 21 additions & 3 deletions server/mergin/auth/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import pytz
from datetime import datetime, timedelta
from connexion import NoContent
from sqlalchemy import func, desc, asc, or_
from sqlalchemy import func, desc, asc
from sqlalchemy.sql.operators import is_
from flask import request, current_app, jsonify, abort, render_template
from flask_login import login_user, logout_user, current_user
Expand Down Expand Up @@ -35,8 +35,9 @@
UserChangePasswordForm,
ApiLoginForm,
)
from ..app import DEPRECATION_API_MSG, db
from ..utils import format_time_delta
from ..app import db
from ..sync.models import Project
from ..sync.utils import files_size


# public endpoints
Expand Down Expand Up @@ -498,3 +499,20 @@ def get_user_info():
for inv in invitations
]
return user_info, 200


@auth_required(permissions=["admin"])
def get_server_usage():
data = {
"active_monthly_contributors": [
current_app.ws_handler.monthly_contributors_count(),
current_app.ws_handler.monthly_contributors_count(month_offset=1),
current_app.ws_handler.monthly_contributors_count(month_offset=2),
current_app.ws_handler.monthly_contributors_count(month_offset=3),
],
"projects": Project.query.count(),
"storage": files_size(),
"users": User.query.count(),
"workspaces": current_app.ws_handler.workspace_count(),
}
return data, 200
1 change: 0 additions & 1 deletion server/mergin/sync/public_api_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
from typing import Dict
from urllib.parse import quote
import uuid
from time import time
from datetime import datetime
import psycopg2
from blinker import signal
Expand Down
35 changes: 34 additions & 1 deletion server/mergin/sync/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
import secrets
from threading import Timer
from uuid import UUID
from connexion import NoContent
from shapely import wkb
from shapely.errors import ShapelyError
from gevent import sleep
from flask import Request
from typing import Optional
from sqlalchemy import text


def generate_checksum(file, chunk_size=4096):
Expand Down Expand Up @@ -304,3 +304,36 @@ def split_project_path(project_path):
def get_device_id(request: Request) -> Optional[str]:
"""Get device uuid from http header X-Device-Id"""
return request.headers.get("X-Device-Id")


def files_size():
"""Get total size of all files"""
from mergin.app import db

files_size = text(
f"""
WITH partials AS (
WITH latest_files AS (
SELECT distinct unnest(file_history_ids) AS file_id
FROM latest_project_files pf
)
SELECT
SUM(size)
FROM file_history
WHERE change = 'create'::push_change_type OR change = 'update'::push_change_type
UNION
SELECT
SUM(COALESCE((diff ->> 'size')::bigint, 0))
FROM file_history
WHERE change = 'update_diff'::push_change_type
UNION
SELECT
SUM(size)
FROM latest_files lf
LEFT OUTER JOIN file_history fh ON fh.id = lf.file_id
WHERE fh.change = 'update_diff'::push_change_type
)
SELECT pg_size_pretty(SUM(sum)) FROM partials;
"""
)
return db.session.execute(files_size).scalar()
4 changes: 2 additions & 2 deletions server/mergin/sync/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,15 +255,15 @@ def workspace_count():
return 1

@staticmethod
def monthly_contributors_count():
def monthly_contributors_count(month_offset=0):
today = datetime.now(timezone.utc)
year = today.year
month = today.month
return (
db.session.query(ProjectVersion.author_id)
.filter(
extract("year", ProjectVersion.created) == year,
extract("month", ProjectVersion.created) == month,
extract("month", ProjectVersion.created) == month - month_offset,
)
.group_by(ProjectVersion.author_id)
.count()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Migrage project version author name to user.id
Revision ID: 1ab5b02ce532
Revises: 57d0de13ce4a
Revises: 1c23e3be03a3
Create Date: 2024-09-06 14:01:40.668483
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ import { useRoute } from 'vue-router'
const route = useRoute()

const sidebarItems = computed<SideBarItemModel[]>(() => [
{
title: 'Overview',
to: '/overview',
icon: 'ti ti-layout-dashboard',
active: route.matched.some((item) => item.name === AdminRoutes.OVERVIEW)
},
{
title: 'Accounts',
to: '/accounts',
Expand Down
15 changes: 13 additions & 2 deletions web-app/packages/admin-app/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import {
ProjectFilesView,
ProjectSettingsView,
ProjectVersionView,
ProjectVersionsView
ProjectVersionsView,
OverviewView
} from '@mergin/admin-lib'
import {
NotFoundView,
Expand Down Expand Up @@ -48,7 +49,7 @@ export const createRouter = (pinia: Pinia) => {
{
path: '/',
name: 'admin',
redirect: '/accounts'
redirect: '/overview'
},
{
path: '/accounts',
Expand Down Expand Up @@ -144,6 +145,16 @@ export const createRouter = (pinia: Pinia) => {
header: AppHeader
},
props: true
},
{
path: '/overview',
name: AdminRoutes.OVERVIEW,
components: {
default: OverviewView,
sidebar: Sidebar,
header: AppHeader
},
props: true
}
]
})
Expand Down
1 change: 1 addition & 0 deletions web-app/packages/admin-lib/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ declare module 'vue' {
PInputSwitch: typeof import('primevue/inputswitch')['default']
PInputText: typeof import('primevue/inputtext')['default']
PPassword: typeof import('primevue/password')['default']
PProgressBar: typeof import('primevue/progressbar')['default']
PTabPanel: typeof import('primevue/tabpanel')['default']
PTabView: typeof import('primevue/tabview')['default']
RouterLink: typeof import('vue-router')['RouterLink']
Expand Down
10 changes: 6 additions & 4 deletions web-app/packages/admin-lib/src/modules/admin/adminApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial

import {
ApiRequestSuccessInfo,
errorUtils,
LoginData,
PaginatedUsersParams,
UserProfileResponse,
Expand All @@ -14,13 +12,13 @@ import { AxiosResponse } from 'axios'

import { AdminModule } from '@/modules/admin/module'
import {
UsersParams,
UpdateUserData,
UsersResponse,
LatestServerVersionResponse,
CreateUserData,
PaginatedAdminProjectsResponse,
PaginatedAdminProjectsParams
PaginatedAdminProjectsParams,
ServerUsageResponse
} from '@/modules/admin/types'

export const AdminApi = {
Expand Down Expand Up @@ -98,5 +96,9 @@ export const AdminApi = {
`/app/project/removed-project/restore/${id}`,
null
)
},

async getServerUsage(): Promise<AxiosResponse<ServerUsageResponse>> {
return AdminModule.httpService.get('/app/admin/usage', )
}
}
1 change: 1 addition & 0 deletions web-app/packages/admin-lib/src/modules/admin/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { RouteRecord } from 'vue-router'
export enum AdminRoutes {
ACCOUNTS = 'accounts',
ACCOUNT = 'account',
OVERVIEW = 'overview',
PROJECTS = 'projects',
PROJECT = 'project',
SETTINGS = 'settings',
Expand Down
15 changes: 14 additions & 1 deletion web-app/packages/admin-lib/src/modules/admin/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { AdminApi } from '@/modules/admin/adminApi'
import {
LatestServerVersionResponse,
PaginatedAdminProjectsParams,
PaginatedAdminProjectsResponse,
PaginatedAdminProjectsResponse, ServerUsageResponse,
UpdateUserPayload,
UsersResponse
} from '@/modules/admin/types'
Expand Down Expand Up @@ -112,6 +112,9 @@ export const useAdminStore = defineStore('adminModule', {
setIsServerConfigHidden(value: boolean) {
this.isServerConfigHidden = value
},
setUsage(data: ServerUsageResponse){
this.usage = data
},

async fetchUsers(payload: { params: PaginatedUsersParams }) {
const notificationStore = useNotificationStore()
Expand Down Expand Up @@ -313,6 +316,16 @@ export const useAdminStore = defineStore('adminModule', {
text: 'Unable to remove project'
})
}
},

async getServerUsage() {
const notificationStore = useNotificationStore()
try {
const response = await AdminApi.getServerUsage()
this.setUsage(response.data)
} catch (e) {
notificationStore.error({ text: errorUtils.getErrorMessage(e) })
}
}
}
})
11 changes: 11 additions & 0 deletions web-app/packages/admin-lib/src/modules/admin/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,15 @@ export type PaginatedAdminProjectsResponse =
export interface PaginatedAdminProjectsParams extends PaginatedRequestParams {
like?: string
}

export type ServerUsageResponse = ServerUsage

export interface ServerUsage {
active_monthly_contributors: number[]
projects: number
storage: string
users: number
workspaces: number
}

/* eslint-enable camelcase */
Loading

0 comments on commit 339b67f

Please sign in to comment.