Big update to login and other logic

This commit is contained in:
maximstewart 2021-02-13 19:17:11 -06:00
parent 7f9466f4f9
commit b86eb15bde
23 changed files with 587 additions and 201 deletions

143
.gitignore vendored Normal file
View File

@ -0,0 +1,143 @@
*.db
*.pyc
app.pid
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.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
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.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/

View File

@ -4,8 +4,14 @@ Dropper is an uploading/downloading application to push and pull data from devic
# Notes # Notes
* Need python 2+ * Need python 2+
* Set the fields in static/google-api-data.json file to use the google drive picker api. * Set the fields in static/google-api-data.json file to use the google drive picker api.
* You will need Keycloak setup to use this application.
* DNS over HTTPS can affect hosts file usage so make sure to disable that if using Firefox and editing hosts file.
``` NOTE: These will get set from loadPicker... # Setup
You will need Keycloak setup to use this application. The file 'client_secrets.json' has the predefined structure setup so you can use it for reference- modify accordingly. If you use the same realms and clients in Keycloak, you'll still need to change the 'client_secret' key; the one shown is an example of what you need to get from Keycloak (CHANGE and KEEP this SECRET if using on a public facing site!). In addition, use the hosts file on your computer to setup redirects for 'www.ssoapps.com' (Keycloak) and 'www.dropper.com' (Dropper App).
``` NOTE: These need to be set for loadPicker... (See: Dropper/dropper/static/google-api-data.json)
// The Browser API key obtained from the Google API Console. // The Browser API key obtained from the Google API Console.
// Replace with your own Browser API key, or your own key. // Replace with your own Browser API key, or your own key.
let developerKey = ''; let developerKey = '';

40
create_venv.sh Executable file
View File

@ -0,0 +1,40 @@
#!/bin/bash
. CONFIG.sh
# set -o xtrace ## To debug scripts
# set -o errexit ## To exit on error
# set -o errunset ## To exit if a variable is referenced but not set
function main() {
rm -rf venv/
clear
python -m venv venv/
sleep 2
source "./venv/bin/activate"
ANSR="-1"
while [[ $ANSR != "0" ]] && [[ $ANSR != "1" ]] && [[ $ANSR != "2" ]]; do
clear
menu_mesage
read -p "--> : " ANSR
done
case $ANSR in
"1" ) pip install -r linux-requirements.txt;;
"2" ) pip install -r windows-requirements.txt;;
"0" ) exit;;
* ) echo "Don't know how you got here but that's a bad sign...";;
esac
}
function menu_mesage() {
echo "NOTE: Make sure to have Python 3 installed!"
echo -e "\nWhat do you want to do?"
echo -e "\t1) Generate Linux/Mac supported venv. (Installs Repuirements)"
echo -e "\t2) Generate Windows supported venv. (Installs Repuirements)"
echo -e "\t0) EXIT"
}
main $@;

View File

@ -1,6 +1,7 @@
Click==7.0 Click==7.0
Flask==1.1.1 Flask==1.1.1
Flask-Uploads==0.2.1 Flask-Uploads==0.2.1
flask-oidc==1.4.0
gunicorn==19.9.0 gunicorn==19.9.0
itsdangerous==1.1.0 itsdangerous==1.1.0
Jinja2==2.10.3 Jinja2==2.10.3

View File

@ -1,7 +1,33 @@
# system import
import os, secrets
from datetime import timedelta
# Flask imports
from flask import Flask, Blueprint from flask import Flask, Blueprint
from flask_oidc import OpenIDConnect
ROOT_FILE_PTH = os.path.dirname(os.path.realpath(__file__))
app = Flask(__name__) app = Flask(__name__)
app.secret_key = 'super secret key' app.config.from_object("dropper.config.ProductionConfig")
isDebugging = False # app.config.from_object("dropper.config.DevelopmentConfig")
from . import routes
oidc = OpenIDConnect(app)
def oidc_loggedin():
return oidc.user_loggedin
def oidc_isAdmin():
if oidc_loggedin():
isAdmin = oidc.user_getfield("isAdmin")
if isAdmin == "yes" :
return True
return False
app.jinja_env.globals['oidc_loggedin'] = oidc_loggedin
app.jinja_env.globals['oidc_isAdmin'] = oidc_isAdmin
app.jinja_env.globals['TITLE'] = app.config["TITLE"]
from dropper import routes

