first commit

This commit is contained in:
usernames122
2025-08-10 23:42:02 +02:00
commit 877a246134
24 changed files with 957 additions and 0 deletions

203
.gitignore vendored Normal file
View File

@@ -0,0 +1,203 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[codz]
*$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/
.webassets-cache
# 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
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.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
#poetry.toml
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
#pdm.lock
#pdm.toml
.pdm-python
.pdm-build/
# pixi
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
#pixi.lock
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
# in the .venv directory. It is recommended not to include this directory in version control.
.pixi
# 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
.env
.envrc
.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/
# Abstra
# Abstra is an AI-powered process automation framework.
# Ignore directories containing user credentials, local state, and settings.
# Learn more at https://abstra.io/docs
.abstra/
# Visual Studio Code
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
# and can be added to the global gitignore or merged into this file. However, if you prefer,
# you could uncomment the following to ignore the entire vscode folder
# .vscode/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
# Marimo
marimo/_static/
marimo/_lsp/
__marimo__/
# Streamlit
.streamlit/secrets.toml

44
app.py Normal file
View File

@@ -0,0 +1,44 @@
import os
import importlib
from flask import Flask, Blueprint
from models import db
import secrets
app = Flask(__name__, static_folder='static', static_url_path='/static')
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
if os.path.isfile("secret.key"):
with open("secret.key", "r") as f:
app.config['SECRET_KEY'] = f.read().strip()
else:
app.config['SECRET_KEY'] = secrets.token_hex(16)
with open("secret.key", "w") as f:
f.write(app.config['SECRET_KEY'])
def register_blueprints(app, package_name, package_path):
"""
Auto-register all blueprints found in the package folder.
Supports files like index.py as well.
Each module must expose a blueprint named `<something>_bp`.
"""
for filename in os.listdir(package_path):
# Accept .py files including index.py, except __init__.py
if filename.endswith('.py') and filename != '__init__.py':
module_name = filename[:-3] # strip .py
module = importlib.import_module(f"{package_name}.{module_name}")
# Register all blueprint instances in the module
for attr_name in dir(module):
attr = getattr(module, attr_name)
if isinstance(attr, Blueprint):
app.register_blueprint(attr)
register_blueprints(app, 'pages', os.path.join(os.path.dirname(__file__), 'pages'))
db.init_app(app)
with app.app_context():
db.create_all() # if you want to create tables
if __name__ == '__main__':
app.run(debug=True)

3
goodrun.sh Normal file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
source ./.venv/bin/activate
gunicorn -w 4 -b 0.0.0.0:8000 app:app

17
models.py Normal file
View File

@@ -0,0 +1,17 @@
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password = db.Column(db.String(128), nullable=False) # hashed password storage
register_time = db.Column(db.DateTime, nullable=False)
class Finding(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(120), nullable=False)
path = db.Column(db.String(120), nullable=False) # Path on laminax.org
find_time = db.Column(db.DateTime, nullable=False)
found_by = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
content_preview = db.Column(db.Text, nullable=True) # Scraped preview of the finding

122
pages/findings.py Normal file
View File

