This commit is contained in:
QTech Servers 2024-10-03 22:05:46 -04:00
commit 67bc482155
14 changed files with 695 additions and 0 deletions

191
.gitignore vendored Normal file
View file

@ -0,0 +1,191 @@
# Created by https://www.toptal.com/developers/gitignore/api/vim,flask
# Edit at https://www.toptal.com/developers/gitignore?templates=vim,flask
### Flask ###
instance/*
!instance/.gitignore
.webassets-cache
.env
### Flask.Python Stack ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
### Vim ###
# Swap
[._]*.s[a-v][a-z]
!*.svg # comment out if you don't need vector files
[._]*.sw[a-p]
[._]s[a-rt-v][a-z]
[._]ss[a-gi-z]
[._]sw[a-p]
# Session
Session.vim
Sessionx.vim
# Temporary
.netrwhist
*~
# Auto-generated tag files
tags
# Persistent undo
[._]*.un~
# End of https://www.toptal.com/developers/gitignore/api/vim,flask

15
Dockerfile Normal file
View file

@ -0,0 +1,15 @@
FROM alpine:latest
RUN apk add python3 py3-pip
WORKDIR /app
ADD requirements.txt .
RUN python3 -m pip install -r requirements.txt
COPY . /app
ENTRYPOINT [ "python3" ]
CMD [ "main.py" ]

24
app/__init__.py Normal file
View file

@ -0,0 +1,24 @@
from flask import Flask
# from flask_login import LoginManager
from flask_sqlalchemy import SQLAlchemy
UPLOAD_FOLDER = 'app/static/uploads/'
app = Flask(__name__)
app.secret_key = 'secret key'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////db/db.sqlite3'
# app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://user:password@sql/ece3553'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# login_manager = LoginManager(app)
db = SQLAlchemy(app)
from app import views
from app import admin_views
from app import tables
with app.app_context():
db.create_all()

9
app/admin_views.py Normal file
View file

@ -0,0 +1,9 @@
from functools import wraps
from app import app, db
from app.tables import Swipe, User
from flask import request, redirect, url_for, render_template, session, abort
from sqlalchemy import desc
# from flask_login import login_user, login_required, logout_user, current_user

70
app/static/css/main.css Normal file
View file

@ -0,0 +1,70 @@
body {
background-color: #242424;
color: #fff;
}
header {
}
nav {
display: inline-block;
background-color: #141414;
width: 100%;
padding: 5px;
}
nav ul {
list-style-type: none;
padding: 0;
margin: 0;
}
nav ul li {
float: left;
margin: 0px 5px;
}
nav ul li a {
display: block;
color: white;
background-color: #888;
text-decoration: none;
padding: 15px 24px;
}
nav ul li a:hover {
background-color: #005c75;
}
.nav-cur {
background-color: #008cff;
transform: translate(0px, 10px);
}
.nav-cur:hover {
background-color: #00bcff;
}
.content {
display: block;
min-height: 200px;
position: relative;
}
.center {
position: absolute;
left: 50%;
transform: translate(-50%);
}
.hcenter {
text-align: center;
}
.hvcenter {
margin: 0;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}

22
app/static/js/kiosk.js Normal file
View file

@ -0,0 +1,22 @@
function update() {
fetch('https://time.qtechofficial.com/kiosk/data')
.then(response => response.json())
.then(data => {
let tbody = document.getElementById('tbody');
tbody.innerHTML = "";
console.log(data);
data.forEach(person => {
let tr = document.createElement('tr');
tbody.appendChild(tr);
let td_name = document.createElement('td');
td_name.innerHTML = person.first_name + " " + person.last_name;
tr.appendChild(td_name);
let td_time_here = document.createElement('td');
td_time_here.textContent = person.time_here;
tr.appendChild(td_time_here);
})
})
.catch(error => console.error(error))
}
setInterval(update, 1000);

21
app/tables.py Normal file
View file

@ -0,0 +1,21 @@
from datetime import datetime
from app import db
class Swipe(db.Model):
__tablename__ = 'swipes'
id = db.Column('id', db.Integer, primary_key=True)
time = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
uuid = db.Column(db.String(100))
def __init__(self, uuid):
self.uuid = uuid
class User(db.Model):
__tablename__ = 'users'
id = db.Column('id', db.Integer, primary_key=True)
uuid = db.Column(db.String(100))
first_name = db.Column(db.String(100))
last_name = db.Column(db.String(100))
role = db.Column(db.String(100))

View file

@ -0,0 +1,46 @@
<?xml version = "1.0"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/main.css') }}" />
<title>Go Fuck Yourself</title>
</head>
<body>
<div class="main_page">
<header>
<hgroup>
<div align="center">
<h1>Go Fuck Yourself</h1>
<h2>Consider Eating A Dick</h2>
</div>
</hgroup>
</header>
<nav>
<ul>
<li><a href="{{ '' }}">Home</a></li>
<li><a href="{{ '' }}">Launch Planner</a></li>
<li><a href="{{ '' }}">Silos</a></li>
<li><a href="{{ '' }}">Notes</a></li>
<li><a href="{{ '' }}">Blueprints n Shit</a></li>
<li><a href="{{ '' }}">C&C</a></li>
<li><a href="{{ '' }}">About</a></li>
<li style="float: right;"><a href="{{ '' }}">Logout</a></li>
</ul>
</nav>
<div class="content">
<p class="hcenter">
<h3>Why you should go fuck yourself</h3>
</p>
<p class="hcenter">
<p>hello and welcome to my presentation to why you should go fuck yourself.</p>
<p>Spoiler Alert, it's because you are a piece of shit that no one likes. But why are you shit? were you raised this way or, unfortunate circumstances to blame?, No, your just terrible. Still confused? of course you are, here's a pie chart that should straighten things up, here's a quick visual aid and i think this next slide really drives the point home, fuck you go fuck yourself.</p>
<p>Now this graph is, really interesting. As you can see it shows the number of people that want you to go fuck yourself over time. Of note, this isn't 99% percent of the population. This isn't even 99.999% of the population. This is the 100% of the population, that's right, even you want you to go fuck yourself so what are you doing here? Go on go do it now, You may be asking, does all this hatred represent a weakness on my own part?</p>
<p>Could it be that nobody deserves to hear the words "go fuck yourself" because every single human being on the planet is a worthwhile and a valuable member of society? No!, go fuck yourself.</p>
<p>In conclusion, i want you to know that i don't really mean anything i'm saying here. I think you're a good person and ultimately i don't want you to go fuck yourself. Just kidding haha, go fuck yourself.</p>
<p>Thank you.</p>
</p>
</div>
</div>
</body>
</html>

16
app/templates/kiosk.html Normal file
View file

@ -0,0 +1,16 @@
<?xml version = "1.0"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/main.css') }}" />
<script type="text/javascript" src="{{ url_for('static', filename='js/kiosk.js') }}"></script>
<title>Kiosk</title>
</head>
<body>
<table>
<tbody id="tbody">
</tbody>
</table>
</body>
</html>

29
app/templates/users.html Normal file
View file

@ -0,0 +1,29 @@
<?xml version = "1.0"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/main.css') }}" />
<title>Users</title>
</head>
<body>
<form action="/users/modify" method="POST">
{% for user in users %}
<input type="radio" name="uuid" value="{{user.uuid}}">
{{ user.id }}, {{ user.uuid }}, {{ user.first_name | safe }}, {{ user.last_name | safe }}, {{ user.role }}
</input> <br>
{% endfor %}
<input type="text", name="first_name", placeholder="First Name" />
<input type="text", name="last_name", placeholder="Last Name" /><br>
<input type="text", name="new_uuid", placeholder="0000000000" /><br>
<select name='role'>
<option value='Student'>Student</option>
<option value='Mentor'>Mentor</option>
</select><br>
<button type="submit", name="action", value="set_name">Change Name</button>
<button type="submit", name="action", value="set_uid">Change ID</button>
<button type="submit", name="action", value="new_user">New User</button>
<button type="submit", name="action", value="delete">Delete</button><br>
</form>
</body>
</html>

240
app/views.py Normal file
View file

@ -0,0 +1,240 @@
from app import app, db
from app.tables import Swipe, User
from flask import request, redirect, url_for, render_template
from sqlalchemy import desc
from pytz import timezone
from datetime import datetime
from datetime import date
import json
# from flask_login import login_user, login_required, logout_user
@app.route('/')
def index():
return 'No.'
'''
@app.route('/auth', methods=['POST'])
def auth():
data = request.get_json(force=True)
print(data)
if 'uuid' in data:
print(data['uuid'])
uuid = AllowedUUIDs.query.filter_by(uuid=data['uuid']).first()
print(uuid)
if uuid:
print('login')
login_user(uuid)
return ''
'''
@app.route('/users', methods=['GET'])
def users():
users = db.session.query(User)
return render_template('users.html', users=users)
@app.route('/users/modify', methods=['POST'])
def users_modify():
data = request.form
if data['action'] == 'set_name':
if data['first_name'] == '' or data['last_name'] == '':
return 'Fill out the form...'
if data['uuid'] == '0000005222':
return 'No.'
user = User.query.filter_by(uuid=data['uuid']).first()
user.first_name = data['first_name']
user.last_name = data['last_name']
db.session.commit()
elif data['action'] == 'set_uid':
if data['uuid'] == '':
return 'Fill out the form...'
if data['uuid'] == '0000005222':
return 'No.'
user = User.query.filter_by(uuid=data['uuid']).first()
user.uuid = data['new_uuid']
db.session.commit()
elif data['action'] == 'delete':
if data['uuid'] == '0000005222':
return 'No.'
user = User.query.filter_by(uuid=data['uuid']).first()
db.session.delete(user)
db.session.commit()
elif data['action'] == 'new_user':
if data['new_uuid'] == '0000005222':
return 'No.'
user = User()
user.uuid = data['new_uuid']
user.first_name = data['first_name']
user.last_name = data['last_name']
user.role = data['role']
db.session.add(user)
db.session.commit()
return redirect(url_for('users'), code=302)
@app.route('/swipe', methods=['POST'])
def swipe():
data = request.get_json(force=True)
uuid = data.get('uuid')
if not uuid:
return 'NACK'
print(uuid)
swipe = Swipe(uuid)
db.session.add(swipe)
db.session.commit()
return 'OK'
@app.route('/log', methods=['GET'])
def log():
swipes = db.session.query(Swipe)
result = ''
for swipe in swipes:
names = User.query.filter_by(uuid=swipe.uuid).first()
if names:
result += f'{swipe.time} | {names.first_name} | {names.last_name}<br>'
else:
result += f'{swipe.time} | WHO DIS {swipe.uuid}<br>'
return result
@app.route('/kiosk', methods=['GET'])
def kiosk():
return render_template('kiosk.html')
@app.route('/kiosk/data', methods=['GET'])
def kiosk_data():
swipes = db.session.query(Swipe).filter(Swipe.time >= '2023-07-16')
counts = {}
last_swipes = {}
for swipe in swipes:
if swipe.uuid not in counts:
counts[swipe.uuid] = 0
counts[swipe.uuid] += 1
last_swipes[swipe.uuid] = swipe
res = []
for uuid, count in counts.items():
# User already signed out
if count % 2 == 0:
continue
swipe = last_swipes[uuid]
dt = swipe.time
tz = timezone('EST')
est = dt.astimezone(tz)
user = db.session.query(User).filter(User.uuid == swipe.uuid).first()
time_here = datetime.now() - dt
time_here_str = f'{time_here.seconds//3600}:{(time_here.seconds//60)%60:02d}'
rec = {
'first_name': user.first_name,
'last_name': user.last_name,
'time_here': time_here_str
}
res.append(rec)
return json.dumps(res)
@app.route('/report', methods=['GET', 'POST'])
def report():
after_date = '2024-08-22'
if request.method == 'POST':
after_date = request.form['after_date']
days = {}
swipes = db.session.query(Swipe).filter(Swipe.time >= after_date)
for swipe in swipes:
dt = swipe.time
tz = timezone('EST')
est = dt.astimezone(tz)
date = est.date()
if date not in days:
days[date] = {}
name_dict = days[date]
if swipe.uuid not in name_dict:
name_dict[swipe.uuid] = []
name_list = name_dict[swipe.uuid]
name_list.append(swipe.time)
result = f'<form action="report" method="POST"><input type="date" name="after_date" value="{after_date}" /><input type="submit" /></form>'
result += '<table border=1 style="table-layout:fixed;">'
name_query = db.session.query(User)
names = []
result += '<thead><tr><th></th>'
for name_obj in name_query:
name = f'{name_obj.first_name} {name_obj.last_name}'
names.append(name)
result += f'<th style="width: 100px; position: sticky; top: 0; background: white;">{name}</th>'
result += '</tr></thead>'
totals = {}
last_weekday = 0
for date, name_dict in days.items():
day = date.isoformat()
column = {}
for uuid, times in name_dict.items():
names_query = User.query.filter_by(uuid=uuid).first()
if names_query:
name = f'{names_query.first_name} {names_query.last_name}'
else:
name = f'Unknown: {uuid}'
# result += f'{swipe.time} | WHO DIS {swipe.uuid}<br>'
# result += f'{day} {uuid}: {times}<br>'
if len(times) % 2 == 1:
# result += f'{day} | {name} uneven scans<br>'
column[name] = 'X'
continue
total_hours = 0.0
for i in range(len(times) // 2):
in_ = times[i * 2 + 0]
out = times[i * 2 + 1]
dt = out - in_
total_hours += dt.seconds / 60 / 60
if name not in totals:
totals[name] = 0.0
totals[name] += total_hours
# result += f'{day} | {name}: {total_hours:0.2f}<br>'
column[name] = f'{total_hours:0.2f}'
if date.weekday() < last_weekday:
result += f'<tr><td colspan="{len(names)+1}">'
result += '<hr style="margin: 0; border: none; background: black; height: 10px" />'
result += '</td></tr>'
last_weekday = date.weekday()
result += f'<tr><td style="white-space: nowrap;">{day}</td>'
for name in names:
if name in column:
result += f'<td>{column[name]}</td>'
else:
result += '<td></td>'
result += '</tr>'
result += '<tr><td>Totals</td>'
for name_obj in name_query:
name = f'{name_obj.first_name} {name_obj.last_name}'
if name in totals:
total = totals[name]
else:
total = 0.0
result += f'<td>{total:0.2f}</td>'
result += '</tr>'
result += '</table>'
return result

2
env Normal file
View file

@ -0,0 +1,2 @@
export FLASK_APP=main.py
export FLASK_ENV=development

5
main.py Normal file
View file

@ -0,0 +1,5 @@
from app import app
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)

5
requirements.txt Normal file
View file

@ -0,0 +1,5 @@
Flask
flask-sqlalchemy
flask-login
pymysql
pytz