View File

@ -0,0 +1,14 @@
{
"web": {
"auth_uri": "https://www.ssoapps.com/auth/realms/apps/protocol/openid-connect/auth",
"client_id": "apps",
"issuer": "https://www.ssoapps.com/auth/realms/apps",
"client_secret": "9028c2ac-d6e0-4d96-86bd-02624b91695d",
"redirect_uris": [
"https%3A%2F%2Fwww.dropper.com%2F"
],
"userinfo_uri": "https://www.ssoapps.com/auth/realms/apps/protocol/openid-connect/userinfo",
"token_uri": "https://www.ssoapps.com/auth/realms/apps/protocol/openid-connect/token",
"token_introspection_uri": "https://www.ssoapps.com/auth/realms/apps/protocol/openid-connect/token/introspect"
}
}

54
src/dropper/config.py Normal file
View File

@ -0,0 +1,54 @@
# System import
import os, secrets
from datetime import timedelta
# Lib imports
# Apoplication imports
# Configs
APP_NAME = 'Dropper'
class Config(object):
TITLE = APP_NAME
DEBUG = False
TESTING = False
THREADED = True
SECRET_KEY = secrets.token_hex(32)
HOME_PTH = os.path.expanduser("~")
ROOT_FILE_PTH = os.path.dirname(os.path.realpath(__file__))
PERMANENT_SESSION_LIFETIME = timedelta(days = 7).total_seconds()
SQLALCHEMY_DATABASE_URI = "sqlite:///static/db/webfm.db"
SQLALCHEMY_TRACK_MODIFICATIONS = False
OIDC_TOKEN_TYPE_HINT = 'access_token'
APP_REDIRECT_URI = "https%3A%2F%2Fwww.dropper.com%2F" # This path is submitted as the redirect URI in certain code flows
OIDC_CLIENT_SECRETS = ROOT_FILE_PTH + '/client_secrets.json'
OIDC_ID_TOKEN_COOKIE_SECURE = True
OIDC_REQUIRE_VERIFIED_EMAIL = False
OIDC_USER_INFO_ENABLED = True
OIDC_VALID_ISSUERS = [
'http://www.ssoapps.com/auth/realms/apps',
'https://www.ssoapps.com/auth/realms/apps'
]
class ProductionConfig(Config):
pass
class DevelopmentConfig(Config):
DEBUG = True
USE_RELOADER = True
OIDC_ID_TOKEN_COOKIE_SECURE = False
OIDC_REQUIRE_VERIFIED_EMAIL = False
class TestingConfig(Config):
TESTING = True

View File

