diff --git a/apiserver/apiserver/api/serializers.py b/apiserver/apiserver/api/serializers.py index 7ae197c..31793bc 100644 --- a/apiserver/apiserver/api/serializers.py +++ b/apiserver/apiserver/api/serializers.py @@ -16,10 +16,15 @@ import datetime, time from . import models, fields, utils, utils_ldap, utils_auth, utils_stats from .. import settings, secrets -#class UsageSerializer(serializers.ModelSerializer): -# class Meta: -# model = models.UsageTrack -# fields = '__all__' +class UsageSerializer(serializers.ModelSerializer): + first_name = serializers.SerializerMethodField() + + class Meta: + model = models.Usage + fields = '__all__' + + def get_first_name(self, obj): + return obj.user.member.preferred_name class TransactionSerializer(serializers.ModelSerializer): # fields directly from old portal. replace with slugs we want diff --git a/apiserver/apiserver/api/views.py b/apiserver/apiserver/api/views.py index af31848..153cf07 100644 --- a/apiserver/apiserver/api/views.py +++ b/apiserver/apiserver/api/views.py @@ -683,6 +683,33 @@ class StatsViewSet(viewsets.ViewSet, List): 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']) def autoscan(self, request): if 'autoscan' not in request.data: diff --git a/webclient/src/App.js b/webclient/src/App.js index cf1fe86..ec4c681 100644 --- a/webclient/src/App.js +++ b/webclient/src/App.js @@ -21,6 +21,7 @@ import { Courses, CourseDetail } from './Courses.js'; import { ClassFeed, Classes, ClassDetail } from './Classes.js'; import { Members, MemberDetail } from './Members.js'; import { Charts } from './Charts.js'; +import { Usage } from './Usage.js'; import { Auth } from './Auth.js'; import { Subscribe } from './PayPal.js'; import { PasswordReset, ConfirmReset } from './PasswordReset.js'; @@ -117,6 +118,10 @@ function App() { + + + +
diff --git a/webclient/src/Usage.js b/webclient/src/Usage.js new file mode 100644 index 0000000..bbb1cea --- /dev/null +++ b/webclient/src/Usage.js @@ -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 ( + +
+ + {!fullElement && +

+ +

+ } + + {showUsage ? + <> +
Hello,
+ +

+ {usage.session.first_name} +

+ +
Session Time
+ +

+ {parseInt(moment.duration(moment(now).diff(usage.session.start_time)).asMinutes())} mins +

+ +
Laser Time
+ +

+ {parseInt(usage.session.num_seconds / 60)} mins +

+ + : + <> +
{title} Usage
+

Waiting for session

+ + } + +
+
+ ); +}; diff --git a/webclient/src/light.css b/webclient/src/light.css index fd7be49..84fb504 100644 --- a/webclient/src/light.css +++ b/webclient/src/light.css @@ -133,6 +133,24 @@ body { 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 { margin-top: -20rem; background: black;