@@ -0,0 +1,122 @@
from flask import Blueprint, render_template
from models import Finding, User
from sqlalchemy import desc
findings_bp = Blueprint('findings', __name__, url_prefix='/findings')
@findings_bp.route('/')
def latest_findings():
latest = Finding.query.order_by(desc(Finding.find_time)).limit(20).all()
# Eager load user data if needed
user_map = {u.id: u for u in User.query.filter(User.id.in_([f.found_by for f in latest])).all()}
return render_template('latest_findings.html', findings=latest, user_map=user_map)
@findings_bp.route('/<int:finding_id>')
def finding_detail(finding_id):
finding = Finding.query.get_or_404(finding_id)
user = User.query.get(finding.found_by)
return render_template('finding_detail.html', finding=finding, user=user)
import requests
from flask import Blueprint, render_template, request, session, flash, redirect, url_for
from datetime import datetime
from bs4 import BeautifulSoup
from models import db, Finding
@findings_bp.route('/create', methods=['GET', 'POST'])
def create_finding():
if not session.get('loggedin'):
flash("Please log in to create a finding.", "warning")
return redirect(url_for('login.login'))
if request.method == 'POST':
path = request.form.get('path', '').strip()
lorekey = request.form.get('lorekey', '').strip()
# Validate inputs
if not path and not lorekey:
flash("Title, Path, and Lorekey are required.", "danger")
return render_template('create_finding.html', path=path, lorekey=lorekey)
# Validate path exists on laminax.org (non-404)
if path:
try:
path_res = requests.get(f'https://laminax.org/{path}')
if path_res.status_code == 404:
flash(f"The path '{path}' does not exist on laminax.org.", "danger")
return render_template('create_finding.html', path=path, lorekey=lorekey)
else:
soup = BeautifulSoup(path_res.text, 'html.parser')
for hr in soup.find_all('hr'):
hr.replace_with('----------')
content_text = soup.get_text(separator='\n')
content_text = soup.get_text(separator='\n')
# Get title element
title = (soup.title.string if soup.title else None) or "No title found"
# Save finding
new_finding = Finding(
title=f'https://laminax.org/{path}',
path=f'https://laminax.org/{path}',
find_time=datetime.utcnow(),
found_by=session.get('id'),
content_preview=content_text
)
db.session.add(new_finding)
db.session.commit()
flash("Finding created successfully!", "success")
return redirect("/findings/"+str(new_finding.id)) # Resort to manually redirecting for now
except Exception as e:
flash(f"Error validating path: {e}", "danger")
return render_template('create_finding.html', path=path, lorekey=lorekey)
# Check lorekey with external service
if lorekey:
try:
res = requests.post('https://worker.laminax.org/check-password', json={"password": lorekey})
if res.ok:
data = res.json()
if data.get('redirect'):
redirect_url = data['redirect']
# Fetch redirect page content
page_res = requests.get(redirect_url)
title = None
if page_res.ok:
# Parse html and replace all <hr> with 10 dashes using bs4
soup = BeautifulSoup(page_res.text, 'html.parser')
for hr in soup.find_all('hr'):
hr.replace_with('----------')
content_text = soup.get_text(separator='\n')
# Get title element
title = (soup.title.string if soup.title else None) or "No title found"
else:
content_text = None
title = "Unable to fetch redirect page content."
# Save finding
new_finding = Finding(
title=redirect_url,
path=redirect_url,
find_time=datetime.utcnow(),
found_by=session.get('id'),
content_preview=content_text
)
db.session.add(new_finding)
db.session.commit()
flash("Finding created successfully!", "success")
return redirect(url_for('findings.finding_detail', finding_id=new_finding.id))
else:
flash("Lorekey check failed or no redirect returned.", "danger")
elif res.status_code == 401:
flash("Invalid Lorekey provided.", "danger")
else:
flash("Lorekey service error, try again later.", "danger")
except Exception as e:
flash(f"An error occurred: {e}", "danger")
# GET or fallback render
return render_template('create_finding.html')

7
pages/index.py Normal file
View File

@@ -0,0 +1,7 @@
from flask import Blueprint, render_template
from models import db, User
index_bp = Blueprint('index', __name__)
@index_bp.route('/')
def index():
return render_template('home.html',users=User.query.limit(20).all()) # or your index page template

56
pages/login.py Normal file
View File

