API & Client - display uploaded files size in admin + minor changes
This commit is contained in:
parent
abca323936
commit
5aa1d77553
@ -7,7 +7,7 @@ from sqlalchemy import func
|
|||||||
from ..users.models import User
|
from ..users.models import User
|
||||||
from ..users.utils import authenticate, authenticate_as_admin
|
from ..users.utils import authenticate, authenticate_as_admin
|
||||||
from .models import Activity, Sport
|
from .models import Activity, Sport
|
||||||
from .utils import get_datetime_with_tz
|
from .utils import get_datetime_with_tz, get_upload_dir_size
|
||||||
from .utils_format import convert_timedelta_to_integer
|
from .utils_format import convert_timedelta_to_integer
|
||||||
|
|
||||||
stats_blueprint = Blueprint('stats', __name__)
|
stats_blueprint = Blueprint('stats', __name__)
|
||||||
@ -376,6 +376,7 @@ def get_application_stats(auth_user_id):
|
|||||||
'activities': nb_activities,
|
'activities': nb_activities,
|
||||||
'sports': nb_sports,
|
'sports': nb_sports,
|
||||||
'users': nb_users,
|
'users': nb_users,
|
||||||
|
'uploads_dir_size': get_upload_dir_size(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return jsonify(response_object), 200
|
return jsonify(response_object), 200
|
||||||
|
@ -338,3 +338,13 @@ def process_files(auth_user_id, activity_data, activity_file, folders):
|
|||||||
return [process_one_gpx_file(common_params, filename)]
|
return [process_one_gpx_file(common_params, filename)]
|
||||||
else:
|
else:
|
||||||
return process_zip_archive(common_params, folders['extract_dir'])
|
return process_zip_archive(common_params, folders['extract_dir'])
|
||||||
|
|
||||||
|
|
||||||
|
def get_upload_dir_size():
|
||||||
|
upload_path = get_absolute_file_path('')
|
||||||
|
total_size = 0
|
||||||
|
for dir_path, _, filenames in os.walk(upload_path):
|
||||||
|
for f in filenames:
|
||||||
|
fp = os.path.join(dir_path, f)
|
||||||
|
total_size += os.path.getsize(fp)
|
||||||
|
return total_size
|
||||||
|
@ -968,7 +968,10 @@ def test_get_app_stats_without_activities(app, user_1_admin):
|
|||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert 'success' in data['status']
|
assert 'success' in data['status']
|
||||||
assert data['data'] == {'activities': 0, 'sports': 0, 'users': 1}
|
assert data['data']['activities'] == 0
|
||||||
|
assert data['data']['sports'] == 0
|
||||||
|
assert data['data']['users'] == 1
|
||||||
|
assert 'uploads_dir_size' in data['data']
|
||||||
|
|
||||||
|
|
||||||
def test_get_app_stats_with_activities(
|
def test_get_app_stats_with_activities(
|
||||||
@ -999,7 +1002,10 @@ def test_get_app_stats_with_activities(
|
|||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert 'success' in data['status']
|
assert 'success' in data['status']
|
||||||
assert data['data'] == {'activities': 3, 'sports': 2, 'users': 3}
|
assert data['data']['activities'] == 3
|
||||||
|
assert data['data']['sports'] == 2
|
||||||
|
assert data['data']['users'] == 3
|
||||||
|
assert 'uploads_dir_size' in data['data']
|
||||||
|
|
||||||
|
|
||||||
def test_get_app_stats_no_admin(
|
def test_get_app_stats_no_admin(
|
||||||
|
@ -3,9 +3,6 @@ import { Link } from 'react-router-dom'
|
|||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
|
|
||||||
import AdminStats from './AdminStats'
|
import AdminStats from './AdminStats'
|
||||||
import { capitalize } from '../../utils'
|
|
||||||
|
|
||||||
const menuItems = ['application', 'sports', 'users']
|
|
||||||
|
|
||||||
export default function AdminDashboard(props) {
|
export default function AdminDashboard(props) {
|
||||||
const { t } = props
|
const { t } = props
|
||||||
@ -18,19 +15,51 @@ export default function AdminDashboard(props) {
|
|||||||
<div className="card-header">{t('administration:Administration')}</div>
|
<div className="card-header">{t('administration:Administration')}</div>
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<AdminStats />
|
<AdminStats />
|
||||||
<ul className="admin-items">
|
<br />
|
||||||
{menuItems.map(item => (
|
<dl className="admin-items">
|
||||||
<li key={item}>
|
<dt>
|
||||||
<Link
|
<Link
|
||||||
to={{
|
to={{
|
||||||
pathname: `/admin/${item}`,
|
pathname: '/admin/application',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t(`administration:${capitalize(item)}`)}
|
{t('administration:Application')}
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</dt>
|
||||||
))}
|
<dd>
|
||||||
</ul>
|
{t(
|
||||||
|
'administration:Update application configuration ' +
|
||||||
|
'(maximum number of registered users, maximum files size).'
|
||||||
|
)}{' '}
|
||||||
|
</dd>
|
||||||
|
<br />
|
||||||
|
<dt>
|
||||||
|
<Link
|
||||||
|
to={{
|
||||||
|
pathname: '/admin/sports',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('administration:Sports')}
|
||||||
|
</Link>
|
||||||
|
</dt>
|
||||||
|
<dd>{t('administration:Enable/disable sports.')}</dd>
|
||||||
|
<br />
|
||||||
|
<dt>
|
||||||
|
<Link
|
||||||
|
to={{
|
||||||
|
pathname: '/admin/users',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('administration:Users')}
|
||||||
|
</Link>
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
{t(
|
||||||
|
'administration:Add/remove admin rigths, ' +
|
||||||
|
'delete user account.'
|
||||||
|
)}
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -3,6 +3,7 @@ import { withTranslation } from 'react-i18next'
|
|||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
|
|
||||||
import { getAppData } from '../../actions/application'
|
import { getAppData } from '../../actions/application'
|
||||||
|
import { getFileSize } from '../../utils'
|
||||||
|
|
||||||
class AdminStats extends React.Component {
|
class AdminStats extends React.Component {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -11,9 +12,10 @@ class AdminStats extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { appStats, t } = this.props
|
const { appStats, t } = this.props
|
||||||
|
const uploadDirSize = getFileSize(appStats.uploads_dir_size, false)
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-md-4">
|
<div className="col-lg-3 col-md-6 col-sm-6">
|
||||||
<div className="card activity-card">
|
<div className="card activity-card">
|
||||||
<div className="card-body row">
|
<div className="card-body row">
|
||||||
<div className="col-3">
|
<div className="col-3">
|
||||||
@ -32,7 +34,7 @@ class AdminStats extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-4">
|
<div className="col-lg-3 col-md-6 col-sm-6">
|
||||||
<div className="card activity-card">
|
<div className="card activity-card">
|
||||||
<div className="card-body row">
|
<div className="card-body row">
|
||||||
<div className="col-3">
|
<div className="col-3">
|
||||||
@ -49,7 +51,7 @@ class AdminStats extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-4">
|
<div className="col-lg-3 col-md-6 col-sm-6">
|
||||||
<div className="card activity-card">
|
<div className="card activity-card">
|
||||||
<div className="card-body row">
|
<div className="card-body row">
|
||||||
<div className="col-3">
|
<div className="col-3">
|
||||||
@ -68,6 +70,21 @@ class AdminStats extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="col-lg-3 col-md-6 col-sm-6">
|
||||||
|
<div className="card activity-card">
|
||||||
|
<div className="card-body row">
|
||||||
|
<div className="col-3">
|
||||||
|
<i className="fa fa-folder-open fa-3x fa-color" />
|
||||||
|
</div>
|
||||||
|
<div className="col-9 text-right">
|
||||||
|
<div className="huge">{uploadDirSize.size}</div>
|
||||||
|
<div>
|
||||||
|
{uploadDirSize.suffix} ({t('administration:uploads')})
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,7 @@ class AdminSports extends React.Component {
|
|||||||
type="submit"
|
type="submit"
|
||||||
className="btn btn-secondary"
|
className="btn btn-secondary"
|
||||||
onClick={() => history.push('/admin/')}
|
onClick={() => history.push('/admin/')}
|
||||||
value={t('administration:Back')}
|
value={t('common:Back')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -89,8 +89,8 @@ class AdminUsers extends React.Component {
|
|||||||
disabled={user.username === authUser.username}
|
disabled={user.username === authUser.username}
|
||||||
value={
|
value={
|
||||||
user.admin
|
user.admin
|
||||||
? t('administration:Remove')
|
? t('administration:Remove admin rights')
|
||||||
: t('administration:Add')
|
: t('administration:Add admin rights')
|
||||||
}
|
}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
updateUser(user.username, !user.admin)
|
updateUser(user.username, !user.admin)
|
||||||
@ -105,7 +105,7 @@ class AdminUsers extends React.Component {
|
|||||||
type="submit"
|
type="submit"
|
||||||
className="btn btn-secondary"
|
className="btn btn-secondary"
|
||||||
onClick={() => history.push('/admin/')}
|
onClick={() => history.push('/admin/')}
|
||||||
value={t('administration:Back')}
|
value={t('common:Back')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,7 +13,7 @@ export default function UserStatistics(props) {
|
|||||||
duration = `${duration.split(':')[0]}h ${duration.split(':')[1]}min`
|
duration = `${duration.split(':')[0]}h ${duration.split(':')[1]}min`
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-md-3">
|
<div className="col-lg-3 col-md-6 col-sm-6">
|
||||||
<div className="card activity-card">
|
<div className="card activity-card">
|
||||||
<div className="card-body row">
|
<div className="card-body row">
|
||||||
<div className="col-3">
|
<div className="col-3">
|
||||||
@ -30,7 +30,7 @@ export default function UserStatistics(props) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-3">
|
<div className="col-lg-3 col-md-6 col-sm-6">
|
||||||
<div className="card activity-card">
|
<div className="card activity-card">
|
||||||
<div className="card-body row">
|
<div className="card-body row">
|
||||||
<div className="col-3">
|
<div className="col-3">
|
||||||
@ -45,7 +45,7 @@ export default function UserStatistics(props) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-3">
|
<div className="col-lg-3 col-md-6 col-sm-6">
|
||||||
<div className="card activity-card">
|
<div className="card activity-card">
|
||||||
<div className="card-body row">
|
<div className="card-body row">
|
||||||
<div className="col-3">
|
<div className="col-3">
|
||||||
@ -58,7 +58,7 @@ export default function UserStatistics(props) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-3">
|
<div className="col-lg-3 col-md-6 col-sm-6">
|
||||||
<div className="card activity-card">
|
<div className="card activity-card">
|
||||||
<div className="card-body row">
|
<div className="card-body row">
|
||||||
<div className="col-3">
|
<div className="col-3">
|
||||||
|
@ -8,6 +8,7 @@ import { Link } from 'react-router-dom'
|
|||||||
import Message from '../Common/Message'
|
import Message from '../Common/Message'
|
||||||
import { deletePicture, uploadPicture } from '../../actions/user'
|
import { deletePicture, uploadPicture } from '../../actions/user'
|
||||||
import { apiUrl, getFileSize } from '../../utils'
|
import { apiUrl, getFileSize } from '../../utils'
|
||||||
|
import { history } from '../../index'
|
||||||
|
|
||||||
function ProfileDetail({
|
function ProfileDetail({
|
||||||
appConfig,
|
appConfig,
|
||||||
@ -135,6 +136,12 @@ function ProfileDetail({
|
|||||||
)}{' '}
|
)}{' '}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<input
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-secondary"
|
||||||
|
onClick={() => history.go(-1)}
|
||||||
|
value={t('common:Back')}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,13 +2,15 @@
|
|||||||
"Actions": "Actions",
|
"Actions": "Actions",
|
||||||
"Active": "Active",
|
"Active": "Active",
|
||||||
"activities exist": "activities exist",
|
"activities exist": "activities exist",
|
||||||
"Add": "Add",
|
"Add admin rights": "Add admin rights",
|
||||||
|
"Add/remove admin rigths, delete user account.": "Add/remove admin rigths, delete user account.",
|
||||||
"Administration": "Administration",
|
"Administration": "Administration",
|
||||||
"Application": "Application",
|
"Application": "Application",
|
||||||
"Application configuration": "Application configuration",
|
"Application configuration": "Application configuration",
|
||||||
"Back": "Back",
|
"Back": "Back",
|
||||||
"Disable": "Disable",
|
"Disable": "Disable",
|
||||||
"Enable": "Enable",
|
"Enable": "Enable",
|
||||||
|
"Enable/disable sports.": "Enable/disable sports.",
|
||||||
"FitTrackee administration": "FitTrackee administration",
|
"FitTrackee administration": "FitTrackee administration",
|
||||||
"id": "id",
|
"id": "id",
|
||||||
"Image": "Image",
|
"Image": "Image",
|
||||||
@ -19,8 +21,10 @@
|
|||||||
"Max. size of uploaded files (in Mb)": "Max. size of uploaded files (in Mb)",
|
"Max. size of uploaded files (in Mb)": "Max. size of uploaded files (in Mb)",
|
||||||
"Max. size of zip archive": "Max. size of zip archive",
|
"Max. size of zip archive": "Max. size of zip archive",
|
||||||
"Max. size of zip archive (in Mb)": "Max. size of zip archive (in Mb)",
|
"Max. size of zip archive (in Mb)": "Max. size of zip archive (in Mb)",
|
||||||
"Remove": "Remove",
|
"Remove admin rights": "Remove admin rights",
|
||||||
"Sports": "Sports",
|
"Sports": "Sports",
|
||||||
|
"Update application configuration (maximum number of registered users, maximum files size).": "Update application configuration (maximum number of registered users, maximum files size).",
|
||||||
|
"uploads": "uploads",
|
||||||
"user": "user",
|
"user": "user",
|
||||||
"Users": "Users",
|
"Users": "Users",
|
||||||
"users": "users"
|
"users": "users"
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"Actions": "Actions",
|
"Actions": "Actions",
|
||||||
"Active": "Active",
|
"Active": "Active",
|
||||||
"Add": "Ajouter",
|
"Add admin rights": "Ajouter des droits d'admin",
|
||||||
|
"Add/remove admin rigths, delete user account.": "Ajouter/retirer des droits d'adminsitration, supprimer des comptes utilisateurs.",
|
||||||
"Administration": "Administration",
|
"Administration": "Administration",
|
||||||
"activities exist": "des activités existent",
|
"activities exist": "des activités existent",
|
||||||
"Application": "Application",
|
"Application": "Application",
|
||||||
@ -9,6 +10,7 @@
|
|||||||
"Back": "Retour",
|
"Back": "Retour",
|
||||||
"Disable": "désactiver",
|
"Disable": "désactiver",
|
||||||
"Enable": "activer",
|
"Enable": "activer",
|
||||||
|
"Enable/disable sports.": "Activer/désactiver des sports.",
|
||||||
"FitTrackee administration": "Administration de FitTrackee",
|
"FitTrackee administration": "Administration de FitTrackee",
|
||||||
"id": "id",
|
"id": "id",
|
||||||
"Image": "Image",
|
"Image": "Image",
|
||||||
@ -19,8 +21,10 @@
|
|||||||
"Max. size of uploaded files (in Mb)": "Taille max. des fichiers (en Mo)",
|
"Max. size of uploaded files (in Mb)": "Taille max. des fichiers (en Mo)",
|
||||||
"Max. size of zip archive": "Taille max. des archives zip",
|
"Max. size of zip archive": "Taille max. des archives zip",
|
||||||
"Max. size of zip archive (in Mb)": "Taille max. des archives zip (en Mo)",
|
"Max. size of zip archive (in Mb)": "Taille max. des archives zip (en Mo)",
|
||||||
"Remove": "Retirer",
|
"Remove admin rights": "Retirer des droits d'admin",
|
||||||
"Sports": "Sports",
|
"Sports": "Sports",
|
||||||
|
"Update application configuration (maximum number of registered users, maximum files size).": "Configurer l'application (nombre maximum d'utilisateurs inscrits, taille maximale des fichers).",
|
||||||
|
"uploads": "fichiers",
|
||||||
"user": "user",
|
"user": "user",
|
||||||
"Users": "Utilisateurs",
|
"Users": "Utilisateurs",
|
||||||
"users": "utilisateurs"
|
"users": "utilisateurs"
|
||||||
|
@ -2,12 +2,14 @@ import { format, parse } from 'date-fns'
|
|||||||
import { DateTime } from 'luxon'
|
import { DateTime } from 'luxon'
|
||||||
|
|
||||||
const suffixes = ['bytes', 'KB', 'MB', 'GB', 'TB']
|
const suffixes = ['bytes', 'KB', 'MB', 'GB', 'TB']
|
||||||
export const getFileSize = fileSize => {
|
export const getFileSize = (fileSize, asText = true) => {
|
||||||
const i = Math.floor(Math.log(fileSize) / Math.log(1024))
|
const i = Math.floor(Math.log(fileSize) / Math.log(1024))
|
||||||
return (
|
if (!fileSize) {
|
||||||
(!fileSize && '0 bytes') ||
|
return asText ? '0 bytes' : { size: 0, suffix: 'bytes' }
|
||||||
`${(fileSize / Math.pow(1024, i)).toFixed(1)}${suffixes[i]}`
|
}
|
||||||
)
|
const size = (fileSize / Math.pow(1024, i)).toFixed(1)
|
||||||
|
const suffix = suffixes[i]
|
||||||
|
return asText ? `${size}${suffix}` : { size, suffix }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getFileSizeInMB = fileSize => {
|
export const getFileSizeInMB = fileSize => {
|
||||||
|
Loading…
Reference in New Issue
Block a user