mirror of
https://github.com/TitanEmbeds/Titan.git
synced 2024-12-25 06:27:03 +01:00
Basic dashboard support for Authenticated users
This commit is contained in:
parent
11c326b35d
commit
2e5f6fafbf
26
run.py
26
run.py
@ -3,5 +3,31 @@ from titanembeds.app import app
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import os
|
import os
|
||||||
|
from flask import jsonify, request
|
||||||
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' # Testing oauthlib
|
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' # Testing oauthlib
|
||||||
|
|
||||||
|
# Session viewer https://gist.github.com/babldev/502364a3f7c9bafaa6db
|
||||||
|
def decode_flask_cookie(secret_key, cookie_str):
|
||||||
|
import hashlib
|
||||||
|
from itsdangerous import URLSafeTimedSerializer
|
||||||
|
from flask.sessions import TaggedJSONSerializer
|
||||||
|
salt = 'cookie-session'
|
||||||
|
serializer = TaggedJSONSerializer()
|
||||||
|
signer_kwargs = {
|
||||||
|
'key_derivation': 'hmac',
|
||||||
|
'digest_method': hashlib.sha1
|
||||||
|
}
|
||||||
|
s = URLSafeTimedSerializer(secret_key, salt=salt, serializer=serializer, signer_kwargs=signer_kwargs)
|
||||||
|
return s.loads(cookie_str)
|
||||||
|
|
||||||
|
@app.route("/session")
|
||||||
|
def session():
|
||||||
|
cookie = request.cookies.get('session')
|
||||||
|
if cookie:
|
||||||
|
decoded = decode_flask_cookie(app.secret_key, request.cookies.get('session'))
|
||||||
|
else:
|
||||||
|
decoded = None
|
||||||
|
return jsonify(session_cookie=decoded)
|
||||||
|
|
||||||
|
|
||||||
app.run(host="0.0.0.0",port=3000,debug=True)
|
app.run(host="0.0.0.0",port=3000,debug=True)
|
||||||
|
@ -2,7 +2,8 @@ session:
|
|||||||
- unauthenticated T/F
|
- unauthenticated T/F
|
||||||
- user_id - random generated 4 digit discrim upon nick creation, otherwise the discord client id if authed
|
- user_id - random generated 4 digit discrim upon nick creation, otherwise the discord client id if authed
|
||||||
- username
|
- username
|
||||||
- user_keys {guildid: key, ...} - unused if authed
|
- avatar
|
||||||
|
- user_keys {guildid: key, ...} - replaced with discord token dict if authed
|
||||||
|
|
||||||
database:
|
database:
|
||||||
|
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
from config import config
|
from config import config
|
||||||
from database import db
|
from database import db
|
||||||
from flask import Flask, render_template, request, session, url_for, redirect
|
from flask import Flask, render_template, request, session, url_for, redirect, jsonify
|
||||||
import blueprints.api
|
import blueprints.api
|
||||||
import blueprints.user
|
import blueprints.user
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
os.chdir(config['app-location'])
|
os.chdir(config['app-location'])
|
||||||
app = Flask(__name__)
|
app = Flask(__name__, static_folder="static")
|
||||||
app.config['SQLALCHEMY_DATABASE_URI'] = config['database-uri']
|
app.config['SQLALCHEMY_DATABASE_URI'] = config['database-uri']
|
||||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # Suppress the warning/no need this on for now.
|
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # Suppress the warning/no need this on for now.
|
||||||
app.secret_key = config['app-secret']
|
app.secret_key = config['app-secret']
|
||||||
@ -27,14 +27,11 @@ def post_set_username(guildid, channelid):
|
|||||||
return redirect(url_for("embed_get", guildid=guildid, channelid=channelid))
|
return redirect(url_for("embed_get", guildid=guildid, channelid=channelid))
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def hello():
|
def index():
|
||||||
return "This page is not blank"
|
return render_template("index.html.jinja2")
|
||||||
|
|
||||||
@app.route("/embed/<guildid>/<channelid>")
|
@app.route("/embed/<guildid>/<channelid>")
|
||||||
def embed_get(guildid, channelid):
|
def embed_get(guildid, channelid):
|
||||||
if 'username' not in session:
|
if 'username' not in session:
|
||||||
return redirect(url_for("get_set_username", guildid=guildid, channelid=channelid))
|
return redirect(url_for("get_set_username", guildid=guildid, channelid=channelid))
|
||||||
return render_template("embed.html")
|
return render_template("embed.html")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
app.run(host="0.0.0.0",port=3000,debug=True)
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from titanembeds.database import db, Guilds, UnauthenticatedUsers, UnauthenticatedBans
|
from titanembeds.database import db, Guilds, UnauthenticatedUsers, UnauthenticatedBans, AuthenticatedUsers
|
||||||
from titanembeds.decorators import valid_session_required
|
from titanembeds.decorators import valid_session_required, discord_users_only
|
||||||
from titanembeds.discordrest import DiscordREST
|
from titanembeds.discordrest import DiscordREST
|
||||||
from flask import Blueprint, abort, jsonify, session, request
|
from flask import Blueprint, abort, jsonify, session, request
|
||||||
from sqlalchemy import and_
|
from sqlalchemy import and_
|
||||||
@ -141,8 +141,21 @@ def create_unauthenticated_user():
|
|||||||
status = {'banned': True}
|
status = {'banned': True}
|
||||||
return jsonify(status=status)
|
return jsonify(status=status)
|
||||||
|
|
||||||
|
@api.route("/new_guild", methods=["POST"])
|
||||||
|
@discord_users_only(api=True)
|
||||||
|
def post_new_guild():
|
||||||
|
pass
|
||||||
|
|
||||||
@api.route("/query_guild", methods=["GET"])
|
@api.route("/query_guild", methods=["GET"])
|
||||||
@valid_session_required(api=True)
|
@valid_session_required(api=True)
|
||||||
def query_guild():
|
def query_guild():
|
||||||
guild_id = request.args.get('guild_id')
|
guild_id = request.args.get('guild_id')
|
||||||
return jsonify(exists=check_guild_existance(guild_id))
|
return jsonify(exists=check_guild_existance(guild_id))
|
||||||
|
|
||||||
|
@api.route("/check_discord_authentication", methods=["GET"])
|
||||||
|
@discord_users_only(api=True)
|
||||||
|
def check_discord_authentication():
|
||||||
|
if not session['unauthenticated']:
|
||||||
|
return jsonify(error=False)
|
||||||
|
else:
|
||||||
|
return jsonify(error=True)
|
||||||
|
@ -1,35 +1,64 @@
|
|||||||
from flask import Blueprint, request, redirect, jsonify, abort, session
|
from flask import Blueprint, request, redirect, jsonify, abort, session, url_for, render_template
|
||||||
from requests_oauthlib import OAuth2Session
|
from requests_oauthlib import OAuth2Session
|
||||||
from config import config
|
from config import config
|
||||||
|
from titanembeds.decorators import discord_users_only
|
||||||
|
|
||||||
user = Blueprint("user", __name__)
|
user = Blueprint("user", __name__)
|
||||||
redirect_url = config['app-base-url'] + "/user/callback"
|
redirect_url = config['app-base-url'] + "/user/callback"
|
||||||
authorize_url = "https://discordapp.com/api/oauth2/authorize"
|
authorize_url = "https://discordapp.com/api/oauth2/authorize"
|
||||||
token_url = "https://discordapp.com/api/oauth2/token"
|
token_url = "https://discordapp.com/api/oauth2/token"
|
||||||
avatar_base_url = "https://cdn.discordapp.com/avatars/"
|
avatar_base_url = "https://cdn.discordapp.com/avatars/"
|
||||||
|
guild_icon_url = "https://cdn.discordapp.com/icons/"
|
||||||
|
|
||||||
def make_session(token=None, state=None, scope=None):
|
def make_authenticated_session(token=None, state=None, scope=None):
|
||||||
return OAuth2Session(
|
return OAuth2Session(
|
||||||
client_id=config['client-id'],
|
client_id=config['client-id'],
|
||||||
token=token,
|
token=token,
|
||||||
state=state,
|
state=state,
|
||||||
scope=scope,
|
scope=scope,
|
||||||
redirect_uri=redirect_url,
|
redirect_uri=request.url_root + "user/callback",
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_current_user():
|
def discordrest_from_user(endpoint):
|
||||||
token = session['discord_token']
|
token = session['user_keys']
|
||||||
discord = make_session(token=token)
|
discord = make_authenticated_session(token=token)
|
||||||
req = discord.get("https://discordapp.com/api/users/@me")
|
req = discord.get("https://discordapp.com/api/v6{}".format(endpoint))
|
||||||
|
return req
|
||||||
|
|
||||||
|
def get_current_authenticated_user():
|
||||||
|
req = discordrest_from_user("/users/@me")
|
||||||
if req.status_code != 200:
|
if req.status_code != 200:
|
||||||
abort(req.status_code)
|
abort(req.status_code)
|
||||||
user = req.json()
|
user = req.json()
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
def user_has_permission(permission, index):
|
||||||
|
return bool((int(permission) >> index) & 1)
|
||||||
|
|
||||||
|
def get_user_guilds():
|
||||||
|
req = discordrest_from_user("/users/@me/guilds")
|
||||||
|
return req
|
||||||
|
|
||||||
|
def get_user_managed_servers():
|
||||||
|
guilds = get_user_guilds().json()
|
||||||
|
filtered = []
|
||||||
|
for guild in guilds:
|
||||||
|
permission = guild['permissions'] # Manage Server, Ban Members, Kick Members
|
||||||
|
if guild['owner'] or user_has_permission(permission, 5) or user_has_permission(permission, 2) or user_has_permission(permission, 1):
|
||||||
|
filtered.append(guild)
|
||||||
|
return filtered
|
||||||
|
|
||||||
|
def generate_avatar_url(id, av):
|
||||||
|
return avatar_base_url + str(id) + '/' + str(av) + '.jpg'
|
||||||
|
|
||||||
|
def generate_guild_icon_url(id, hash):
|
||||||
|
return guild_icon_url + str(id) + "/" + str(hash) + ".jpg"
|
||||||
|
|
||||||
@user.route("/login_authenticated", methods=["GET"])
|
@user.route("/login_authenticated", methods=["GET"])
|
||||||
def login_authenticated():
|
def login_authenticated():
|
||||||
|
session["redirect"] = request.args.get("redirect")
|
||||||
scope = ['identify', 'guilds', 'guilds.join']
|
scope = ['identify', 'guilds', 'guilds.join']
|
||||||
discord = make_session(scope=scope)
|
discord = make_authenticated_session(scope=scope)
|
||||||
authorization_url, state = discord.authorization_url(
|
authorization_url, state = discord.authorization_url(
|
||||||
authorize_url,
|
authorize_url,
|
||||||
access_type="offline"
|
access_type="offline"
|
||||||
@ -41,27 +70,40 @@ def login_authenticated():
|
|||||||
def callback():
|
def callback():
|
||||||
state = session.get('oauth2_state')
|
state = session.get('oauth2_state')
|
||||||
if not state or request.values.get('error'):
|
if not state or request.values.get('error'):
|
||||||
return "state error"
|
return redirect(url_for('user.logout'))
|
||||||
discord = make_session(state=state)
|
discord = make_authenticated_session(state=state)
|
||||||
discord_token = discord.fetch_token(
|
discord_token = discord.fetch_token(
|
||||||
token_url,
|
token_url,
|
||||||
client_secret=config['client-secret'],
|
client_secret=config['client-secret'],
|
||||||
authorization_response=request.url)
|
authorization_response=request.url)
|
||||||
if not discord_token:
|
if not discord_token:
|
||||||
return "no discord token"
|
return redirect(url_for('user.logout'))
|
||||||
session['discord_token'] = discord_token
|
session['user_keys'] = discord_token
|
||||||
return str(discord_token)
|
session['unauthenticated'] = False
|
||||||
|
user = get_current_authenticated_user()
|
||||||
|
session['user_id'] = user['id']
|
||||||
|
session['username'] = user['username']
|
||||||
|
session['avatar'] = generate_avatar_url(user['id'], user['avatar'])
|
||||||
|
if session["redirect"]:
|
||||||
|
return redirect(session["redirect"])
|
||||||
|
return redirect(url_for("user.dashboard"))
|
||||||
|
|
||||||
@user.route('/logout', methods=["GET"])
|
@user.route('/logout', methods=["GET"])
|
||||||
def logout():
|
def logout():
|
||||||
session.clear()
|
session.clear()
|
||||||
return "logged out"
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
|
@user.route("/dashboard")
|
||||||
|
@discord_users_only()
|
||||||
|
def dashboard():
|
||||||
|
return render_template("dashboard.html.jinja2", servers=get_user_managed_servers(), icon_generate=generate_guild_icon_url)
|
||||||
|
|
||||||
|
@user.route("/administrate_guild/<guild_id>")
|
||||||
|
@discord_users_only()
|
||||||
|
def administrate_guild(guild_id):
|
||||||
|
return str(guild_id)
|
||||||
|
|
||||||
@user.route('/me')
|
@user.route('/me')
|
||||||
|
@discord_users_only()
|
||||||
def me():
|
def me():
|
||||||
return jsonify(user=get_current_user())
|
return jsonify(user=get_current_authenticated_user())
|
||||||
|
|
||||||
@user.route('/avatar')
|
|
||||||
def avatar():
|
|
||||||
user = get_current_user()
|
|
||||||
return avatar_base_url + str(user['id']) + '/' + str(user['avatar']) + '.jpg'
|
|
||||||
|
@ -1,17 +1,28 @@
|
|||||||
from functools import wraps
|
from functools import wraps
|
||||||
from flask import url_for, redirect, session, jsonify
|
from flask import url_for, redirect, session, jsonify, abort
|
||||||
|
|
||||||
def valid_session_required(api=False):
|
def valid_session_required(api=False):
|
||||||
def decorator(f):
|
def decorator(f):
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
def decorated_function(*args, **kwargs):
|
def decorated_function(*args, **kwargs):
|
||||||
if 'unauthenticated' not in session or 'user_id' not in session or 'username' not in session:
|
if 'unauthenticated' not in session or 'user_id' not in session or 'username' not in session:
|
||||||
session.clear()
|
|
||||||
if api:
|
if api:
|
||||||
return jsonify(error=True, message="Unauthenticated session"), 403
|
return jsonify(error=True, message="Unauthenticated session"), 403
|
||||||
redirect(url_for('index'))
|
redirect(url_for('user.logout'))
|
||||||
if session['unauthenticated'] and 'user_keys' not in session:
|
if session['unauthenticated'] and 'user_keys' not in session:
|
||||||
session['user_keys'] = {}
|
session['user_keys'] = {}
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
return decorated_function
|
return decorated_function
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
def discord_users_only(api=False):
|
||||||
|
def decorator(f):
|
||||||
|
@wraps(f)
|
||||||
|
def decorated_function(*args, **kwargs):
|
||||||
|
if 'unauthenticated' not in session or session['unauthenticated']:
|
||||||
|
if api:
|
||||||
|
return jsonify(error=True, message="Not logged in as a discord user"), 403
|
||||||
|
return redirect(url_for("user.login_authenticated"))
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
return decorated_function
|
||||||
|
return decorator
|
||||||
|
@ -105,6 +105,11 @@ class DiscordREST:
|
|||||||
# Guild
|
# Guild
|
||||||
#####################
|
#####################
|
||||||
|
|
||||||
|
def get_guild(self, guild_id):
|
||||||
|
_endpoint = "/guilds/{guild_id}".format(guild_id=guild_id)
|
||||||
|
r = self.request("GET", _endpoint)
|
||||||
|
return r
|
||||||
|
|
||||||
def get_guild_channels(self, guild_id):
|
def get_guild_channels(self, guild_id):
|
||||||
_endpoint = "/guilds/{guild_id}/channels".format(guild_id=guild_id)
|
_endpoint = "/guilds/{guild_id}/channels".format(guild_id=guild_id)
|
||||||
r = self.request("GET", _endpoint)
|
r = self.request("GET", _endpoint)
|
||||||
|
52
titanembeds/static/css/style.css
Normal file
52
titanembeds/static/css/style.css
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
html {
|
||||||
|
background-color: #7986cb;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
min-height: 100vh;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
flex: 1 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
background-color: #3f51b5;
|
||||||
|
background: linear-gradient(rgba(63, 81, 181, 1), rgba(255,0,0,0));
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-footer {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 992px) {
|
||||||
|
nav .brand-logo {
|
||||||
|
left: 10%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
background-color: #303f9f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
background-color: #3f51b5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:focus {
|
||||||
|
background-color: #536dfe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar_menu {
|
||||||
|
background-size: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center_content {
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
32
titanembeds/templates/dashboard.html.jinja2
Normal file
32
titanembeds/templates/dashboard.html.jinja2
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{% extends 'site_layout.html.jinja2' %}
|
||||||
|
{% block title %}Dashboard{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>User Dashboard</h1>
|
||||||
|
<p class="flow-text">Select a server to configure Titan Embeds.</p>
|
||||||
|
<p>*List missing some servers? It's because you must have either <strong>Manage Server</strong>, <strong>Kick Members</strong>, or <strong>Ban Members</strong> permissions to modify embed settings.</p>
|
||||||
|
<div class="row">
|
||||||
|
{% for server in servers %}
|
||||||
|
<div class="col s6">
|
||||||
|
<div class="card-panel grey lighten-5 z-depth-1">
|
||||||
|
<div class="row valign-wrapper">
|
||||||
|
<div class="col s2">
|
||||||
|
{% if server.icon %}
|
||||||
|
<img src="{{ icon_generate(server.id, server.icon) }}" alt="" class="circle responsive-img">
|
||||||
|
{% else %}
|
||||||
|
<span class="black-text">No icon :(</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col s10">
|
||||||
|
<span class="black-text">
|
||||||
|
<p class="flow-text">{{ server.name }}</p>
|
||||||
|
<br>
|
||||||
|
<a class="waves-effect waves-light btn" href="{{url_for('user.administrate_guild', guild_id=server['id'])}}">Modify</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
17
titanembeds/templates/index.html.jinja2
Normal file
17
titanembeds/templates/index.html.jinja2
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{% extends 'site_layout.html.jinja2' %}
|
||||||
|
{% block title %}Index{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1 class="center-align">Embed Discord like a<br><strong>true Titan</strong></h1>
|
||||||
|
<p class="flow-text center-align">Add <strong>Titan</strong> to your discord server to create your own personalized chat embed!</p>
|
||||||
|
<a class="waves-effect waves-light btn btn-large center_content" href="{{ url_for('user.dashboard') }}">Start here!</a>
|
||||||
|
<br /><br />
|
||||||
|
<div style="display: flex;align-items: center;">
|
||||||
|
<video preload="true" loop="" style="width:100%; border-radius: 10px;">
|
||||||
|
<source src="http://mee6.xyz/static/mee6.mp4" type="video/mp4">
|
||||||
|
<source src="http://mee6.xyz/static/mee6.webm" type="video/webm; codecs=vp8, vorbis">
|
||||||
|
<source type="video/ogg; codecs=theora, vorbis" src="http://mee6.xyz/static/mee6.ogg">
|
||||||
|
Your browser does not support the video tag.
|
||||||
|
</video> <!-- TODO: Fix video and add autoplay -->
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
55
titanembeds/templates/site_layout.html.jinja2
Normal file
55
titanembeds/templates/site_layout.html.jinja2
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<!--Import Google Icon Font-->
|
||||||
|
<link href="http://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||||
|
<!--Import materialize.css-->
|
||||||
|
<link type="text/css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.98.1/css/materialize.min.css" integrity="sha256-6DQKO56c9MZL0LAc7QNtxqJyqSa3rS9Gq5FVcIhtA+w=" crossorigin="anonymous" media="screen,projection"/>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||||
|
|
||||||
|
<!--Let browser know website is optimized for mobile-->
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
|
||||||
|
<title>{% block title %}{% endblock %} - Titan Embeds for Discord</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
{% if session['unauthenticated'] is defined and not session['unauthenticated'] %}
|
||||||
|
<ul id="menu_dropdown" class="dropdown-content">
|
||||||
|
<li><a href="{{ url_for('user.dashboard') }}">Dashboard</a></li>
|
||||||
|
<li class="divider"></li>
|
||||||
|
<li><a href="{{ url_for('user.logout') }}">Logout</a></li>
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<nav>
|
||||||
|
<div class="nav-wrapper container">
|
||||||
|
<a href="/" class="brand-logo"><b>Titan</b>Embeds</a>
|
||||||
|
<ul id="nav-mobile" class="right">
|
||||||
|
<li><a href="#" class="waves-effect btn z-depth-3">Visit Us!</a></li> <!-- TODO: Add discord guild invite -->
|
||||||
|
{% if session['unauthenticated'] is defined and not session['unauthenticated'] %}
|
||||||
|
<li><a id="menu_drop" data-activates="menu_dropdown" class="waves-effect btn z-depth-3 btn-floating dropdown-button avatar_menu" style='background-image: url(" {{ session['avatar'] }} ")'></a></li>
|
||||||
|
{% else %}
|
||||||
|
<li><a href="{{ url_for('user.login_authenticated') }}" class="waves-effect btn z-depth-3">Login</a></li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="container">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<footer class="page-footer">
|
||||||
|
<div class="footer-copyright">
|
||||||
|
<div class="container">
|
||||||
|
A project by EndenDragon
|
||||||
|
<a class="grey-text text-lighten-4 right" href="https://github.com/EndenDragon/Titan">GitHub Repo</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<!--Import jQuery before materialize.js-->
|
||||||
|
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
|
||||||
|
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.98.1/js/materialize.min.js" integrity="sha256-ToPQhpo/E89yaCd7+V8LUCjobNRkjilRXfho6x3twLU=" crossorigin="anonymous"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user