commit 8b129324e46226598afa8de5d28100cf4704e9b7 Author: Leo VIALLON-GALINIER Date: Wed Nov 4 18:59:18 2020 +0100 First commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0a6af12 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..b9d743a --- /dev/null +++ b/README.md @@ -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 + +Copyright (C) 2020 L. Viallon-Galinier diff --git a/app.py b/app.py new file mode 100644 index 0000000..aaf0802 --- /dev/null +++ b/app.py @@ -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 + diff --git a/db.py b/db.py new file mode 100644 index 0000000..ae44ceb --- /dev/null +++ b/db.py @@ -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 ''.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) + diff --git a/fs.py b/fs.py new file mode 100644 index 0000000..2124c0a --- /dev/null +++ b/fs.py @@ -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 i0: + name = lnewnames[i] + else: + name = f.filename + if i0: + 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//', defaults={'raw': False}, methods=['GET', 'POST']) +@app.route('/d///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/') +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//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_admin0: + 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//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', 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//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 ) + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6bd6b97 --- /dev/null +++ b/requirements.txt @@ -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 diff --git a/settings.example.cfg b/settings.example.cfg new file mode 100644 index 0000000..0f8f842 --- /dev/null +++ b/settings.example.cfg @@ -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 diff --git a/settings.example.py b/settings.example.py new file mode 100644 index 0000000..b73b424 --- /dev/null +++ b/settings.example.py @@ -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') diff --git a/static/croix.png b/static/croix.png new file mode 100644 index 0000000..00f7d5e Binary files /dev/null and b/static/croix.png differ diff --git a/static/edit.png b/static/edit.png new file mode 100644 index 0000000..2d7cca2 Binary files /dev/null and b/static/edit.png differ diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 0000000..226669c Binary files /dev/null and b/static/favicon.ico differ diff --git a/static/fleche-bas.gif b/static/fleche-bas.gif new file mode 100644 index 0000000..8de7b46 Binary files /dev/null and b/static/fleche-bas.gif differ diff --git a/static/fleche-haut.gif b/static/fleche-haut.gif new file mode 100644 index 0000000..5b6ad65 Binary files /dev/null and b/static/fleche-haut.gif differ diff --git a/static/fs.css b/static/fs.css new file mode 100644 index 0000000..831d079 --- /dev/null +++ b/static/fs.css @@ -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; +} diff --git a/static/logo.png b/static/logo.png new file mode 100644 index 0000000..b81be6a Binary files /dev/null and b/static/logo.png differ diff --git a/static/ok.png b/static/ok.png new file mode 100644 index 0000000..476318f Binary files /dev/null and b/static/ok.png differ diff --git a/static/plus.png b/static/plus.png new file mode 100644 index 0000000..6d2cba7 Binary files /dev/null and b/static/plus.png differ diff --git a/static/script.js b/static/script.js new file mode 100644 index 0000000..a3af765 --- /dev/null +++ b/static/script.js @@ -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() */ +}); diff --git a/templates/admin_user_del.html b/templates/admin_user_del.html new file mode 100644 index 0000000..054aaa0 --- /dev/null +++ b/templates/admin_user_del.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} + +{% block title %} Administration {% endblock %} + +{% block titre %} Delete user {{ useredit.name }} {% endblock %} + +{% block contenu %} +
+
Confirm +

Delete user {{ useredit.name }} ({{useredit.login}}) ?

+
+ + +
Yes No
+
+
+{% endblock %} diff --git a/templates/admin_user_edit.html b/templates/admin_user_edit.html new file mode 100644 index 0000000..7199475 --- /dev/null +++ b/templates/admin_user_edit.html @@ -0,0 +1,40 @@ +{% extends "base.html" %} + +{% block title %} Administration {% endblock %} + +{% block titre %} Administration : Edit user {% endblock %} + +{% block contenu %} +
+
+
+ +
User + {% for message in get_flashed_messages() %} +
+ + {{ message }} +
+ {% endfor %} + + + + + + +
Username:
Full name:
Password:
Permissions: +
 
