First commit
36
.gitignore
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
## Editors:
|
||||
*.bak
|
||||
*.sav
|
||||
*.backup
|
||||
.*.swp
|
||||
*~
|
||||
|
||||
# Secret configuration
|
||||
settings.cfg
|
||||
settings.py
|
||||
*.sqlite
|
||||
|
||||
# Personal conf
|
||||
static/jquery.js
|
||||
templates/head-perso.html
|
||||
templates/scripts-perso.html
|
41
README.md
Normal file
@ -0,0 +1,41 @@
|
||||
# Jeu de belote
|
||||
|
||||
Simple file server to be used easily with curl.
|
||||
|
||||
L'ensemble de ce dépôt est sous licence GPL v3.
|
||||
|
||||
## Installation
|
||||
|
||||
1. Créer un environnement virtuel `venv` (`python3 -m venv venv`)
|
||||
2. Activer l'environnement virtuel (`. venv/bin/activate`)
|
||||
3. Installer les prérequis (`pip install -r requirements.txt`)
|
||||
4. Vous pouvez démarrer le serveur `python fs.py` !
|
||||
|
||||
|
||||
## Principe
|
||||
|
||||
* `fs.py` définit les routes
|
||||
* `db.py` gère le jeu et l'enregistrement en base de donnée
|
||||
* Le dossier `templates` contient les template jinja2 pour le rendu web
|
||||
* Le dossier `static` contient les scripts, images et feuilles de style
|
||||
|
||||
Si aucun utilisateur n'existe dans la base de donnée, un premier utilisateur
|
||||
de login `admin` et de mot de passe `admin` est créé.
|
||||
A vous de changer le mot de passe avant mise en ligne !
|
||||
|
||||
## Licence
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
|
||||
Copyright (C) 2020 L. Viallon-Galinier
|
10
app.py
Normal file
@ -0,0 +1,10 @@
|
||||
import os
|
||||
import sys
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
os.chdir(here)
|
||||
sys.path.insert(0,here+'/venv/lib/python{}.{}/site-packages'.format(sys.version_info.major, sys.version_info.minor))
|
||||
sys.path.insert(0,here)
|
||||
|
||||
import settings
|
||||
from fs import app
|
||||
|
71
db.py
Normal file
@ -0,0 +1,71 @@
|
||||
from flask_login import UserMixin
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
db = SQLAlchemy()
|
||||
|
||||
levels_users = {'Inactive':0, 'Simple user':1, 'Admin users':5, 'Admin':10}
|
||||
|
||||
#######################################################################
|
||||
# User class #
|
||||
#######################################################################
|
||||
|
||||
class User(UserMixin, db.Model):
|
||||
"""Model for user accounts."""
|
||||
|
||||
__tablename__ = 'users'
|
||||
|
||||
login = db.Column(db.String(100),
|
||||
primary_key=True)
|
||||
name = db.Column(db.String(100),
|
||||
nullable=False,
|
||||
unique=False)
|
||||
password = db.Column(db.String(100),
|
||||
primary_key=False,
|
||||
unique=False,
|
||||
nullable=False)
|
||||
is_admin = db.Column(db.Integer, default=1)
|
||||
|
||||
def set_password(self, password):
|
||||
"""Create hashed password."""
|
||||
self.password = generate_password_hash(password, method='sha256')
|
||||
|
||||
def check_password(self, password):
|
||||
"""Check hashed password."""
|
||||
return check_password_hash(self.password, password)
|
||||
|
||||
def get_id(self):
|
||||
return self.login
|
||||
|
||||
def __repr__(self):
|
||||
return '<User {}>'.format(self.login)
|
||||
|
||||
#######################################################################
|
||||
# File class #
|
||||
#######################################################################
|
||||
|
||||
class File(db.Model):
|
||||
__tablename__ = 'files'
|
||||
|
||||
id = db.Column(db.Integer,
|
||||
primary_key=True)
|
||||
name = db.Column(db.String(200))
|
||||
desc = db.Column(db.String(200))
|
||||
user = db.Column(db.String(100), db.ForeignKey('users.login'))
|
||||
password = db.Column(db.String(100), default='')
|
||||
visible = db.Column(db.Boolean, default=False)
|
||||
date = db.Column(db.DateTime)
|
||||
|
||||
def set_password(self, password):
|
||||
"""Create hashed password."""
|
||||
if password is None or password == '':
|
||||
self.password = ''
|
||||
else:
|
||||
self.password = generate_password_hash(password, method='sha256')
|
||||
|
||||
def check_password(self, password):
|
||||
"""Check hashed password."""
|
||||
if self.password == '':
|
||||
return True
|
||||
return check_password_hash(self.password, password)
|
||||
|
449
fs.py
Normal file
@ -0,0 +1,449 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import flask
|
||||
import flask_login as fll
|
||||
import datetime
|
||||
from db import db, File, User, levels_users
|
||||
import settings
|
||||
from werkzeug.utils import secure_filename
|
||||
import os
|
||||
import os.path
|
||||
_here = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
login_manager = fll.LoginManager()
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
app.config.from_pyfile('settings.cfg')
|
||||
|
||||
app.config['UPLOAD_FOLDER'] = settings.UPLOAD_FOLDER
|
||||
app.config['MAX_CONTENT_LENGTH'] = settings.MAX_FILE_SIZE * 1024 * 1024
|
||||
|
||||
#######################################################################
|
||||
# Plugin management #
|
||||
#######################################################################
|
||||
|
||||
# Login manager
|
||||
login_manager.init_app(app)
|
||||
|
||||
# Database management
|
||||
db.app = app
|
||||
db.init_app(app)
|
||||
db.create_all()
|
||||
db.session.commit()
|
||||
|
||||
# Insert admin if not exist
|
||||
if User.query.limit(1).first() is None:
|
||||
admin = User(login='admin', name='Admin', is_admin=levels_users['Admin'])
|
||||
admin.set_password('admin')
|
||||
db.session.add(admin)
|
||||
db.session.commit()
|
||||
|
||||
# Login management
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
if user_id is None :
|
||||
return None
|
||||
return User.query.get(user_id)
|
||||
|
||||
@login_manager.unauthorized_handler
|
||||
def unauthorized_callback():
|
||||
return flask.redirect('/login?next=' + flask.request.path)
|
||||
|
||||
|
||||
#######################################################################
|
||||
# Global variables or fuctions #
|
||||
#######################################################################
|
||||
URL_DEFAULT = '/myrepo'
|
||||
|
||||
@app.route('/')
|
||||
def home():
|
||||
user = fll.current_user
|
||||
if not hasattr(user, 'is_admin'):
|
||||
user = None
|
||||
return flask.render_template("index.html", user=user)
|
||||
|
||||
@app.route('/favicon.ico')
|
||||
def favicon():
|
||||
return flask.redirect('/static/favicon.ico')
|
||||
#######################################################################
|
||||
# Login, logout #
|
||||
#######################################################################
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if fll.current_user.is_authenticated:
|
||||
return flask.redirect(URL_DEFAULT)
|
||||
if flask.request.method=="POST":
|
||||
username = flask.request.form['username']
|
||||
password = flask.request.form['password']
|
||||
user = User.query.get(username)
|
||||
if user and user.is_admin > levels_users['Inactive'] and user.check_password(password):
|
||||
fll.login_user(user)
|
||||
next = flask.request.args.get('next')
|
||||
if next is not None and next[0]=='/':
|
||||
return flask.redirect(next)
|
||||
else:
|
||||
return flask.redirect(URL_DEFAULT)
|
||||
else:
|
||||
app.logger.warning('Login fail for user {} from {}'.format(user, flask.request.remote_addr))
|
||||
flask.flash('Nom d\'utilisateur ou mot de passe incorrect')
|
||||
return flask.render_template('login.html', user=None)
|
||||
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
fll.logout_user()
|
||||
return flask.redirect('/')
|
||||
|
||||
#######################################################################
|
||||
# Upload form #
|
||||
#######################################################################
|
||||
|
||||
def check_filename_in_db(filename, user):
|
||||
exist = File.query.filter_by(name=filename, user=user.login).first()
|
||||
return exist is not None
|
||||
|
||||
def check_filename(filename, user):
|
||||
filepath = genpath(filename, user)
|
||||
if os.path.lexists(filepath) or check_filename_in_db(filename, user):
|
||||
fn, ext = os.path.splitext(filename)
|
||||
i = 0
|
||||
if settings.NMAX_EXTENSION == 0:
|
||||
return None
|
||||
filename = '{}_{:04d}{}'.format(fn, i, ext)
|
||||
while os.path.lexists(genpath(filename, user)) or check_filename_in_db(filename, user):
|
||||
i+=1
|
||||
filename = '{}_{:04d}{}'.format(fn, i, ext)
|
||||
if i > settings.NMAX_EXTENSION:
|
||||
break
|
||||
else:
|
||||
return filename
|
||||
return None
|
||||
return filename
|
||||
|
||||
def genpath(filename, user):
|
||||
if isinstance(user, str):
|
||||
username = user
|
||||
else:
|
||||
username = user.login
|
||||
return os.path.join(app.config['UPLOAD_FOLDER'], username, filename)
|
||||
|
||||
@app.route('/up', methods=['GET', 'POST'])
|
||||
def uploaderpage():
|
||||
user = fll.current_user
|
||||
if not hasattr(user, 'is_admin') or user.is_admin < levels_users['Simple user']:
|
||||
user = None
|
||||
|
||||
if not flask.request.method=="POST":
|
||||
return flask.render_template('up.html', user=user)
|
||||
|
||||
if flask.request.method=="POST" :
|
||||
# Parse global things
|
||||
if 'graphical' in flask.request.form and flask.request.form['graphical'] == '1':
|
||||
graphical = True
|
||||
else:
|
||||
graphical = False
|
||||
if 'visible' in flask.request.form and flask.request.form['visible']:
|
||||
visible = True
|
||||
else:
|
||||
visible = False
|
||||
if 'filepwd' in flask.request.form and len(flask.request.form['filepwd'])>0:
|
||||
filepwd = flask.request.form['filepwd']
|
||||
else:
|
||||
filepwd = None
|
||||
|
||||
# Check auth first
|
||||
if user is None:
|
||||
if 'username' not in flask.request.form or 'password' not in flask.request.form:
|
||||
return returnuploader(graphical, 'ERR: You have to provide creedentials or log in', None)
|
||||
username = flask.request.form['username']
|
||||
password = flask.request.form['password']
|
||||
user = User.query.get(username)
|
||||
if not user or user.is_admin <= levels_users['Inactive'] or not user.check_password(password):
|
||||
return returnuploader(graphical, 'ERR: Incorrect login credentials', None)
|
||||
fll.login_user(user)
|
||||
|
||||
# When auth ok, check for files !
|
||||
if 'file[]' not in flask.request.files:
|
||||
return returnuploader(graphical, 'ERR: File have to be provided (file[] field)', user)
|
||||
|
||||
lfiles = flask.request.files.getlist('file[]')
|
||||
lnewnames = flask.request.form.getlist('filename[]')
|
||||
ldesc = flask.request.form.getlist('desc[]')
|
||||
|
||||
done = 0
|
||||
ret="OK"
|
||||
for i in range(len(lfiles)):
|
||||
# if user does not select file, browser also
|
||||
# submit an empty part without filename
|
||||
f = lfiles[i]
|
||||
if f.filename == '':
|
||||
continue
|
||||
if i<len(lnewnames) and len(lnewnames[i])>0:
|
||||
name = lnewnames[i]
|
||||
else:
|
||||
name = f.filename
|
||||
if i<len(ldesc) and len(ldesc[i])>0:
|
||||
desc = ldesc[i]
|
||||
else:
|
||||
desc = ''
|
||||
name = secure_filename(name)
|
||||
name1 = check_filename(name, user)
|
||||
if name1 is None:
|
||||
return returnuploader(graphical, 'ERR: Already existing name {}'.format(name), user)
|
||||
|
||||
# Add to database
|
||||
toadd = File(name=name1, user=user.login, visible=visible, date=datetime.datetime.now(), desc=desc)
|
||||
toadd.set_password(filepwd)
|
||||
db.session.add(toadd)
|
||||
db.session.commit()
|
||||
|
||||
# Save it to disk
|
||||
path = genpath(name1, user)
|
||||
try:
|
||||
if not os.path.isdir(os.path.dirname(path)):
|
||||
os.mkdir(os.path.dirname(path))
|
||||
f.save(path)
|
||||
ret += "\n/d/{}/{}".format(user.login, name1)
|
||||
except IOError as err:
|
||||
return returnuploader(graphical, 'ERR: Something went wrong when trying to write file to disk', user)
|
||||
|
||||
done += 1
|
||||
|
||||
# return
|
||||
if done == 0:
|
||||
return returnuploader(graphical, 'ERR: You have to provide at least one valid file', user)
|
||||
|
||||
if graphical:
|
||||
return flask.redirect(URL_DEFAULT)
|
||||
else:
|
||||
return ret
|
||||
|
||||
def returnuploader(graphical, info, user):
|
||||
"""
|
||||
Function to create return value for uploaderpage function
|
||||
If graphical is True, return the web page, otherwise return simple text
|
||||
|
||||
graphical:bool: it is graphical session ?
|
||||
info:str: text to send
|
||||
user:User instance or None if not logged in
|
||||
"""
|
||||
if not graphical:
|
||||
return info
|
||||
else:
|
||||
flask.flash(info)
|
||||
return flask.render_template('up.html', user=user)
|
||||
|
||||
#######################################################################
|
||||
# Download pages #
|
||||
#######################################################################
|
||||
@app.route('/d/<user>/<filename>', defaults={'raw': False}, methods=['GET', 'POST'])
|
||||
@app.route('/d/<user>/<filename>/raw', defaults = {'raw': True}, methods=['GET', 'POST'])
|
||||
def dl(user, filename, raw=False):
|
||||
cuser = fll.current_user
|
||||
if not hasattr(cuser, 'is_admin') or cuser.is_admin < levels_users['Simple user']:
|
||||
cuser = None
|
||||
fl = File.query.filter_by(name=filename, user=user).first()
|
||||
if fl is None:
|
||||
return flask.abort(404)
|
||||
pwd = None
|
||||
if flask.request.method=="POST":
|
||||
if 'password' in flask.request.form:
|
||||
pwd = flask.request.form['password']
|
||||
if fl.check_password(pwd):
|
||||
path = genpath(filename, user)
|
||||
folder = os.path.dirname(path)
|
||||
fn = os.path.basename(path)
|
||||
if raw:
|
||||
return flask.send_from_directory(folder, filename=fn, as_attachment=False)
|
||||
else:
|
||||
return flask.send_from_directory(folder, filename=fn, as_attachment=False)
|
||||
else:
|
||||
if raw:
|
||||
return 'ERR: Incorrect password'
|
||||
else:
|
||||
if flask.request.method == "POST":
|
||||
flask.flash('Incorrect password')
|
||||
return flask.render_template('dl_pwd.html', user=cuser, filename=filename)
|
||||
pass
|
||||
|
||||
#######################################################################
|
||||
# Show repos #
|
||||
#######################################################################
|
||||
@app.route('/myrepo')
|
||||
@fll.login_required
|
||||
def myrepo():
|
||||
return flask.redirect('/d/{}'.format(fll.current_user.login))
|
||||
|
||||
@app.route('/d/<user>')
|
||||
def repo(user):
|
||||
user = User.query.get(user)
|
||||
if user is None:
|
||||
return flask.abort(404)
|
||||
cuser = fll.current_user
|
||||
if not hasattr(cuser, 'is_admin') or cuser.is_admin < levels_users['Simple user']:
|
||||
cuser = None
|
||||
|
||||
ismine = user.login == cuser.login if cuser is not None else False
|
||||
isadmin = cuser.is_admin>=levels_users['Admin'] or ismine if cuser is not None else False
|
||||
|
||||
if isadmin:
|
||||
l = File.query.filter_by(user=user.login).all()
|
||||
else:
|
||||
l = File.query.filter_by(user=user.login, visible=True).all()
|
||||
|
||||
return flask.render_template('repo.html', user=cuser, isadmin=isadmin, ismine=ismine, folder=user.login, l=l)
|
||||
|
||||
#######################################################################
|
||||
# Edit files #
|
||||
#######################################################################
|
||||
@app.route('/f/<id>/edit', methods=['GET', 'POST'])
|
||||
@fll.login_required
|
||||
def editfile(id):
|
||||
user = fll.current_user
|
||||
f = File.query.get(id)
|
||||
if f is None:
|
||||
return flask.redirect(URL_DEFAULT)
|
||||
if f.user != user and user.is_admin<levels_users['Admin']:
|
||||
return flask.redirect(URL_DEFAULT)
|
||||
|
||||
if flask.request.method == "POST":
|
||||
if 'desc' in flask.request.form:
|
||||
f.desc = flask.request.form['desc']
|
||||
f.visible = False
|
||||
if 'visible' in flask.request.form:
|
||||
if flask.request.form['visible']=='1':
|
||||
f.visible = True
|
||||
if 'password' in flask.request.form and len(flask.request.form['password'])>0:
|
||||
f.set_password(flask.request.form['password'])
|
||||
# TODO: Add name change but this have to be done with file also ! <04-11-20, Léo Viallon-Galinier> #
|
||||
db.session.commit()
|
||||
return flask.redirect('/d/{}'.format(f.user))
|
||||
else:
|
||||
return flask.render_template('file_edit.html', user=user, f=f)
|
||||
|
||||
@app.route('/f/<id>/del')
|
||||
@fll.login_required
|
||||
def delfile(id):
|
||||
user = fll.current_user
|
||||
f = File.query.get(id)
|
||||
if f is None:
|
||||
return flask.redirect(URL_DEFAULT)
|
||||
if f.user != user and user.is_admin<levels_users['Admin']:
|
||||
return flask.redirect(URL_DEFAULT)
|
||||
|
||||
confirm = flask.request.args.get('confirm')
|
||||
if confirm is not None:
|
||||
fpath = genpath(f.name, f.user)
|
||||
if os.path.isfile(fpath):
|
||||
os.remove(fpath)
|
||||
db.session.delete(f)
|
||||
db.session.commit()
|
||||
return flask.redirect('/d/{}'.format(f.user))
|
||||
return flask.render_template('file_del.html', user=user, f=f)
|
||||
|
||||
|
||||
#######################################################################
|
||||
# Users management #
|
||||
#######################################################################
|
||||
@app.route('/admin/users')
|
||||
@fll.login_required
|
||||
def admin_users():
|
||||
if fll.current_user.is_admin < levels_users['Admin users']:
|
||||
return flask.redirect(URL_DEFAULT)
|
||||
users = User.query.all()
|
||||
return flask.render_template('admin_users.html', user=fll.current_user, users=users)
|
||||
|
||||
@app.route('/admin/users/<user>', methods=['GET', 'POST'])
|
||||
@fll.login_required
|
||||
def edituser(user):
|
||||
new = user=='new'
|
||||
user = User.query.get(user)
|
||||
if not new and user is None:
|
||||
return flask.redirect('/admin/users')
|
||||
if fll.current_user.is_admin >= levels_users['Admin users'] and \
|
||||
(new or fll.current_user.is_admin >= user.is_admin):
|
||||
if flask.request.method=="POST":
|
||||
username = flask.request.form['username']
|
||||
name = flask.request.form['name']
|
||||
password = flask.request.form['password']
|
||||
level = int(flask.request.form['level'])
|
||||
if level not in levels_users.values():
|
||||
flask.flash('Level unknown')
|
||||
return flask.render_template("admin_user_edit.html", user=fll.current_user, useredit=user, levels_users=levels_users)
|
||||
if fll.current_user.is_admin < levels_users['Admin']:
|
||||
if not level < fll.current_user.is_admin:
|
||||
flask.flash('Invalid level')
|
||||
return flask.render_template("admin_user_edit.html", user=fll.current_user, useredit=user, levels_users=levels_users)
|
||||
if len(password)<2 and new:
|
||||
flask.flash('Mot de passe trop court')
|
||||
return flask.render_template("admin_user_edit.html", user=fll.current_user, useredit=user, levels_users=levels_users)
|
||||
if new and User.query.get(username) is not None:
|
||||
flask.flash('Login déjà utilisé ! Choisissez un autre !')
|
||||
return flask.render_template("admin_user_edit.html", user=fll.current_user, useredit=user, levels_users=levels_users)
|
||||
if new:
|
||||
usertoadd = User(login=username, name=name, is_admin=level)
|
||||
usertoadd.set_password(password)
|
||||
db.session.add(usertoadd)
|
||||
else:
|
||||
user.name = name
|
||||
if len(password)>=2:
|
||||
user.set_password(password)
|
||||
user.is_admin = level
|
||||
db.session.commit()
|
||||
return flask.redirect('/admin/users')
|
||||
else:
|
||||
return flask.render_template("admin_user_edit.html", user=fll.current_user, useredit=user, levels_users=levels_users)
|
||||
return flask.redirect(URL_DEFAULT)
|
||||
|
||||
@app.route('/admin/users/<user>/del')
|
||||
@fll.login_required
|
||||
def deluser(user):
|
||||
user = User.query.get(user)
|
||||
if user is None:
|
||||
return flask.redirect('/admin/users')
|
||||
if fll.current_user.is_admin >= levels_users['Admin'] or \
|
||||
fll.current_user.is_admin >= levels_users['Admin users'] and \
|
||||
fll.current_user.is_admin > user.is_admin:
|
||||
confirm = flask.request.args.get('confirm')
|
||||
if confirm is not None:
|
||||
delete_files(user=user)
|
||||
db.session.delete(user)
|
||||
db.session.commit()
|
||||
return flask.redirect('/admin/users')
|
||||
return flask.render_template("admin_user_del.html", user=fll.current_user, useredit=user)
|
||||
|
||||
@app.route('/password', methods=['GET', 'POST'])
|
||||
@fll.login_required
|
||||
def change_password():
|
||||
if flask.request.method=="POST":
|
||||
password = flask.request.form['password']
|
||||
if len(password) < 5:
|
||||
flask.flash('Mot de passe trop court !')
|
||||
else:
|
||||
fll.current_user.set_password(password)
|
||||
db.session.commit()
|
||||
return flask.redirect(URL_DEFAULT)
|
||||
return flask.render_template("password.html", user=fll.current_user)
|
||||
|
||||
#######################################################################
|
||||
# Files managament #
|
||||
#######################################################################
|
||||
|
||||
def delete_files(user=None):
|
||||
if isinstance(user, str):
|
||||
username = user
|
||||
else:
|
||||
username = user.login
|
||||
lf = File.query.filter_by(user=username).all()
|
||||
for f in lf:
|
||||
fpath = genpath(f.name, f.user)
|
||||
if os.path.isfile(fpath):
|
||||
os.remove(fpath)
|
||||
db.session.delete(f)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True, host='127.0.0.1', port=8080 )
|
||||
|
5
requirements.txt
Normal file
@ -0,0 +1,5 @@
|
||||
Flask==1.1.2
|
||||
Flask-Login==0.5.0
|
||||
Flask-SQLAlchemy==2.4.1
|
||||
SQLAlchemy==1.3.16
|
||||
Werkzeug==1.0.1
|
9
settings.example.cfg
Normal file
@ -0,0 +1,9 @@
|
||||
# App configuration file
|
||||
# copy this file to settings.cfg
|
||||
|
||||
# Generate a secure key with
|
||||
# python -c "import os, base64; print(base64.b64encode(os.urandom(24)))"
|
||||
SECRET_KEY = "to-be-changed"
|
||||
|
||||
SQLALCHEMY_DATABASE_URI = 'sqlite:///db.sqlite'
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
16
settings.example.py
Normal file
@ -0,0 +1,16 @@
|
||||
# The public url for preventing
|
||||
# Cross_origin bugs
|
||||
PUBLIC_URL='127.0.0.1'
|
||||
# Listen url
|
||||
LISTEN_URL = '127.0.0.1'
|
||||
|
||||
# If filename exist, will try to append _XXXX to filename up to NMAX_EXTENSION
|
||||
NMAX_EXTENSION = 100
|
||||
|
||||
# Maximum file size for upload
|
||||
MAX_FILE_SIZE = 1024 #Mb
|
||||
|
||||
# Upload folder
|
||||
import os.path
|
||||
_here = os.path.dirname(os.path.realpath(__file__))
|
||||
UPLOAD_FOLDER = os.path.join(_here, 'data')
|
BIN
static/croix.png
Normal file
After Width: | Height: | Size: 326 B |
BIN
static/edit.png
Normal file
After Width: | Height: | Size: 719 B |
BIN
static/favicon.ico
Normal file
After Width: | Height: | Size: 8.2 KiB |
BIN
static/fleche-bas.gif
Normal file
After Width: | Height: | Size: 76 B |
BIN
static/fleche-haut.gif
Normal file
After Width: | Height: | Size: 76 B |
192
static/fs.css
Normal file
@ -0,0 +1,192 @@
|
||||
/* =========================
|
||||
la Belote
|
||||
========================= */
|
||||
/****************/
|
||||
/* Reset */
|
||||
/****************/
|
||||
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;vertical-align:baseline;background:transparent}
|
||||
body{line-height:1}
|
||||
ol,ul{list-style:none}
|
||||
blockquote,q{quotes:none}
|
||||
blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}
|
||||
:focus{outline:0}
|
||||
ins{text-decoration:none}
|
||||
del{text-decoration:line-through}
|
||||
table{border-collapse:collapse;border-spacing:0}
|
||||
/****************/
|
||||
/* généralités */
|
||||
/****************/
|
||||
body {
|
||||
margin: 0px;
|
||||
background-color: #619092;
|
||||
}
|
||||
div.clear {
|
||||
clear: both;
|
||||
}
|
||||
div.clearfix-head::after {
|
||||
content:"";
|
||||
clear: both;
|
||||
display: table;
|
||||
border: 1px solid black;
|
||||
width: 100%;
|
||||
}
|
||||
div.clearfix::after{
|
||||
content:"";
|
||||
clear: both;
|
||||
display: table;
|
||||
}
|
||||
div, p, li, a, th, td {
|
||||
font-family: 'Segoe UI', Arial, Helvetica, sans-serif;
|
||||
font-size: 15px;
|
||||
line-height: 17px;
|
||||
color: #092E1E;
|
||||
}
|
||||
/****************/
|
||||
/* conteneur */
|
||||
/****************/
|
||||
#conteneur {
|
||||
margin-top: 8px;
|
||||
margin-right: auto;
|
||||
margin-bottom: 0px;
|
||||
margin-left: auto;
|
||||
height: 600px;
|
||||
width: 1200px;
|
||||
/* border: 1px solid #092E1E; */
|
||||
}
|
||||
/****************/
|
||||
/* entete */
|
||||
/****************/
|
||||
#entete {
|
||||
position: relative;
|
||||
height: 66px;
|
||||
margin: 2px;
|
||||
/*
|
||||
border: 1px solid #092E1E;
|
||||
*/
|
||||
}
|
||||
#logo {
|
||||
margin: 2px 10px 2px 2px;
|
||||
float: left;
|
||||
}
|
||||
#logo img {
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
#titre {
|
||||
width: 55%;
|
||||
line-height: 60px;
|
||||
margin: 2px 15px 2px 2px;
|
||||
text-align: center;
|
||||
font-size: 140%;
|
||||
font-weight: bold;
|
||||
float: left;
|
||||
}
|
||||
#username {
|
||||
width: 20%;
|
||||
height: 60px;
|
||||
line-height: 60px;
|
||||
margin: 2px 15px 2px 30px;
|
||||
float: right;
|
||||
|
||||
font-size: 140%;
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
}
|
||||
#username-fleche {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
cursor: pointer;
|
||||
padding-left: 10px;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
#username-items {
|
||||
position: absolute;
|
||||
top: 68px;
|
||||
right: 0px;
|
||||
width: 180px;
|
||||
visibility: hidden;
|
||||
margin-top: 2px ;
|
||||
padding: 5px;
|
||||
border: 1px solid #01393C;
|
||||
overflow: hidden;
|
||||
z-index: 100;
|
||||
background-color: #3E7A7C;
|
||||
}
|
||||
.menu-item:hover {
|
||||
cursor: pointer;
|
||||
background-color: #0E4E51;
|
||||
}
|
||||
|
||||
/****************/
|
||||
/* zone-de-contenu */
|
||||
/****************/
|
||||
#zone-de-contenu {
|
||||
width:80%;
|
||||
margin:auto;
|
||||
}
|
||||
#zone-de-contenu>div {
|
||||
margin:auto;
|
||||
}
|
||||
.tablelist {
|
||||
width:100%;
|
||||
}
|
||||
|
||||
/****************/
|
||||
/* other */
|
||||
/****************/
|
||||
button#add{
|
||||
min-width:180px;
|
||||
margin:auto;
|
||||
padding:10px;
|
||||
background: #3E7A7C;
|
||||
-moz-border-radius: 10px;
|
||||
-webkit-border-radius: 10px;
|
||||
-khtml-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
display:block;
|
||||
}
|
||||
|
||||
thead{
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
fieldset.fieldset {
|
||||
border:1px solid #071842;
|
||||
padding:30px;
|
||||
margin:20px;
|
||||
}
|
||||
|
||||
|
||||
.alert{
|
||||
width:80%;
|
||||
margin: 10px auto 10px auto;
|
||||
padding:10px;
|
||||
background: #FF9E77;
|
||||
-moz-border-radius: 10px;
|
||||
-webkit-border-radius: 10px;
|
||||
-khtml-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
display:block;
|
||||
border: 1px solid #DF531C;
|
||||
}
|
||||
|
||||
.alert > .close{
|
||||
background: #EC784A;
|
||||
border: none;
|
||||
}
|
||||
|
||||
table.tablelogin td{
|
||||
padding: 10px 0px 10px 0px;
|
||||
}
|
||||
|
||||
#info {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 280px;
|
||||
padding:3px;
|
||||
text-align: center;
|
||||
background: #CE9667;
|
||||
}
|
BIN
static/logo.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
static/ok.png
Normal file
After Width: | Height: | Size: 506 B |
BIN
static/plus.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
78
static/script.js
Normal file
@ -0,0 +1,78 @@
|
||||
/*************************
|
||||
la Belote
|
||||
**************************/
|
||||
var go = function(url) {
|
||||
document.location.href=url;
|
||||
}
|
||||
|
||||
var hidealert = function(id){
|
||||
var element = document.getElementById(id);
|
||||
element.parentNode.removeChild(element);
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function(event) {
|
||||
/*==============================
|
||||
listeners à activer à ready
|
||||
===============================*/
|
||||
|
||||
document.getElementById("username").addEventListener("click",function() {apparaitDisparaitMenu('username-items','username-fleche')});
|
||||
|
||||
var options = document.getElementById("options");
|
||||
if(options){
|
||||
options.addEventListener("click",function() {apparaitDisparaitAccordeon('options-valeur','options-fleche')});
|
||||
}
|
||||
var derpli = document.getElementById("dernier-pli")
|
||||
if(derpli){
|
||||
derpli.addEventListener("click",function() {apparaitDisparaitAccordeon('dernier-pli-valeur','dernier-pli-fleche')});
|
||||
}
|
||||
|
||||
|
||||
/*==============================
|
||||
fonctions
|
||||
===============================*/
|
||||
/*
|
||||
fonction apparaitDisparaitUserAttr
|
||||
apparition/disparition d'un menu
|
||||
*/
|
||||
function apparaitDisparaitMenu (idMenu,idFleche) {
|
||||
var element = document.getElementById(idMenu);
|
||||
if(element.style.visibility == "hidden" || element.style.visibility == "") {
|
||||
document.getElementById(idMenu).style.visibility = "visible";
|
||||
}
|
||||
else {
|
||||
document.getElementById(idMenu).style.visibility = "hidden";
|
||||
};
|
||||
var elementFleche = document.getElementById(idFleche);
|
||||
var splitElement = elementFleche.src.split('/');
|
||||
var lastElement = splitElement[splitElement.length-1];
|
||||
if(lastElement == "fleche-bas.gif") {
|
||||
document.getElementById(idFleche).src = "/static/fleche-haut.gif";
|
||||
}
|
||||
else {
|
||||
document.getElementById(idFleche).src = "/static/fleche-bas.gif";
|
||||
}
|
||||
return(0);
|
||||
}
|
||||
|
||||
function apparaitDisparaitAccordeon (idZone,idFleche) {
|
||||
var element = document.getElementById(idZone);
|
||||
if(element.style.display == "none" || element.style.display == "") {
|
||||
document.getElementById(idZone).style.display = "block";
|
||||
}
|
||||
else {
|
||||
document.getElementById(idZone).style.display = "none";
|
||||
};
|
||||
var elementFleche = document.getElementById(idFleche);
|
||||
var splitElement = elementFleche.src.split('/');
|
||||
var lastElement = splitElement[splitElement.length-1];
|
||||
if(lastElement == "fleche-bas.gif") {
|
||||
document.getElementById(idFleche).src = "/static/fleche-haut.gif";
|
||||
}
|
||||
else {
|
||||
document.getElementById(idFleche).src = "/static/fleche-bas.gif";
|
||||
}
|
||||
return(0);
|
||||
}
|
||||
|
||||
/* fin de $(document).ready() */
|
||||
});
|
17
templates/admin_user_del.html
Normal file
@ -0,0 +1,17 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %} Administration {% endblock %}
|
||||
|
||||
{% block titre %} Delete user {{ useredit.name }} {% endblock %}
|
||||
|
||||
{% block contenu %}
|
||||
<div id="zone-de-contenu">
|
||||
<fieldset class="fieldset"><legend style="font-size:25px;">Confirm</legend>
|
||||
<p>Delete user <b>{{ useredit.name }} ({{useredit.login}})</b> ? </p>
|
||||
<br />
|
||||
<table width=100% style="text-align:center">
|
||||
<tr><td><a href="?confirm=1">Yes</a></td><td> <a href="/admin/users">No</a></td></tr>
|
||||
</table>
|
||||
</fieldset>
|
||||
</div>
|
||||
{% endblock %}
|
40
templates/admin_user_edit.html
Normal file
@ -0,0 +1,40 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %} Administration {% endblock %}
|
||||
|
||||
{% block titre %} Administration : Edit user {% endblock %}
|
||||
|
||||
{% block contenu %}
|
||||
<div id="zone-de-contenu">
|
||||
<form method="POST" action="">
|
||||
<div style="width:100%;margin:20px;"> </div>
|
||||
|
||||
<fieldset class="fieldset"><legend style="font-size:25px;">User</legend>
|
||||
{% for message in get_flashed_messages() %}
|
||||
<div class="alert" id="alert{{loop.index}}">
|
||||
<button type="button" class="close" onclick="javascript:hidealert('alert{{loop.index}}')">×</button>
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<table width=100%>
|
||||
<tr><td>Username: </td><td> <input type="text" name="username" placeholder="jean.dupont" {% if useredit is not none %} value="{{useredit.login}}" readonly="readonly"{% endif %} /></td></tr>
|
||||
<tr><td>Full name: </td><td> <input type="text" name="name" placeholder="Mr Jean Dupont" {% if useredit is not none %} value="{{useredit.name}}" {% endif %} /></td></tr>
|
||||
<tr><td> Password: </td><td> <input type="password" name="password" placeholder="Mot de passe"/></td></tr>
|
||||
<tr><td> Permissions: </td><td> <select name="level">
|
||||
{% for e in levels_users %}
|
||||
{% if useredit is none %}
|
||||
<option value="{{ levels_users[e] }}" {% if e == 'Simple user' %}selected{% endif %}>{{ e }}</option>
|
||||
{% else %}
|
||||
<option value="{{ levels_users[e] }}" {% if useredit.is_admin == levels_users[e] %}selected{% endif %}>{{ e }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td></tr>
|
||||
<tr><td> </td> <td><input id="submit" type="submit" value=" OK "></td></tr>
|
||||
</table>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
25
templates/admin_users.html
Normal file
@ -0,0 +1,25 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %} Administration {% endblock %}
|
||||
|
||||
{% block titre %}Administration : users {% endblock %}
|
||||
|
||||
{% block contenu %}
|
||||
<div id="zone-de-contenu">
|
||||
<fieldset class="fieldset" ><legend style="font-size:25px;">Add user</legend>
|
||||
<button id="add" onclick="javascript:go('/admin/users/new')">
|
||||
<img src="/static/plus.png" style="float:left;" alt=''/><div style="float:right; margin:5px;">New user</div>
|
||||
</button>
|
||||
</fieldset>
|
||||
<fieldset class="fieldset"><legend style="font-size:25px;">List of users</legend>
|
||||
<table width=100%>
|
||||
<thead>
|
||||
<tr><td>Login</td><td>Name</td><td>Level</td><td>Actions</td></tr>
|
||||
</thead>
|
||||
{% for u in users %}
|
||||
<tr><td><a href="/d/{{u.login}}">{{ u.login }}</a></td><td>{{ u.name }}</td><td>{{ u.is_admin }}</td><td><a href="/admin/users/{{ u.login }}"><img src="/static/edit.png" alt="Edit" /></a> <a href="/admin/users/{{ u.login }}/del"><img src="/static/croix.png" alt="Del" /></a></td></tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</fieldset>
|
||||
</div>
|
||||
{% endblock %}
|
52
templates/base.html
Normal file
@ -0,0 +1,52 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<link rel="shortcut icon" href="/static/favicon.ico">
|
||||
{% include ['head-perso.html', 'head.html'] %}
|
||||
{% block head %}
|
||||
<title>{% block title %}{% endblock %} - FS</title>
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<div id="conteneur">
|
||||
<!-- ENTETE -->
|
||||
<div id="entete" class="clearfix-head">
|
||||
<div id="logo">
|
||||
<img src="/static/logo.png" height=100% width=100% />
|
||||
</div><!-- logo -->
|
||||
<div id="titre">
|
||||
{% block titre %} {% endblock %}
|
||||
</div><!-- titre -->
|
||||
<div id="username">
|
||||
{% if not user == None %}
|
||||
{{ user.name }}
|
||||
{% else %}
|
||||
Log in
|
||||
{% endif %}
|
||||
<img id="username-fleche" src="/static/fleche-bas.gif"/>
|
||||
</div><!-- usernamev -->
|
||||
<div id="username-items">
|
||||
{% if not user == None %}
|
||||
<div id="username-item1" class="menu-item" onclick="javascript:go('/myrepo')">My repo</div>
|
||||
<div id="username-item2" class="menu-item" onclick="javascript:go('/password')">Change password</div>
|
||||
<div id="username-item3" class="menu-item" onclick="javascript:go('/logout')">Log out</div>
|
||||
{% if user is not none and user.is_admin >= 5 %}
|
||||
<div id="username-item4" class="menu-item" onclick="javascript:go('/admin/users')">Admin users</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div id="username-item1" class="menu-item" onclick="javascript:go('/login')">Log in</div>
|
||||
{% endif %}
|
||||
</div><!-- username-items-->
|
||||
</div><!-- entete -->
|
||||
|
||||
<!-- CONTENU -->
|
||||
{% block contenu %}
|
||||
{% endblock %}
|
||||
<!-- / CONTENU -->
|
||||
|
||||
</div>
|
||||
{% block script %} {% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
27
templates/dl_pwd.html
Normal file
@ -0,0 +1,27 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %} File access {% endblock %}
|
||||
|
||||
{% block titre %} {{filename}} {% endblock %}
|
||||
|
||||
{% block contenu %}
|
||||
<div id="zone-de-contenu">
|
||||
<div style="width:100%;margin:20px;"> </div>
|
||||
|
||||
<fieldset class="fieldset"><legend style="font-size:25px;">Protected</legend>
|
||||
{% for message in get_flashed_messages() %}
|
||||
<div class="alert" id="alert{{loop.index}}">
|
||||
<button type="button" class="close" onclick="javascript:hidealert('alert{{loop.index}}')">×</button>
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<form action="" method="POST" accept-charset="utf-8">
|
||||
Password : <input type="password" name="password" />
|
||||
<input id="submit" type="submit" value="OK">
|
||||
</form>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
17
templates/file_del.html
Normal file
@ -0,0 +1,17 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %} Administration {% endblock %}
|
||||
|
||||
{% block titre %} Delete {{ f.name }} {% endblock %}
|
||||
|
||||
{% block contenu %}
|
||||
<div id="zone-de-contenu">
|
||||
<fieldset class="fieldset"><legend style="font-size:25px;">Confirm</legend>
|
||||
<p>Delete <b>{{ f.name }} ({{f.user}})</b> ? </p>
|
||||
<br />
|
||||
<table width=100% style="text-align:center">
|
||||
<tr><td><a href="?confirm=1">Yes</a></td><td> <a href="/d/{{f.user}}">No</a></td></tr>
|
||||
</table>
|
||||
</fieldset>
|
||||
</div>
|
||||
{% endblock %}
|
33
templates/file_edit.html
Normal file
@ -0,0 +1,33 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %} Administration {% endblock %}
|
||||
|
||||
{% block titre %} Administration : Edit {{f.name}} {% endblock %}
|
||||
|
||||
{% block contenu %}
|
||||
<div id="zone-de-contenu">
|
||||
<form method="POST" action="">
|
||||
<div style="width:100%;margin:20px;"> </div>
|
||||
|
||||
<fieldset class="fieldset"><legend style="font-size:25px;">File</legend>
|
||||
{% for message in get_flashed_messages() %}
|
||||
<div class="alert" id="alert{{loop.index}}">
|
||||
<button type="button" class="close" onclick="javascript:hidealert('alert{{loop.index}}')">×</button>
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<table width=100%>
|
||||
<tr><td>Name : </td><td> <input type="text" name="name" value="{{f.name}}" readonly="readonly" /></td></tr>
|
||||
<tr><td>Description : </td><td> <input type="text" name="desc" placeholder="This is a file" {% if f.desc is not none %} value="{{f.desc}}" {% endif %} /></td></tr>
|
||||
<tr><td> Password : </td><td> {% if f.password == "" %} No {% else %} Yes {% endif %}</td></tr>
|
||||
<tr><td> Change password : </td><td> <input type="password" name="password" placeholder="XXXX"/></td></tr>
|
||||
<tr><td>Publicly visible : </td><td> <input type="checkbox" name="visible" value='1' {% if f.visible %} checked {%endif%}/></td></tr>
|
||||
<tr><td>Date : </td><td> {{f.date}} </td></tr>
|
||||
<tr><td> </td> <td><input id="submit" type="submit" value=" OK "></td></tr>
|
||||
</table>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
3
templates/head.html
Normal file
@ -0,0 +1,3 @@
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<meta http-equiv="cache-control" content="no-cache" />
|
||||
<link rel='stylesheet' type='text/css' href='/static/fs.css' />
|
15
templates/index.html
Normal file
@ -0,0 +1,15 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %} Welcome {% endblock %}
|
||||
|
||||
{% block titre %} Welcome {% endblock %}
|
||||
|
||||
{% block contenu %}
|
||||
<div id="zone-de-contenu">
|
||||
<fieldset class="fieldset"><legend style="font-size:25px;">Welcome</legend>
|
||||
<p> Welcome on SFS !</p>
|
||||
<p> To upload a file, see <a href="/up">upload page</a></p>
|
||||
<p> To manage your data, you have to log in, go to <a href="/login">login page</a></p>
|
||||
</fieldset>
|
||||
</div>
|
||||
{% endblock %}
|
29
templates/login.html
Normal file
@ -0,0 +1,29 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %} Login {% endblock %}
|
||||
|
||||
{% block titre %} Login {% endblock %}
|
||||
|
||||
{% block contenu %}
|
||||
<div id="zone-de-contenu">
|
||||
<form method="POST" action="">
|
||||
<div style="width:100%;margin:20px;"> </div>
|
||||
<fieldset class="fieldset"><legend style="font-size:25px;">Identifiez-vous</legend>
|
||||
{% for message in get_flashed_messages() %}
|
||||
<div class="alert" id="alert{{loop.index}}">
|
||||
<button type="button" class="close" onclick="javascript:hidealert('alert{{loop.index}}')">×</button>
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<table width=100% class="tablelogin">
|
||||
<tr><td>Username : </td><td> <input type="text" name="username" placeholder="Your username" /></td></tr>
|
||||
<tr><td>Password: </td><td> <input type="password" name="password" placeholder="Your password"/></td></tr>
|
||||
<tr><td> </td> <td><input id="submit" type="submit" value=" OK "></td></tr>
|
||||
</table>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
27
templates/password.html
Normal file
@ -0,0 +1,27 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %} Mot de passe {% endblock %}
|
||||
|
||||
{% block titre %} Change password {% endblock %}
|
||||
|
||||
{% block contenu %}
|
||||
<div id="zone-de-contenu">
|
||||
<div style="width:100%;margin:20px;"> </div>
|
||||
|
||||
<fieldset class="fieldset"><legend style="font-size:25px;">Change password</legend>
|
||||
{% for message in get_flashed_messages() %}
|
||||
<div class="alert" id="alert{{loop.index}}">
|
||||
<button type="button" class="close" onclick="javascript:hidealert('alert{{loop.index}}')">×</button>
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<form action="" method="POST" accept-charset="utf-8">
|
||||
New password: <input type="password" name="password" />
|
||||
<input id="submit" type="submit" value="Valider">
|
||||
</form>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
33
templates/repo.html
Normal file
@ -0,0 +1,33 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %} Files {% endblock %}
|
||||
|
||||
{% block titre %} Repository /{{folder}} {% endblock %}
|
||||
|
||||
{% block contenu %}
|
||||
<div id="zone-de-contenu">
|
||||
{% if ismine %}
|
||||
<fieldset class="fieldset" ><legend style="font-size:25px;">Upload</legend>
|
||||
<button id="add" onclick="javascript:go('/up')">
|
||||
<img src="/static/plus.png" style="float:left;" alt=''/><div style="float:right; margin:5px;">Upload files</div>
|
||||
</button>
|
||||
</fieldset>
|
||||
{% endif %}
|
||||
{% if l|length > 0 %}
|
||||
<fieldset class="fieldset" ><legend style="font-size:25px;">Repository</legend>
|
||||
<table class="tablelist">
|
||||
{% for f in l %}
|
||||
<tr>
|
||||
<td width=80%><a href="/d/{{folder}}/{{f.name}}">{{ f.name }}</a></td>
|
||||
{% if isadmin %}
|
||||
<td width=20%><a href="/f/{{f.id}}/edit"><img src="/static/edit.png" alt="Edit"/></a><a href="/f/{{f.id}}/del"><img src="/static/croix.png" alt="Supprimer"/></a></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</fieldset>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
79
templates/up.html
Normal file
@ -0,0 +1,79 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %} Upload {% endblock %}
|
||||
|
||||
{% block titre %} Upload {% endblock %}
|
||||
|
||||
{% block contenu %}
|
||||
<div id="zone-de-contenu">
|
||||
<form method="POST" action="" enctype=multipart/form-data>
|
||||
<div style="width:100%;margin:20px;"> </div>
|
||||
<fieldset class="fieldset">
|
||||
{% for message in get_flashed_messages() %}
|
||||
<div class="alert" id="alert{{loop.index}}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% if user == none %}
|
||||
<h3> Identification </h3>
|
||||
<table width=100% class="tablelogin">
|
||||
<tr><td>Username: </td><td> <input type="text" name="username" placeholder="Your username" /></td></tr>
|
||||
<tr><td>Password: </td><td> <input type="password" name="password" placeholder="**********"/></td></tr>
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
<h3>Files to transfer</h3>
|
||||
<table width=100% class="tablelogin" id="filelist">
|
||||
<tr id="line1">
|
||||
<td><input type="file" name="file[]"></td>
|
||||
<td> <input type="text" name="filename[]" placeholder="Change file name" /></td>
|
||||
<td> <input type="text" name="desc[]" placeholder="Description" /></td>
|
||||
<td><a href="javascript:dellinef('line1')"><img src="static/croix.png" alt="Del" /></a></td>
|
||||
</tr>
|
||||
</table>
|
||||
<a href="javascript:addlinef()"><img src="static/plus.png" alt="Add" width=20px/>Add a file</a>
|
||||
|
||||
<br/>
|
||||
<br />
|
||||
<table>
|
||||
<tr>
|
||||
<td>Publicly visible: </td><td><input type="checkbox" name="visible" id="visible"> </td>
|
||||
</tr><tr>
|
||||
<td>Set a password: </td><td><input type="password" name="filepwd" id="filepwd" value=""></td>
|
||||
</tr>
|
||||
</table>
|
||||
<input type="hidden" name="graphical" id="graphical" value="1">
|
||||
<center>
|
||||
<input id="submit" type="submit" value=" Téléverser ">
|
||||
</center>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var maxline = 1;
|
||||
function dellinef(elementId) {
|
||||
// Removes an element from the document
|
||||
var element = document.getElementById(elementId);
|
||||
element.parentNode.removeChild(element);
|
||||
}
|
||||
function addElement(parentId, elementTag, elementId, html) {
|
||||
// Adds an element to the document
|
||||
var p = document.getElementById(parentId);
|
||||
var newElement = document.createElement(elementTag);
|
||||
newElement.setAttribute('id', elementId);
|
||||
newElement.innerHTML = html;
|
||||
p.appendChild(newElement);
|
||||
}
|
||||
function addlinef() {
|
||||
maxline++;
|
||||
var html = `<td><input type="file" name="file[]"></td>
|
||||
<td> <input type="text" name="filename[]" placeholder="Change file name" /></td>
|
||||
<td><a href="javascript:dellinef('line${maxline}')"><img src="static/croix.png" alt="Del" /></a></td>`
|
||||
addElement('filelist', 'tr', 'line' + maxline, html);
|
||||
}
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
|