Add a special page showing the latest laser usage
This commit is contained in:
parent
8d8a399f33
commit
680039fa51
|
@ -16,10 +16,15 @@ import datetime, time
|
||||||
from . import models, fields, utils, utils_ldap, utils_auth, utils_stats
|
from . import models, fields, utils, utils_ldap, utils_auth, utils_stats
|
||||||
from .. import settings, secrets
|
from .. import settings, secrets
|
||||||
|
|
||||||
#class UsageSerializer(serializers.ModelSerializer):
|
class UsageSerializer(serializers.ModelSerializer):
|
||||||
# class Meta:
|
first_name = serializers.SerializerMethodField()
|
||||||
# model = models.UsageTrack
|
|
||||||
# fields = '__all__'
|
class Meta:
|
||||||
|
model = models.Usage
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
def get_first_name(self, obj):
|
||||||
|
return obj.user.member.preferred_name
|
||||||
|
|
||||||
class TransactionSerializer(serializers.ModelSerializer):
|
class TransactionSerializer(serializers.ModelSerializer):
|
||||||
# fields directly from old portal. replace with slugs we want
|
# fields directly from old portal. replace with slugs we want
|
||||||
|
|
|
@ -683,6 +683,33 @@ class StatsViewSet(viewsets.ViewSet, List):
|
||||||
|
|
||||||
return Response(200)
|
return Response(200)
|
||||||
|
|
||||||
|
@action(detail=False, methods=['get'])
|
||||||
|
def usage_data(self, request):
|
||||||
|
if 'device' not in request.query_params:
|
||||||
|
raise exceptions.ValidationError(dict(device='This field is required.'))
|
||||||
|
|
||||||
|
if not utils.is_request_from_protospace(request):
|
||||||
|
raise exceptions.PermissionDenied()
|
||||||
|
|
||||||
|
device = request.query_params['device']
|
||||||
|
last_session = models.Usage.objects.filter(device=device).last()
|
||||||
|
|
||||||
|
if not last_session:
|
||||||
|
raise exceptions.ValidationError(dict(device='Session not found.'))
|
||||||
|
|
||||||
|
serializer = serializers.UsageSerializer(last_session)
|
||||||
|
|
||||||
|
try:
|
||||||
|
track = cache.get('track', {})[device]
|
||||||
|
except KeyError:
|
||||||
|
track = False
|
||||||
|
|
||||||
|
return Response(dict(
|
||||||
|
track=track,
|
||||||
|
session=serializer.data
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
@action(detail=False, methods=['post'])
|
@action(detail=False, methods=['post'])
|
||||||
def autoscan(self, request):
|
def autoscan(self, request):
|
||||||
if 'autoscan' not in request.data:
|
if 'autoscan' not in request.data:
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { Courses, CourseDetail } from './Courses.js';
|
||||||
import { ClassFeed, Classes, ClassDetail } from './Classes.js';
|
import { ClassFeed, Classes, ClassDetail } from './Classes.js';
|
||||||
import { Members, MemberDetail } from './Members.js';
|
import { Members, MemberDetail } from './Members.js';
|
||||||
import { Charts } from './Charts.js';
|
import { Charts } from './Charts.js';
|
||||||
|
import { Usage } from './Usage.js';
|
||||||
import { Auth } from './Auth.js';
|
import { Auth } from './Auth.js';
|
||||||
import { Subscribe } from './PayPal.js';
|
import { Subscribe } from './PayPal.js';
|
||||||
import { PasswordReset, ConfirmReset } from './PasswordReset.js';
|
import { PasswordReset, ConfirmReset } from './PasswordReset.js';
|
||||||
|
@ -117,6 +118,10 @@ function App() {
|
||||||
<ClassFeed />
|
<ClassFeed />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
|
<Route exact path='/usage/:name'>
|
||||||
|
<Usage />
|
||||||
|
</Route>
|
||||||
|
|
||||||
<Route path='/'>
|
<Route path='/'>
|
||||||
<Container>
|
<Container>
|
||||||
<div className='hero'>
|
<div className='hero'>
|
||||||
|
|
92
webclient/src/Usage.js
Normal file
92
webclient/src/Usage.js
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
import React, { useRef, useState, useEffect, useReducer } from 'react';
|
||||||
|
import { BrowserRouter as Router, Switch, Route, Link, useParams, useLocation } from 'react-router-dom';
|
||||||
|
import moment from 'moment-timezone';
|
||||||
|
import QRCode from 'react-qr-code';
|
||||||
|
import { Button, Container, Divider, Dropdown, Form, Grid, Header, Icon, Image, Menu, Message, Popup, Segment, Table } from 'semantic-ui-react';
|
||||||
|
import { statusColor, BasicTable, siteUrl, staticUrl, requester, isAdmin } from './utils.js';
|
||||||
|
|
||||||
|
const deviceNames = {
|
||||||
|
'trotec': {title: 'Trotec', device: 'TROTECS300'},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Usage(props) {
|
||||||
|
const { name } = useParams();
|
||||||
|
const title = deviceNames[name].title;
|
||||||
|
const device = deviceNames[name].device;
|
||||||
|
const [usage, setUsage] = useState(false);
|
||||||
|
const [fullElement, setFullElement] = useState(false);
|
||||||
|
const ref = useRef(null);
|
||||||
|
|
||||||
|
const getUsage = () => {
|
||||||
|
requester('/stats/usage_data/?device='+device, 'GET', '')
|
||||||
|
.then(res => {
|
||||||
|
setUsage(res);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
setUsage(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getUsage();
|
||||||
|
const interval = setInterval(getUsage, 60000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const goFullScreen = () => {
|
||||||
|
if ('wakeLock' in navigator) {
|
||||||
|
navigator.wakeLock.request('screen');
|
||||||
|
}
|
||||||
|
|
||||||
|
ref.current.requestFullscreen({ navigationUI: 'hide' }).then(() => {
|
||||||
|
setFullElement(true);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const inUse = usage && moment().unix() - usage.track.time < 300;
|
||||||
|
const showUsage = usage && inUse && usage.track.username === usage.session.username;
|
||||||
|
|
||||||
|
const now = moment();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<div className='usage' ref={ref}>
|
||||||
|
|
||||||
|
{!fullElement &&
|
||||||
|
<p>
|
||||||
|
<Button onClick={goFullScreen}>Fullscreen</Button>
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
{showUsage ?
|
||||||
|
<>
|
||||||
|
<Header size='medium'>Hello,</Header>
|
||||||
|
|
||||||
|
<p className='stat'>
|
||||||
|
{usage.session.first_name}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<Header size='medium'>Session Time</Header>
|
||||||
|
|
||||||
|
<p className='stat'>
|
||||||
|
{parseInt(moment.duration(moment(now).diff(usage.session.start_time)).asMinutes())} mins
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<Header size='medium'>Laser Time</Header>
|
||||||
|
|
||||||
|
<p className='stat'>
|
||||||
|
{parseInt(usage.session.num_seconds / 60)} mins
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
:
|
||||||
|
<>
|
||||||
|
<Header size='large'>{title} Usage</Header>
|
||||||
|
<p>Waiting for session</p>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
|
@ -133,6 +133,24 @@ body {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.usage {
|
||||||
|
height: 100vh;
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
padding: 0.5em;
|
||||||
|
font-size: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usage .ui.header {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usage .stat {
|
||||||
|
font-size: 2em;
|
||||||
|
margin-bottom: 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
margin-top: -20rem;
|
margin-top: -20rem;
|
||||||
background: black;
|
background: black;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user