36 Commits
1.0.0 ... 1.0.1

Author SHA1 Message Date
c2aa010812 Merge pull request '1.0.1 release' (#15) from testing into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #15
2023-03-21 18:10:14 +00:00
79592c39fb Merge pull request 'fix worker app' (#14) from worker-context-bugfix into testing
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #14
2023-03-21 00:25:52 +00:00
c2ba1ca5e7 fix worker app 2023-03-20 20:00:40 -04:00
3830bdf240 Merge pull request 'adjust worker for mysql, add more stable background process' (#9) from stable-cron into testing
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #9
2023-03-20 02:34:56 +00:00
c502f359e7 adjust worker for mysql, add more stable background process 2023-03-19 22:34:00 -04:00
a1bfc827d4 Merge pull request 'mysql-migration' (#8) from mysql-migration into testing
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #8
2023-03-17 04:58:27 +00:00
ccdc9940df update dockerfile and docker-compose for mysql 2023-03-17 00:56:12 -04:00
a4a3193e05 change sqlite config to mysql 2023-03-17 00:38:09 -04:00
d970954c1c adjust init_db for mysql 2023-03-17 00:37:40 -04:00
0e8565dd2a delete alembic versions from sqlite 2023-03-17 00:27:18 -04:00
80a9266431 update requirements to include mysqlclient 2023-03-17 00:24:19 -04:00
d5d18014a7 add mariadb to docker-compose file 2023-03-17 00:23:08 -04:00
aa87962b24 modify gitignore for local db directory 2023-03-16 23:25:15 -04:00
b3de7df4fc manifest url typo fix
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2022-12-01 23:06:14 -05:00
cab022e2cb Remove input requirement for task description
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-01 22:53:00 -05:00
da225224b0 adjust jinja2 templates to handle cases of no description 2022-12-01 22:51:34 -05:00
2b315caf9e favicon support
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-01 01:16:10 -05:00
f15d145144 add prevDay variable for previous day, primarily for queries 2022-12-01 00:54:41 -05:00
a441b4612a implement gunicorn for prod WSGI server
All checks were successful
continuous-integration/drone/push Build is passing
2022-11-30 01:09:22 -05:00
645fc4a56b docker-compose file update 2 electric boogalo 2022-11-30 00:46:17 -05:00
71fa0bec22 change docker-compose for new env variables 2022-11-29 22:51:13 -05:00
727941edd3 Error handling and custom error pages
All checks were successful
continuous-integration/drone/push Build is passing
2022-11-29 22:23:01 -05:00
ddc90da012 Add "remember me" option to login
All checks were successful
continuous-integration/drone/push Build is passing
2022-11-29 20:55:32 -05:00
dfa8eed12d remove hardcoded secret key, rely on env variable
All checks were successful
continuous-integration/drone/push Build is passing
2022-11-29 20:41:56 -05:00
bf539fca09 create separate worker app for background tasks
Some checks reported errors
continuous-integration/drone/push Build encountered an error
continuous-integration/drone Build is passing
2022-11-29 18:12:55 -05:00
130825d3dc re-add createEvents script to newPeriod function
All checks were successful
continuous-integration/drone/push Build is passing
2022-11-26 11:57:08 -05:00
1a1d530b47 fix formatting issue on tasks html template
All checks were successful
continuous-integration/drone/push Build is passing
2022-11-25 20:50:24 -05:00
bbd44e9ec6 add scheduler to generate events, remove hacky workaround 2022-11-25 20:48:16 -05:00
04c8dfb5b4 update docker-compose file to reflect port change
All checks were successful
continuous-integration/drone/push Build is passing
2022-11-25 15:18:33 -05:00
56f7f907df modify drone to brand commits before docker build
All checks were successful
continuous-integration/drone/push Build is passing
2022-11-25 15:12:34 -05:00
ac8bed1bad add footer and support showing version/commit 2022-11-25 15:01:13 -05:00
00c4bc960e prevent app from crashing when SIGNUP_ENABLED env variable does not exist
All checks were successful
continuous-integration/drone/push Build is passing
2022-11-24 19:06:42 -05:00
cc57dfdb32 change flask to use port 80
All checks were successful
continuous-integration/drone/push Build is passing
2022-11-24 18:07:36 -05:00
ec31206127 Add settings page, fix responsive login/logout link
All checks were successful
continuous-integration/drone/push Build is passing
2022-11-24 13:36:37 -05:00
8ed6336e20 create settings page, full timezone support
All checks were successful
continuous-integration/drone/push Build is passing
2022-11-23 20:36:38 -05:00
5c26838c50 begin timezone support 2022-11-22 05:25:11 -05:00
32 changed files with 335 additions and 96 deletions

View File

@@ -9,6 +9,10 @@ globals:
password: password:
from_secret: REGISTRY_PASSWORD from_secret: REGISTRY_PASSWORD
steps: steps:
- name: brand commit before build
image: bash
commands:
- echo ${DRONE_COMMIT_SHA:0:7} > app/__commit__
- name: build app-testing - name: build app-testing
image: plugins/docker image: plugins/docker
settings: settings:
@@ -35,6 +39,10 @@ globals:
password: password:
from_secret: REGISTRY_PASSWORD from_secret: REGISTRY_PASSWORD
steps: steps:
- name: brand commit before build
image: bash
commands:
- echo ${DRONE_COMMIT_SHA:0:7} > app/__commit__
- name: build-app-prod - name: build-app-prod
image: plugins/docker image: plugins/docker
settings: settings:
@@ -43,7 +51,6 @@ steps:
dockerfile: ./Dockerfile dockerfile: ./Dockerfile
tags: ["${DRONE_COMMIT_SHA:0:7}", "latest-prod"] tags: ["${DRONE_COMMIT_SHA:0:7}", "latest-prod"]
<<: *docker_creds <<: *docker_creds
trigger: trigger:
branch: branch:
- master - master

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
### Database file ### Database file
database.db database.db
database_mysql/
### Python template ### Python template
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files

View File

@@ -1,6 +1,7 @@
FROM python:3.11.0-alpine3.16 FROM python:3.11.0-alpine3.16
COPY ./app /app COPY ./app /app
WORKDIR /app WORKDIR /app
RUN apk add gcc musl-dev mariadb-connector-c-dev
RUN pip3 install -r requirements.txt RUN pip3 install -r requirements.txt
ENV FLASK_APP=app.py ENV FLASK_APP=app.py
CMD ["python3", "-m", "flask", "run", "--host=0.0.0.0"] CMD ["gunicorn", "-b", "0.0.0.0:80", "-w", "4", "app:app"]

1
app/__commit__ Normal file
View File

@@ -0,0 +1 @@
unknown

1
app/__version__ Normal file
View File

@@ -0,0 +1 @@
1.0.1

View File

@@ -3,17 +3,21 @@ from flask import Flask, render_template, redirect, url_for, request, flash
from flask_migrate import Migrate from flask_migrate import Migrate
from werkzeug.security import generate_password_hash, check_password_hash from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import (LoginManager, login_user, login_required, logout_user, current_user) from flask_login import (LoginManager, login_user, login_required, logout_user, current_user)
from misc import datetime, date, time, currDay from misc import datetime, date, time, currDay, prevDay, ZoneInfo, currVersion, currCommit
from db import (db, Period, Task, Event, User) from db import (db, Period, Task, Event, User)
from forms import (TaskForm, EventForm, PeriodForm, SignupForm, LoginForm) from forms import (TaskForm, EventForm, PeriodForm, SignupForm, LoginForm, SettingsForm)
from create_events import createEvents from create_events import createEvents
basedir = os.path.abspath(os.path.dirname(__file__)) basedir = os.path.abspath(os.path.dirname(__file__))
app = Flask(__name__) app = Flask(__name__)
app.config['SECRET_KEY'] = 'HwG55rpe83jcaglifXm8NuF4WEeXyJV4' app.config['SECRET_KEY'] = os.environ['SECRET_KEY']
app.config['SQLALCHEMY_DATABASE_URI'] =\ app.config['SQLALCHEMY_DATABASE_URI'] =\
'sqlite:///' + os.path.join(basedir, os.environ['SQLITE_DB']) 'mysql://' + os.environ['MYSQL_USER'] + \
':' + os.environ['MYSQL_PASSWORD'] + \
'@' + os.environ['MYSQL_HOST'] + \
':' + os.environ['MYSQL_PORT'] + \
'/' + os.environ['MYSQL_DB']
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app) db.init_app(app)
@@ -21,14 +25,21 @@ db.init_app(app)
migrate = Migrate() migrate = Migrate()
migrate.init_app(app, db) migrate.init_app(app, db)
# Schedule creation of events every hour
#with app.app_context():
# scheduleCreateEvents(app, db, currDay, Period, Event, createEvents)
# Authentication stuff # Authentication stuff
login_manager = LoginManager() login_manager = LoginManager()
login_manager.login_view = 'login' login_manager.login_view = 'login'
login_manager.init_app(app) login_manager.init_app(app)
if os.environ['SIGNUP_ENABLED'] == "YES": if 'SIGNUP_ENABLED' in os.environ:
signup_enabled = True if os.environ['SIGNUP_ENABLED'] == "YES":
signup_enabled = True
else:
signup_enabled = False
else: else:
signup_enabled = False signup_enabled = False
@@ -36,10 +47,32 @@ else:
def load_user(user_id): def load_user(user_id):
return User.query.get(int(user_id)) return User.query.get(int(user_id))
# Context processor injects current version and commit
@app.context_processor
def injectVerCommit():
return dict(currVersion=currVersion, currCommit=currCommit, datetime=datetime, ZoneInfo=ZoneInfo)
# Error handling
# Pass env variables for debugging in prod
NODE_NAME = os.environ['NODE_NAME']
POD_NAME = os.environ['POD_NAME']
# Error Routes
@app.errorhandler(404)
def notFound(e):
return render_template('errors/404.html', NODE_NAME=NODE_NAME, POD_NAME=POD_NAME), 404
@app.errorhandler(403)
def forbidden(e):
return render_template('errors/403.html', NODE_NAME=NODE_NAME, POD_NAME=POD_NAME), 403
@app.errorhandler(500)
def ISEerror(e):
return render_template('errors/500.html', NODE_NAME=NODE_NAME, POD_NAME=POD_NAME), 500
# Index route # Index route
@app.route('/') @app.route('/')
def index(): def index():
createEvents(db, currDay, Period, Event)
return redirect('/events') return redirect('/events')
# Authentication routes # Authentication routes
@@ -49,12 +82,13 @@ def login():
if form.validate_on_submit(): if form.validate_on_submit():
userName = form.userName.data userName = form.userName.data
password = form.password.data password = form.password.data
remember = form.rememberMe.data
user = User.query.filter_by(userName=userName).first() user = User.query.filter_by(userName=userName).first()
if not user or not check_password_hash(user.password, password): if not user or not check_password_hash(user.password, password):
flash('Credentials incorrect! Please try again') flash('Credentials incorrect! Please try again')
return redirect(url_for('login')) return redirect(url_for('login'))
login_user(user) login_user(user, remember=remember)
return redirect(url_for('events')) return redirect(url_for('events'))
return render_template('login.html', form=form) return render_template('login.html', form=form)
@@ -82,6 +116,19 @@ def logout():
logout_user() logout_user()
return redirect(url_for('index')) return redirect(url_for('index'))
@app.route('/settings', methods=('GET', 'POST'))
@login_required
def settings():
user = User.query.get_or_404(current_user.id)
form = SettingsForm(obj=user)
if form.validate_on_submit():
user.realName = form.realName.data
user.timezone = form.timezone.data
if form.password.data != '':
user.password = generate_password_hash(form.password.data, method='sha256')
db.session.commit()
return render_template('settings.html', form=form)
# Periods routes # Periods routes
@app.route('/periods') @app.route('/periods')
@login_required @login_required
@@ -99,6 +146,7 @@ def newPeriod():
) )
db.session.add(period) db.session.add(period)
db.session.commit() db.session.commit()
# Run createEvents upon adding new period
createEvents(db, currDay, Period, Event) createEvents(db, currDay, Period, Event)
return redirect(f'/period/edit/{period.period}') return redirect(f'/period/edit/{period.period}')
return render_template('newPeriod.html', form=form) return render_template('newPeriod.html', form=form)
@@ -131,9 +179,8 @@ def delete_period(periodNum):
def events(): def events():
events = Event.query.all() events = Event.query.all()
periods = Period.query.all() periods = Period.query.all()
createEvents(db, currDay, Period, Event)
return render_template('events.html', events=events, periods=periods, datetime=datetime, date=date) return render_template('events.html', events=events, periods=periods, datetime=datetime, date=date, ZoneInfo=ZoneInfo)
@app.route('/event/edit/<int:event_id>/', methods=('GET', 'POST')) @app.route('/event/edit/<int:event_id>/', methods=('GET', 'POST'))
@login_required @login_required

View File

@@ -9,3 +9,5 @@ def createEvents(db, currDay, Period, Event):
) )
db.session.add(event) db.session.add(event)
db.session.commit() db.session.commit()
print("createEvents script ran successfully")

