add new statistic page
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
|||||||
"backend/env"
|
"backend/env"
|
||||||
"backend/models"
|
"backend/models"
|
||||||
"backend/server"
|
"backend/server"
|
||||||
|
"backend/stats"
|
||||||
"backend/utils"
|
"backend/utils"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -119,6 +120,10 @@ func main() {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
//set logger for AuthMiddleware
|
//set logger for AuthMiddleware
|
||||||
|
s.Routes.Use(func(c *gin.Context) {
|
||||||
|
c.Set("logger", logger)
|
||||||
|
c.Next()
|
||||||
|
})
|
||||||
accessHandler.SetMiddlewareLogger(s.Routes)
|
accessHandler.SetMiddlewareLogger(s.Routes)
|
||||||
api := s.Routes.Group("/api")
|
api := s.Routes.Group("/api")
|
||||||
//set routes
|
//set routes
|
||||||
@@ -136,6 +141,7 @@ func main() {
|
|||||||
role.GET("/members", dbHandler.GetMember)
|
role.GET("/members", dbHandler.GetMember)
|
||||||
auth.GET("/events", dbHandler.GetEvent)
|
auth.GET("/events", dbHandler.GetEvent)
|
||||||
auth.GET("/groups", dbHandler.GetGroup)
|
auth.GET("/groups", dbHandler.GetGroup)
|
||||||
|
auth.POST("/stats", stats.GetStats)
|
||||||
|
|
||||||
auth.GET("/users", accessHandler.GetUser)
|
auth.GET("/users", accessHandler.GetUser)
|
||||||
auth.GET("/roles", accessHandler.GetRole)
|
auth.GET("/roles", accessHandler.GetRole)
|
||||||
|
|||||||
54
backend/stats/stats.go
Normal file
54
backend/stats/stats.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package stats
|
||||||
|
|
||||||
|
import (
|
||||||
|
"backend/utils"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"gitea.tecamino.com/paadi/tecamino-logger/logging"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Stats struct {
|
||||||
|
Database string `json:"database,omitempty"`
|
||||||
|
DatabaseSize int64 `json:"databaseSize,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetStats(c *gin.Context) {
|
||||||
|
middlewareData, err := utils.GetMiddlewareData(c, "logger")
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
logger, ok := middlewareData.(*logging.Logger)
|
||||||
|
if !ok {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"error": "middleware logger for state not defined",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var stats Stats
|
||||||
|
err = c.BindJSON(&stats)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("GetStats", err)
|
||||||
|
c.JSON(http.StatusInternalServerError, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Stat(stats.Database)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("GetStats", err)
|
||||||
|
c.JSON(http.StatusInternalServerError, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var returnStats = Stats{
|
||||||
|
DatabaseSize: f.Size(),
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"data": returnStats,
|
||||||
|
})
|
||||||
|
}
|
||||||
28
backend/utils/middleware.go
Normal file
28
backend/utils/middleware.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetMiddlewareData(r *gin.Engine, key string, data any) {
|
||||||
|
//set logger for middleware
|
||||||
|
r.Use(func(c *gin.Context) {
|
||||||
|
c.Set(key, data)
|
||||||
|
c.Next()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMiddlewareData(c *gin.Context, key string) (any, error) {
|
||||||
|
// Retrieve logger from Gin context
|
||||||
|
data, ok := c.Get("logger")
|
||||||
|
if !ok {
|
||||||
|
log.Fatal("middleware logger not set — use SetMiddlewareLogger first")
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, http.StatusInternalServerError)
|
||||||
|
return nil, fmt.Errorf("middleware key '%s'not set ", key)
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
@@ -63,6 +63,22 @@
|
|||||||
>
|
>
|
||||||
<q-item-section>{{ $t('groups') }}</q-item-section>
|
<q-item-section>{{ $t('groups') }}</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
|
<q-item v-if="!autorized" to="/login" exact clickable v-ripple @click="closeDrawer">
|
||||||
|
<q-item-section>{{ $t('login') }}</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<!-- <q-item
|
||||||
|
v-if="autorized || user.isPermittedTo('members', 'read')"
|
||||||
|
to="/report"
|
||||||
|
exact
|
||||||
|
clickable
|
||||||
|
v-ripple
|
||||||
|
@click="closeDrawer"
|
||||||
|
>
|
||||||
|
<q-item-section> {{ $t('report') }}</q-item-section>
|
||||||
|
</q-item> -->
|
||||||
|
<q-item v-if="autorized" to="/stats" exact clickable v-ripple @click="closeDrawer">
|
||||||
|
<q-item-section> {{ $t('stats') }}</q-item-section>
|
||||||
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
</q-drawer>
|
</q-drawer>
|
||||||
<q-page-container>
|
<q-page-container>
|
||||||
|
|||||||
116
src/pages/StatsPage.vue
Normal file
116
src/pages/StatsPage.vue
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
<template>
|
||||||
|
<site-title :title="$t('stats')" />
|
||||||
|
<q-card class="q-ma-md">
|
||||||
|
<q-card-section>
|
||||||
|
<div class="row q-ma-md q-gutter-md">
|
||||||
|
<q-card class="row col-auto">
|
||||||
|
<div class="col">
|
||||||
|
<h5 class="text-primary q-ma-md">{{ i18n.global.t('database') }}</h5>
|
||||||
|
<q-card-section>
|
||||||
|
<div class="column q-gutter-y-sm">
|
||||||
|
<div class="row items-center">
|
||||||
|
<p class="text-bold" style="min-width: 180px">
|
||||||
|
{{ i18n.global.t('databaseSize') }}:
|
||||||
|
</p>
|
||||||
|
<p class="q-ml-md">{{ stats }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="row items-center">
|
||||||
|
<p class="text-bold" style="min-width: 180px">
|
||||||
|
{{ i18n.global.t('numberOfMembers') }}:
|
||||||
|
</p>
|
||||||
|
<p class="q-ml-md">{{ amounts?.members }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="row items-center">
|
||||||
|
<p class="text-bold" style="min-width: 180px">
|
||||||
|
{{ i18n.global.t('numberOfEvents') }}:
|
||||||
|
</p>
|
||||||
|
<p class="q-ml-md">{{ amounts?.events }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="row items-center">
|
||||||
|
<p class="text-bold" style="min-width: 180px">
|
||||||
|
{{ i18n.global.t('numberOfResponsibles') }}:
|
||||||
|
</p>
|
||||||
|
<p class="q-ml-md">{{ amounts?.responsibles }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="row items-center">
|
||||||
|
<p class="text-bold" style="min-width: 180px">
|
||||||
|
{{ i18n.global.t('numberOfGroups') }}:
|
||||||
|
</p>
|
||||||
|
<p class="q-ml-md">{{ amounts?.groups }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
</div>
|
||||||
|
</q-card>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { appApi } from 'src/boot/axios';
|
||||||
|
import { i18n } from 'src/boot/lang';
|
||||||
|
import SiteTitle from 'src/vueLib/general/SiteTitle.vue';
|
||||||
|
import { useNotify } from 'src/vueLib/general/useNotify';
|
||||||
|
import { databaseName } from 'src/vueLib/models/settings';
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
const stats = ref();
|
||||||
|
const amounts = ref<{
|
||||||
|
members: number;
|
||||||
|
events: number;
|
||||||
|
responsibles: number;
|
||||||
|
groups: number;
|
||||||
|
}>({
|
||||||
|
members: 0,
|
||||||
|
events: 0,
|
||||||
|
responsibles: 0,
|
||||||
|
groups: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { NotifyResponse } = useNotify();
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
stats.value = await appApi
|
||||||
|
.post('/stats', { database: databaseName.value })
|
||||||
|
.then((resp) => {
|
||||||
|
if ((resp.data.databaseSize as number) >= 1000000000) {
|
||||||
|
return (resp.data.data.databaseSize / 1000000000).toFixed(2) + ' GB';
|
||||||
|
} else if ((resp.data.data.databaseSize as number) >= 1000000) {
|
||||||
|
return (resp.data.data.databaseSize / 1000000).toFixed(2) + ' MB';
|
||||||
|
} else if ((resp.data.data.databaseSize as number) >= 1000) {
|
||||||
|
return (resp.data.data.databaseSize / 1000).toFixed(2) + ' kB';
|
||||||
|
}
|
||||||
|
return resp.data.data.databaseSize + ' B';
|
||||||
|
})
|
||||||
|
.catch((err) => NotifyResponse(err, 'error'));
|
||||||
|
|
||||||
|
amounts.value.members = await appApi
|
||||||
|
.get('/members')
|
||||||
|
.then((resp) => {
|
||||||
|
return resp.data.length;
|
||||||
|
})
|
||||||
|
.catch((err) => NotifyResponse(err, 'error'));
|
||||||
|
|
||||||
|
amounts.value.events = await appApi
|
||||||
|
.get('/events')
|
||||||
|
.then((resp) => {
|
||||||
|
return resp.data.length;
|
||||||
|
})
|
||||||
|
.catch((err) => NotifyResponse(err, 'error'));
|
||||||
|
|
||||||
|
amounts.value.responsibles = await appApi
|
||||||
|
.get('responsible')
|
||||||
|
.then((resp) => {
|
||||||
|
return resp.data.length;
|
||||||
|
})
|
||||||
|
.catch((err) => NotifyResponse(err, 'error'));
|
||||||
|
|
||||||
|
amounts.value.groups = await appApi
|
||||||
|
.get('/groups')
|
||||||
|
.then((resp) => {
|
||||||
|
return resp.data.length;
|
||||||
|
})
|
||||||
|
.catch((err) => NotifyResponse(err, 'error'));
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -40,6 +40,7 @@ export default defineRouter(function (/* { store, ssrContext } */) {
|
|||||||
|
|
||||||
Router.beforeEach((to, from, next) => {
|
Router.beforeEach((to, from, next) => {
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
const isLoggedIn = userStore.isAuthenticated;
|
const isLoggedIn = userStore.isAuthenticated;
|
||||||
if (!userStore.$state.firstLogin && to.path === '/firstlogin') {
|
if (!userStore.$state.firstLogin && to.path === '/firstlogin') {
|
||||||
next('/login');
|
next('/login');
|
||||||
@@ -51,7 +52,11 @@ export default defineRouter(function (/* { store, ssrContext } */) {
|
|||||||
to.meta.requiresAdmin &&
|
to.meta.requiresAdmin &&
|
||||||
!userStore.isPermittedTo(to.path.replace('/', ''), 'read')
|
!userStore.isPermittedTo(to.path.replace('/', ''), 'read')
|
||||||
) {
|
) {
|
||||||
|
if (to.meta.noBackendAdmin) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
next('/');
|
next('/');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,16 @@ const routes: RouteRecordRaw[] = [
|
|||||||
component: () => import('src/pages/GroupTable.vue'),
|
component: () => import('src/pages/GroupTable.vue'),
|
||||||
meta: { requiresAuth: true, requiresAdmin: true },
|
meta: { requiresAuth: true, requiresAdmin: true },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'report',
|
||||||
|
component: () => import('src/pages/ReportPage.vue'),
|
||||||
|
meta: { requiresAuth: true, requiresAdmin: false },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'stats',
|
||||||
|
component: () => import('src/pages/StatsPage.vue'),
|
||||||
|
meta: { requiresAuth: true, requiresAdmin: true, noBackendAdmin: true },
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'settings',
|
path: 'settings',
|
||||||
component: () => import('pages/SettingsPage.vue'),
|
component: () => import('pages/SettingsPage.vue'),
|
||||||
|
|||||||
Reference in New Issue
Block a user