Adding archives

Added functionality to add archives.
This commit is contained in:
Michael Rodin 2023-10-19 13:25:59 +02:00
parent 978c634067
commit c3bd73a068
7 changed files with 212 additions and 61 deletions

View file

@ -36,10 +36,12 @@
<yjs:SolidColorFill x:Key="1" color="#FFE8EEF7"/> <yjs:SolidColorFill x:Key="1" color="#FFE8EEF7"/>
<yjs:SolidColorFill x:Key="2" color="#FFB7C9E3"/> <yjs:SolidColorFill x:Key="2" color="#FFB7C9E3"/>
<y:FreeNodePortLocationModelParameter x:Key="3" Ratio="0.007575757575757569,0.5"/> <y:FreeNodePortLocationModelParameter x:Key="3" Ratio="0.007575757575757569,0.5"/>
<yjs:SolidColorFill x:Key="4" color="#FF663800"/> <yjs:SolidColorFill x:Key="4" color="#FFCEDED3"/>
<yjs:Stroke x:Key="5" fill="{y:GraphMLReference 4}"/> <yjs:SolidColorFill x:Key="5" color="#FFB7E3C5"/>
<yjs:Arrow x:Key="6" type="TRIANGLE" scale="0.75" stroke="#FF663800" fill="{y:GraphMLReference 4}" cropLength="1"/> <yjs:SolidColorFill x:Key="6" color="#FF663800"/>
<yjs:PolylineEdgeStyle x:Key="7" stroke="{y:GraphMLReference 5}" targetArrow="{y:GraphMLReference 6}"/> <yjs:Stroke x:Key="7" fill="{y:GraphMLReference 6}"/>
<yjs:Arrow x:Key="8" type="TRIANGLE" scale="0.75" stroke="#FF663800" fill="{y:GraphMLReference 6}" cropLength="1"/>
<yjs:PolylineEdgeStyle x:Key="9" stroke="{y:GraphMLReference 7}" targetArrow="{y:GraphMLReference 8}"/>
</y:SharedData> </y:SharedData>
</data> </data>
<graph id="G" edgedefault="directed"> <graph id="G" edgedefault="directed">
@ -135,7 +137,6 @@
<x:Array Type="sys:Object"> <x:Array Type="sys:Object">
<sys:String>ID</sys:String> <sys:String>ID</sys:String>
<sys:String>LABEL</sys:String> <sys:String>LABEL</sys:String>
<sys:String>CATEGORY</sys:String>
<sys:String>TYPE</sys:String> <sys:String>TYPE</sys:String>
</x:Array> </x:Array>
</x0:EntityRelationshipModel.attributes> </x0:EntityRelationshipModel.attributes>
@ -165,7 +166,7 @@
<y:RectD X="-27.5" Y="-250" Width="130" Height="130"/> <y:RectD X="-27.5" Y="-250" Width="130" Height="130"/>
</data> </data>
<data key="d7"> <data key="d7">
<x0:EntityNodeStyle fill="#FFCEDED3" insetFill="#FFB7E3C5" stroke="BLACK"> <x0:EntityNodeStyle fill="{y:GraphMLReference 4}" insetFill="{y:GraphMLReference 5}" stroke="BLACK">
<x0:EntityNodeStyle.model> <x0:EntityNodeStyle.model>
<x0:EntityRelationshipModel> <x0:EntityRelationshipModel>
<x0:EntityRelationshipModel.title>ArchLab</x0:EntityRelationshipModel.title> <x0:EntityRelationshipModel.title>ArchLab</x0:EntityRelationshipModel.title>
@ -253,7 +254,7 @@
<node id="n6"> <node id="n6">
<data key="d0">8</data> <data key="d0">8</data>
<data key="d5"> <data key="d5">
<y:RectD X="210.5" Y="-453" Width="130" Height="130"/> <y:RectD X="210.5" Y="-48" Width="130" Height="130"/>
</data> </data>
<data key="d7"> <data key="d7">
<x0:EntityNodeStyle fill="{y:GraphMLReference 1}" insetFill="{y:GraphMLReference 2}" stroke="BLACK"> <x0:EntityNodeStyle fill="{y:GraphMLReference 1}" insetFill="{y:GraphMLReference 2}" stroke="BLACK">
@ -281,53 +282,105 @@
<y:FreeNodePortLocationModelParameter Ratio="0.5,0.9924242424242424"/> <y:FreeNodePortLocationModelParameter Ratio="0.5,0.9924242424242424"/>
</data> </data>
</port> </port>
<port name="p2">
<data key="d16">
<y:FreeNodePortLocationModelParameter Ratio="0.007575757575757569,0.5"/>
</data>
</port>
</node>
<node id="n7">
<data key="d0">9</data>
<data key="d5">
<y:RectD X="-27.5" Y="-48" Width="130" Height="130"/>
</data>
<data key="d7">
<x0:EntityNodeStyle fill="{y:GraphMLReference 4}" insetFill="{y:GraphMLReference 5}" stroke="BLACK">
<x0:EntityNodeStyle.model>
<x0:EntityRelationshipModel>
<x0:EntityRelationshipModel.title>CatLabType</x0:EntityRelationshipModel.title>
<x0:EntityRelationshipModel.attributes>
<x:Array Type="sys:Object">
<sys:String>ID</sys:String>
<sys:String>CATID</sys:String>
<sys:String>LABID</sys:String>
</x:Array>
</x0:EntityRelationshipModel.attributes>
</x0:EntityRelationshipModel>
</x0:EntityNodeStyle.model>
</x0:EntityNodeStyle>
</data>
<data key="d8">
<y:ViewState>
<y:FolderNodeState x:Key="c|view" Layout="-12.5,-235,130,130">
<y:FolderNodeState.Style>
<x0:EntityNodeStyle fill="{y:GraphMLReference 4}" insetFill="{y:GraphMLReference 5}" stroke="BLACK">
<x0:EntityNodeStyle.model>
<x0:EntityRelationshipModel>
<x0:EntityRelationshipModel.title>ArchLab</x0:EntityRelationshipModel.title>
<x0:EntityRelationshipModel.attributes>
<x:Array Type="sys:Object">
<sys:String>ID</sys:String>
<sys:String>ARCHID</sys:String>
<sys:String>LABID</sys:String>
</x:Array>
</x0:EntityRelationshipModel.attributes>
</x0:EntityRelationshipModel>
</x0:EntityNodeStyle.model>
</x0:EntityNodeStyle>
</y:FolderNodeState.Style>
</y:FolderNodeState>
</y:ViewState>
</data>
</node> </node>
<edge id="e0" source="n0" target="n2" sourceport="p0" targetport="p0"> <edge id="e0" source="n0" target="n2" sourceport="p0" targetport="p0">
<data key="d10"><![CDATA[]]></data> <data key="d10"><![CDATA[]]></data>
<data key="d13"> <data key="d13">
<yjs:PolylineEdgeStyle stroke="{y:GraphMLReference 5}" targetArrow="{y:GraphMLReference 6}"> <yjs:PolylineEdgeStyle stroke="{y:GraphMLReference 7}" targetArrow="{y:GraphMLReference 8}">
<yjs:PolylineEdgeStyle.sourceArrow> <yjs:PolylineEdgeStyle.sourceArrow>
<yjs:Arrow type="TRIANGLE" scale="0.75" stroke="#FF663800" fill="{y:GraphMLReference 4}" cropLength="1"/> <yjs:Arrow type="TRIANGLE" scale="0.75" stroke="#FF663800" fill="{y:GraphMLReference 6}" cropLength="1"/>
</yjs:PolylineEdgeStyle.sourceArrow> </yjs:PolylineEdgeStyle.sourceArrow>
</yjs:PolylineEdgeStyle> </yjs:PolylineEdgeStyle>
</data> </data>
</edge> </edge>
<edge id="e1" source="n0" target="n1" sourceport="p1" targetport="p0"> <edge id="e1" source="n0" target="n1" sourceport="p1" targetport="p0">
<data key="d13"> <data key="d13">
<y:GraphMLReference ResourceKey="7"/> <y:GraphMLReference ResourceKey="9"/>
</data> </data>
</edge> </edge>
<edge id="e2" source="n0" target="n4" sourceport="p2" targetport="p0"> <edge id="e2" source="n0" target="n4" sourceport="p2" targetport="p0">
<data key="d13"> <data key="d13">
<y:GraphMLReference ResourceKey="7"/> <y:GraphMLReference ResourceKey="9"/>
</data> </data>
</edge> </edge>
<edge id="e3" source="n4" target="n5" sourceport="p1" targetport="p0"> <edge id="e3" source="n4" target="n5" sourceport="p1" targetport="p0">
<data key="d13"> <data key="d13">
<yjs:PolylineEdgeStyle stroke="{y:GraphMLReference 5}"> <yjs:PolylineEdgeStyle stroke="{y:GraphMLReference 7}">
<yjs:PolylineEdgeStyle.targetArrow> <yjs:PolylineEdgeStyle.targetArrow>
<yjs:Arrow type="NONE" scale="0.75" stroke="#FF663800" fill="{y:GraphMLReference 4}"/> <yjs:Arrow type="NONE" scale="0.75" stroke="#FF663800" fill="{y:GraphMLReference 6}"/>
</yjs:PolylineEdgeStyle.targetArrow> </yjs:PolylineEdgeStyle.targetArrow>
<yjs:PolylineEdgeStyle.sourceArrow> <yjs:PolylineEdgeStyle.sourceArrow>
<yjs:Arrow type="NONE" scale="0.75" stroke="#FF663800" fill="{y:GraphMLReference 4}"/> <yjs:Arrow type="NONE" scale="0.75" stroke="#FF663800" fill="{y:GraphMLReference 6}"/>
</yjs:PolylineEdgeStyle.sourceArrow> </yjs:PolylineEdgeStyle.sourceArrow>
</yjs:PolylineEdgeStyle> </yjs:PolylineEdgeStyle>
</data> </data>
</edge> </edge>
<edge id="e4" source="n6" target="n2" sourceport="p1" targetport="p1"> <edge id="e4" source="n6" target="n2" sourceport="p1" targetport="p1">
<data key="d13"> <data key="d13">
<yjs:PolylineEdgeStyle stroke="{y:GraphMLReference 5}"> <yjs:PolylineEdgeStyle stroke="{y:GraphMLReference 7}">
<yjs:PolylineEdgeStyle.targetArrow> <yjs:PolylineEdgeStyle.targetArrow>
<yjs:Arrow type="NONE" scale="0.75" stroke="#FF663800" fill="{y:GraphMLReference 4}"/> <yjs:Arrow type="NONE" scale="0.75" stroke="#FF663800" fill="{y:GraphMLReference 6}"/>
</yjs:PolylineEdgeStyle.targetArrow> </yjs:PolylineEdgeStyle.targetArrow>
<yjs:PolylineEdgeStyle.sourceArrow>
<yjs:Arrow type="TRIANGLE" scale="0.75" stroke="#FF663800" fill="{y:GraphMLReference 6}" cropLength="1"/>
</yjs:PolylineEdgeStyle.sourceArrow>
</yjs:PolylineEdgeStyle> </yjs:PolylineEdgeStyle>
</data> </data>
</edge> </edge>
<edge id="e5" source="n2" target="n1" sourceport="p2" targetport="p1"> <edge id="e5" source="n6" target="n1" sourceport="p2" targetport="p1">
<data key="d13"> <data key="d13">
<yjs:PolylineEdgeStyle stroke="{y:GraphMLReference 5}" targetArrow="{y:GraphMLReference 6}"> <yjs:PolylineEdgeStyle stroke="{y:GraphMLReference 7}" targetArrow="{y:GraphMLReference 8}">
<yjs:PolylineEdgeStyle.sourceArrow> <yjs:PolylineEdgeStyle.sourceArrow>
<yjs:Arrow type="NONE" scale="0.75" stroke="#FF663800" fill="{y:GraphMLReference 4}"/> <yjs:Arrow type="TRIANGLE" scale="0.75" stroke="#FF663800" fill="{y:GraphMLReference 6}" cropLength="1"/>
</yjs:PolylineEdgeStyle.sourceArrow> </yjs:PolylineEdgeStyle.sourceArrow>
</yjs:PolylineEdgeStyle> </yjs:PolylineEdgeStyle>
</data> </data>