View File

@@ -43,3 +43,4 @@ class User(UserMixin, db.Model):
email = db.Column(db.String(100), unique=True) email = db.Column(db.String(100), unique=True)
password = db.Column(db.String(100)) password = db.Column(db.String(100))
realName = db.Column(db.String(1000)) realName = db.Column(db.String(1000))
timezone = db.Column(db.String(20), default='UTC')

View File

@@ -1,6 +1,7 @@
import pytz
from db import Task from db import Task
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import (StringField, DateField, TimeField, TextAreaField, IntegerField, BooleanField, from wtforms import (StringField, DateField, TimeField, TextAreaField, IntegerField, SelectField, BooleanField,
RadioField, EmailField, PasswordField) RadioField, EmailField, PasswordField)
from wtforms.validators import InputRequired, Length from wtforms.validators import InputRequired, Length
from wtforms_sqlalchemy.orm import QuerySelectField from wtforms_sqlalchemy.orm import QuerySelectField
@@ -10,8 +11,7 @@ def get_tasks():
class TaskForm(FlaskForm): class TaskForm(FlaskForm):
title = StringField('Title', validators=[InputRequired(), title = StringField('Title', validators=[InputRequired(),
Length(min=5, max=100)]) Length(min=5, max=100)])
description = TextAreaField('Description', validators=[InputRequired(), description = TextAreaField('Description', validators=[Length(max=200)])
Length(max=200)])
class EventForm(FlaskForm): class EventForm(FlaskForm):
# eventDate = DateField('Date', validators=[InputRequired()], format='m-%d-%Y') # eventDate = DateField('Date', validators=[InputRequired()], format='m-%d-%Y')
# period_num = IntegerField(validators=[InputRequired()]) # period_num = IntegerField(validators=[InputRequired()])
@@ -21,6 +21,11 @@ class PeriodForm(FlaskForm):
weekendSchedule = BooleanField(label='Include on Weekends?', false_values=None) weekendSchedule = BooleanField(label='Include on Weekends?', false_values=None)
periodTime = TimeField('Time', format="%H:%M") periodTime = TimeField('Time', format="%H:%M")
class SettingsForm(FlaskForm):
password = PasswordField('Password')
realName = StringField('Real Name')
timezone = SelectField('Time Zone', choices=pytz.all_timezones)
class SignupForm(FlaskForm): class SignupForm(FlaskForm):
userName = StringField('Username', validators=[InputRequired()]) userName = StringField('Username', validators=[InputRequired()])
password = PasswordField('Password', validators=[InputRequired()]) password = PasswordField('Password', validators=[InputRequired()])
@@ -29,4 +34,5 @@ class SignupForm(FlaskForm):
class LoginForm(FlaskForm): class LoginForm(FlaskForm):
userName = StringField('Username', validators=[InputRequired()]) userName = StringField('Username', validators=[InputRequired()])
password = PasswordField('Password', validators=[InputRequired()]) password = PasswordField('Password', validators=[InputRequired()])
rememberMe = BooleanField(label='Remember me?')

