Démelage de spaghettis !
Before Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 1.6 MiB |
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 133 KiB |
Before Width: | Height: | Size: 386 B After Width: | Height: | Size: 386 B |
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 938 B After Width: | Height: | Size: 938 B |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 321 B After Width: | Height: | Size: 321 B |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 327 B After Width: | Height: | Size: 327 B |
Before Width: | Height: | Size: 312 B After Width: | Height: | Size: 312 B |
Before Width: | Height: | Size: 357 B After Width: | Height: | Size: 357 B |
Before Width: | Height: | Size: 371 B After Width: | Height: | Size: 371 B |
Before Width: | Height: | Size: 878 B After Width: | Height: | Size: 878 B |
Before Width: | Height: | Size: 244 B After Width: | Height: | Size: 244 B |
4
tetawebapp/.gitignore
vendored
@ -1,4 +0,0 @@
|
|||||||
*un~
|
|
||||||
*.swp
|
|
||||||
*.pyc
|
|
||||||
*.wsgi
|
|
@ -1,4 +0,0 @@
|
|||||||
# Config
|
|
||||||
SQLALCHEMY_TRACK_MODIFICATIONS = True
|
|
||||||
SQLALCHEMY_DATABASE_URI = "postgresql://tetawebapp:tetawebapp@localhost/tetawebapp"
|
|
||||||
UPLOADED_FILES_DEST = "./upload"
|
|
@ -1 +0,0 @@
|
|||||||
config.local.py
|
|
Before Width: | Height: | Size: 133 KiB |
Before Width: | Height: | Size: 386 B |
Before Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 938 B |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 321 B |
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 327 B |
Before Width: | Height: | Size: 312 B |
Before Width: | Height: | Size: 357 B |
Before Width: | Height: | Size: 371 B |
Before Width: | Height: | Size: 878 B |
Before Width: | Height: | Size: 244 B |
@ -1,249 +0,0 @@
|
|||||||
var red = "#FF0000";
|
|
||||||
var green = "#00FF00";
|
|
||||||
var light_red = "#FCD5DC";
|
|
||||||
var light_green = "#D5FCD8";
|
|
||||||
var base_bg = "#FFFFFF";
|
|
||||||
var base_border = "#888888";
|
|
||||||
var coloured_bg = "#FF5D00";
|
|
||||||
var clear_bg = "#E5E5E5";
|
|
||||||
var text_color = "#555555";
|
|
||||||
|
|
||||||
/* **************************************************************************************
|
|
||||||
* GLOBAL
|
|
||||||
* **************************************************************************************/
|
|
||||||
|
|
||||||
// Cookies
|
|
||||||
function setcookie(cname, cvalue, exdays) {
|
|
||||||
// Set cookie
|
|
||||||
var d = new Date();
|
|
||||||
d.setTime(d.getTime() + (exdays*24*60*60*1000));
|
|
||||||
var expires = "expires="+ d.toUTCString();
|
|
||||||
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
|
|
||||||
}
|
|
||||||
|
|
||||||
function getcookie(cname) {
|
|
||||||
// Get cookie by name
|
|
||||||
var value = "; " + document.cookie;
|
|
||||||
var parts = value.split("; " + cname + "=");
|
|
||||||
if (parts.length == 2) return parts.pop().split(";").shift();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Eye candies
|
|
||||||
function valid_input(obj) {
|
|
||||||
// Valid input makes obj background to glow green for 2 seconds
|
|
||||||
// If obj borders were red, they get they normal color back
|
|
||||||
obj.style.backgroundColor = light_green;
|
|
||||||
obj.style.borderColor = base_border;
|
|
||||||
setTimeout( function() {
|
|
||||||
obj.style.backgroundColor = base_bg;
|
|
||||||
}
|
|
||||||
, 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function invalid_input(obj) {
|
|
||||||
// Invalid input makes obj borders and background to glow red for 2 seconds
|
|
||||||
// Border color will stay red until a valid input is sent
|
|
||||||
obj.style.backgroundColor = light_red;
|
|
||||||
obj.style.borderColor = red;
|
|
||||||
setTimeout( function() {
|
|
||||||
obj.style.backgroundColor = base_bg;
|
|
||||||
}
|
|
||||||
, 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function valid_upload(obj) {
|
|
||||||
// Valid input makes obj background to glow green for 2 seconds
|
|
||||||
// If obj borders were red, they get they normal color back
|
|
||||||
obj.style.backgroundColor = green;
|
|
||||||
obj.style.borderColor = text_color;
|
|
||||||
obj.style.borderStyle = 'solid';
|
|
||||||
setTimeout( function() {
|
|
||||||
obj.style.backgroundColor = clear_bg;
|
|
||||||
obj.style.borderStyle = 'none';
|
|
||||||
}
|
|
||||||
, 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function invalid_upload(obj) {
|
|
||||||
// Invalid input makes obj borders and background to glow red for 2 seconds
|
|
||||||
// Border color will stay red until a valid input is sent
|
|
||||||
obj.style.backgroundColor = red;
|
|
||||||
obj.style.borderColor = text_color;
|
|
||||||
obj.style.borderStyle = 'solid';
|
|
||||||
setTimeout( function() {
|
|
||||||
obj.style.borderStyle = 'solid';
|
|
||||||
obj.style.backgroundColor = clear_bg;
|
|
||||||
obj.style.borderColor = red;
|
|
||||||
}
|
|
||||||
, 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function lit(obj) {
|
|
||||||
// Lit bacground and border on obj (use by input type=file)
|
|
||||||
obj.style.backgroundColor = coloured_bg;
|
|
||||||
obj.style.borderColor = text_color;
|
|
||||||
obj.style.borderStyle = 'solid';
|
|
||||||
}
|
|
||||||
|
|
||||||
function unlit(obj) {
|
|
||||||
// Unlit bacground and border on obj (use by input type=file)
|
|
||||||
obj.style.backgroundColor = clear_bg;
|
|
||||||
obj.style.borderColor = clear_bg;
|
|
||||||
obj.style.borderStyle = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function verify_login() {
|
|
||||||
// Verify login inputs
|
|
||||||
login = document.getElementById('login');
|
|
||||||
password = document.getElementById('password');
|
|
||||||
if (login.value.length > 0) {
|
|
||||||
valid_input(login);
|
|
||||||
if (password.value.length > 0) {
|
|
||||||
valid_input(password);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
invalid_input(password);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
invalid_input(login);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function logout() {
|
|
||||||
// Logout user
|
|
||||||
setcookie('token', '', 30);
|
|
||||||
document.location = '/';
|
|
||||||
}
|
|
||||||
|
|
||||||
/* **************************************************************************************
|
|
||||||
* AJAX
|
|
||||||
* **************************************************************************************/
|
|
||||||
|
|
||||||
function get_html_from_ajax(obj, url) {
|
|
||||||
// Get HTML content from AJAX request from url argument
|
|
||||||
// HTML content is then put as innerHTML to obj
|
|
||||||
var xhttp = new XMLHttpRequest();
|
|
||||||
xhttp.onerror = function(){
|
|
||||||
obj.innerHTML = "Error while getting content (1)";
|
|
||||||
};
|
|
||||||
|
|
||||||
xhttp.onload = function(){
|
|
||||||
if (xhttp.status != 200) {
|
|
||||||
obj.innerHTML = "Error while getting content (2)";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xhttp.onreadystatechange = function() {
|
|
||||||
if (xhttp.readyState == 4 && xhttp.status == 200) {
|
|
||||||
var response = xhttp.responseText;
|
|
||||||
obj.innerHTML = response;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhttp.open('POST', url, true);
|
|
||||||
xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
|
||||||
xhttp.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
function set_value_from_ajax(obj, url, err_code) {
|
|
||||||
// Send value from obj.value via AJAX request to url argument
|
|
||||||
// obj.value is passed to URL in a REST sheme like <URL>/<VALUE>
|
|
||||||
// If err_code response is received, then a server side
|
|
||||||
// error has occured and input is invalidated.
|
|
||||||
url = url + '/' + obj.value;
|
|
||||||
var xhttp = new XMLHttpRequest();
|
|
||||||
xhttp.onerror = function(){
|
|
||||||
invalid_input(obj);
|
|
||||||
};
|
|
||||||
|
|
||||||
xhttp.onload = function(){
|
|
||||||
if (xhttp.status != 200) {
|
|
||||||
invalid_input(obj);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xhttp.onreadystatechange = function() {
|
|
||||||
if (xhttp.readyState == 4 && xhttp.status == 200) {
|
|
||||||
var response = xhttp.responseText;
|
|
||||||
if (response == err_code) {
|
|
||||||
invalid_input(obj);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
valid_input(obj);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhttp.open('POST', url, true);
|
|
||||||
xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
|
||||||
xhttp.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_value_from_ajax(obj, url, err_code) {
|
|
||||||
// Get value from AJAX request
|
|
||||||
// The returned value is then set to obj.value.
|
|
||||||
// If err_code response is received, then a server side
|
|
||||||
// error has occured and input is invalidated
|
|
||||||
var xhttp = new XMLHttpRequest();
|
|
||||||
xhttp.onerror = function(){
|
|
||||||
invalid_input(obj);
|
|
||||||
};
|
|
||||||
|
|
||||||
xhttp.onload = function(){
|
|
||||||
if (xhttp.status != 200) {
|
|
||||||
invalid_input(obj);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xhttp.onreadystatechange = function() {
|
|
||||||
if (xhttp.readyState == 4 && xhttp.status == 200) {
|
|
||||||
var response = xhttp.responseText;
|
|
||||||
if (response == err_code) {
|
|
||||||
invalid_input(obj);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
obj.value = response;
|
|
||||||
valid_input(obj);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhttp.open('POST', url, true);
|
|
||||||
xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
|
||||||
xhttp.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
function upload_file_from_ajax(obj, url, err_code) {
|
|
||||||
// Upload files get from <obj> to the specified <url>
|
|
||||||
// if <errcode> is returned input is invalidated
|
|
||||||
var files = obj.files;
|
|
||||||
var icon_id = obj.id.substring(obj.id.lastIndexOf("_") + 1);
|
|
||||||
var icon_obj = document.getElementById("upload_icon_" + icon_id)
|
|
||||||
var xhttp = new XMLHttpRequest();
|
|
||||||
xhttp.onerror = function(){
|
|
||||||
invalid_upload(icon_obj);
|
|
||||||
};
|
|
||||||
|
|
||||||
xhttp.onload = function(){
|
|
||||||
if (xhttp.status != 200) {
|
|
||||||
invalid_upload(icon_obj);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xhttp.onreadystatechange = function() {
|
|
||||||
if (xhttp.readyState == 4 && xhttp.status == 200) {
|
|
||||||
var response = xhttp.responseText;
|
|
||||||
if (response == err_code) {
|
|
||||||
invalid_upload(icon_obj);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
valid_upload(icon_obj);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xhttp.open('POST', url, true);
|
|
||||||
var formData = new FormData();
|
|
||||||
for (var i=0; i < files.length; i++){
|
|
||||||
formData.append("files", files[i], files[i].name);
|
|
||||||
}
|
|
||||||
xhttp.send(formData);
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
/*
|
|
||||||
* Here are the base color scheme and icon set.
|
|
||||||
* You can modify it or create your own using the same variables
|
|
||||||
* and make it loaded after this one but before the fonts.css in
|
|
||||||
* the HTML header section of the index.html template file.
|
|
||||||
*/
|
|
||||||
:root {
|
|
||||||
--coloured-bg: #FF5D00;
|
|
||||||
--light-coloured-bg: #FFB387;
|
|
||||||
--clear-bg: #E5E5E5;
|
|
||||||
--mid-bg: #BBBBBB;
|
|
||||||
--dark-bg: #2B2B2B;
|
|
||||||
--dark-border: #888888;
|
|
||||||
--text-color: #555555;
|
|
||||||
--white: #FFFFFF;
|
|
||||||
--black: #000000;
|
|
||||||
--font-normal: url("/static/fonts/RobotoCondensed-Regular.ttf") format("truetype");
|
|
||||||
--font-bold: url("/static/fonts/RobotoCondensed-Bold.ttf") format("truetype");
|
|
||||||
--banner-logo: url(/static/images/logo.png);
|
|
||||||
--add_icon: url(/static/images/add.png);
|
|
||||||
--edit_icon: url(/static/images/edit.png);
|
|
||||||
--login_icon: url(/static/images/login.png);
|
|
||||||
--logout_icon: url(/static/images/logout.png);
|
|
||||||
--refresh_icon: url(/static/images/refresh.png);
|
|
||||||
--save_icon: url(/static/images/save.png);
|
|
||||||
--search_icon: url(/static/images/search.png);
|
|
||||||
--trash_icon: url(/static/images/trash.png);
|
|
||||||
--upload_icon: url(/static/images/upload.png);
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
/*
|
|
||||||
* Here are the font definitions.
|
|
||||||
* You can modify it or create your own and make it loaded
|
|
||||||
* after this one in the HTML header section of the index.html
|
|
||||||
* template file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "Roboto Condensed";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
src: var(--font-normal);
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "Roboto Condensed";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 700;
|
|
||||||
src: var(--font-bold);
|
|
||||||
}
|
|
@ -1,370 +0,0 @@
|
|||||||
/*
|
|
||||||
* Do NOT modify this file:
|
|
||||||
* ------------------------
|
|
||||||
* If you want to add or modify classes, create a new
|
|
||||||
* CSS files and make it loaded after this one in the
|
|
||||||
* HTML header section of the index.html template file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 10px;
|
|
||||||
font-family: "Roboto Condensed";
|
|
||||||
background-color: var(--dark-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
div.content {
|
|
||||||
display: flex;
|
|
||||||
min-height: calc(100vh - 110px);
|
|
||||||
}
|
|
||||||
|
|
||||||
main > article {
|
|
||||||
flex: 1;
|
|
||||||
background-color: var(--clear-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
main > section.inline {
|
|
||||||
display: flex;
|
|
||||||
background-color: var(--clear-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
main > section.inline > article.left {
|
|
||||||
flex: 0 0 50%;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
main > section.inline > article.right {
|
|
||||||
flex: 1;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.content > nav.vertical {
|
|
||||||
flex: 0 0 200px;
|
|
||||||
background-color: var(--clear-bg);
|
|
||||||
border-right-color: var(--mid-bg);
|
|
||||||
border-right-style: solid;
|
|
||||||
border-right-width: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.content > nav.vertical {
|
|
||||||
order: -1;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.content > nav.vertical > a {
|
|
||||||
display: block;
|
|
||||||
background-color: var(--clear-bg);
|
|
||||||
font-size: 20px;
|
|
||||||
color: var(--text-color);
|
|
||||||
padding: 5px;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.content > nav.vertical > a:hover {
|
|
||||||
background-color: var(--coloured-bg);
|
|
||||||
color: var(--white);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.content > nav.vertical > a.selected {
|
|
||||||
background-color: var(--light-coloured-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
color: var(--text-color);
|
|
||||||
background-color: var(--clear-bg);
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
main > div.navbar_container {
|
|
||||||
text-align: center;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
main > div.navbar_container > ul.horizontal {
|
|
||||||
display: inline-block;
|
|
||||||
list-style-type: none;
|
|
||||||
margin: 10px;
|
|
||||||
padding: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
background-color: var(--white);
|
|
||||||
border-color: var(--coloured-bg);
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 1px;
|
|
||||||
color: var(--text-color);
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
main > div.navbar_container > ul.horizontal > li {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
main > div.navbar_container > ul.horizontal > li > a {
|
|
||||||
display: block;
|
|
||||||
color: var(--text-color);
|
|
||||||
text-align: center;
|
|
||||||
padding: 5px;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
main > div.navbar_container > ul.horizontal > li > a:hover {
|
|
||||||
background-color: var(--coloured-bg);
|
|
||||||
color: var(--white);
|
|
||||||
}
|
|
||||||
|
|
||||||
main > div.navbar_container > ul.horizontal > li > a.right_border {
|
|
||||||
border-right-color: var(--coloured-bg);
|
|
||||||
border-right-style: solid;
|
|
||||||
border-right-width: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
main > div.navbar_container > ul.horizontal > li > a.selected {
|
|
||||||
background-color: var(--light-coloured-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
main > article {
|
|
||||||
padding: 10px;
|
|
||||||
color: var(--text-color);
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
main > article.error,
|
|
||||||
main > article.error > p {
|
|
||||||
padding: 10px;
|
|
||||||
color: var(--text-color);
|
|
||||||
display: block;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
main > article > h3,
|
|
||||||
main > section.inline > article > h3 {
|
|
||||||
font-size: 30px;
|
|
||||||
color: var(--text-color);
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
main > article > p,
|
|
||||||
main > article > ul,
|
|
||||||
main > article > ol {
|
|
||||||
color: var(--text-color);
|
|
||||||
text-align: justify;
|
|
||||||
text-justify: distribute;
|
|
||||||
}
|
|
||||||
|
|
||||||
main > hr {
|
|
||||||
border-color: var(--mid-bg);
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
main > article > img {
|
|
||||||
display:inline-block;
|
|
||||||
border-color: var(--mid-bg);
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 1px;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
main > article > p > a {
|
|
||||||
color: var(--coloured-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
main > article > p > a:hover {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
main > article.right > img {
|
|
||||||
float: right;
|
|
||||||
margin: 0 0 0px 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
main > article.left > img {
|
|
||||||
float: left;
|
|
||||||
margin: 0 10px 0px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
height: 65px;
|
|
||||||
font-size: 34px;
|
|
||||||
padding: 10px;
|
|
||||||
text-align: right;
|
|
||||||
color: var(--white);
|
|
||||||
background: var(--banner-logo);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: 10px;
|
|
||||||
text-shadow: 0 0 1px var(--black);
|
|
||||||
border-bottom-color: var(--dark-border);
|
|
||||||
border-bottom-style: solid;
|
|
||||||
border-bottom-width: 1px;
|
|
||||||
border-top-color: var(--white);
|
|
||||||
border-top-style: solid;
|
|
||||||
border-top-width: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
height: 35px;
|
|
||||||
font-size: 12px;
|
|
||||||
text-align: center;
|
|
||||||
padding: 1em;
|
|
||||||
border-bottom-color: var(--white);
|
|
||||||
border-bottom-style: solid;
|
|
||||||
border-bottom-width: 1px;
|
|
||||||
border-top-color: var(--dark-border);
|
|
||||||
border-top-style: solid;
|
|
||||||
border-top-width: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
header,
|
|
||||||
footer {
|
|
||||||
background-color: var(--coloured-bg);
|
|
||||||
color: var(--white);
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="text"],
|
|
||||||
input[type="password"],
|
|
||||||
textarea,
|
|
||||||
select,
|
|
||||||
pre {
|
|
||||||
border-color: var(--dark-border);
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 1px;
|
|
||||||
background-color: var(--white);
|
|
||||||
color: var(--text-color);
|
|
||||||
padding: 5px;
|
|
||||||
font-family: "Roboto Condensed";
|
|
||||||
margin: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
border-color: var(--coloured-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
button,
|
|
||||||
input[type="button"],
|
|
||||||
input[type="submit"] {
|
|
||||||
border-color: var(--dark-border);
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 1px;
|
|
||||||
background-color: var(--coloured-bg);
|
|
||||||
color: var(--white);
|
|
||||||
font-weight: bold;
|
|
||||||
padding: 5px;
|
|
||||||
font-family: "Roboto Condensed";
|
|
||||||
margin: 5px;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover,
|
|
||||||
input[type="button"]:hover,
|
|
||||||
input[type="submit"]:hover,
|
|
||||||
input[type="file"]:hover {
|
|
||||||
background-color: var(--light-coloured-bg);
|
|
||||||
color: var(--text-color);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.file_upload {
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
border-radius: 2px;
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 1px;
|
|
||||||
border-color: var(--clear-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="file"] {
|
|
||||||
position: absolute;
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
left: 0;
|
|
||||||
top: 1px;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
input.add,
|
|
||||||
input.edit,
|
|
||||||
input.login,
|
|
||||||
input.logout,
|
|
||||||
input.refresh,
|
|
||||||
input.save,
|
|
||||||
input.search,
|
|
||||||
input.trash,
|
|
||||||
input.upload {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
border-radius: 2px;
|
|
||||||
border-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
input.add:hover,
|
|
||||||
input.edit:hover,
|
|
||||||
input.login:hover,
|
|
||||||
input.logout:hover,
|
|
||||||
input.refresh:hover,
|
|
||||||
input.save:hover,
|
|
||||||
input.search:hover,
|
|
||||||
input.trash:hover,
|
|
||||||
input.upload:hover {
|
|
||||||
border-color: var(--text-color);
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 1px;
|
|
||||||
background-color: var(--coloured-bg);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
input.add {
|
|
||||||
background: var(--add_icon);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center center;
|
|
||||||
}
|
|
||||||
input.edit {
|
|
||||||
background: var(--edit_icon);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center center;
|
|
||||||
}
|
|
||||||
input.login {
|
|
||||||
background: var(--login_icon);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center center;
|
|
||||||
}
|
|
||||||
input.logout {
|
|
||||||
background: var(--logout_icon);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center center;
|
|
||||||
}
|
|
||||||
input.refresh {
|
|
||||||
background: var(--refresh_icon);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center center;
|
|
||||||
}
|
|
||||||
input.save {
|
|
||||||
background: var(--save_icon);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center center;
|
|
||||||
}
|
|
||||||
input.search {
|
|
||||||
background: var(--search_icon);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center center;
|
|
||||||
}
|
|
||||||
input.trash {
|
|
||||||
background: var(--trash_icon);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center center;
|
|
||||||
}
|
|
||||||
input.upload {
|
|
||||||
background: var(--upload_icon);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center center;
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
{% extends "index.html" %}
|
|
||||||
{% block title %}Ajax{% endblock %}
|
|
||||||
{% block main %}
|
|
||||||
<section class='inline'>
|
|
||||||
<article class='left'>
|
|
||||||
<h3>Get HTML response from AJAX</h3>
|
|
||||||
<p>Click the refresh button to get the HTML response.</p>
|
|
||||||
<p>The response may randomly be a voluntary error so you should try it more than once.</p>
|
|
||||||
Refresh: <input type='button' class='refresh' value=' '
|
|
||||||
onclick='javascript:get_html_from_ajax(document.getElementById("html_container"), "/get_html_from_ajax");'>
|
|
||||||
</article>
|
|
||||||
<article class='right'>
|
|
||||||
<h3>Upload files with AJAX</h3>
|
|
||||||
<p>Select files to upload</p>
|
|
||||||
<p>The response may randomly be a voluntary error so you should try it more than once.</p>
|
|
||||||
Upload files:
|
|
||||||
<div class='file_upload'>
|
|
||||||
<!--
|
|
||||||
Input file is a tricky hack (see tetawebapp.css and tetawebapp.js)
|
|
||||||
-->
|
|
||||||
<input type='button' id='upload_icon_1' class='upload' title='Upload' value=' '/>
|
|
||||||
<input type='file' id='upload_input_1' multiple
|
|
||||||
title='Upload'
|
|
||||||
onchange='javascript:upload_file_from_ajax(this, "/upload", "TETA_ERR");'
|
|
||||||
onmouseover='javascript:lit(document.getElementById("upload_icon_1"));'
|
|
||||||
onmouseout='javascript:unlit(document.getElementById("upload_icon_1"));'/>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</section>
|
|
||||||
<hr/>
|
|
||||||
<article class='right' id='html_container'></article>
|
|
||||||
<hr/>
|
|
||||||
<section class='inline'>
|
|
||||||
<article class='left'>
|
|
||||||
<h3>Set value via AJAX</h3>
|
|
||||||
<p>Send value to the application.</p>
|
|
||||||
<p>If value is empty or is "We Make Porn" (case sensitive), an error is raised.</p>
|
|
||||||
<input type='text' id='value_sender'>
|
|
||||||
<input type='button' value="Try me" onclick='javascript:set_value_from_ajax(document.getElementById("value_sender"), "/set_value_from_ajax", "TETA_ERR");'>
|
|
||||||
</article>
|
|
||||||
<article class='right'>
|
|
||||||
<h3>Get value from AJAX</h3>
|
|
||||||
<p>Get a random value from the application.</p>
|
|
||||||
<p>Randomly raises a voluntary error so you should try it more than once.</p>
|
|
||||||
<input type='text' id='value_receiver'>
|
|
||||||
<input type='button' value="Try me" onclick='javascript:get_value_from_ajax(document.getElementById("value_receiver"), "/get_value_from_ajax", "TETA_ERR");'>
|
|
||||||
</article>
|
|
||||||
</section>
|
|
||||||
{% endblock %}
|
|
@ -1,24 +0,0 @@
|
|||||||
<h3>This is the title</h3>
|
|
||||||
<img src='/static/images/dummy_pic.png' alt='dummy pic' title='dummy pic'/>
|
|
||||||
<p>
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
|
|
||||||
et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
|
|
||||||
aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
|
|
||||||
dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
|
|
||||||
officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit,
|
|
||||||
sed do eiusmod tempor incididunt ut labore
|
|
||||||
</p>
|
|
||||||
<p>This <a href='/plop.html'>link</a> will lead to an error page</p>
|
|
||||||
<p>
|
|
||||||
et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
|
|
||||||
aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
|
|
||||||
dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
|
|
||||||
officia desers unt mollit anim id est laborum.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
|
|
||||||
et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
|
|
||||||
aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
|
|
||||||
dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
|
|
||||||
officia deserunt mollit anim id est laborum.
|
|
||||||
</p>
|
|
@ -1,12 +0,0 @@
|
|||||||
{% extends "index.html" %}
|
|
||||||
{% block title %}Articles{% endblock %}
|
|
||||||
{% block main %}
|
|
||||||
<article>
|
|
||||||
<h3>Choose your article</h3>
|
|
||||||
<p>
|
|
||||||
Please select your article
|
|
||||||
</p>
|
|
||||||
</article>
|
|
||||||
<article id='article_receiver'>
|
|
||||||
</article>
|
|
||||||
{% endblock %}
|
|
@ -1,65 +0,0 @@
|
|||||||
{% extends "index.html" %}
|
|
||||||
{% block title %}Articles{% endblock %}
|
|
||||||
{% block main %}
|
|
||||||
<article class='right'>
|
|
||||||
<h3>Article #{{ ID }}</h3>
|
|
||||||
<img src='/static/images/dummy_pic.png' alt='dummy pic' title='dummy pic'/>
|
|
||||||
<p>
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
|
|
||||||
et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
|
|
||||||
aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
|
|
||||||
dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
|
|
||||||
officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit,
|
|
||||||
sed do eiusmod tempor incididunt ut labore
|
|
||||||
</p>
|
|
||||||
<p>This <a href='/plop.html'>link</a> will lead to an error page</p>
|
|
||||||
<p>
|
|
||||||
et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
|
|
||||||
aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
|
|
||||||
dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
|
|
||||||
officia desers unt mollit anim id est laborum.
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
<li>plop</li>
|
|
||||||
<li>plap</li>
|
|
||||||
<li>plip</li>
|
|
||||||
</ul>
|
|
||||||
<ol>
|
|
||||||
<li>plop</li>
|
|
||||||
<li>plap</li>
|
|
||||||
<li>plip</li>
|
|
||||||
</ol>
|
|
||||||
<p>
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
|
|
||||||
et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
|
|
||||||
aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
|
|
||||||
dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
|
|
||||||
officia deserunt mollit anim id est laborum.
|
|
||||||
</p>
|
|
||||||
</article>
|
|
||||||
<article class='left'>
|
|
||||||
<h3>Another disposition</h3>
|
|
||||||
<img src='/static/images/dummy_pic.png' alt='dummy pic' title='dummy pic'/>
|
|
||||||
<p>
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
|
|
||||||
et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
|
|
||||||
aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
|
|
||||||
dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
|
|
||||||
officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit,
|
|
||||||
sed do eiusmod tempor incididunt ut labore
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
|
|
||||||
aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
|
|
||||||
dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
|
|
||||||
officia deserunt mollit anim id est laborum.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
|
|
||||||
et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
|
|
||||||
aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
|
|
||||||
dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
|
|
||||||
officia deserunt mollit anim id est laborum.
|
|
||||||
</p>
|
|
||||||
</article>
|
|
||||||
{% endblock %}
|
|
@ -1,68 +0,0 @@
|
|||||||
{% extends "index.html" %}
|
|
||||||
{% block title %}Basics{% endblock %}
|
|
||||||
{% block main %}
|
|
||||||
<article class='right'>
|
|
||||||
<h3>Basics</h3>
|
|
||||||
<p>
|
|
||||||
Thanks to <a href='http://flask.pocoo.org/'>Python/Flask</a> with <strong>TetaWebApp</strong> most of the output things come to life via
|
|
||||||
<a href='http://jinja.pocoo.org/docs/2.10/'>Jinja2 HTML templates</a>
|
|
||||||
and is 100% <strong title='Bulshit inside'>HTML5 ready©</strong>.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Colors and fonts are managed from separated CSS files letting you easily
|
|
||||||
change the default theme to your favorite colors and icon set.
|
|
||||||
</p>
|
|
||||||
<pre>
|
|
||||||
/*
|
|
||||||
* Here are the font definitions.
|
|
||||||
* You can modify it or create your own and make it loaded
|
|
||||||
* after this one in the HTML header section of the index.html
|
|
||||||
* template file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "Roboto Condensed";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
src: var(--font-normal);
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "Roboto Condensed";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 700;
|
|
||||||
src: var(--font-bold);
|
|
||||||
}
|
|
||||||
</pre>
|
|
||||||
<pre>
|
|
||||||
/*
|
|
||||||
* Here are the base color scheme and icon set.
|
|
||||||
* You can modify it or create your own using the same variables
|
|
||||||
* and make it loaded after this one but before the fonts.css in
|
|
||||||
* the HTML header section of the index.html template file.
|
|
||||||
*/
|
|
||||||
:root {
|
|
||||||
--coloured-bg: #FF5D00;
|
|
||||||
--light-coloured-bg: #FFB387;
|
|
||||||
--clear-bg: #E5E5E5;
|
|
||||||
--mid-bg: #BBBBBB;
|
|
||||||
--dark-bg: #2B2B2B;
|
|
||||||
--dark-border: #888888;
|
|
||||||
--text-color: #555555;
|
|
||||||
--white: #FFFFFF;
|
|
||||||
--black: #000000;
|
|
||||||
--font-normal: url("/static/fonts/RobotoCondensed-Regular.ttf") format("truetype");
|
|
||||||
--font-bold: url("/static/fonts/RobotoCondensed-Bold.ttf") format("truetype");
|
|
||||||
--banner-logo: url(/static/images/logo.png);
|
|
||||||
--add_icon: url(/static/images/add.png);
|
|
||||||
--edit_icon: url(/static/images/edit.png);
|
|
||||||
--login_icon: url(/static/images/login.png);
|
|
||||||
--logout_icon: url(/static/images/logout.png);
|
|
||||||
--refresh_icon: url(/static/images/refresh.png);
|
|
||||||
--save_icon: url(/static/images/save.png);
|
|
||||||
--search_icon: url(/static/images/search.png);
|
|
||||||
--trash_icon: url(/static/images/trash.png);
|
|
||||||
}
|
|
||||||
</pre>
|
|
||||||
</article>
|
|
||||||
{% endblock %}
|
|
@ -1,12 +0,0 @@
|
|||||||
{% extends "index.html" %}
|
|
||||||
{% block title %}Database{% endblock %}
|
|
||||||
{% block main %}
|
|
||||||
<article class='right'>
|
|
||||||
<h3>Accessing database</h3>
|
|
||||||
<p>
|
|
||||||
Even if using <a href='http://flask-sqlalchemy.pocoo.org/2.3/'>Flask-SQLAlchemy</a> to retrieve data
|
|
||||||
stored in <strong>Postgres</strong> databases is the recommended way to use <strong>TetaWebApp</strong>,
|
|
||||||
you're free to use the database connector that suits your need.
|
|
||||||
</p>
|
|
||||||
</article>
|
|
||||||
{% endblock %}
|
|
@ -1,15 +0,0 @@
|
|||||||
{% extends "index.html" %}
|
|
||||||
{% block title %}Erreur{% endblock %}
|
|
||||||
{% block nav %}{% endblock %}
|
|
||||||
{% block main %}
|
|
||||||
<article class='error'>
|
|
||||||
<h3>404 - Not found</h3>
|
|
||||||
<p>The page you asked for was not found on this server.<br/>
|
|
||||||
It may has been lost, it may has never existed.<br/>
|
|
||||||
Maybe we don't care at all...</p>
|
|
||||||
<p>
|
|
||||||
<input type='button' value='Get me back to business' onclick='javascript:document.location="/";'/>
|
|
||||||
</p>
|
|
||||||
<img src='/static/images/404.png' alt='404 - Not found' title='404 - Not found'/>
|
|
||||||
</article>
|
|
||||||
{% endblock %}
|
|
@ -1,106 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang='zxx'>
|
|
||||||
<head>
|
|
||||||
<title>TetaWebApp - {% block title %}Accueil{% endblock %}</title>
|
|
||||||
<meta name="viewport" content="initial-scale=1.0" />
|
|
||||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
|
||||||
<link rel="stylesheet" type="text/css" href="/static/styles/colors.css" />
|
|
||||||
<link rel="stylesheet" type="text/css" href="/static/styles/fonts.css" />
|
|
||||||
<link rel="stylesheet" type="text/css" href="/static/styles/tetawebapp.css" />
|
|
||||||
<link rel="icon" type="image/png" href="/static/images/favicon.png" />
|
|
||||||
<script src="/static/scripts/tetawebapp.js"></script>
|
|
||||||
</head>
|
|
||||||
{% block bodyheader %}
|
|
||||||
<body>
|
|
||||||
{% endblock %}
|
|
||||||
<header>{% block banner %}TetaWebApp{% endblock %}</header>
|
|
||||||
<div class='content'>
|
|
||||||
{% block nav %}
|
|
||||||
<nav class='vertical'>
|
|
||||||
{% block menu %}
|
|
||||||
{% for item in menu %}
|
|
||||||
{% for key in item[1] %}
|
|
||||||
{% if item[2] == 1 %}
|
|
||||||
<a class='selected' href='{{ key }}'>{{ item[0] }}</a>
|
|
||||||
{% else %}
|
|
||||||
<a href='{{ key }}'>{{ item[0] }}</a>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endblock %}
|
|
||||||
</nav>
|
|
||||||
{% endblock%}
|
|
||||||
<main>
|
|
||||||
{% if navbar %}
|
|
||||||
<div class='navbar_container'>
|
|
||||||
<ul class='horizontal'>
|
|
||||||
{% for item in navbar %}
|
|
||||||
{% set selected = ['', 'selected'] %}
|
|
||||||
{% set last = ['right_border', 'last'] %}
|
|
||||||
{% for url in item[1] %}
|
|
||||||
<li><a class='{{ last[item[3]] }} {{ selected[item[2]] }}' href='{{ url }}'>{{ item[0] }}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% block main %}
|
|
||||||
<article class='right'>
|
|
||||||
<h3>TetaWebApp demo</h3>
|
|
||||||
<p>
|
|
||||||
Welcome to the <strong>TetaWebApp</strong> demo
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
TetaWebApp is a basic web application template based on <a href='http://flask.pocoo.org/'>Python/Flask</a>
|
|
||||||
and <a href='https://www.w3schools.com/js/js_ajax_intro.asp'>AJAX</a> made by
|
|
||||||
<a href='mailto:doug.letough@free.fr'>Doug Le Tough</a> from <a href='https://www.tetalab.org'>Tetalab</a>.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
The goal of this project is to provide a basic framework to make any web application you need while
|
|
||||||
letting you complete freedom on how to use or extend it <strong>without</strong> using any Google,
|
|
||||||
Bootstrap or any other piece of <strong>shitty free spyware</strong>.
|
|
||||||
</p>
|
|
||||||
TetaWebApp will <strong>never</strong> download or upload anything in any way.
|
|
||||||
<p>
|
|
||||||
</p>
|
|
||||||
<p>There is <strong>no</strong> limitation, you can use all or only parts of <strong>TetaWebApp</strong>
|
|
||||||
and you can <strong title='bullshit inside'>virtually</strong> do any app you want with TetaWebApp.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
But be sure that freedom has a cost: You <strong>will</strong> need work to make it work ;-)
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<strong>TetaWebApp</strong> is released under the only real <strong>free</strong> license: The
|
|
||||||
<a href='http://www.wtfpl.net/'><img src='http://www.wtfpl.net/wp-content/uploads/2012/12/wtfpl-badge-2.png'
|
|
||||||
title='WTFPL' alt='WTFPL' /></a>.
|
|
||||||
</p>
|
|
||||||
<pre>
|
|
||||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
||||||
Version 2, December 2004
|
|
||||||
|
|
||||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
|
||||||
|
|
||||||
Everyone is permitted to copy and distribute verbatim or modified
|
|
||||||
copies of this license document, and changing it is allowed as long
|
|
||||||
as the name is changed.
|
|
||||||
|
|
||||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
||||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
|
||||||
|
|
||||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
|
||||||
</pre>
|
|
||||||
<p>
|
|
||||||
Get a copy of <strong>TetaWebApp</strong>:<br/>
|
|
||||||
<pre>
|
|
||||||
git clone git://git.tetalab.org/tetalab/tetawebapp
|
|
||||||
</pre>
|
|
||||||
</p>
|
|
||||||
</article>
|
|
||||||
{% endblock %}
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
{% block footer %}
|
|
||||||
<footer>© - Tetalab - Le hacker space Toulousaing' putaing' cong' -</footer>
|
|
||||||
{% endblock%}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,53 +0,0 @@
|
|||||||
{% extends "index.html" %}
|
|
||||||
{% block title %}Inputs{% endblock %}
|
|
||||||
{% block main %}
|
|
||||||
<article class='right'>
|
|
||||||
<h3>The input collection</h3>
|
|
||||||
<p>
|
|
||||||
Have a look to the input collection:
|
|
||||||
</p>
|
|
||||||
<input type='text'/>
|
|
||||||
<button>Click me</button>
|
|
||||||
<br/>
|
|
||||||
<textarea cols='25'></textarea>
|
|
||||||
<br/>
|
|
||||||
<select>
|
|
||||||
{% for item in menu %}
|
|
||||||
<option>{{ item[0] }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
<input type='submit' value='Click me too'/>
|
|
||||||
<br/>
|
|
||||||
<input type='button' value='And me !' />
|
|
||||||
<br/>
|
|
||||||
<input type='button' class='add' title='Add' value=' '/>
|
|
||||||
<input type='button' class='edit' title='Edit' value=' '/>
|
|
||||||
<input type='button' class='login' title='Login' value=' '/>
|
|
||||||
<input type='button' class='logout' title='Logout' value=' ' onclick='javascript:logout();'/>
|
|
||||||
<input type='button' class='refresh' title='Refresh' value=' '/>
|
|
||||||
<input type='button' class='save' title='Save' value=' '/>
|
|
||||||
<input type='button' class='search' title='Search' value=' '/>
|
|
||||||
<input type='button' class='trash' title='Trash' value=' '/>
|
|
||||||
<!--
|
|
||||||
Input file is a tricky hack (see tetawebapp.css and tetawebapp.js)
|
|
||||||
-->
|
|
||||||
<div class='file_upload'>
|
|
||||||
<input type='button' id='upload_icon_1' class='upload' title='Upload' value=' '/>
|
|
||||||
<input type='file' id='upload_input_1' name='files' multiple
|
|
||||||
title='Upload'
|
|
||||||
onchange='javascript:upload_file_from_ajax(this, "/upload", "TETA_ERR");'
|
|
||||||
onmouseover='javascript:lit(document.getElementById("upload_icon_1"));'
|
|
||||||
onmouseout='javascript:unlit(document.getElementById("upload_icon_1"));'/>
|
|
||||||
</div>
|
|
||||||
<br/>
|
|
||||||
<pre>
|
|
||||||
#!/bin/sh
|
|
||||||
# This is code sample
|
|
||||||
while [ 1 ]
|
|
||||||
do
|
|
||||||
echo "Tits or GTFO !"
|
|
||||||
sleep .1
|
|
||||||
done
|
|
||||||
</pre>
|
|
||||||
</article>
|
|
||||||
{% endblock %}
|
|
@ -1,23 +0,0 @@
|
|||||||
{% extends "index.html" %}
|
|
||||||
{% block title %}Login{% endblock %}
|
|
||||||
{% block nav %}{% endblock %}
|
|
||||||
{% block main %}
|
|
||||||
<article class='login'>
|
|
||||||
<h3>Login</h3>
|
|
||||||
<p>The demo login is:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Login: demo</li>
|
|
||||||
<li>Password: demo</li>
|
|
||||||
</ul>
|
|
||||||
</article>
|
|
||||||
{% if message != '' %}
|
|
||||||
<pre>{{ message }}</pre>
|
|
||||||
{% endif %}
|
|
||||||
<article class='left'>
|
|
||||||
<form method='POST' action='/login'>
|
|
||||||
Login: <input id='login' name='login' type='text' />
|
|
||||||
Password: <input id='password' name='password' type='password' />
|
|
||||||
<input type='submit' value='Log me in' onclick='javascript:return verify_login();'>
|
|
||||||
</form>
|
|
||||||
</article>
|
|
||||||
{% endblock %}
|
|
@ -1,18 +0,0 @@
|
|||||||
{% extends "index.html" %}
|
|
||||||
{% block title %}TODO{% endblock %}
|
|
||||||
{% block main %}
|
|
||||||
<article class='right'>
|
|
||||||
<h3>TODO list</h3>
|
|
||||||
<ul>
|
|
||||||
<li><strike>Basic menu management</strike></li>
|
|
||||||
<li>Installation wizard</li>
|
|
||||||
<li>Back office for basic content management</li>
|
|
||||||
<li><strike>Basic Ajax support</strike></li>
|
|
||||||
<li><strike>Session management</strike></li>
|
|
||||||
<li>File upload</li>
|
|
||||||
<li>Basic documentation</li>
|
|
||||||
<li><strike>Horizontal navbar</strike></li>
|
|
||||||
<li><strike>License</strike></li>
|
|
||||||
</ul>
|
|
||||||
</article>
|
|
||||||
{% endblock %}
|
|
@ -1,304 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8
|
|
||||||
|
|
||||||
# Required modules
|
|
||||||
import os
|
|
||||||
import inspect
|
|
||||||
import random
|
|
||||||
import binascii
|
|
||||||
import bcrypt
|
|
||||||
from flask import Flask, request, session, g, redirect, url_for, abort, render_template, flash
|
|
||||||
from functools import wraps
|
|
||||||
|
|
||||||
# Optionnal modules
|
|
||||||
import psycopg2
|
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
|
||||||
|
|
||||||
########################################################################
|
|
||||||
# App settings
|
|
||||||
########################################################################
|
|
||||||
app = Flask(__name__)
|
|
||||||
# Path to static files
|
|
||||||
app.static_url_path='/static'
|
|
||||||
# Set debug mode to False for production
|
|
||||||
app.debug = True
|
|
||||||
# Various configuration settings belong here (optionnal)
|
|
||||||
app.config.from_pyfile('config.local.py')
|
|
||||||
# Generate a new key: head -n 40 /dev/urandom | md5sum | cut -d' ' -f1
|
|
||||||
app.secret_key = 'ce1d1c9ff0ff388a838b3a1e3207dd27'
|
|
||||||
# Feel free to use SQLAlchemy (or not)
|
|
||||||
db = SQLAlchemy(app)
|
|
||||||
|
|
||||||
|
|
||||||
########################################################################
|
|
||||||
# Sample user database
|
|
||||||
########################################################################
|
|
||||||
class Tetawebapp_users(db.Model):
|
|
||||||
__tablename__ = 'tetawebapp_users'
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
|
||||||
mail = db.Column(db.Text, nullable=False)
|
|
||||||
password = db.Column(db.Text, nullable=False)
|
|
||||||
name = db.Column(db.Text, nullable=False)
|
|
||||||
|
|
||||||
|
|
||||||
########################################################################
|
|
||||||
# Menu and navigation management
|
|
||||||
########################################################################
|
|
||||||
|
|
||||||
def get_menu(page):
|
|
||||||
""" The main menu is a list of lists in the followin format:
|
|
||||||
[unicode caption,
|
|
||||||
{unicode URL endpoint: [unicode route, ...]},
|
|
||||||
int 0]
|
|
||||||
- The URL end point is the URL where to point to (href)
|
|
||||||
- One of the routes MUST match the route called by request
|
|
||||||
- The int 0 is used to determine which menu entry is actally called.
|
|
||||||
The value MUST be 0."""
|
|
||||||
menu = [[u'Home', {u'/': [u'/']}, 0],
|
|
||||||
[u'Articles', {u'/articles': [u'/articles', u'/articles/<ID>']}, 0],
|
|
||||||
[u'Basics', {u'/basics': [u'/basics']}, 0],
|
|
||||||
[u'Inputs', {u'/inputs': [u'/inputs']}, 0],
|
|
||||||
[u'Ajax', {u'/ajax': [u'/ajax']}, 0],
|
|
||||||
[u'Database', {u'/database': [u'/database']}, 0],
|
|
||||||
[u'Todo', {u'/todo': [u'/todo']}, 0],
|
|
||||||
]
|
|
||||||
for item in menu:
|
|
||||||
for url in item[1]:
|
|
||||||
for route in item[1][url]:
|
|
||||||
if route == page:
|
|
||||||
item[2] = 1
|
|
||||||
return menu
|
|
||||||
# This should never happen
|
|
||||||
return menu
|
|
||||||
|
|
||||||
def get_navbar(page, selected):
|
|
||||||
""" The horizontal navbar is a list of lists in the followin format:
|
|
||||||
[unicode caption, {unicode URL endpoint: [unicode route, ...]}, int 0]
|
|
||||||
- The URL end point is the URL where to point to (href)
|
|
||||||
- One of the routes MUST match the route called by request
|
|
||||||
- The int 0 is used to de """
|
|
||||||
navbars = [[u'First article', {u'/articles/1': [u'/articles', u'/articles/<ID>']}, 0, 0],
|
|
||||||
[u'Second article', {u'/articles/2': [u'/articles', u'/articles/<ID>']}, 0, 0],
|
|
||||||
[u'Third article', {u'/articles/3': [u'/articles', u'/articles/<ID>']}, 0, 0]
|
|
||||||
]
|
|
||||||
navbar = []
|
|
||||||
for item in navbars:
|
|
||||||
for url in item[1]:
|
|
||||||
if url == selected:
|
|
||||||
item[2] = 1
|
|
||||||
for route in item[1][url]:
|
|
||||||
if route == page:
|
|
||||||
navbar.append(item)
|
|
||||||
navbar[len(navbar) - 1][3] = 1
|
|
||||||
return navbar
|
|
||||||
|
|
||||||
########################################################################
|
|
||||||
# Session management
|
|
||||||
########################################################################
|
|
||||||
|
|
||||||
def sync_session(request, session):
|
|
||||||
""" Synchronize cookies with session """
|
|
||||||
for key in request.cookies:
|
|
||||||
session[key] = request.cookies[key].encode('utf8')
|
|
||||||
|
|
||||||
def sync_cookies(response, session):
|
|
||||||
""" Synchronize session with cookies """
|
|
||||||
for key in session:
|
|
||||||
response.set_cookie(key, value=str(session[key]))
|
|
||||||
|
|
||||||
def check_session(func):
|
|
||||||
""" Check if the session has required token cookie set.
|
|
||||||
If not, redirects to the login page. """
|
|
||||||
@wraps(func)
|
|
||||||
def check(*args, **kwargs):
|
|
||||||
try:
|
|
||||||
if session['token'] == request.cookies['token'] and len(session['token']) > 0:
|
|
||||||
return func(*args, **kwargs)
|
|
||||||
else:
|
|
||||||
session['token'] = ''
|
|
||||||
response = app.make_response(render_template('login.html', message=''))
|
|
||||||
sync_cookies(response, session)
|
|
||||||
return response
|
|
||||||
except KeyError:
|
|
||||||
return render_template('login.html', message='')
|
|
||||||
return check
|
|
||||||
|
|
||||||
def check_login(login, password):
|
|
||||||
""" Puts the login verification code here """
|
|
||||||
password = password.encode('utf-8')
|
|
||||||
hashed_password = bcrypt.hashpw(password, bcrypt.gensalt())
|
|
||||||
stored_hash = Tetawebapp_users.query.filter_by(mail=login).with_entities(Tetawebapp_users.password).first()
|
|
||||||
if stored_hash:
|
|
||||||
if bcrypt.checkpw(password, stored_hash[0].encode('utf-8')):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def gen_token():
|
|
||||||
""" Generate a random token to be stored in session and cookie """
|
|
||||||
token = binascii.hexlify(os.urandom(42))
|
|
||||||
return token
|
|
||||||
|
|
||||||
########################################################################
|
|
||||||
# Routes:
|
|
||||||
# -------
|
|
||||||
# Except for the index function, the function name MUST have the same
|
|
||||||
# name than the URL endpoint to make the menu work properly
|
|
||||||
########################################################################
|
|
||||||
@app.errorhandler(404)
|
|
||||||
def page_not_found(e):
|
|
||||||
""" 404 not found """
|
|
||||||
return render_template('error.html'), 404
|
|
||||||
|
|
||||||
@app.route("/login", methods=['GET', 'POST'])
|
|
||||||
def login():
|
|
||||||
login = request.form.get('login')
|
|
||||||
password = request.form.get('password')
|
|
||||||
if check_login(login, password):
|
|
||||||
# Generate and store a token in session
|
|
||||||
session['token'] = gen_token()
|
|
||||||
# Return user to index page
|
|
||||||
page = '/'
|
|
||||||
menu = get_menu(page)
|
|
||||||
response = app.make_response(render_template('index.html', menu=menu))
|
|
||||||
# Push token to cookie
|
|
||||||
sync_cookies(response, session)
|
|
||||||
return response
|
|
||||||
# Credentials are not valid
|
|
||||||
response = app.make_response(render_template('login.html', message='Invalid user or password'))
|
|
||||||
session['token'] = ''
|
|
||||||
sync_cookies(response, session)
|
|
||||||
return response
|
|
||||||
|
|
||||||
@app.route("/", methods=['GET', 'POST'])
|
|
||||||
@check_session
|
|
||||||
def index():
|
|
||||||
""" Index page """
|
|
||||||
page = str(request.url_rule)
|
|
||||||
menu = get_menu(page)
|
|
||||||
return render_template('index.html', menu=menu)
|
|
||||||
|
|
||||||
@app.route("/articles", methods=['GET', 'POST'])
|
|
||||||
@check_session
|
|
||||||
def articles():
|
|
||||||
""" Arcticles page """
|
|
||||||
page = str(request.url_rule)
|
|
||||||
menu = get_menu(page)
|
|
||||||
navbar = get_navbar(page, '')
|
|
||||||
return render_template('articles.html', menu=menu, navbar=navbar)
|
|
||||||
|
|
||||||
@app.route("/articles/<ID>", methods=['GET', 'POST'])
|
|
||||||
@check_session
|
|
||||||
def articles_by_id(ID):
|
|
||||||
""" Arcticles page """
|
|
||||||
page = str(request.url_rule)
|
|
||||||
menu = get_menu(page)
|
|
||||||
selected = page.replace('<ID>', ID)
|
|
||||||
navbar = get_navbar(page, selected)
|
|
||||||
return render_template('articles_by_id.html', menu=menu, navbar=navbar, ID=ID)
|
|
||||||
|
|
||||||
@app.route("/basics", methods=['GET', 'POST'])
|
|
||||||
@check_session
|
|
||||||
def basics():
|
|
||||||
""" Basics page """
|
|
||||||
page = str(request.url_rule)
|
|
||||||
menu = get_menu(page)
|
|
||||||
return render_template('basics.html', menu=menu)
|
|
||||||
|
|
||||||
@app.route("/inputs", methods=['GET', 'POST'])
|
|
||||||
@check_session
|
|
||||||
def inputs():
|
|
||||||
""" Show the input collection """
|
|
||||||
page = str(request.url_rule)
|
|
||||||
menu = get_menu(page)
|
|
||||||
return render_template('inputs.html', menu=menu)
|
|
||||||
|
|
||||||
@app.route("/ajax", methods=['GET', 'POST'])
|
|
||||||
@check_session
|
|
||||||
def ajax():
|
|
||||||
""" Propose various AJAX tests """
|
|
||||||
page = str(request.url_rule)
|
|
||||||
menu = get_menu(page)
|
|
||||||
return render_template('ajax.html', menu=menu)
|
|
||||||
|
|
||||||
@app.route("/database", methods=['GET', 'POST'])
|
|
||||||
@check_session
|
|
||||||
def database():
|
|
||||||
""" A blah on using databases """
|
|
||||||
page = str(request.url_rule)
|
|
||||||
menu = get_menu(page)
|
|
||||||
return render_template('database.html', menu=menu)
|
|
||||||
|
|
||||||
@app.route("/todo", methods=['GET', 'POST'])
|
|
||||||
@check_session
|
|
||||||
def todo():
|
|
||||||
""" The famous TODO list """
|
|
||||||
page = str(request.url_rule)
|
|
||||||
menu = get_menu(page)
|
|
||||||
return render_template('todo.html', menu=menu)
|
|
||||||
|
|
||||||
########################################################################
|
|
||||||
# AJAX routes
|
|
||||||
########################################################################
|
|
||||||
|
|
||||||
@app.route("/get_html_from_ajax", methods=['GET', 'POST'])
|
|
||||||
@check_session
|
|
||||||
def get_html_from_ajax():
|
|
||||||
""" Return HTML code to an AJAX request
|
|
||||||
It may generate a 404 http error for testing purpose """
|
|
||||||
if int(random.random()*10) % 2:
|
|
||||||
# Randomly generate 404 HTTP response
|
|
||||||
return render_template('error.html'), 404
|
|
||||||
return render_template('ajax_html.html')
|
|
||||||
|
|
||||||
@app.route("/get_value_from_ajax", methods=['GET', 'POST'])
|
|
||||||
@check_session
|
|
||||||
def get_value_from_ajax():
|
|
||||||
""" Return a randomly generated value to an AJAX request
|
|
||||||
It may return an error code for testing purpose """
|
|
||||||
err_code = 'TETA_ERR'
|
|
||||||
RND = int(random.random()*10)
|
|
||||||
if RND % 2:
|
|
||||||
# Randomly generate error
|
|
||||||
return err_code
|
|
||||||
return str(RND)
|
|
||||||
|
|
||||||
@app.route("/set_value_from_ajax/<value>", methods=['GET', 'POST'])
|
|
||||||
@check_session
|
|
||||||
def set_value_from_ajax(value):
|
|
||||||
""" Accept a value from an AJAX request
|
|
||||||
It may return an error code for testing purpose """
|
|
||||||
err_code = 'TETA_ERR'
|
|
||||||
if value != 'We Make Porn':
|
|
||||||
return 'True'
|
|
||||||
return err_code
|
|
||||||
|
|
||||||
@app.route("/upload", methods=['POST'])
|
|
||||||
@check_session
|
|
||||||
def upload():
|
|
||||||
""" Save a file from AJAX request
|
|
||||||
Files are saved in UPLOADED_FILES_DEST (see config.local.py) """
|
|
||||||
err_code = 'TETA_ERR'
|
|
||||||
RND = int(random.random()*10)
|
|
||||||
if RND % 2:
|
|
||||||
# Randomly generate error
|
|
||||||
print err_code
|
|
||||||
return err_code
|
|
||||||
uploaded_files = []
|
|
||||||
if len(request.files) > 0 and request.files['files']:
|
|
||||||
uploaded_files = request.files.getlist("files")
|
|
||||||
print "Uploaded files:"
|
|
||||||
for f in uploaded_files:
|
|
||||||
print ' [+] %s [%s]' % (f.filename, f.content_type)
|
|
||||||
# Before saving you should:
|
|
||||||
# - Secure the filename
|
|
||||||
# - Check file size
|
|
||||||
# - Check content type
|
|
||||||
f.save(os.path.join(app.config['UPLOADED_FILES_DEST'], f.filename))
|
|
||||||
f.close()
|
|
||||||
return "OK"
|
|
||||||
|
|
||||||
########################################################################
|
|
||||||
# Main
|
|
||||||
########################################################################
|
|
||||||
if __name__ == '__main__':
|
|
||||||
app.run(host='0.0.0.0')
|
|
@ -1,48 +0,0 @@
|
|||||||
\echo ******************************
|
|
||||||
\echo * Dropping database tetawebapp
|
|
||||||
\echo ******************************
|
|
||||||
|
|
||||||
\c postgres;
|
|
||||||
drop database tetawebapp;
|
|
||||||
|
|
||||||
\echo **************************
|
|
||||||
\echo * Dropping role tetawebapp
|
|
||||||
\echo **************************
|
|
||||||
drop role tetawebapp;
|
|
||||||
|
|
||||||
\echo ***************************************************
|
|
||||||
\echo * Creating role tetawebapp with password tetawebapp
|
|
||||||
\echo ***************************************************
|
|
||||||
create role tetawebapp with LOGIN ENCRYPTED PASSWORD 'tetawebapp';
|
|
||||||
|
|
||||||
\echo ******************************
|
|
||||||
\echo * Creating database tetawebapp
|
|
||||||
\echo ******************************
|
|
||||||
create database tetawebapp;
|
|
||||||
|
|
||||||
\echo *******************************************
|
|
||||||
\echo * Giving tetawebapp ownership to tetawebapp
|
|
||||||
\echo *******************************************
|
|
||||||
alter database tetawebapp owner to tetawebapp;
|
|
||||||
|
|
||||||
\echo *********************************
|
|
||||||
\echo * Creating tetawebapp_users table
|
|
||||||
\echo *********************************
|
|
||||||
|
|
||||||
\c tetawebapp;
|
|
||||||
CREATE TABLE tetawebapp_users (
|
|
||||||
id serial primary key,
|
|
||||||
mail text not NULL,
|
|
||||||
password text not NULL,
|
|
||||||
name text not NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
\echo *************************************************
|
|
||||||
\echo * Giving tetawebapp_users ownership to tetawebapp
|
|
||||||
\echo *************************************************
|
|
||||||
alter table tetawebapp_users owner to tetawebapp;
|
|
||||||
|
|
||||||
\echo *********************************************************************
|
|
||||||
\echo * Inserting user demo identified by password demo to tetawebapp_users
|
|
||||||
\echo *********************************************************************
|
|
||||||
insert into tetawebapp_users (mail, password, name) values ('demo', '$2b$12$yjv4QMctGJFj2HmmbF6u5uDq9ATIl/Y9Z96MbaqRrcG6AE0CGHKSS', 'demo');
|
|