+
+
+
+ +{% endblock %} + diff --git a/templates/admin_users.html b/templates/admin_users.html new file mode 100644 index 0000000..66d768b --- /dev/null +++ b/templates/admin_users.html @@ -0,0 +1,25 @@ +{% extends "base.html" %} + +{% block title %} Administration {% endblock %} + +{% block titre %}Administration : users {% endblock %} + +{% block contenu %} +
+
Add user + +
+
List of users + + + + + {% for u in users %} + + {% endfor %} +
LoginNameLevelActions
{{ u.login }}{{ u.name }}{{ u.is_admin }}Edit Del
+
+
+{% endblock %} diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..eb1c809 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,52 @@ + + + + + {% include ['head-perso.html', 'head.html'] %} + {% block head %} + {% block title %}{% endblock %} - FS + {% endblock %} + + +
+ +
+ +
+ {% block titre %} {% endblock %} +
+
+ {% if not user == None %} + {{ user.name }} + {% else %} + Log in + {% endif %} + +
+
+ {% if not user == None %} + + + + {% if user is not none and user.is_admin >= 5 %} + + {% endif %} + {% else %} + + {% endif %} +
+
+ + + {% block contenu %} + {% endblock %} + + +
+ {% block script %} {% endblock %} + + + + diff --git a/templates/dl_pwd.html b/templates/dl_pwd.html new file mode 100644 index 0000000..ef5163f --- /dev/null +++ b/templates/dl_pwd.html @@ -0,0 +1,27 @@ +{% extends "base.html" %} + +{% block title %} File access {% endblock %} + +{% block titre %} {{filename}} {% endblock %} + +{% block contenu %} +
+
+ +
Protected + {% for message in get_flashed_messages() %} +
+ + {{ message }} +
+ {% endfor %} +
+ Password : + +
+
+
+ +{% endblock %} + + diff --git a/templates/file_del.html b/templates/file_del.html new file mode 100644 index 0000000..4459917 --- /dev/null +++ b/templates/file_del.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} + +{% block title %} Administration {% endblock %} + +{% block titre %} Delete {{ f.name }} {% endblock %} + +{% block contenu %} +
+
Confirm +

Delete {{ f.name }} ({{f.user}}) ?

+
+ + +
Yes No
+
+
+{% endblock %} diff --git a/templates/file_edit.html b/templates/file_edit.html new file mode 100644 index 0000000..26ecc1c --- /dev/null +++ b/templates/file_edit.html @@ -0,0 +1,33 @@ +{% extends "base.html" %} + +{% block title %} Administration {% endblock %} + +{% block titre %} Administration : Edit {{f.name}} {% endblock %} + +{% block contenu %} +
+
+
+ +
File + {% for message in get_flashed_messages() %} +
+ + {{ message }} +
+ {% endfor %} + + + + + + + + +
Name :
Description :
Password : {% if f.password == "" %} No {% else %} Yes {% endif %}
Change password :
Publicly visible :
Date : {{f.date}}
 
+
+
+
+ +{% endblock %} + diff --git a/templates/head.html b/templates/head.html new file mode 100644 index 0000000..4d49ff7 --- /dev/null +++ b/templates/head.html @@ -0,0 +1,3 @@ + + + diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..db9ce11 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} + +{% block title %} Welcome {% endblock %} + +{% block titre %} Welcome {% endblock %} + +{% block contenu %} +
+
Welcome +

Welcome on SFS !

+

To upload a file, see upload page

+

To manage your data, you have to log in, go to login page

+
+
+{% endblock %} diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..f1f4961 --- /dev/null +++ b/templates/login.html @@ -0,0 +1,29 @@ +{% extends "base.html" %} + +{% block title %} Login {% endblock %} + +{% block titre %} Login {% endblock %} + +{% block contenu %} +
+
+
+
Identifiez-vous + {% for message in get_flashed_messages() %} +
+ + {{ message }} +
+ {% endfor %} + + + + + +
Username :
Password:
 
+
+
+
+ +{% endblock %} + diff --git a/templates/password.html b/templates/password.html new file mode 100644 index 0000000..7c87286 --- /dev/null +++ b/templates/password.html @@ -0,0 +1,27 @@ +{% extends "base.html" %} + +{% block title %} Mot de passe {% endblock %} + +{% block titre %} Change password {% endblock %} + +{% block contenu %} +
+
+ +
Change password + {% for message in get_flashed_messages() %} +
+ + {{ message }} +
+ {% endfor %} +
+ New password: + +
+
+
+ +{% endblock %} + + diff --git a/templates/repo.html b/templates/repo.html new file mode 100644 index 0000000..0460fb5 --- /dev/null +++ b/templates/repo.html @@ -0,0 +1,33 @@ +{% extends "base.html" %} + +{% block title %} Files {% endblock %} + +{% block titre %} Repository /{{folder}} {% endblock %} + +{% block contenu %} +
+ {% if ismine %} +
Upload + +
+ {% endif %} + {% if l|length > 0 %} +
Repository + + {% for f in l %} + + + {% if isadmin %} + + {% endif %} + + {% endfor %} +
{{ f.name }}EditSupprimer
+
+ {% endif %} +
+ +{% endblock %} + diff --git a/templates/up.html b/templates/up.html new file mode 100644 index 0000000..04cb4a9 --- /dev/null +++ b/templates/up.html @@ -0,0 +1,79 @@ +{% extends "base.html" %} + +{% block title %} Upload {% endblock %} + +{% block titre %} Upload {% endblock %} + +{% block contenu %} +
+
+
+
+ {% for message in get_flashed_messages() %} +
+ {{ message }} +
+ {% endfor %} + + {% if user == none %} +

Identification

+ + + +
Username:
Password:
+ {% endif %} + +

Files to transfer

+ + + + + + + +
Del
+ AddAdd a file + +
+
+ + + + + + +
Publicly visible:
Set a password:
+ +
+ +
+
+
+
+ + + +{% endblock %} +