View File

@@ -16,49 +16,49 @@ period9 = Period(period=9, periodTime='15:30:00', weekendSchedule=False)
task1 = Task(id=1, task1 = Task(id=1,
title="Sexy ERP time", title="Sexy ERP time",
description="Be the dominant partner and fuck so much XD", description="Be the dominant partner and fuck so much XD",
created_timestamp='1668278862', due_timestamp='') created_timestamp=1668278862, due_timestamp=None)
task2 = Task(id=2, title="Test task", task2 = Task(id=2, title="Test task",
description="Test", description="Test",
created_timestamp='1668278862', created_timestamp=1668278862,
due_timestamp='') due_timestamp=None)
task3 = Task(id=3, title="La Nager", task3 = Task(id=3, title="La Nager",
description="Francis stuff", description="Francis stuff",
created_timestamp='1668278862', created_timestamp=1668278862,
due_timestamp='') due_timestamp=None)
task4 = Task(id=4, title="Ops/Tech Meeting", task4 = Task(id=4, title="Ops/Tech Meeting",
description="HEIL GEORGE!", description="HEIL GEORGE!",
created_timestamp='1668278862', created_timestamp=1668278862,
due_timestamp='') due_timestamp=None)
task5 = Task(id=5, title="Tech Team Meeting", task5 = Task(id=5, title="Tech Team Meeting",
description="Awkward AF", description="Awkward AF",
created_timestamp='1668278862', created_timestamp=1668278862,
due_timestamp='') due_timestamp=None)
task6 = Task(id=6, title="Write BellScheduler", task6 = Task(id=6, title="Write BellScheduler",
description="heh", description="heh",
created_timestamp='1668278862', created_timestamp=1668278862,
due_timestamp='') due_timestamp=None)
task7 = Task(id=7, title="Fap to cunny porn", task7 = Task(id=7, title="Fap to cunny porn",
description="FBI OPEN UP!", description="FBI OPEN UP!",
created_timestamp='1668278862', created_timestamp=1668278862,
due_timestamp='') due_timestamp=None)
task8 = Task(id=8, title="Go on vrchat", task8 = Task(id=8, title="Go on vrchat",
description="Mmm... virtual headpats!", description="Mmm... virtual headpats!",
created_timestamp='1668278862', created_timestamp=1668278862,
due_timestamp='') due_timestamp=None)
task9 = Task(id=9, title="Brush teeth", task9 = Task(id=9, title="Brush teeth",
description="Ya dont do that more often, ya gross fuck", description="Ya dont do that more often, ya gross fuck",
created_timestamp='1668278862', created_timestamp=1668278862,
due_timestamp='') due_timestamp=None)
event1 = Event(id=1, scheduled_date='11-12-2022', period_num=1, task_id=4) event1 = Event(id=1, scheduled_date='03-17-2023', period_num=1, task_id=4)
event2 = Event(id=2, scheduled_date='11-12-2022', period_num=2, task_id=2) event2 = Event(id=2, scheduled_date='03-17-2023', period_num=2, task_id=2)
event3 = Event(id=3, scheduled_date='11-12-2022', period_num=3, task_id=5) event3 = Event(id=3, scheduled_date='03-17-2023', period_num=3, task_id=5)
event4 = Event(id=4, scheduled_date='11-12-2022', period_num=4, task_id=6) event4 = Event(id=4, scheduled_date='03-17-2023', period_num=4, task_id=6)
event5 = Event(id=5, scheduled_date='11-12-2022', period_num=5, task_id=1) event5 = Event(id=5, scheduled_date='03-17-2023', period_num=5, task_id=1)
event6 = Event(id=6, scheduled_date='11-12-2022', period_num=6, task_id=3) event6 = Event(id=6, scheduled_date='03-17-2023', period_num=6, task_id=3)
event7 = Event(id=7, scheduled_date='11-12-2022', period_num=7, task_id=7) event7 = Event(id=7, scheduled_date='03-17-2023', period_num=7, task_id=7)
event8 = Event(id=8, scheduled_date='11-12-2022', period_num=8, task_id=8) event8 = Event(id=8, scheduled_date='03-17-2023', period_num=8, task_id=8)
event9 = Event(id=9, scheduled_date='11-12-2022', period_num=9, task_id=9) event9 = Event(id=9, scheduled_date='03-17-2023', period_num=9, task_id=9)
db.session.add_all([period1, period2, period3, period4, period5, period6, period7, period8, period9]) db.session.add_all([period1, period2, period3, period4, period5, period6, period7, period8, period9])
db.session.add_all([task1, task2, task3, task4, task5, task6, task7, task8, task9]) db.session.add_all([task1, task2, task3, task4, task5, task6, task7, task8, task9])