@@ -0,0 +1,56 @@
from flask import Blueprint, render_template, request, redirect, url_for, session
from werkzeug.security import check_password_hash
from models import db, User
login_bp = Blueprint('login', __name__, url_prefix='/login')
@login_bp.route('/', methods=['GET', 'POST'])
def login():
if session.get('loggedin'):
return redirect(url_for('index.index'))
username = ""
username_err = ""
password_err = ""
login_err = ""
if request.method == 'POST':
username = request.form.get('username', '').strip()
password = request.form.get('password', '').strip()
if not username:
username_err = "Please enter username."
if not password:
password_err = "Please enter your password."
if not username_err and not password_err:
# Admin bypass (same as before) but don't do this in production!
if False: # username == "adm" and password == "dont add this in please":
session['loggedin'] = True
session['id'] = -1
session['username'] = "Admin"
return redirect(url_for('index.index'))
# Query User via SQLAlchemy
user = User.query.filter_by(username=username).first()
if user:
# Here you need to store hashed passwords somewhere
# Your User model doesn't have a password field yet, so let's assume:
# You should add it like: password = db.Column(db.String(128), nullable=False)
# For now, assuming you have a password attribute
if hasattr(user, 'password') and check_password_hash(user.password, password):
session['loggedin'] = True
session['id'] = user.id
session['username'] = user.username
return redirect(url_for('index.index'))
else:
login_err = "Invalid username or password."
else:
login_err = "Invalid username or password."
return render_template('login.html',
username=username,
username_err=username_err,
password_err=password_err,
login_err=login_err)

8
pages/logout.py Normal file
View File

@@ -0,0 +1,8 @@
from flask import Blueprint, session, redirect, url_for
logout_bp = Blueprint('logout', __name__)
@logout_bp.route('/logout')
def logout():
session.clear() # Clear all session data
return redirect(url_for('login.login')) # Redirect to login page

28
pages/profile.py Normal file
View File

@@ -0,0 +1,28 @@
from flask import Blueprint, render_template, session, redirect, url_for
from models import User, Finding
profile_bp = Blueprint('profile', __name__, url_prefix='/profile')
@profile_bp.route('/')
def my_findings():
# Check if user is logged in
if not session.get('loggedin'):
return redirect(url_for('login.login'))
user_id = session.get('id')
user = User.query.get(user_id)
if not user:
return redirect(url_for('login.login'))
# Get all findings by this user, exclude content_preview
findings = Finding.query.filter_by(found_by=user_id).all()
return render_template('profile.html', user=user, findings=findings)
@profile_bp.route('/get/<int:id>', methods=['GET'])
def view_profile(id):
user = User.query.get(id)
if not user:
return "User not found. Please try again later.",
findings = Finding.query.filter_by(found_by=id).all()
return render_template('view_profile.html', user=user, findings=findings)

68
pages/register.py Normal file
View File

@@ -0,0 +1,68 @@
import re
from datetime import datetime
from flask import Blueprint, render_template, request, redirect, url_for, flash
from werkzeug.security import generate_password_hash
from models import db, User
register_bp = Blueprint('register', __name__, url_prefix='/register')
@register_bp.route('/', methods=['GET', 'POST'])
def register():
username = ''
password = ''
confirm_password = ''
username_err = ''
password_err = ''
confirm_password_err = ''
if request.method == 'POST':
username = request.form.get('username', '').strip()
password = request.form.get('password', '').strip()
confirm_password = request.form.get('confirm_password', '').strip()
# Validate username
if not username:
username_err = "Please enter a username."
elif not re.match(r'^[a-zA-Z0-9_]+$', username):
username_err = "Username can only contain letters, numbers, and underscores."
else:
# Check if username already exists
if User.query.filter_by(username=username).first():
username_err = "This username is already taken."
# Validate password
if not password:
password_err = "Please enter a password."
elif len(password) < 6:
password_err = "Password must have at least 6 characters."
# Validate confirm password
if not confirm_password:
confirm_password_err = "Please confirm password."
elif password != confirm_password:
confirm_password_err = "Password did not match."
# If no errors, insert new user
if not username_err and not password_err and not confirm_password_err:
hashed_password = generate_password_hash(password)
new_user = User(
username=username,
password=hashed_password,
register_time=datetime.utcnow()
)
try:
db.session.add(new_user)
db.session.commit()
flash("Registration successful! Please login.", "success")
return redirect(url_for('login.login'))
except Exception as e:
db.session.rollback()
flash("Oops! Something went wrong. Please try again.", "danger")
return render_template('register.html',
username=username,
password=password,
confirm_password=confirm_password,
username_err=username_err,
password_err=password_err,
confirm_password_err=confirm_password_err)

