First commit

This commit is contained in:
Léo 2020-11-04 18:59:18 +01:00
commit 8b129324e4
31 changed files with 1304 additions and 0 deletions

36
.gitignore vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

BIN
static/edit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 719 B

BIN
static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

BIN
static/fleche-bas.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 B

BIN
static/fleche-haut.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 B

192
static/fs.css Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
static/ok.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 506 B

BIN
static/plus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

78
static/script.js Normal file
View 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() */
});

View 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 %}

View 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}}')">&times;</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>&nbsp;</td> <td><input id="submit" type="submit" value="&nbsp;OK&nbsp;"></td></tr>
</table>
</fieldset>
</form>
</div>
{% endblock %}

View 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>&nbsp;<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
View 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
View 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}}')">&times;</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
View 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
View 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}}')">&times;</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>&nbsp;</td> <td><input id="submit" type="submit" value="&nbsp;OK&nbsp;"></td></tr>
</table>
</fieldset>
</form>
</div>
{% endblock %}

3
templates/head.html Normal file
View 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
View 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
View 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}}')">&times;</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>&nbsp;</td> <td><input id="submit" type="submit" value="&nbsp;OK&nbsp;"></td></tr>
</table>
</fieldset>
</form>
</div>
{% endblock %}

27
templates/password.html Normal file
View 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}}')">&times;</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
View 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
View 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="&nbsp;Téléverser&nbsp;">
</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 %}