View File

@@ -1,36 +0,0 @@
"""empty message
Revision ID: bf65c9f77f9f
Revises:
Create Date: 2022-11-19 09:49:20.565127
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'bf65c9f77f9f'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('user',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('userName', sa.String(length=1000), nullable=True),
sa.Column('email', sa.String(length=100), nullable=True),
sa.Column('password', sa.String(length=100), nullable=True),
sa.Column('realName', sa.String(length=1000), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('email')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('user')
# ### end Alembic commands ###

View File

@@ -1,4 +1,17 @@
import time import time
from datetime import datetime, date from datetime import datetime, date, timedelta
from zoneinfo import ZoneInfo
# Get current day in UTC and put it in str format
currDay = datetime.now() currDay = datetime.now()
currDay = currDay.strftime('%m-%d-%Y') currDay = currDay.strftime('%m-%d-%Y')
# Get previous day in UTC and put it in str format
prevDay = datetime.now() + timedelta(days=-1)
prevDay = prevDay.strftime('%m-%d-%Y')
with open('__version__','r') as file:
currVersion = file.read()
with open('__commit__','r') as file:
currCommit = file.read()

View File

@@ -1,6 +1,9 @@
# This file is used by pip to install required python packages # This file is used by pip to install required python packages
# Usage: pip install -r requirements.txt # Usage: pip install -r requirements.txt
# Gunicorn WSGI Server
gunicorn==20.1.0
# Flask Framework # Flask Framework
click==8.1.3 click==8.1.3
Flask==2.2.2 Flask==2.2.2
@@ -20,6 +23,15 @@ Flask-User==1.0.2.2
# WTForms Extensions # WTForms Extensions
WTForms-SQLAlchemy==0.3.0 WTForms-SQLAlchemy==0.3.0
# APScheduler automated scheduler
APScheduler==3.9.1.post1
# Python Time packages
pytz==2022.6
# Automated tests # Automated tests
pytest==7.2.0 pytest==7.2.0
pytest-cov==4.0.0 pytest-cov==4.0.0
# MySQL Package
mysqlclient==2.1.1

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 816 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
app/static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

1
app/static/manifest.json Normal file
View File

@@ -0,0 +1 @@
{"name":"","short_name":"","icons":[{"src":"/static/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/static/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}

View File

@@ -1,6 +1,10 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<!-- Favicon manifest tags -->
<link rel="manifest" href="/static/manifest.json" />
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
<!-- Required meta tags --> <!-- Required meta tags -->
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
@@ -10,14 +14,14 @@
<title>{% block title %} {% endblock %} - BellScheduler</title> <title>{% block title %} {% endblock %} - BellScheduler</title>
</head> </head>
<body> <body class="d-flex flex-column min-vh-100">
<nav class="navbar navbar-expand-md navbar-light bg-light"> <nav class="navbar navbar-expand-md navbar-light bg-light">
<a class="navbar-brand" href="{{ url_for('index') }}">BellScheduler</a> <a class="navbar-brand" href="{{ url_for('index') }}">BellScheduler</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav"> <ul class="navbar-nav mr-auto mt-3 mt-lg-0">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/events">Events</a> <a class="nav-link" href="/events">Events</a>
</li> </li>
@@ -27,14 +31,17 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/periods">Periods</a> <a class="nav-link" href="/periods">Periods</a>
</li> </li>
</ul>
</div>
<div class="float-right">
{% if current_user.is_authenticated %} {% if current_user.is_authenticated %}
<a href="{{ url_for('logout') }}" class="nav-link"> Logout {{current_user.userName}} </a> <li class="nav-item">
<a class="nav-link" href="/settings">Settings</a>
</li>
{% endif %}
</ul>
{% if current_user.is_authenticated %}
<a href="{{ url_for('logout') }}"> Logout {{current_user.userName}} </a>
{% endif %} {% endif %}
{% if not current_user.is_authenticated %} {% if not current_user.is_authenticated %}
<a href="{{ url_for('login') }}" class="nav-link"> Login </a> <a href="{{ url_for('login') }}"> Login </a>
{% endif %} {% endif %}
</div> </div>
</nav> </nav>
@@ -42,7 +49,9 @@
<div class="container"> <div class="container">
{% block content %} {% endblock %} {% block content %} {% endblock %}
</div> </div>
<footer class="card-footer mt-auto">
Version {{ currVersion }} &nbsp; &nbsp; Commit: {{ currCommit }}
</footer>
<!-- Optional JavaScript --> <!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS --> <!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>

View File

@@ -9,7 +9,10 @@
<p> <p>
<b><a href="/task/{{ event.tasks.id }}"> <b><a href="/task/{{ event.tasks.id }}">
{{ event.tasks.title }} {{ event.tasks.title }}
</a></b> <br> {{ event.tasks.description }}
</a></b> <br>{% if event.tasks.description != None %}
<p>{{ event.tasks.description }}</p>
{% endif %}
</p> </p>
{% else %} {% else %}
<div><p>(no task assigned... yet)</p></div> <div><p>(no task assigned... yet)</p></div>

View File

@@ -0,0 +1,22 @@
{% extends 'base.html' %}
{% set currDay = datetime.now() %}
{% set currTime = currDay.strftime('%I:%M %p') %}
{% set currDay = currDay.strftime('%m-%d-%Y') %}
{% block content %}
<span><h1><b>{% block title %} Error 403 {% endblock %}</h1></b></span>
<div>
<div>
<h2> Forbidden <br> <br> </h2>
</div>
<div>
Date: {{ currDay }} <br>
Time {{ currTime }} (UTC) <br> <br>
</div>
<div>
Node: {{ NODE_NAME }} <br>
Pod: {{ POD_NAME }}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,22 @@
{% extends 'base.html' %}
{% set currDay = datetime.now() %}
{% set currTime = currDay.strftime('%I:%M %p') %}
{% set currDay = currDay.strftime('%m-%d-%Y') %}
{% block content %}
<span><h1><b>{% block title %} Error 404 {% endblock %}</h1></b></span>
<div>
<div>
<h2> Page Not Found <br> <br> </h2>
</div>
<div>
Date: {{ currDay }} <br>
Time {{ currTime }} (UTC) <br> <br>
</div>
<div>
Node: {{ NODE_NAME }} <br>
Pod: {{ POD_NAME }}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,22 @@
{% extends 'base.html' %}
{% set currDay = datetime.now() %}
{% set currTime = currDay.strftime('%I:%M %p') %}
{% set currDay = currDay.strftime('%m-%d-%Y') %}
{% block content %}
<span><h1><b>{% block title %} Error 500 {% endblock %}</b></h1></span>
<div>
<div>
<h2> Internal Server Error <br> <br> </h2>
</div>
<div>
Date: {{ currDay }} <br>
Time {{ currTime }} (UTC) <br> <br>
</div>
<div>
Node: {{ NODE_NAME }} <br>
Pod: {{ POD_NAME }}
</div>
</div>
{% endblock %}

View File

@@ -1,10 +1,12 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% set currDay = datetime.now() %} {% set currDay = datetime.now() %}
{% set currDay = currDay.astimezone(ZoneInfo(current_user.timezone)) %}
{% set currTime = currDay.strftime('%I:%M %p') %}
{% set currDay = currDay.strftime('%m-%d-%Y') %} {% set currDay = currDay.strftime('%m-%d-%Y') %}
{% block content %} {% block content %}
<span><h1>{% block title %} Events {% endblock %}</h1></span> <span><h1>{% block title %} Events {% endblock %}</h1></span>
<b>Current Date: {{ currDay }} </b> <br> <br> <b>Current Date: {{ currDay }} </b> <br> <b>Current Time: {{ currTime }}</b> <br> <br>
<div> <div>
{% for period in periods %} {% for period in periods %}
<div> <div>
@@ -18,7 +20,9 @@
</a> </a>
</b> </b>
<div> <div>
{% if event.tasks.description != None %}
<p>{{ event.tasks.description }}</p> <p>{{ event.tasks.description }}</p>
{% endif %}
</div> </div>
{% else %} {% else %}
<div><p>(no task assigned... yet)</p></div> <div><p>(no task assigned... yet)</p></div>

View File

@@ -19,6 +19,9 @@
{{ form.password.label }} {{ form.password.label }}
{{ form.password }} {{ form.password }}
</p> </p>
<p>
{{ form.rememberMe.label}} {{ form.rememberMe }}
</p>
<p> <p>
<button class="btn btn-primary" type="submit">Submit</button> <button class="btn btn-primary" type="submit">Submit</button>
</p> </p>

View File

@@ -0,0 +1,23 @@
{% extends 'base.html' %}
{% block content %}
<span><h1>{% block title %} Settings {% endblock %}</h1></span>
<form method="post">
{{ form.csrf_token }}
<p>
<b>User: {{ current_user.userName }}</b>
<div>
{{ form.realName.label }} {{ form.realName }}
</div> <br>
<div>
{{ form.password.label }} {{ form.password }}
</div> <br>
<div>
{{ form.timezone.label }} {{ form.timezone }}
</div>
</p>
<p>
<button class="btn btn-primary" type="submit">Submit</button>
</p>
</form>
{% endblock %}

View File

@@ -8,7 +8,9 @@
<div> <div>
<div> <div>
<br> <br>
<p>{{ task.description }}</p> {% if task.description != None %}
<p>{{ task.description }}</p>
{% endif %}
</div> </div>
<div> <div>
<p>Created: {{ createdTime.strftime('%Y-%m-%d %I:%M %p') }} </p> <p>Created: {{ createdTime.strftime('%Y-%m-%d %I:%M %p') }} </p>

View File

@@ -11,7 +11,9 @@
<p><b>{{ task.title }}</b> </p> <p><b>{{ task.title }}</b> </p>
</a> </a>
<b> <b>
{{ task.description }} {% if task.description != None %}
{{ task.description }}
{% endif %}
</b> </b>
<div> <div>
<p>Created: {{ createdTime.strftime('%Y-%m-%d %I:%M %p') }}</p> <p>Created: {{ createdTime.strftime('%Y-%m-%d %I:%M %p') }}</p>
@@ -19,4 +21,5 @@
<hr> <hr>
</div> </div>
{% endfor %} {% endfor %}
</div>
{% endblock %} {% endblock %}

35
app/worker.py Normal file
View File

@@ -0,0 +1,35 @@
import os
from flask import Flask
from misc import currDay, datetime, time, timedelta
from db import db, Period, Event
from create_events import createEvents
from apscheduler.schedulers.background import BlockingScheduler
basedir = os.path.abspath(os.path.dirname(__file__))
app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ['SECRET_KEY']
app.config['SQLALCHEMY_DATABASE_URI'] =\
'mysql://' + os.environ['MYSQL_USER'] + \
':' + os.environ['MYSQL_PASSWORD'] + \
'@' + os.environ['MYSQL_HOST'] + \
':' + os.environ['MYSQL_PORT'] + \
'/' + os.environ['MYSQL_DB']
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app)
# Define function to run create_events with app context
def run_create_events():
with app.app_context():
createEvents(db, currDay, Period, Event)
# Call createEvents on initial launch of the script
run_create_events()
# Set up scheduler to run function at 59th minute of every hour
scheduler = BlockingScheduler()
scheduler.add_job(run_create_events, 'cron', minute=59)
# Start scheduler
scheduler.start()

View File

@@ -4,8 +4,42 @@ services:
image: container-registry.infra.dubyatp.xyz/bellscheduler/app:latest-testing image: container-registry.infra.dubyatp.xyz/bellscheduler/app:latest-testing
restart: always restart: always
environment: environment:
- SQLITE_DB=database.db - MYSQL_USER=root
volumes: - MYSQL_PASSWORD=notasecuresecretkeyonlyuseforlocaldevelopment
- ./database.db:/app/database.db - MYSQL_HOST=db
- MYSQL_PORT=3306
- MYSQL_DB=bellscheduler
- SECRET_KEY=notasecuresecretkeyonlyuseforlocaldevelopment
- NODE_NAME=local
- POD_NAME=local
- FLASK_ENV=development
- FLASK_DEBUG=1
- PYTHONUNBUFFERED=1
ports: ports:
- 127.0.0.1:5000:5000 - 127.0.0.1:80:80
worker:
image: container-registry.infra.dubyatp.xyz/bellscheduler/app:latest-testing
restart: always
entrypoint: python3
command: "-m worker"
environment:
- MYSQL_USER=root
- MYSQL_PASSWORD=notasecuresecretkeyonlyuseforlocaldevelopment
- MYSQL_HOST=db
- MYSQL_PORT=3306
- MYSQL_DB=bellscheduler
- SECRET_KEY=notasecuresecretkeyonlyuseforlocaldevelopment
- NODE_NAME=local
- POD_NAME=local
- FLASK_ENV=development
- FLASK_DEBUG=1
- PYTHONUNBUFFERED=1
db:
image: mariadb:10.7.8-focal
restart: always
environment:
- MARIADB_ROOT_PASSWORD=notasecuresecretkeyonlyuseforlocaldevelopment
volumes:
- ./database_mysql:/var/lib/mysql
ports:
- 127.0.0.1:3306:3306