Initial
This commit is contained in:
96
.gitignore
vendored
Normal file
96
.gitignore
vendored
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
env/
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*,cover
|
||||||
|
.hypothesis/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# IPython Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# celery beat schedule file
|
||||||
|
celerybeat-schedule
|
||||||
|
|
||||||
|
# dotenv
|
||||||
|
.env
|
||||||
|
|
||||||
|
# virtualenv
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
.idea
|
||||||
|
install.txt
|
||||||
|
Screenshots/
|
||||||
|
.vscode
|
||||||
|
typesheds/
|
||||||
|
*.db
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
178
app.py
Normal file
178
app.py
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
import functools
|
||||||
|
from flask import Flask, g, render_template, request, redirect, session
|
||||||
|
|
||||||
|
import db
|
||||||
|
import model
|
||||||
|
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
app.secret_key = 'THIS IS A TEST KEY'
|
||||||
|
|
||||||
|
ROLES = {
|
||||||
|
'123': 'readonly',
|
||||||
|
'1234': 'editor'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@app.cli.command('initdb')
|
||||||
|
def init_db_command():
|
||||||
|
"""Initializes the database."""
|
||||||
|
db.init_db()
|
||||||
|
|
||||||
|
|
||||||
|
@app.teardown_appcontext
|
||||||
|
def close_connection(exception):
|
||||||
|
db = getattr(g, '_database', None)
|
||||||
|
if db is not None:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
def set_user_role(data):
|
||||||
|
"""Set the users role in the flask g object for later usage"""
|
||||||
|
g.is_editor = data == 'editor'
|
||||||
|
|
||||||
|
|
||||||
|
def authorize(func):
|
||||||
|
@functools.wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
set_user_role(session['role'])
|
||||||
|
except KeyError:
|
||||||
|
return redirect('/login')
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/login', methods=['GET', 'POST'])
|
||||||
|
def login():
|
||||||
|
if request.method == 'GET':
|
||||||
|
return render_template('login.html')
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
password = request.form['password']
|
||||||
|
session['role'] = ROLES[password]
|
||||||
|
return redirect('/')
|
||||||
|
except KeyError:
|
||||||
|
return redirect('login')
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/logout')
|
||||||
|
def logout():
|
||||||
|
session.pop('role', None)
|
||||||
|
return redirect('login')
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
@authorize
|
||||||
|
def landing():
|
||||||
|
if 'editor' in session['role']:
|
||||||
|
print('editor')
|
||||||
|
else:
|
||||||
|
print('not editor')
|
||||||
|
return render_template('seasons.html')
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/seasons')
|
||||||
|
@authorize
|
||||||
|
def seasons():
|
||||||
|
return render_template('seasons.html')
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/newplayer', methods=['GET'])
|
||||||
|
@authorize
|
||||||
|
def new_player():
|
||||||
|
return render_template('editplayer.html', model={})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/saveplayer', methods=['POST'])
|
||||||
|
@authorize
|
||||||
|
def update_player():
|
||||||
|
data = request.form
|
||||||
|
player = model.Player(
|
||||||
|
id=data.get('id', None),
|
||||||
|
real_name=data['real_name'],
|
||||||
|
alias=data['alias'],
|
||||||
|
hex_id=data['hex_id'],
|
||||||
|
anon=data.get('anon', False))
|
||||||
|
res = db.save_player(player)
|
||||||
|
return redirect('/players')
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/players')
|
||||||
|
@authorize
|
||||||
|
def players():
|
||||||
|
loaded = db.load_players()
|
||||||
|
model = {
|
||||||
|
'player_list': loaded,
|
||||||
|
'columns': [('id', 'ID'),
|
||||||
|
('name', 'Player Name'),
|
||||||
|
('alias', 'Alias'),
|
||||||
|
('hex_id', 'Hex ID')],
|
||||||
|
'controls': [('edit', 'Edit')]
|
||||||
|
}
|
||||||
|
return render_template('players.html', model=model)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/players/<id>', methods=['GET'])
|
||||||
|
@authorize
|
||||||
|
def edit_player(id: int):
|
||||||
|
loaded = db.load_players(id)[0]
|
||||||
|
return render_template('editplayer.html', model=loaded)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/drinks')
|
||||||
|
@authorize
|
||||||
|
def drinks():
|
||||||
|
loaded = db.load_drinks()
|
||||||
|
model = {
|
||||||
|
'drinks': loaded,
|
||||||
|
'columns': [
|
||||||
|
('id', 'ID'),
|
||||||
|
('name', 'Drink Name'),
|
||||||
|
('vol', 'Alcohol %')
|
||||||
|
],
|
||||||
|
'controls': [('edit', 'Edit')]
|
||||||
|
}
|
||||||
|
return render_template('drinks.html', model=model)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/drinks/<id>', methods=['GET'])
|
||||||
|
@authorize
|
||||||
|
def show_drink(id: int):
|
||||||
|
loaded = db.load_drinks(id)[0]
|
||||||
|
return render_template('editdrink.html', model=loaded)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/newdrink', methods=['GET'])
|
||||||
|
@authorize
|
||||||
|
def new_drink():
|
||||||
|
return render_template('editdrink.html', model={})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/savedrink', methods=['POST'])
|
||||||
|
@authorize
|
||||||
|
def save_drink():
|
||||||
|
drink = model.Drink.from_form(request.form)
|
||||||
|
res = db.save_drink(drink)
|
||||||
|
return redirect('/drinks')
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/enemies')
|
||||||
|
@authorize
|
||||||
|
def enemies():
|
||||||
|
loaded = db.load_enemies()
|
||||||
|
model = {
|
||||||
|
'enemies': loaded,
|
||||||
|
'columns': [
|
||||||
|
('id', 'ID'),
|
||||||
|
('name', 'Enemy Name'),
|
||||||
|
('vol', 'Is Boss')
|
||||||
|
],
|
||||||
|
'controls': [('edit', 'Edit')]
|
||||||
|
}
|
||||||
|
return render_template('enemies.html', model=model)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run()
|
||||||
124
db.py
Normal file
124
db.py
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import sqlite3
|
||||||
|
import logging as log
|
||||||
|
from flask import g
|
||||||
|
|
||||||
|
import model
|
||||||
|
|
||||||
|
|
||||||
|
DATABASE = 'es_debug.db'
|
||||||
|
|
||||||
|
|
||||||
|
def connect_db():
|
||||||
|
"""Create a new sqlite3 connection and register it in 'g._database'"""
|
||||||
|
db = getattr(g, '_database', None)
|
||||||
|
if db is None:
|
||||||
|
db = g._database = sqlite3.connect(DATABASE)
|
||||||
|
|
||||||
|
db.row_factory = sqlite3.Row
|
||||||
|
return db
|
||||||
|
|
||||||
|
|
||||||
|
def query_db(query, args=(), one=False):
|
||||||
|
"""Runs an SQL query on an new database connection, returning the fetched rows"""
|
||||||
|
log.info(f'Running query ({query})\nwith arguments ({args})')
|
||||||
|
cur = connect_db().execute(query, args)
|
||||||
|
rv = cur.fetchall()
|
||||||
|
cur.close()
|
||||||
|
return (rv[0] if rv else None) if one else rv
|
||||||
|
|
||||||
|
|
||||||
|
def update_db(query, args=()):
|
||||||
|
"""
|
||||||
|
Runs an changing query on the database
|
||||||
|
Returns either False if no error has occurred, or an sqlite3 Exception
|
||||||
|
"""
|
||||||
|
with connect_db() as con:
|
||||||
|
try:
|
||||||
|
con.cursor().execute(query, args)
|
||||||
|
except sqlite3.Error as err:
|
||||||
|
return err
|
||||||
|
con.commit()
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def init_db():
|
||||||
|
"""Initialize the database from the 'schema.sql' script file"""
|
||||||
|
file_name = 'schema.sql'
|
||||||
|
print(f'Creating database from file: "{file_name}"')
|
||||||
|
with connect_db() as conn:
|
||||||
|
with open(file_name, 'r') as f:
|
||||||
|
try:
|
||||||
|
conn.cursor().executescript(f.read())
|
||||||
|
except sqlite3.OperationalError as err:
|
||||||
|
log.error(f'Cannot create database: {err}')
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def save_player_query(player):
|
||||||
|
if not player.id:
|
||||||
|
sql = 'insert into player values (?, ?, ?, ?, ?)'
|
||||||
|
args = (None, player.real_name, player.alias, player.hex_id, player.anon)
|
||||||
|
else:
|
||||||
|
sql = 'update player ' \
|
||||||
|
'set real_name=?, alias=?, hex_id=?, anon=? ' \
|
||||||
|
'where id==?'
|
||||||
|
args = (player.real_name, player.alias, player.hex_id, player.anon, player.id)
|
||||||
|
return sql, args
|
||||||
|
|
||||||
|
|
||||||
|
def save_player(player):
|
||||||
|
sql, args = save_player_query(player)
|
||||||
|
return update_db(sql, args)
|
||||||
|
|
||||||
|
|
||||||
|
def load_players(id=None):
|
||||||
|
sql = 'select * from player'
|
||||||
|
args = ()
|
||||||
|
if id:
|
||||||
|
sql += ' where player.id = ?'
|
||||||
|
args = (id, )
|
||||||
|
sql += ' order by player.id'
|
||||||
|
rows = query_db(sql, args)
|
||||||
|
players = [model.Player(**row) for row in rows]
|
||||||
|
return players
|
||||||
|
|
||||||
|
|
||||||
|
def load_drinks(id=None):
|
||||||
|
sql = 'select * from drink'
|
||||||
|
args = ()
|
||||||
|
if id:
|
||||||
|
sql += ' where drink.id = ?'
|
||||||
|
args = (id, )
|
||||||
|
sql += ' order by drink.id'
|
||||||
|
rows = query_db(sql, args)
|
||||||
|
drinks = [model.Drink(**row) for row in rows]
|
||||||
|
return drinks
|
||||||
|
|
||||||
|
|
||||||
|
def save_drink_query(drink):
|
||||||
|
if not drink.id:
|
||||||
|
sql = 'insert into drink values (?, ?, ?)'
|
||||||
|
args = (None, drink.name, drink.vol)
|
||||||
|
else:
|
||||||
|
sql = 'update drink ' \
|
||||||
|
'set name=?, vol=? ' \
|
||||||
|
'where id==?'
|
||||||
|
args = (drink.name, drink.vol, drink.id)
|
||||||
|
return sql, args
|
||||||
|
|
||||||
|
|
||||||
|
def save_drink(drink):
|
||||||
|
sql, args = save_drink_query(drink)
|
||||||
|
return update_db(sql, args)
|
||||||
|
|
||||||
|
|
||||||
|
def load_enemies(id=None):
|
||||||
|
sql = 'select * from enemy'
|
||||||
|
args = ()
|
||||||
|
if id:
|
||||||
|
sql += ' where enemy.id = ?'
|
||||||
|
args = (id, )
|
||||||
|
sql += ' order by enemy.id'
|
||||||
|
rows = query_db(sql, args)
|
||||||
|
enemies = [model.Enemy(**row) for row in rows]
|
||||||
|
return enemies
|
||||||
72
model.py
Normal file
72
model.py
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
from _ctypes import ArgumentError
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
INVALID_STR = 'Form entry "{}" is invalid'
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Player:
|
||||||
|
id: int
|
||||||
|
real_name: str
|
||||||
|
name: str
|
||||||
|
alias: str
|
||||||
|
hex_id: str
|
||||||
|
anon: bool = False
|
||||||
|
|
||||||
|
def __init__(self, real_name, alias, hex_id, anon=False, id=None):
|
||||||
|
self.real_name = str(real_name)
|
||||||
|
self.name = str(real_name) if not anon else alias
|
||||||
|
self.hex_id = str(hex_id)
|
||||||
|
self.alias = str(alias)
|
||||||
|
self.anon = bool(anon)
|
||||||
|
self.id = id
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Drink:
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
vol: float
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_form(cls, form):
|
||||||
|
id = form.get('id', None)
|
||||||
|
id = int(id) if id else None
|
||||||
|
|
||||||
|
name = form.get('name', None)
|
||||||
|
if not name:
|
||||||
|
raise ArgumentError('Form data contains no field "name"')
|
||||||
|
name = str(name)
|
||||||
|
|
||||||
|
vol = form.get('vol', None)
|
||||||
|
if not vol:
|
||||||
|
raise ArgumentError('Form data contains no field "vol"')
|
||||||
|
vol = float(vol)
|
||||||
|
|
||||||
|
self = cls(id=id, name=name, vol=vol)
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Enemy:
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
boss: bool
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_form(cls, form):
|
||||||
|
id = form.get('id', None)
|
||||||
|
id = int(id) if id else None
|
||||||
|
|
||||||
|
name = form.get('name', None)
|
||||||
|
if not name:
|
||||||
|
raise ArgumentError(INVALID_STR.format('name'))
|
||||||
|
name = str(name)
|
||||||
|
|
||||||
|
boss = form.get('boss', '')
|
||||||
|
if boss not in [True, False, 'True', 'False']:
|
||||||
|
raise ArgumentError(INVALID_STR.format('boss'))
|
||||||
|
|
||||||
|
self = cls(id=id, name=name, boss=boss)
|
||||||
|
return self
|
||||||
|
|
||||||
72
schema.sql
Normal file
72
schema.sql
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
create table if not exists season
|
||||||
|
(
|
||||||
|
id integer not null
|
||||||
|
constraint season_pk
|
||||||
|
primary key autoincrement,
|
||||||
|
game text,
|
||||||
|
description text,
|
||||||
|
start text,
|
||||||
|
end text
|
||||||
|
);
|
||||||
|
|
||||||
|
create unique index if not exists season_id_uindex
|
||||||
|
on season (id);
|
||||||
|
|
||||||
|
create table if not exists player
|
||||||
|
(
|
||||||
|
id integer not null
|
||||||
|
constraint player_pk
|
||||||
|
primary key autoincrement,
|
||||||
|
real_name text,
|
||||||
|
alias text not null,
|
||||||
|
hex_id text,
|
||||||
|
anon integer not null
|
||||||
|
);
|
||||||
|
|
||||||
|
create unique index if not exists player_id_uindex
|
||||||
|
on player (id);
|
||||||
|
|
||||||
|
|
||||||
|
create table if not exists drink
|
||||||
|
(
|
||||||
|
id integer not null
|
||||||
|
constraint drink_pk
|
||||||
|
primary key autoincrement,
|
||||||
|
name text not null,
|
||||||
|
vol real not null
|
||||||
|
);
|
||||||
|
|
||||||
|
create unique index if not exists drink_id_uindex
|
||||||
|
on drink (id);
|
||||||
|
|
||||||
|
|
||||||
|
create table if not exists enemy
|
||||||
|
(
|
||||||
|
id integer not null
|
||||||
|
constraint enemy_pk
|
||||||
|
primary key autoincrement,
|
||||||
|
name text not null,
|
||||||
|
boss integer not null
|
||||||
|
);
|
||||||
|
|
||||||
|
create unique index if not exists enemy_id_uindex
|
||||||
|
on enemy (id);
|
||||||
|
|
||||||
|
create table if not exists season_enemy
|
||||||
|
(
|
||||||
|
id integer not null
|
||||||
|
constraint season_enemy_pk
|
||||||
|
primary key autoincrement,
|
||||||
|
season_id integer
|
||||||
|
constraint season_enemy_season_id_fk
|
||||||
|
references season,
|
||||||
|
enemy_id integer
|
||||||
|
constraint season_enemy_enemy_id_fk
|
||||||
|
references enemy
|
||||||
|
);
|
||||||
|
|
||||||
|
create unique index if not exists season_enemy_id_uindex
|
||||||
|
on season_enemy (id);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
3719
static/bootstrap-grid.css
vendored
Normal file
3719
static/bootstrap-grid.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
static/bootstrap-grid.css.map
Normal file
1
static/bootstrap-grid.css.map
Normal file
File diff suppressed because one or more lines are too long
7
static/bootstrap-grid.min.css
vendored
Normal file
7
static/bootstrap-grid.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
static/bootstrap-grid.min.css.map
Normal file
1
static/bootstrap-grid.min.css.map
Normal file
File diff suppressed because one or more lines are too long
319
static/bootstrap-reboot.css
vendored
Normal file
319
static/bootstrap-reboot.css
vendored
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
/*!
|
||||||
|
* Bootstrap Reboot v4.2.1 (https://getbootstrap.com/)
|
||||||
|
* Copyright 2011-2018 The Bootstrap Authors
|
||||||
|
* Copyright 2011-2018 Twitter, Inc.
|
||||||
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||||
|
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
||||||
|
*/
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-family: sans-serif;
|
||||||
|
line-height: 1.15;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #212529;
|
||||||
|
text-align: left;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
[tabindex="-1"]:focus {
|
||||||
|
outline: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
box-sizing: content-box;
|
||||||
|
height: 0;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
abbr[title],
|
||||||
|
abbr[data-original-title] {
|
||||||
|
text-decoration: underline;
|
||||||
|
-webkit-text-decoration: underline dotted;
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
cursor: help;
|
||||||
|
border-bottom: 0;
|
||||||
|
text-decoration-skip-ink: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
address {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-style: normal;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul,
|
||||||
|
dl {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol ol,
|
||||||
|
ul ul,
|
||||||
|
ol ul,
|
||||||
|
ul ol {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
dt {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd {
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
position: relative;
|
||||||
|
font-size: 75%;
|
||||||
|
line-height: 0;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub {
|
||||||
|
bottom: -.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
sup {
|
||||||
|
top: -.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #007bff;
|
||||||
|
text-decoration: none;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #0056b3;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:not([href]):not([tabindex]) {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:not([href]):not([tabindex]):focus {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre,
|
||||||
|
code,
|
||||||
|
kbd,
|
||||||
|
samp {
|
||||||
|
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
vertical-align: middle;
|
||||||
|
border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
overflow: hidden;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
caption {
|
||||||
|
padding-top: 0.75rem;
|
||||||
|
padding-bottom: 0.75rem;
|
||||||
|
color: #6c757d;
|
||||||
|
text-align: left;
|
||||||
|
caption-side: bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-align: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:focus {
|
||||||
|
outline: 1px dotted;
|
||||||
|
outline: 5px auto -webkit-focus-ring-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
button,
|
||||||
|
select,
|
||||||
|
optgroup,
|
||||||
|
textarea {
|
||||||
|
margin: 0;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
input {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
select {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
[type="button"],
|
||||||
|
[type="reset"],
|
||||||
|
[type="submit"] {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
|
||||||
|
button::-moz-focus-inner,
|
||||||
|
[type="button"]::-moz-focus-inner,
|
||||||
|
[type="reset"]::-moz-focus-inner,
|
||||||
|
[type="submit"]::-moz-focus-inner {
|
||||||
|
padding: 0;
|
||||||
|
border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="radio"],
|
||||||
|
input[type="checkbox"] {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="date"],
|
||||||
|
input[type="time"],
|
||||||
|
input[type="datetime-local"],
|
||||||
|
input[type="month"] {
|
||||||
|
-webkit-appearance: listbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
overflow: auto;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
legend {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
line-height: inherit;
|
||||||
|
color: inherit;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress {
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type="number"]::-webkit-inner-spin-button,
|
||||||
|
[type="number"]::-webkit-outer-spin-button {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type="search"] {
|
||||||
|
outline-offset: -2px;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type="search"]::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-file-upload-button {
|
||||||
|
font: inherit;
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
|
||||||
|
output {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary {
|
||||||
|
display: list-item;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
template {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[hidden] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
/*# sourceMappingURL=bootstrap-reboot.css.map */
|
||||||
1
static/bootstrap-reboot.css.map
Normal file
1
static/bootstrap-reboot.css.map
Normal file
File diff suppressed because one or more lines are too long
8
static/bootstrap-reboot.min.css
vendored
Normal file
8
static/bootstrap-reboot.min.css
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/*!
|
||||||
|
* Bootstrap Reboot v4.2.1 (https://getbootstrap.com/)
|
||||||
|
* Copyright 2011-2018 The Bootstrap Authors
|
||||||
|
* Copyright 2011-2018 Twitter, Inc.
|
||||||
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||||
|
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
||||||
|
*/*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
|
||||||
|
/*# sourceMappingURL=bootstrap-reboot.min.css.map */
|
||||||
1
static/bootstrap-reboot.min.css.map
Normal file
1
static/bootstrap-reboot.min.css.map
Normal file
File diff suppressed because one or more lines are too long
9887
static/bootstrap.css
vendored
Normal file
9887
static/bootstrap.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
static/bootstrap.css.map
Normal file
1
static/bootstrap.css.map
Normal file
File diff suppressed because one or more lines are too long
7
static/bootstrap.min.css
vendored
Normal file
7
static/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
static/bootstrap.min.css.map
Normal file
1
static/bootstrap.min.css.map
Normal file
File diff suppressed because one or more lines are too long
7
static/custom.css
Normal file
7
static/custom.css
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.vertical-center {
|
||||||
|
min-height: 100%; /* Fallback for browsers do NOT support vh unit */
|
||||||
|
min-height: 100vh; /* These two lines are counted as one :-) */
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
48
templates/base.html
Normal file
48
templates/base.html
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link rel="stylesheet" href="/static/bootstrap.css">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.1/css/all.css" integrity="sha384-gfdkjb5BdAXd+lj+gudLWI+BXq4IuLW5IT+brZEZsLFm++aCMlF1V92rMkPaX4PP" crossorigin="anonymous">
|
||||||
|
<title>{% block title %}{% endblock %} - Estus Shots</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
{% set nav_bar = [
|
||||||
|
('/seasons', 'seasons', 'Seasons', 'fas fa-calendar'),
|
||||||
|
('/players', 'players', 'Players', 'fas fa-users'),
|
||||||
|
('/enemies', 'enemies', 'Enemies', 'fas fa-skull-crossbones'),
|
||||||
|
('/drinks', 'drinks', 'Drinks', 'fas fa-beer')
|
||||||
|
]-%}
|
||||||
|
<nav class="navbar navbar-expand-sm bg-dark navbar-dark">
|
||||||
|
<ul class="nav navbar-nav mr-auto">
|
||||||
|
{% for href, id, caption, icon in nav_bar %}
|
||||||
|
<li class="nav-item{% if id == active_page %} active{% endif %}">
|
||||||
|
<a class="nav-link" href="{{ href|e }}">
|
||||||
|
<i class="{{ icon|e }}"></i> {{ caption|e }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
<ul class="nav navbar-nav ml-auto">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/logout">Logout</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div>{% block content %}{% endblock %}</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{% block footer %}
|
||||||
|
© A product of D⁵: <a href="#">Durstiger Donnerstag Digital Drinking Divison</a>.
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
53
templates/drinks.html
Normal file
53
templates/drinks.html
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% set active_page = "drinks" %}
|
||||||
|
{% block title %}Drinks{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% if g.is_editor %}
|
||||||
|
<div class="btn-toolbar" role="toolbar">
|
||||||
|
<a class="btn btn-primary" href="/newdrink" role="button">New Drink</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if not model.drinks %}
|
||||||
|
There are no drinks.
|
||||||
|
{% else %}
|
||||||
|
<table class="table table-hover table-striped table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{% for prop, caption in model.columns %}
|
||||||
|
<th scope="col" class="col-sm-auto text-center">{{ caption }}</th>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if g.is_editor %}
|
||||||
|
{% for id, caption in model.controls %}
|
||||||
|
<th scope="col" class="col-sm-auto text-center">{{ caption }}</th>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for drink in model.drinks %}
|
||||||
|
<tr>
|
||||||
|
{% for prop, caption in model.columns %}
|
||||||
|
<td class="col-sm-auto text-center">{{ drink[prop] }}</td>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if g.is_editor %}
|
||||||
|
{% for name, caption in model.controls %}
|
||||||
|
<td class="col-sm-auto text-center">
|
||||||
|
<a class="btn btn-dark"
|
||||||
|
href="{% if name == 'edit'%}
|
||||||
|
/drinks/{{ drink.id }}
|
||||||
|
{% endif %}">
|
||||||
|
{{ caption }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
60
templates/editdrink.html
Normal file
60
templates/editdrink.html
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% set active_page = "drinks" %}
|
||||||
|
{% block title %}Drinks{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form class="needs-validation" novalidate action="/savedrink" method="post">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="id">ID</label>
|
||||||
|
<input name="id"
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="id"
|
||||||
|
value="{{ model['id'] }}"
|
||||||
|
readonly>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">Drink Name</label>
|
||||||
|
<input name="name"
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="name"
|
||||||
|
placeholder="Drink name..."
|
||||||
|
value="{{ model['name'] }}"
|
||||||
|
required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="drinkVol">Alcohol %</label>
|
||||||
|
<input name="vol"
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="drinkVol"
|
||||||
|
placeholder="Drink volume..."
|
||||||
|
value="{{ model['vol'] }}"
|
||||||
|
required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Submit</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Example starter JavaScript for disabling form submissions if there are invalid fields
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
window.addEventListener('load', function() {
|
||||||
|
// Fetch all the forms we want to apply custom Bootstrap validation styles to
|
||||||
|
let forms = document.getElementsByClassName('needs-validation');
|
||||||
|
// Loop over them and prevent submission
|
||||||
|
let validation = Array.prototype.filter.call(forms, function (form) {
|
||||||
|
form.addEventListener('submit', function (event) {
|
||||||
|
if (form.checkValidity() === false) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
form.classList.add('was-validated');
|
||||||
|
}, false);
|
||||||
|
});
|
||||||
|
}, false);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
79
templates/editplayer.html
Normal file
79
templates/editplayer.html
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% set active_page = "players" %}
|
||||||
|
{% block title %}Players{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form class="needs-validation" novalidate action="/saveplayer" method="post">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="playerId">ID</label>
|
||||||
|
<input name="id"
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="playerId"
|
||||||
|
value="{{ model['id'] }}"
|
||||||
|
readonly>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="newPlayerRealName">Real Name</label>
|
||||||
|
<input name="real_name"
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="newPlayerRealName"
|
||||||
|
placeholder="Enter name..."
|
||||||
|
value="{{ model['real_name'] }}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="newPlayerAlias">Player Alias</label>
|
||||||
|
<input name="alias"
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="newPlayerAlias"
|
||||||
|
placeholder="Enter alias..."
|
||||||
|
value="{{ model['alias'] }}"
|
||||||
|
required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="newPlayerHexID">Hex ID</label>
|
||||||
|
<input name="hex_id"
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="newPlayerHexID"
|
||||||
|
placeholder="Enter Hex ID..."
|
||||||
|
value="{{ model['hex_id'] }}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group form-check">
|
||||||
|
<input name="anon"
|
||||||
|
type="checkbox"
|
||||||
|
class="form-check-input"
|
||||||
|
id="newPlayerAnon"
|
||||||
|
value="True"
|
||||||
|
{% if model['anon'] %}
|
||||||
|
checked
|
||||||
|
{% endif %}>
|
||||||
|
<label class="form-check-label" for="newPlayerAnon">Anonymize (Show only player alias)</label>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Submit</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Example starter JavaScript for disabling form submissions if there are invalid fields
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
window.addEventListener('load', function() {
|
||||||
|
// Fetch all the forms we want to apply custom Bootstrap validation styles to
|
||||||
|
let forms = document.getElementsByClassName('needs-validation');
|
||||||
|
// Loop over them and prevent submission
|
||||||
|
let validation = Array.prototype.filter.call(forms, function (form) {
|
||||||
|
form.addEventListener('submit', function (event) {
|
||||||
|
if (form.checkValidity() === false) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
form.classList.add('was-validated');
|
||||||
|
}, false);
|
||||||
|
});
|
||||||
|
}, false);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
53
templates/enemies.html
Normal file
53
templates/enemies.html
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% set active_page = "enemies" %}
|
||||||
|
{% block title %}Enemies{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% if g.is_editor %}
|
||||||
|
<div class="btn-toolbar" role="toolbar">
|
||||||
|
<a class="btn btn-primary" href="/newenemy" role="button">New Enemy</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if not model.enemies %}
|
||||||
|
There are no enemies.
|
||||||
|
{% else %}
|
||||||
|
<table class="table table-hover table-striped table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{% for prop, caption in model.columns %}
|
||||||
|
<th scope="col" class="col-sm-auto text-center">{{ caption }}</th>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if g.is_editor %}
|
||||||
|
{% for id, caption in model.controls %}
|
||||||
|
<th scope="col" class="col-sm-auto text-center">{{ caption }}</th>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for item in model.enemies %}
|
||||||
|
<tr>
|
||||||
|
{% for prop, caption in model.columns %}
|
||||||
|
<td class="col-sm-auto text-center">{{ item[prop] }}</td>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if g.is_editor %}
|
||||||
|
{% for name, caption in model.controls %}
|
||||||
|
<td class="col-sm-auto text-center">
|
||||||
|
<a class="btn btn-dark"
|
||||||
|
href="{% if name == 'edit'%}
|
||||||
|
/enemies/{{ item.id }}
|
||||||
|
{% endif %}">
|
||||||
|
{{ caption }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
31
templates/login.html
Normal file
31
templates/login.html
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link rel="stylesheet" href="/static/bootstrap.css">
|
||||||
|
<link rel="stylesheet" href="/static/custom.css">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.1/css/all.css" integrity="sha384-gfdkjb5BdAXd+lj+gudLWI+BXq4IuLW5IT+brZEZsLFm++aCMlF1V92rMkPaX4PP" crossorigin="anonymous">
|
||||||
|
<title>Login - Estus Shots</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container vertical-center">
|
||||||
|
<div class="card text-center mx-auto" style="width: 15rem">
|
||||||
|
<div class="card-header">Login</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="post">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input type="password" id="password" name="password" class="form-control">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Enter</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
48
templates/players.html
Normal file
48
templates/players.html
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% set active_page = "players" %}
|
||||||
|
{% block title %}Players{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% if g.is_editor %}
|
||||||
|
<div class="btn-toolbar" role="toolbar">
|
||||||
|
<a class="btn btn-primary" href="/newplayer" role="button">New Player</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if not model.player_list %}
|
||||||
|
There are no players.
|
||||||
|
{% else %}
|
||||||
|
<table class="table table-hover table-striped table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{% for prop, caption in model.columns %}
|
||||||
|
<th scope="col" class="col-sm-auto text-center">{{ caption }}</th>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if g.is_editor %}
|
||||||
|
{% for name, caption in model.controls %}
|
||||||
|
<th scope="col" class="col-sm-auto text-center">{{ caption }}</th>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for player in model.player_list %}
|
||||||
|
<tr>
|
||||||
|
{% for prop, caption in model.columns %}
|
||||||
|
<td class="col-sm-auto text-center">{{ player[prop] }}</td>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if g.is_editor %}
|
||||||
|
{% for name, caption in model.controls %}
|
||||||
|
<td class="col-sm-auto text-center">
|
||||||
|
<a class="btn btn-dark" href="/players/{{ player['id'] }}">{{ caption }}</a>
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
7
templates/seasons.html
Normal file
7
templates/seasons.html
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% set active_page = "seasons" %}
|
||||||
|
{% block title %}Seasons{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
There are no seasons.
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user