View file

@ -19,7 +19,7 @@ def timectime(s):
def convsize(s): def convsize(s):
sizes=("B","KB","MB","GB","TB") sizes=("B","KB","MB","GB","TB")
n=0 n=0
while s > 1000: while s >= 1000:
n+=1 n+=1
s=s/1000 s=s/1000
return str("%.2f" % s)+sizes[n] return str("%.2f" % s)+sizes[n]
@ -28,31 +28,48 @@ def convsize(s):
@app.route('/') @app.route('/')
def homepage(): def homepage():
# try to get userdata, else logout state # try to get userdata, else logout state
print(request.base_url)
try:
logged_in,userdata=get_login_info(request.cookies.get('session')) logged_in,userdata=get_login_info(request.cookies.get('session'))
except Exception as e:
logged_in,userdata=False,()
#try: # TODO: CLEAN!
# # get sesskey and get info about user
# sesskey=request.cookies.get('session')
# res,userid=db.check_sesskey(sesskey)
# if not res:
# return 500
# userdata=db.get_user_info(userid)
# logged_in=True
#except Exception as e:
# logged_in=False
# userdata=()
archives=db.get_n_archives() archives=db.get_n_archives()
return render_template("home.html", title="Homepage",userdata=userdata,login=logged_in,archives=archives) return render_template("home.html", title="Homepage",userdata=userdata,login=logged_in,archives=archives)
@app.route('/user') @app.route('/user')
@app.route('/user/<int:userid>') @app.route('/user/<int:userid>')
def userpage(userid:int=0): def userpage(userid:int=0):
if userid == 0: logged_in,userdata=get_login_info(request.cookies.get('session'))
if not logged_in:
return make_response(redirect('/')) return make_response(redirect('/'))
@app.route('/add', methods=['GET','POST'])
def addpage():
# try to get userdata, else yeet to the homepage
logged_in,userdata=get_login_info(request.cookies.get('session'))
if not logged_in:
return make_response(redirect('/'))
# POST: check and add archive, show confirmation/error message at the end
if request.method == 'POST':
postdict={}
# get and save all inputs, error if one doesn't exist or is wrong type
for i,itype in [("name",str),("hash",str),("category",int),("size",float)]:
try:
postdict[i]=itype(request.form[i])
except Exception as e:
return "<h2>ERROR: All fields need to be filled and don't play with their names!</h2> Go back and try again."
try:
postdict["size"]=postdict["size"]*int(request.form['size_multiplier'])
except Exception as e:
return "<h2>ERROR: All fields need to be filled and don't play with their names!</h2> Go back and try again."
postdict["owner"]=userdata[0]
res,archid=db.add_archive(postdict)
if res:
return make_response(redirect(f"/view/{str(archid)}"))
else:
return f"<h2>ERROR: {archid}</h2> Go back and try again.", 400
# GET: return normal page
htmlcatlist=get_category_selection(False)
return render_template("add.html", title="Add Archive",categories=htmlcatlist)
@app.route('/login', methods=["GET","POST"]) @app.route('/login', methods=["GET","POST"])
def loginpage(): def loginpage():
# POST: Process login request # POST: Process login request
@ -78,10 +95,7 @@ def loginpage():
@app.route('/search') @app.route('/search')
def searchpage(): def searchpage():
# try to get userdata, else logout state # try to get userdata, else logout state
try:
logged_in,userdata=get_login_info(request.cookies.get('session')) logged_in,userdata=get_login_info(request.cookies.get('session'))
except Exception as e:
logged_in,userdata=False,()
# try to set all required variables, else defaults # try to set all required variables, else defaults
try: try:
@ -103,26 +117,18 @@ def searchpage():
count=20 count=20
archives=db.get_n_archives(sorttype,category,keywords,count) archives=db.get_n_archives(sorttype,category,keywords,count)
catlist=db.get_all_categories() htmlcatlist=get_category_selection()
htmlcatlist=[]
# parse all categories and sort them into select box
for cat in catlist:
if not cat[2]:
htmlcatlist.append((cat[0],cat[1]))
parent=cat[1]
parentid=cat[0]
for i in catlist:
if i[2] == parentid:
htmlcatlist.append((i[0],f"{parent}/{i[1]}"))
return render_template("search.html", title="Advanced Search",categories=htmlcatlist,userdata=userdata,login=logged_in,archives=archives) return render_template("search.html", title="Advanced Search",categories=htmlcatlist,userdata=userdata,login=logged_in,archives=archives)
## FUNCTIONS ## FUNCTIONS
## Checks if given sesskey is valid and returns user data ## Checks if given sesskey is valid and returns user data
## OUTPUT: (if sesskey valid) logged_in:bool=True, userdata:tup ## OUTPUT: (if sesskey valid) logged_in:bool=True, userdata:tuple
## (if sesskey invalid) ## (if sesskey invalid) logged_in:bool=False, userdata:tuple=()
def get_login_info(sesskey:str): def get_login_info(sesskey:str):
if not sesskey:
return False,()
logged_in,userid=db.check_sesskey(sesskey) logged_in,userid=db.check_sesskey(sesskey)
if logged_in: if logged_in:
userdata=db.get_user_info(userid) userdata=db.get_user_info(userid)
@ -135,6 +141,23 @@ def setcookie(name:str,value:str,lifetime:int=10000):
resp.set_cookie(name, value, max_age=lifetime) resp.set_cookie(name, value, max_age=lifetime)
return resp return resp
## Gets all categories and returns them (with or without parents)
## OUTPUT: […,(ID:int,NAME:str),…]
def get_category_selection(include_parents:bool=True):
catlist=db.get_all_categories()
htmlcatlist=[]
# parse all categories and sort them into list
for cat in catlist:
if not cat[2]:
if include_parents:
htmlcatlist.append((cat[0],cat[1]))
parent=cat[1]
parentid=cat[0]
for i in catlist:
if i[2] == parentid:
htmlcatlist.append((i[0],f"{parent}/{i[1]}"))
return htmlcatlist
## API CALLS (NO THANKS) ## API CALLS (NO THANKS)
# main driver function # main driver function