@ -9,21 +9,21 @@ from flask_uploads import UploadSet, configure_uploads, ALL
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
# Application Imports # Application Imports
from . import app, isDebugging from .. import app, oidc, utils, ROOT_FILE_PTH
from .MessageHandler import MessageHandler
msgHandler = MessageHandler()
SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__)) + "/"
HOME_PTH = os.path.expanduser("~")
NOTES_PTH = SCRIPT_PTH + 'static/' + "NOTES.txt"
UPLOADS_PTH = HOME_PTH + '/Downloads/'
msgHandler = utils.MessageHandler()
isDebugging = app.config["DEBUG"]
SCRIPT_PTH = app.config["ROOT_FILE_PTH"]
HOME_PTH = app.config["HOME_PTH"]
NOTES_PTH = SCRIPT_PTH + '/static/' + "NOTES.txt"
UPLOADS_PTH = HOME_PTH + '/Downloads/'
files = UploadSet('files', ALL, default_dest=lambda x: UPLOADS_PTH) files = UploadSet('files', ALL, default_dest=lambda x: UPLOADS_PTH)
list = [] # stores file name and hash list = [] # stores file name and hash
configure_uploads(app, files)
configure_uploads(app, files)
# Load notes... # Load notes...
notesListEncoded = [] notesListEncoded = []
notesListDecoded = [] notesListDecoded = []
@ -33,9 +33,9 @@ with open(NOTES_PTH) as infile:
for entry in notesJson: for entry in notesJson:
notesListEncoded.append(entry) notesListEncoded.append(entry)
decodedStrPart = base64.urlsafe_b64decode(entry.encode('utf-8')).decode('utf-8') decodedStrPart = base64.urlsafe_b64decode(entry["string"].encode('utf-8')).decode('utf-8')
entryDecoded = unquote(decodedStrPart) entryDecoded = unquote(decodedStrPart)
notesListDecoded.append(entryDecoded) notesListDecoded.append({"id": entry["id"], "string": entryDecoded })
except Exception as e: except Exception as e:
print(repr(e)) print(repr(e))
@ -59,7 +59,8 @@ def root():
hash = hash_file(fpth) hash = hash_file(fpth)
list.append([file, hash]) list.append([file, hash])
return render_template('index.html', title="Dropper", files=list, notes=notesListDecoded) return render_template('index.html', files = list, notes = notesListDecoded)
@app.route('/upload', methods=['GET', 'POST']) @app.route('/upload', methods=['GET', 'POST'])
@ -150,16 +151,15 @@ def deleteFile():
def deleteText(): def deleteText():
if request.method == 'POST': if request.method == 'POST':
try: try:
encodedStr = request.values['noteStr'].strip() noteIndex = int(request.values['noteIndex'].strip())
decodedStrPart = base64.urlsafe_b64decode(encodedStr.encode('utf-8')).decode('utf-8')
decodedStr = unquote(decodedStrPart)
if isDebugging: i = 0
print("Encoded String:\n\t" + encodedStr) for note in notesListDecoded:
print("Decoded String:\n\t" + decodedStr) if note["id"] == noteIndex:
notesListEncoded.pop(i)
notesListDecoded.pop(i)
i += 1
notesListEncoded.remove(encodedStr)
notesListDecoded.remove(decodedStr)
updateNotesFile() updateNotesFile()
msg = "[Success] Deleted entry..." msg = "[Success] Deleted entry..."
@ -198,8 +198,9 @@ def addNote():
print("Encoded String:\n\t" + encodedStr) print("Encoded String:\n\t" + encodedStr)
print("Decoded String:\n\t" + decodedStr) print("Decoded String:\n\t" + decodedStr)
notesListEncoded.append(encodedStr) strIndex = len(notesListEncoded) + 1
notesListDecoded.append(decodedStr) notesListEncoded.append( {"id": strIndex, "string": encodedStr} )
notesListDecoded.append( {"id": strIndex, "string": decodedStr} )
updateNotesFile() updateNotesFile()
msg = "[Success] Added text entry!" msg = "[Success] Added text entry!"
@ -222,7 +223,13 @@ def returnFile(id):
def updateNotesFile(): def updateNotesFile():
with open(NOTES_PTH, 'w') as file: with open(NOTES_PTH, 'w') as file:
file.write( json.dumps(notesListEncoded, indent=4) ) i = 1
objects = []
for note in notesListEncoded:
objects.append({"id": i, "string": note["string"] })
i += 1
file.write( json.dumps(objects, indent=4) )
file.close() file.close()

View File

@ -0,0 +1 @@
from . import Routes

View File

@ -37,8 +37,8 @@ video {
/* Classes */ /* Classes */
.scroller { .scroller {
scrollbar-color: #00000084 #ffffff64; scrollbar-color: #00000084 #ffffff64;
scrollbar-width: thin; scrollbar-width: thin;
} }
.controls-secondary > button, .controls-secondary > button,
@ -69,8 +69,7 @@ video {
} }
.server-image { .server-image {
width: 16em; max-width: 24em;
height: 12em;
} }
/* Theme colors */ /* Theme colors */

View File