7
requirements.txt Normal file
View File

@@ -0,0 +1,7 @@
flask
flask-cors
flask_sqlalchemy
requests
beautifulsoup4
lxml
python-dotenv

1
secret.key Normal file
View File

@@ -0,0 +1 @@
aea68160bc8c772c266e5dbdb40b2023

137
static/main.css Normal file
View File

@@ -0,0 +1,137 @@
body {
margin: 0;
font-family: Arial, Helvetica, sans-serif;
}
/* Navigation styles you already have */
.topnav {
overflow: hidden;
background-color: #333;
}
.topnav a {
float: left;
color: #f2f2f2;
text-align: center;
padding: 14px 16px;
text-decoration: none;
font-size: 17px;
}
.topnav a:hover {
background-color: #ddd;
color: black;
}
.topnav a.active {
background-color: #04AA6D;
color: white;
}
/* Form container wrapper */
.wrapper {
max-width: 480px;
margin: 30px auto;
padding: 20px;
background-color: #f8f9fa;
border-radius: 6px;
box-shadow: 0 0 8px rgba(0,0,0,0.1);
}
/* Form group spacing */
.form-group {
margin-bottom: 1rem;
}
/* Form labels */
label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
color: #333;
}
/* Form inputs */
input[type="text"],
input[type="password"],
input[type="email"],
textarea,
select {
width: 100%;
padding: 8px 12px;
border: 1px solid #ced4da;
border-radius: 4px;
font-size: 1rem;
transition: border-color 0.2s ease-in-out;
box-sizing: border-box;
}
input[type="text"]:focus,
input[type="password"]:focus,
input[type="email"]:focus,
textarea:focus,
select:focus {
border-color: #04AA6D;
outline: none;
box-shadow: 0 0 4px #04AA6D;
}
/* Buttons */
button.btn, input[type="submit"], input[type="reset"] {
padding: 10px 18px;
font-size: 1rem;
border-radius: 4px;
border: none;
cursor: pointer;
color: white;
background-color: #04AA6D;
transition: background-color 0.2s ease-in-out;
}
button.btn:hover,
input[type="submit"]:hover,
input[type="reset"]:hover {
background-color: #039a5b;
}
/* Secondary button */
input[type="reset"] {
background-color: #6c757d;
margin-left: 8px;
}
input[type="reset"]:hover {
background-color: #565e64;
}
/* Form feedback text */
.form-text {
font-size: 0.875rem;
color: #6c757d;
}
/* Flash message styling */
.flash-message {
margin: 1rem 0;
padding: 12px 20px;
border-radius: 4px;
font-weight: 600;
}
.flash-message.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.flash-message.danger {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.flash-message.warning {
background-color: #fff3cd;
color: #856404;
border: 1px solid #ffeeba;
}

31
templates/base.html Normal file
View File

@@ -0,0 +1,31 @@
<!-- base.html -->
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}My Site{% endblock %}</title>
<link rel="stylesheet" href="/static/main.css">
</head>
<body>
{% include 'topbar.html' %}
<!-- Flashed messages -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="flashed-messages">
{% for category, message in messages %}
<div class="alert alert-{{ category }}">{{ message }}</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<!-- This block is the “Outlet” equivalent -->
<main>
{% block content %}
<!-- Child templates will fill this -->
{% endblock %}
</main>
</body>
</html>

View File