View file

@ -1,7 +1,7 @@
## MAIN FUNCTIONS FILE FOR BACK-BACKEND OF FLASK ## MAIN FUNCTIONS FILE FOR BACK-BACKEND OF FLASK
import mariadb as sql import mariadb as sql
from os import environ from os import environ
import time import time,re
## params populated with environment variables, defaults can be changed for a permanent solution ## params populated with environment variables, defaults can be changed for a permanent solution
conn_params={ conn_params={
@ -23,7 +23,7 @@ class db:
self.cur.execute("""CREATE TABLE IF NOT EXISTS Archs( self.cur.execute("""CREATE TABLE IF NOT EXISTS Archs(
ID int PRIMARY KEY AUTO_INCREMENT, ID int PRIMARY KEY AUTO_INCREMENT,
NAME text NOT NULL, NAME text NOT NULL,
HASH text NOT NULL, HASH text NOT NULL UNIQUE,
SIZE int NOT NULL, SIZE int NOT NULL,
IMPORTED int, IMPORTED int,
CATEGORY int, CATEGORY int,
@ -55,10 +55,14 @@ class db:
ARCHID int NOT NULL, ARCHID int NOT NULL,
LABID int NOT NULL LABID int NOT NULL
);""") );""")
self.cur.execute("""CREATE TABLE IF NOT EXISTS CatLabType(
ID int PRIMARY KEY AUTO_INCREMENT,
CATID int NOT NULL,
LABID int NOT NULL
);""")
self.cur.execute("""CREATE TABLE IF NOT EXISTS Labs( self.cur.execute("""CREATE TABLE IF NOT EXISTS Labs(
ID int PRIMARY KEY AUTO_INCREMENT, ID int PRIMARY KEY AUTO_INCREMENT,
LABEL text NOT NULL, LABEL text NOT NULL,
CATEGORY text,
TYPE int NOT NULL TYPE int NOT NULL
);""") );""")
self.cur.execute("""CREATE TABLE IF NOT EXISTS LabType( self.cur.execute("""CREATE TABLE IF NOT EXISTS LabType(
@ -99,8 +103,30 @@ class db:
self.cur.execute(f"SELECT * FROM Users WHERE ID='{userid}'") self.cur.execute(f"SELECT * FROM Users WHERE ID='{userid}'")
return self.cur.fetchone() return self.cur.fetchone()
## Checks information for errors and adds archive to the DB
## OUTPUT: (if successful) res:bool=True, ID:int
## (if unsuccessful) res:bool=False, str
def add_archive(self, archive:dict):
# Check everything for errors or malicious things
archive["hash"]=archive["hash"].upper()
if not re.match('[A-Z0-9]{40}', archive["hash"]):
return False, "Hash needs to be 40 characters in hexadecimal (SHA-1)."
if re.match('.*[^A-Za-z0-9\. _-].*', archive["name"]):
return False, "The name contains illegal characters. Allowed chars: '[A-Za-z0-9\. _-]'"
print(archive["name"])
curtime=time.time()
try:
self.cur.execute(f"INSERT INTO Archs(NAME,HASH,SIZE,IMPORTED,CATEGORY,OWNER) VALUES('{archive['name']}','{archive['hash']}',{archive['size']},{curtime},{archive['category']},{archive['owner']})")
except Exception as e: # hash needs to be unique
return False, e
self.cur.execute(f"SELECT ID FROM Archs WHERE HASH='{archive['hash']}'")
archid=self.cur.fetchone()
return True,archid[0]
## Returns all relevant information about one (1) archive ## Returns all relevant information about one (1) archive
## OUTPUT: archive:tuple=(NAME:str,HASH:str,IMPORTED[UNIX]:int,CATEGORY,str,CATEGORY.DESCRIPTION:str,UNAME:str,DNAME:str) ## OUTPUT: archive:tuple=(NAME:str,HASH:str,IMPORTED[UNIX]:int,CATEGORY,str,CATEGORY.DESCRIPTION:str,UNAME:str,DNAME:str),
## labels:array=[…,(LABEL:str,CATEGORY:str,CATDESC:str,LABTYPE:str,LABDESC:str),…] ## labels:array=[…,(LABEL:str,CATEGORY:str,CATDESC:str,LABTYPE:str,LABDESC:str),…]
def get_archive_info(self, hash:str): def get_archive_info(self, hash:str):
self.cur.execute(f"""SELECT Archs.NAME,Archs.HASH,Archs.IMPORTED,Cats.CATEGORY,Cats.DESCRIPTION,Users.UNAME,Users.DNAME FROM Archs self.cur.execute(f"""SELECT Archs.NAME,Archs.HASH,Archs.IMPORTED,Cats.CATEGORY,Cats.DESCRIPTION,Users.UNAME,Users.DNAME FROM Archs
@ -146,14 +172,14 @@ class db:
# Get all children of category (if exist) and put into query string # Get all children of category (if exist) and put into query string
categories=self.get_all_categories() categories=self.get_all_categories()
catlist=[category] catlist=[str(category)]
for i in categories: for i in categories:
if i[2] == int(category): if i[2] == int(category):
catlist.append(str(i[0])) catlist.append(str(i[0]))
category="(" + ",".join(catlist) + ")" categories="(" + ",".join(catlist) + ")"
self.cur.execute(f"""SELECT ID,NAME,SIZE,IMPORTED FROM Archs self.cur.execute(f"""SELECT ID,NAME,SIZE,IMPORTED FROM Archs
{"WHERE 1=1" if category==0 else " WHERE CATEGORY IN " + category} {"WHERE 1=1" if category==0 else " WHERE CATEGORY IN " + categories}
{keyword_string} {keyword_string}
ORDER BY {sorttype} LIMIT {count};""") ORDER BY {sorttype} LIMIT {count};""")
archives=self.cur.fetchall() archives=self.cur.fetchall()

4
flask/static/add.css Normal file
View file

@ -0,0 +1,4 @@
div.grid-container {
display: grid;
grid-template-columns: max-content max-content;
}

View file

@ -15,6 +15,16 @@ header {
display: flex; display: flex;
} }
header > a {
margin: auto;
height: 2em;
width: 2em;
}
a > img {
height: 2em;
width: 2em;
}
header > div#container { header > div#container {
flex-grow: 1; flex-grow: 1;
display: flex; display: flex;

34
flask/templates/add.html Normal file
View file

@ -0,0 +1,34 @@
{% extends "base.html" %}
{% block meta %}
<link rel="stylesheet" href="/static/add.css" />
{% endblock %}
{% block content %}
<form action="/add" method="post">
<div class="grid-container">
<div><b>Name: </b></div>
<div><input type="text" name="name" placeholder="Archive Name"></div>
<div><b>Hash: </b></div>
<div><input type="text" name="hash" placeholder="Hash"></div>
<div><b>Category: </b></div>
<div>
<select name="category">
{% for i in categories %}
<option value="{{i[0]}}">{{i[1]}}</option>
{% endfor %}
</select>
</div>
<div><b>Size: </b></div>
<div>
<input type="number" name="size" step="0.01" min="0" placeholder="Size">
<select name="size_multiplier">
{% for val,size in ((1,"B"),(1000,"KB"),(1000000,"MB"),(1000000000,"GB"),(1000000000000,"TB")) %}
<option value="{{val}}">{{size}}</option>
{% endfor %}
</select>
</div>
<input type="submit" value="Add Archive">
</div>
</form>
{% endblock %}

View file

@ -10,6 +10,7 @@
<body> <body>
<header> <header>
<a href="/"><img src="/static/favicon.ico" alt="favicon"></a>
<span id="title">{{title}}</span> <span id="title">{{title}}</span>
<div id="container"> <div id="container">
<!-- BUTTONS IF USER LOGGED IN --> <!-- BUTTONS IF USER LOGGED IN -->