@ -36,13 +36,9 @@ const doAjaxUpload = (actionPath, data, fname, action) => {
// For upload tracking with GET... // For upload tracking with GET...
xhttp.onprogress = function (e) { xhttp.onprogress = function (e) {
if (e.lengthComputable) { if (e.lengthComputable) {
percent = parseFloat( Math.floor( percent = (e.loaded / e.total) * 100;
( text = parseFloat(percent).toFixed(2) + '% Complete (' + fname + ')';
(e.loaded / e.total) * 100 ).toFixed(2) if (e.loaded !== e.total ) {
).toFixed(2)
);
text = percent + '% Complete (' + fname + ')';
if (percent <= 95) {
updateProgressBar(progressbar, text, percent, "info"); updateProgressBar(progressbar, text, percent, "info");
} else { } else {
updateProgressBar(progressbar, text, percent, "success"); updateProgressBar(progressbar, text, percent, "success");

View File

@ -15,8 +15,8 @@ $( "#uploadText" ).bind( "click", function(eve) {
}); });
$( "#deleteTextBtn" ).bind( "click", function(eve) { $( "#deleteTextBtn" ).bind( "click", function(eve) {
const note = document.getElementById('toDeleteText').innerText; const id = document.getElementById('deleteTextBtn').getAttribute("textstrid");
deleteTextAction(eve.target, note); deleteTextAction(eve.target, id);
}); });
$( "#googlePicker" ).bind( "click", function(eve) { $( "#googlePicker" ).bind( "click", function(eve) {
@ -61,6 +61,7 @@ const preDeleteText = (elm = null) => {
document.getElementById('toDeleteText').innerText = elm.innerText; document.getElementById('toDeleteText').innerText = elm.innerText;
document.getElementById('deleteTextBtn').setAttribute("textClass", elm.className); document.getElementById('deleteTextBtn').setAttribute("textClass", elm.className);
document.getElementById('deleteTextBtn').setAttribute("textstrid", elm.getAttribute("textstrid"));
} }

View File

@ -1,4 +1,5 @@
// NOTE: These will get set from loadPicker... // NOTE: These will get set from loadPicker...
// Go to: https://console.developers.google.com/apis/credentials/oauthclient/
// The Browser API key obtained from the Google API Console. // The Browser API key obtained from the Google API Console.
// Replace with your own Browser API key, or your own key. // Replace with your own Browser API key, or your own key.
let developerKey = ''; let developerKey = '';

View File

@ -8,7 +8,12 @@ const postAjaxController = (data, action, hash, fname) => {
message = data.message.text message = data.message.text
if (action === "upload-text" || action === "upload-file") { if (action === "upload-text" || action === "upload-file") {
displayMessage(message, type, "page-alert-zone-2", 3); if (action === "upload-text") {
displayMessage(message, type, "page-alert-zone-text", 3);
}
if (action === "upload-file") {
displayMessage(message, type, "page-alert-zone-files", 3);
}
return ; return ;
} }

View File

@ -22,7 +22,7 @@ const uploadFiles = (files = null) => {
const size = files.length; const size = files.length;
if (files == null || size < 1) { if (files == null || size < 1) {
displayMessage("Nothing to upload...", "alert-warning", "page-alert-zone-2"); displayMessage("Nothing to upload...", "warning", "page-alert-zone-2");
return ; return ;
} }
@ -43,7 +43,7 @@ const uploadFiles = (files = null) => {
const uploadTextEntry = (note = null) => { const uploadTextEntry = (note = null) => {
if (note == null || note == "") { if (note == null || note == "") {
displayMessage("Nothing to upload...", "alert-warning", "page-alert-zone-2"); displayMessage("Nothing to upload...", "warning", "page-alert-zone-2");
return ; return ;
} }
@ -60,9 +60,9 @@ const deleteAction = async (hash) => {
doAjax('delete-file', params, 'delete', hash); doAjax('delete-file', params, 'delete', hash);
} }
const deleteTextAction = (elm, note) => { const deleteTextAction = (elm, id) => {
if (note == '' || note == undefined) { if (id == '' || id == undefined) {
displayMessage("No text to delete...", "alert-warning"); displayMessage("No text to delete...", "warning");
return; return;
} }
@ -70,9 +70,7 @@ const deleteTextAction = (elm, note) => {
const refElm = document.getElementsByClassName(classRef)[0]; const refElm = document.getElementsByClassName(classRef)[0];
refElm.parentElement.removeChild(refElm); refElm.parentElement.removeChild(refElm);
// Encoded special characters then encode to b64 const params = new URLSearchParams('noteIndex=' + id)
encodedStr = window.btoa(encodeURIComponent(note))
const params = new URLSearchParams('noteStr=' + encodedStr)
doAjax('delete-text', params, 'delete-text'); doAjax('delete-text', params, 'delete-text');
} }

View File

@ -9,15 +9,30 @@
<button class="btn btn-info" data-toggle="modal" <button class="btn btn-info" data-toggle="modal"
data-target="#uploaderModal">Upload...</button> data-target="#uploaderModal">Upload...</button>
</div> </div>
<div class="col"> {% if oidc_isAdmin() %}
<button id="googlePicker" class="btn btn-success">GDrive</button> <div class="col">
</div> <button id="googlePicker" class="btn btn-success">GDrive</button>
<div class="col"> </div>
<button id="notesToViewBtn" class="btn btn-secondary">Notes</button> <div class="col">
</div> <button id="notesToViewBtn" class="btn btn-secondary">Notes</button>
<div class="col"> </div>
<button id="filesToViewBtn" class="btn btn-secondary">Files</button> <div class="col">
</div> <button id="filesToViewBtn" class="btn btn-secondary">Files</button>
</div>
{% endif %}
{% if oidc_loggedin() %}
<a href="/logout">
<button title="Logout..." class="btn btn-danger">
Logout
</button>
</a>
{% else %}
<a href="/login">
<button title="Login..." class="btn btn-success">
Login
</button>
</a>
{% endif %}
</div> </div>
<div class="row"> <div class="row">
@ -35,14 +50,15 @@
</div> </div>
<!-- Upload(ed) List --> <!-- Upload(ed) List -->
<div class="row-fluid"> <div class="row">
<h3 style="display: inline-block">Server Files:</h3> <h3 style="width: 100%;">Server Files:</h3>
<div class="col" style="max-height:35em; overflow-y:scroll;"> {% if oidc_isAdmin() %}
<div class="col scroller" style="height:80vh; overflow-y: scroll;">
<!-- Uploadeed Files List --> <!-- Uploadeed Files List -->
<ul class="list-group"> <ul class="list-group" style="">
<span id="notesToView"></span> <span id="notesToView"></span>
{% for note in notes %} {% for note in notes %}
<li class="row rtext t{{ loop.index }}"> <li class="row rtext t{{ loop.index }}" textStrID="{{note['id']}}">
<div class="col-sm-2 list-group-item text-center"> <div class="col-sm-2 list-group-item text-center">
<p class="server-file-card text-left"> <p class="server-file-card text-left">
<img class="menu-item" src="{{ url_for('static', filename='imgs/octicons/trashcan.svg')}}" title="Delete..." <img class="menu-item" src="{{ url_for('static', filename='imgs/octicons/trashcan.svg')}}" title="Delete..."
@ -52,41 +68,41 @@
</div> </div>
<div class="col-md-10 list-group-item"> <div class="col-md-10 list-group-item">
{% if "youtu.be" in note.strip().lower() or "www.youtube.com/watch?v=" in note.strip().lower() %} {% if "youtu.be" in note["string"].strip().lower() or "www.youtube.com/watch?v=" in note["string"].strip().lower() %}
{% if "youtu.be" in note.strip() %} {% if "youtu.be" in note["string"].strip() %}
<span class="list-group-item list-group-item-action justify-content-center text-center"> <span class="list-group-item list-group-item-action justify-content-center text-center">
{{note}} {{note["string"]}}
<iframe <iframe class="card-img-top"
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
autoplay="" allowfullscreen="true" width="650px" height="400px" frameborder="0" autoplay="" allowfullscreen="true" width="650px" height="400px" frameborder="0"
src="{{note | replace('youtu.be/', 'www.youtube.com/embed/')}}"> src="{{note['string'] | replace('youtu.be/', 'www.youtube.com/embed/')}}">
</iframe> </iframe>
</span> </span>
{% elif "www.youtube.com/watch?v=" in note.strip().lower() %} {% elif "www.youtube.com/watch?v=" in note["string"].strip().lower() %}
<span class="list-group-item list-group-item-action justify-content-center text-center"> <span class="list-group-item list-group-item-action justify-content-center text-center">
{{note}} {{note["string"]}}
<iframe <iframe class="card-img-top"
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
autoplay="" allowfullscreen="true" width="650px" height="400px" frameborder="0" autoplay="" allowfullscreen="true" width="650px" height="400px" frameborder="0"
src="{{note | replace('www.youtube.com/watch?v=', 'www.youtube.com/embed/')}}"> src="{{note['string'] | replace('www.youtube.com/watch?v=', 'www.youtube.com/embed/')}}">
</iframe> </iframe>
</span> </span>
{% endif %} {% endif %}
{% elif note.strip().startswith( ("http:", "https:", "ftp:", "file:") ) %} {% elif note["string"].strip().startswith( ("http:", "https:", "ftp:", "file:") ) %}
{% if note.strip().endswith( (".jpg", ".jpeg", ".png", ".gif") ) %} {% if note["string"].strip().endswith( (".jpg", ".jpeg", ".png", ".gif") ) %}
<div class="list-group-item list-group-item-action justify-content-center text-center"> <div class="list-group-item list-group-item-action justify-content-center text-center">
<a href="{{note}}" target="_blank"> <a href="{{note['string']}}" target="_blank">
{{note}} {{note["string"]}}
</a> </a>
<img src="{{note}}" width="650px" height="auto" /> <img class="card-img-top server-image" src="{{note['string']}}" width="650px" height="auto" />
</div> </div>
{% else %} {% else %}
<a class="list-group-item list-group-item-action" href="{{note}}" target="_blank"> <a class="list-group-item list-group-item-action" href="{{note['string']}}" target="_blank">
{{note}} {{note["string"]}}
</a> </a>
{% endif %} {% endif %}
{% elif note.strip() %} {% elif note["string"].strip() %}
<span class="list-group-item list-group-item-action">{{note}}</span> <pre class="list-group-item list-group-item-action">{{note["string"]}}</pre>
{% endif %} {% endif %}
</div> </div>
</li> </li>
@ -107,10 +123,10 @@
</p> </p>
{% if file[0].lower().endswith( (".png", ".jpg", ".jpeg") ) %} {% if file[0].lower().endswith( (".png", ".jpg", ".jpeg") ) %}
<a class="list-group-item-action content-align-center" href="{{ url_for('uploads')}}/{{file[0]}}" target="_blank"> <a class="list-group-item-action content-align-center" href="{{ url_for('uploads')}}/{{file[0]}}" target="_blank">
<img class="server-image" src="{{ url_for('uploads')}}/{{file[0]}}" alt="{{file[0]}}" /> <img class="card-img-top server-image" src="{{ url_for('uploads')}}/{{file[0]}}" alt="{{file[0]}}" />
</a> </a>
{% elif file[0].lower().endswith( (".mp4", ".webm") ) %} {% elif file[0].lower().endswith( (".mp4", ".webm") ) %}
<video src="{{ url_for('uploads')}}/{{file[0]}}" controls></video> <video class="card-img-top" src="{{ url_for('uploads')}}/{{file[0]}}" controls></video>
{% else %} {% else %}
<a class="list-group-item list-group-item-action" href="{{ url_for('uploads')}}/{{file[0]}}" target="_blank">{{file[0]}}</a> <a class="list-group-item list-group-item-action" href="{{ url_for('uploads')}}/{{file[0]}}" target="_blank">{{file[0]}}</a>
{% endif %} {% endif %}
@ -119,120 +135,13 @@
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
{% else %}
</div> </div>
<div class="row justify-content-center">
<h3>Log in with an Admin account to view files...</h3>
</div>
{% endif %}
</div> </div>
<!-- Modals -->
<!-- Uploader modal -->
<div id="uploaderModal" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Uploader</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="row">
<!-- Text Upload -->
<div class="col-md-6 controls-secondary">
<textarea id="noteArea" rows="4" cols="80" placeholder="Enter note..."
style="background-color: #32383e; color: #fff; width: 100%;
height: 320px; margin-bottom: 1em;"></textarea>
<button id="uploadText" class="btn btn-success">Submit Note...</button>
</div>
<!-- File Upload -->
<div class="col-md-6 controls-secondary">
<!-- To Upload List -->
<ul id="uploadListTitles" class="list-group scroller" style="min-height: 286px; max-height: 286px; overflow: auto;">
</ul>
<input class="btn btn-primary" type="file" id="toUpload" multiple>
<button id="uploadFiles" class="btn btn-success">Submit Files...</button>
</div>
</div>
<!-- Server Messages -->
<div id="page-alert-zone-2"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Delete modal text -->
<div id="deleteDialogModalText" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Delete Text</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p id="toDeleteText">...</p>
</div>
<div class="modal-footer">
<button id="deleteTextBtn" textClass="" type="button" class="btn btn-danger" data-dismiss="modal">Delete</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Delete modal file -->
<div id="deleteDialogModal" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Delete File</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p id="toDeleteFileName">...</p>
</div>
<div class="modal-footer">
<button id="deleteFileBtn" fileHash="" type="button" class="btn btn-danger" data-dismiss="modal" onclick="deleteFile(this)">Delete</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Edit Modal -->
<div id="editDialogModal" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Edit File</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p id="toEditFileName">...</p>
</div>
<input type="text" id="newEditName" placeholder="New File Name..." value="">
<div class="modal-footer">
<button id="editFileBtn" fileHash="" type="button"
class="btn btn-warning" onclick="editFile(this)">Edit</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
{% endblock content %} {% endblock content %}
{% block scripts %} {% block scripts %}
@ -240,9 +149,11 @@
<script src="{{ url_for('static', filename='js/post-ajax.js')}}"></script> <script src="{{ url_for('static', filename='js/post-ajax.js')}}"></script>
<script src="{{ url_for('static', filename='js/ajax.js')}}"></script> <script src="{{ url_for('static', filename='js/ajax.js')}}"></script>
<script src="{{ url_for('static', filename='js/ui-logic.js')}}"></script> <script src="{{ url_for('static', filename='js/ui-logic.js')}}"></script>
<script src="{{ url_for('static', filename='js/google-picker-logic.js')}}"></script>
<script src="{{ url_for('static', filename='js/events.js')}}"></script> <script src="{{ url_for('static', filename='js/events.js')}}"></script>
<!-- The Google API Loader script. --> {% if oidc_isAdmin() %}
<script type="text/javascript" src="https://apis.google.com/js/api.js?onload=loadPicker"></script> <!-- The Google API Loader script. -->
<script src="{{ url_for('static', filename='js/google-picker-logic.js')}}"></script>
<script type="text/javascript" src="https://apis.google.com/js/api.js?onload=loadPicker"></script>
{% endif %}
{% endblock scripts %} {% endblock scripts %}

View File

@ -5,7 +5,7 @@
{% if title %} {% if title %}
<title>{{title}}</title> <title>{{title}}</title>
{% else %} {% else %}
<title>Uploader</title> <title>{{TITLE}}</title>
{% endif %} {% endif %}
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
@ -16,11 +16,17 @@
</head> </head>
<body> <body>
{% block content %}
{% block content %}
{% endblock %} {% endblock %}
{% block modals %}
{% include 'modals.html' %}
{% endblock %}
<!-- For Bootstrap in this exact order... --> <!-- For Bootstrap in this exact order... -->
<script src="{{ url_for('static', filename='js/bootstrap/jquery-3.3.1.slim.min.js')}}"></script> <script src="{{ url_for('static', filename='js/bootstrap/jquery-3.3.1.slim.min.js')}}"></script>
<script src="{{ url_for('static', filename='js/bootstrap/popper.min.js')}}"></script> <script src="{{ url_for('static', filename='js/bootstrap/popper.min.js')}}"></script>

View File

@ -0,0 +1,110 @@
<!-- Modals -->
<!-- Uploader modal -->
<div id="uploaderModal" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Uploader</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="row">
<!-- Text Upload -->
<div class="col-md-6 controls-secondary">
<!-- Server Messages -->
<div id="page-alert-zone-text"></div>
<textarea id="noteArea" rows="4" cols="80" placeholder="Enter note..."
style="background-color: #32383e; color: #fff; width: 100%;
height: 320px; margin-bottom: 1em;"></textarea>
<button id="uploadText" class="btn btn-success">Submit Note...</button>
</div>
<!-- File Upload -->
<div class="col-md-6 controls-secondary">
<!-- Server Messages -->
<div id="page-alert-zone-files"></div>
<!-- To Upload List -->
<ul id="uploadListTitles" class="list-group scroller" style="min-height: 286px; max-height: 286px; overflow: auto;">
</ul>
<input class="btn btn-primary" type="file" id="toUpload" multiple>
<button id="uploadFiles" class="btn btn-success">Submit Files...</button>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Delete modal text -->
<div id="deleteDialogModalText" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Delete Text</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p id="toDeleteText">...</p>
</div>
<div class="modal-footer">
<button id="deleteTextBtn" textClass="" type="button" class="btn btn-danger" data-dismiss="modal">Delete</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Delete modal file -->
<div id="deleteDialogModal" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Delete File</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p id="toDeleteFileName">...</p>
</div>
<div class="modal-footer">
<button id="deleteFileBtn" fileHash="" type="button" class="btn btn-danger" data-dismiss="modal" onclick="deleteFile(this)">Delete</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Edit Modal -->
<div id="editDialogModal" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Edit File</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p id="toEditFileName">...</p>
</div>
<input type="text" id="newEditName" placeholder="New File Name..." value="">
<div class="modal-footer">
<button id="editFileBtn" fileHash="" type="button"
class="btn btn-warning" onclick="editFile(this)">Edit</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,56 @@
# Python imports
import os, logging
# Application imports
class Logger:
def __init__(self):
pass
def get_logger(self, loggerName = "NO_LOGGER_NAME_PASSED", createFile = True):
"""
Create a new logging object and return it.
:note:
NOSET # Don't know the actual log level of this... (defaulting or literally none?)
Log Levels (From least to most)
Type Value
CRITICAL 50
ERROR 40
WARNING 30
INFO 20
DEBUG 10
:param loggerName: Sets the name of the logger object. (Used in log lines)
:param createFile: Whether we create a log file or just pump to terminal
:return: the logging object we created
"""
globalLogLvl = logging.DEBUG # Keep this at highest so that handlers can filter to their desired levels
chLogLevel = logging.CRITICAL # Prety musch the only one we change ever
fhLogLevel = logging.DEBUG
log = logging.getLogger(loggerName)
log.setLevel(globalLogLvl)
# Set our log output styles
fFormatter = logging.Formatter('[%(asctime)s] %(pathname)s:%(lineno)d %(levelname)s - %(message)s', '%m-%d %H:%M:%S')
cFormatter = logging.Formatter('%(pathname)s:%(lineno)d] %(levelname)s - %(message)s')
ch = logging.StreamHandler()
ch.setLevel(level=chLogLevel)
ch.setFormatter(cFormatter)
log.addHandler(ch)
if createFile:
folder = "logs"
file = folder + "/dropper.log"
if not os.path.exists(folder):
os.mkdir(folder)
fh = logging.FileHandler(file)
fh.setLevel(level=fhLogLevel)
fh.setFormatter(fFormatter)
log.addHandler(fh)
return log

View File

@ -0,0 +1,2 @@
from .Logger import Logger
from .MessageHandler import MessageHandler

View File

@ -6,7 +6,7 @@
function main() { function main() {
source "/home/<your user>/<further paths>/uploader-venv/bin/activate" source "../venv/bin/activate"
gunicorn wsgi:app -b 0.0.0.0:1120 # <module>:<app> IE <file>:<flask app variable> gunicorn wsgi:app -b 0.0.0.0:1120 # <module>:<app> IE <file>:<flask app variable>
} }
main $@; main $@;

9
windows-requirements.txt Normal file
View File

@ -0,0 +1,9 @@
Click==7.0
Flask==1.1.1
Flask-Uploads==0.2.1
flask-oidc==1.4.0
waitress==1.4.3
itsdangerous==1.1.0
Jinja2==2.10.3
MarkupSafe==1.1.1
Werkzeug==0.16.0