@@ -0,0 +1,22 @@
{% extends "base.html" %}
{% block title %}Create Finding{% endblock %}
{% block content %}
<h2>Create a New Finding</h2>
<form method="post" action="{{ url_for('findings.create_finding') }}">
<div class="form-group">
<label for="path">Path (on laminax.org, optional if lorekey is provided)</label>
<input id="path" name="path" class="form-control" type="text" value="{{ path or '' }}">
<small class="form-text text-muted">Example: some/path/here</small>
</div>
<div class="form-group">
<label for="lorekey">Lorekey</label>
<input id="lorekey" name="lorekey" class="form-control" type="text" value="{{ lorekey or '' }}">
</div>
<button type="submit" class="btn btn-primary">Create Finding</button>
</form>
{% endblock %}

View File

@@ -0,0 +1,14 @@
{% extends "base.html" %}
{% block title %}Finding: {{ finding.title }}{% endblock %}
{% block content %}
<h2>{{ finding.title }}</h2>
<p><strong>Path:</strong> <a href="{{ finding.path }}">{{ finding.path }}</a></p>
<p><strong>Found by:</strong> {{ user.username }}</p>
<p><strong>Found on:</strong> {{ finding.find_time.strftime('%Y-%m-%d %H:%M') }}</p>
<hr>
<h3>Content Preview</h3>
<pre>{{ finding.content_preview or 'No preview available.' }}</pre>
{% endblock %}

0
templates/findings.html Normal file
View File

18
templates/home.html Normal file
View File

@@ -0,0 +1,18 @@
<!-- home.html -->
{% extends "base.html" %}
{% block title %}Home - LAMINAX.ORG ARG findings{% endblock %}
{% block content %}
<h1>Welcome to ARG findings!</h1>
<p>This site is dedicated to sharing the ARG findings at <a href="https://laminax.org">laminax.org</a></p>
<!--Render latest users-->
<div class="latest-users">
<h2>Latest Users</h2>
<ul>
{% for user in users %}
<li>{{ user.username }}</li>
{% endfor %}
</ul>
</div>
{% endblock %}

View File

@@ -0,0 +1,21 @@
{% extends "base.html" %}
{% block title %}Latest Findings{% endblock %}
{% block content %}
<h2>Latest Findings</h2>
{% if findings %}
<ul>
{% for f in findings %}
<li>
<a href="{{ url_for('findings.finding_detail', finding_id=f.id) }}">{{ f.title }}</a>
by {{ user_map[f.found_by].username if f.found_by in user_map else 'Unknown' }}
— {{ f.find_time.strftime('%Y-%m-%d %H:%M') }}
</li>
{% endfor %}
</ul>
{% else %}
<p>No findings yet.</p>
{% endif %}
{% endblock %}

41
templates/login.html Normal file
View File

@@ -0,0 +1,41 @@
{% extends "base.html" %}
{% block title %}Login{% endblock %}
{% block content %}
<div class="wrapper" style="max-width:360px; padding:20px; margin:auto;">
<h2>Login</h2>
<p>Please fill in your credentials to login.</p>
{% if login_err %}
<div class="alert alert-danger">{{ login_err }}</div>
{% endif %}
<form method="post" action="{{ url_for('login.login') }}">
<div class="form-group">
<label>Username</label>
<input type="text" name="username"
class="form-control {% if username_err %}is-invalid{% endif %}"
value="{{ username }}">
{% if username_err %}
<div class="invalid-feedback">{{ username_err }}</div>
{% endif %}
</div>
<div class="form-group">
<label>Password</label>
<input type="password" name="password"
class="form-control {% if password_err %}is-invalid{% endif %}">
{% if password_err %}
<div class="invalid-feedback">{{ password_err }}</div>
{% endif %}
</div>
<div class="form-group">
<input type="submit" class="btn btn-primary" value="Login">
</div>
<p>Don't have an account? <a href="{{ url_for('register.register') }}">Sign up now</a>.</p>
</form>
</div>
{% endblock %}

23
templates/profile.html Normal file
View File

@@ -0,0 +1,23 @@
{% extends "base.html" %}
{% block title %}My Findings{% endblock %}
{% block content %}
<h2>My Findings</h2>
<p>Welcome, {{ user.username }}!</p>
{% if findings %}
<ul>
{% for finding in findings %}
<li>
<strong>{{ finding.title }}</strong>
Path: <a href="/findings/{{ finding.id }}">{{ finding.path }}</a>
Found on: {{ finding.find_time.strftime('%Y-%m-%d %H:%M') }}
</li>
{% endfor %}
</ul>
{% else %}
<p>You have no findings yet.</p>
{% endif %}
{% endblock %}

49
templates/register.html Normal file
View File

@@ -0,0 +1,49 @@
{% extends "base.html" %}
{% block title %}Sign Up{% endblock %}
{% block content %}
<div class="wrapper" style="max-width:360px; padding:20px; margin:auto;">
<h2>Sign Up</h2>
<p>Please fill this form to create an account.</p>
<form method="post" action="{{ url_for('register.register') }}">
<div class="form-group">
<label>Username</label>
<input type="text" name="username"
class="form-control {% if username_err %}is-invalid{% endif %}"
value="{{ username }}">
{% if username_err %}
<div class="invalid-feedback">{{ username_err }}</div>
{% endif %}
</div>
<div class="form-group">
<label>Password</label>
<input type="password" name="password"
class="form-control {% if password_err %}is-invalid{% endif %}"
value="{{ password }}">
{% if password_err %}
<div class="invalid-feedback">{{ password_err }}</div>
{% endif %}
</div>
<div class="form-group">
<label>Confirm Password</label>
<input type="password" name="confirm_password"
class="form-control {% if confirm_password_err %}is-invalid{% endif %}"
value="{{ confirm_password }}">
{% if confirm_password_err %}
<div class="invalid-feedback">{{ confirm_password_err }}</div>
{% endif %}
</div>
<div class="form-group">
<input type="submit" class="btn btn-primary" value="Submit">
<input type="reset" class="btn btn-secondary ml-2" value="Reset">
</div>
<p>Already have an account? <a href="{{ url_for('login.login') }}">Login here</a>.</p>
</form>
</div>
{% endblock %}

15
templates/topbar.html Normal file
View File

@@ -0,0 +1,15 @@
<div class="topnav">
<a class="active" href="/">Home</a>
<a href="{{ url_for('findings.latest_findings') }}">Latest findings</a>
<a href="https://laminax.co">Main site</a>
<!-- Additional links for login/register if not logged in, else show user profile and logout -->
{% if session.get('loggedin') %}
<a href="{{ url_for('logout.logout') }}">Log out</a>
<a href="{{ url_for('profile.my_findings') }}">My Findings</a>
<a href="{{ url_for('findings.create_finding') }}">Add Finding</a>
<a href="/profile/get/{{ session.get('id') }}">My Profile</a>
{% else %}
<a href="{{ url_for('login.login') }}">Login</a>
<a href="{{ url_for('register.register') }}">Register</a>
{% endif %}
</div>

View File

@@ -0,0 +1,22 @@
{% extends "base.html" %}
{% block title %}My Findings{% endblock %}
{% block content %}
<h1>{{ user.username }}'s profile:</h1>
<p>Their Findings</p>
{% if findings %}
<ul>
{% for finding in findings %}
<li>
<strong>{{ finding.title }}</strong>
Path: <a href="/findings/{{ finding.id }}">{{ finding.path }}</a>
Found on: {{ finding.find_time.strftime('%Y-%m-%d %H:%M') }}
</li>
{% endfor %}
</ul>
{% else %}
<p>You have no findings yet.</p>
{% endif %}
{% endblock %}