mirror of
https://github.com/simon987/Simple-Incremental-Search-Tool.git
synced 2025-04-15 16:26:45 +00:00
commit
7ccc1f14d1
31
README.md
31
README.md
@ -1,6 +1,7 @@
|
|||||||
# Simple incremental search tool
|
# Simple incremental search tool
|
||||||
|
|
||||||
Work in progress! Shouldn't be used in production environnments.
|
|
||||||
|
Portable search tool for local files using Elasticsearch.
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
* Incremental search (Search as you type)
|
* Incremental search (Search as you type)
|
||||||
@ -19,37 +20,21 @@ Work in progress! Shouldn't be used in production environnments.
|
|||||||
|
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
Java and python3 are required. To parse video and audio files, `ffmpeg` needs to be installed
|
Java and python3 are required.
|
||||||
Once the web server is running, you can connect to the search interface by typing `localhost:8080` in your browser.
|
Once the web server is running, you can connect to the search interface by typing `localhost:8080` in your browser.
|
||||||
|
|
||||||
## Setup on Windows
|
## Setup on Windows/Mac/linux (Python 3.5+)
|
||||||
|
|
||||||
|
* Download and install [Elasticsearch](https://www.elastic.co/downloads/elasticsearch)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/simon987/Simple-Incremental-Search-Tool
|
git clone https://github.com/simon987/Simple-Incremental-Search-Tool
|
||||||
cd Simple-Incremental-Search-Tool
|
|
||||||
```
|
|
||||||
[Download latest elasticsearch version](https://www.elastic.co/downloads/elasticsearch) and extract to `Simple-Incremental-Search-Tool\elasticsearch`
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo pip3 install -r requirements.txt
|
|
||||||
|
|
||||||
python3 run.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## Setup on Mac/linux
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/simon987/Simple-Incremental-Search-Tool
|
|
||||||
cd Simple-Incremental-Search-Tool
|
|
||||||
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.2.4.zip
|
|
||||||
unzip elasticsearch-6.2.4.zip
|
|
||||||
rm elasticsearch-6.2.4.zip
|
|
||||||
mv elasticsearch-6.2.4 elasticsearch
|
|
||||||
|
|
||||||
sudo pip3 install -r requirements.txt
|
sudo pip3 install -r requirements.txt
|
||||||
|
|
||||||
python3 run.py
|
python3 run.py
|
||||||
```
|
```
|
||||||
|
|
||||||
## Running unit tests
|
## Running unit tests
|
||||||
```bash
|
```bash
|
||||||
python3 -m unittest
|
python3 -m unittest
|
||||||
```
|
```
|
@ -25,6 +25,9 @@ bcrypt_rounds = 14
|
|||||||
# sqlite3 database path
|
# sqlite3 database path
|
||||||
db_path = "./local_storage.db"
|
db_path = "./local_storage.db"
|
||||||
|
|
||||||
|
# Set to true to allow guests to search any directory
|
||||||
|
allow_guests = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import cairosvg
|
import cairosvg
|
||||||
cairosvg = True
|
cairosvg = True
|
||||||
|
@ -12,7 +12,6 @@ from thumbnail import ThumbnailGenerator
|
|||||||
from storage import Directory
|
from storage import Directory
|
||||||
import shutil
|
import shutil
|
||||||
import config
|
import config
|
||||||
from ctypes import c_char_p
|
|
||||||
|
|
||||||
|
|
||||||
class RunningTask:
|
class RunningTask:
|
||||||
@ -145,9 +144,9 @@ class TaskManager:
|
|||||||
TextFileParser(chksum_calcs, int(directory.get_option("TextFileContentLength")), directory.path),
|
TextFileParser(chksum_calcs, int(directory.get_option("TextFileContentLength")), directory.path),
|
||||||
PictureFileParser(chksum_calcs, directory.path),
|
PictureFileParser(chksum_calcs, directory.path),
|
||||||
FontParser(chksum_calcs, directory.path),
|
FontParser(chksum_calcs, directory.path),
|
||||||
PdfFileParser(chksum_calcs, int(directory.get_option("TextFileContentLength")), directory.path), # todo get content len from other opt
|
PdfFileParser(chksum_calcs, int(directory.get_option("PdfFileContentLength")), directory.path),
|
||||||
DocxParser(chksum_calcs, int(directory.get_option("TextFileContentLength")), directory.path), # todo get content len from other opt
|
DocxParser(chksum_calcs, int(directory.get_option("SpreadsheetContentLength")), directory.path),
|
||||||
EbookParser(chksum_calcs, int(directory.get_option("TextFileContentLength")), directory.path)], # todo get content len from other opt
|
EbookParser(chksum_calcs, int(directory.get_option("EbookContentLength")), directory.path)],
|
||||||
mime_guesser, self.indexer, directory.id)
|
mime_guesser, self.indexer, directory.id)
|
||||||
c.crawl(directory.path, counter)
|
c.crawl(directory.path, counter)
|
||||||
|
|
||||||
|
33
indexer.py
33
indexer.py
@ -1,10 +1,9 @@
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
import elasticsearch
|
import elasticsearch
|
||||||
from threading import Thread
|
|
||||||
import subprocess
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
import config
|
import config
|
||||||
import platform
|
|
||||||
|
|
||||||
|
|
||||||
class Indexer:
|
class Indexer:
|
||||||
@ -14,30 +13,12 @@ class Indexer:
|
|||||||
self.index_name = index
|
self.index_name = index
|
||||||
self.es = elasticsearch.Elasticsearch()
|
self.es = elasticsearch.Elasticsearch()
|
||||||
|
|
||||||
try:
|
requests.head("http://localhost:9200")
|
||||||
requests.head("http://localhost:9200")
|
if self.es.indices.exists(self.index_name):
|
||||||
|
print("Index is already setup")
|
||||||
except requests.exceptions.ConnectionError:
|
|
||||||
import time
|
|
||||||
t = Thread(target=Indexer.run_elasticsearch)
|
|
||||||
t.daemon = True
|
|
||||||
t.start()
|
|
||||||
|
|
||||||
time.sleep(15)
|
|
||||||
|
|
||||||
if self.es.indices.exists(self.index_name):
|
|
||||||
print("Index is already setup")
|
|
||||||
else:
|
|
||||||
print("First time setup...")
|
|
||||||
self.init()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def run_elasticsearch():
|
|
||||||
|
|
||||||
if platform.system() == "Windows":
|
|
||||||
subprocess.Popen(["elasticsearch\\bin\\elasticsearch.bat"])
|
|
||||||
else:
|
else:
|
||||||
subprocess.Popen(["elasticsearch/bin/elasticsearch"])
|
print("First time setup...")
|
||||||
|
self.init()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_bulk_index_string(docs: list, directory: int):
|
def create_bulk_index_string(docs: list, directory: int):
|
||||||
|
@ -272,7 +272,7 @@ class TextFileParser(GenericFileParser):
|
|||||||
"text/x-bibtex", "text/x-tcl", "text/x-c++", "text/x-shellscript", "text/x-msdos-batch",
|
"text/x-bibtex", "text/x-tcl", "text/x-c++", "text/x-shellscript", "text/x-msdos-batch",
|
||||||
"text/x-makefile", "text/rtf", "text/x-objective-c", "text/troff", "text/x-m4",
|
"text/x-makefile", "text/rtf", "text/x-objective-c", "text/troff", "text/x-m4",
|
||||||
"text/x-lisp", "text/x-php", "text/x-gawk", "text/x-awk", "text/x-ruby", "text/x-po",
|
"text/x-lisp", "text/x-php", "text/x-gawk", "text/x-awk", "text/x-ruby", "text/x-po",
|
||||||
"text/x-makefile", "application/javascript", "application/rtf"
|
"text/x-makefile", "application/javascript", "application/rtf", "application/json",
|
||||||
]
|
]
|
||||||
|
|
||||||
def parse(self, full_path: str):
|
def parse(self, full_path: str):
|
||||||
|
@ -16,4 +16,5 @@ html2text
|
|||||||
docx2txt
|
docx2txt
|
||||||
xlrd
|
xlrd
|
||||||
six
|
six
|
||||||
cairosvg
|
cairosvg
|
||||||
|
ffmpeg-python
|
415
run.py
415
run.py
@ -1,17 +1,18 @@
|
|||||||
from flask import Flask, render_template, request, redirect, flash, session, abort, send_file
|
|
||||||
from storage import Directory, Option, Task, User
|
|
||||||
from storage import LocalStorage, DuplicateDirectoryException, DuplicateUserException
|
|
||||||
from crawler import RunningTask, TaskManager
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import bcrypt
|
|
||||||
import config
|
|
||||||
import humanfriendly
|
|
||||||
from search import Search
|
|
||||||
from PIL import Image
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
import bcrypt
|
||||||
|
import humanfriendly
|
||||||
|
from PIL import Image
|
||||||
|
from flask import Flask, render_template, request, redirect, flash, session, abort, send_file
|
||||||
|
|
||||||
|
import config
|
||||||
|
from crawler import TaskManager
|
||||||
|
from search import Search
|
||||||
|
from storage import Directory, Option, Task, User
|
||||||
|
from storage import LocalStorage, DuplicateDirectoryException, DuplicateUserException
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.secret_key = "A very secret key"
|
app.secret_key = "A very secret key"
|
||||||
@ -22,12 +23,10 @@ search = Search("changeme")
|
|||||||
|
|
||||||
|
|
||||||
def get_dir_size(path):
|
def get_dir_size(path):
|
||||||
|
|
||||||
size = 0
|
size = 0
|
||||||
|
|
||||||
for root, dirs, files in os.walk(path):
|
for root, dirs, files in os.walk(path):
|
||||||
for filename in files:
|
for filename in files:
|
||||||
|
|
||||||
full_path = os.path.join(root, filename)
|
full_path = os.path.join(root, filename)
|
||||||
size += os.path.getsize(full_path)
|
size += os.path.getsize(full_path)
|
||||||
|
|
||||||
@ -36,8 +35,11 @@ def get_dir_size(path):
|
|||||||
|
|
||||||
@app.route("/user/<user>")
|
@app.route("/user/<user>")
|
||||||
def user_manage(user):
|
def user_manage(user):
|
||||||
|
if "admin" in session and session["admin"]:
|
||||||
return user
|
return render_template("user_manage.html", directories=storage.dirs(), user=storage.users()[user])
|
||||||
|
else:
|
||||||
|
flash("You are not authorized to access this page", "warning")
|
||||||
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/logout")
|
@app.route("/logout")
|
||||||
@ -57,7 +59,6 @@ def login():
|
|||||||
session["username"] = username
|
session["username"] = username
|
||||||
session["admin"] = storage.users()[username].admin
|
session["admin"] = storage.users()[username].admin
|
||||||
|
|
||||||
print(session["admin"])
|
|
||||||
flash("Successfully logged in", "success")
|
flash("Successfully logged in", "success")
|
||||||
else:
|
else:
|
||||||
flash("Invalid username or password", "danger")
|
flash("Invalid username or password", "danger")
|
||||||
@ -67,35 +68,108 @@ def login():
|
|||||||
|
|
||||||
@app.route("/user")
|
@app.route("/user")
|
||||||
def user_page():
|
def user_page():
|
||||||
|
admin_account_present = False
|
||||||
|
|
||||||
return render_template("user.html", users=storage.users())
|
for user in storage.users():
|
||||||
|
if storage.users()[user].admin:
|
||||||
|
admin_account_present = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not admin_account_present or ("admin" in session and session["admin"]):
|
||||||
|
return render_template("user.html", users=storage.users(), admin_account_present=admin_account_present)
|
||||||
|
else:
|
||||||
|
flash("You are not authorized to access this page", "warning")
|
||||||
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/user/<username>/set_access")
|
||||||
|
def user_set_access(username):
|
||||||
|
if "admin" in session and session["admin"]:
|
||||||
|
dir_id = request.args["dir_id"]
|
||||||
|
user = storage.users()[username]
|
||||||
|
|
||||||
|
if request.args["access"] == "1":
|
||||||
|
user.readable_directories.add(int(dir_id))
|
||||||
|
else:
|
||||||
|
if int(dir_id) in user.readable_directories:
|
||||||
|
user.readable_directories.remove(int(dir_id))
|
||||||
|
|
||||||
|
storage.update_user(user)
|
||||||
|
|
||||||
|
flash("Permissions mises à jour", "success")
|
||||||
|
return redirect("/user/" + username)
|
||||||
|
else:
|
||||||
|
flash("You are not authorized to access this page", "warning")
|
||||||
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/user/<username>/set_admin")
|
||||||
|
def user_set_admin(username):
|
||||||
|
if "admin" in session and session["admin"]:
|
||||||
|
|
||||||
|
user = storage.users()[username]
|
||||||
|
|
||||||
|
if user.username == session["username"]:
|
||||||
|
flash("You cannot modifiy your own account", "warning")
|
||||||
|
else:
|
||||||
|
user.admin = request.args["admin"] == "1"
|
||||||
|
storage.update_user(user)
|
||||||
|
|
||||||
|
flash("Permissions updated", "success")
|
||||||
|
|
||||||
|
return redirect("/user/" + username)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/user/add", methods=['POST'])
|
@app.route("/user/add", methods=['POST'])
|
||||||
def user_add():
|
def user_add():
|
||||||
|
admin_account_present = False
|
||||||
|
|
||||||
username = request.form["username"]
|
for user in storage.users():
|
||||||
password = bcrypt.hashpw(request.form["password"].encode("utf-8"), bcrypt.gensalt(config.bcrypt_rounds))
|
if storage.users()[user].admin:
|
||||||
is_admin = True if "is_admin" in request.form else False
|
admin_account_present = True
|
||||||
|
break
|
||||||
|
|
||||||
try:
|
if not admin_account_present or ("admin" in session and session["admin"]):
|
||||||
storage.save_user(User(username, password, is_admin))
|
username = request.form["username"]
|
||||||
flash("Created new user", "success")
|
password = bcrypt.hashpw(request.form["password"].encode("utf-8"), bcrypt.gensalt(config.bcrypt_rounds))
|
||||||
except DuplicateUserException:
|
is_admin = True if "is_admin" in request.form else False
|
||||||
flash("<strong>Couldn't create user</strong> Make sure that the username is unique", "danger")
|
|
||||||
|
|
||||||
return redirect("/user")
|
try:
|
||||||
|
storage.save_user(User(username, password, is_admin))
|
||||||
|
flash("Created new user", "success")
|
||||||
|
except DuplicateUserException:
|
||||||
|
flash("<strong>Couldn't create user</strong> "
|
||||||
|
"Make sure that the username is unique", "danger")
|
||||||
|
|
||||||
|
return redirect("/user")
|
||||||
|
else:
|
||||||
|
flash("You are not authorized to access this page", "warning")
|
||||||
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/user/<username>/del")
|
||||||
|
def user_del(username):
|
||||||
|
if "admin" in session and session["admin"]:
|
||||||
|
|
||||||
|
if session["username"] == username:
|
||||||
|
flash("You cannot delete your own account", "warning")
|
||||||
|
return redirect("/user/" + username)
|
||||||
|
else:
|
||||||
|
storage.remove_user(username)
|
||||||
|
flash("User deleted", "success")
|
||||||
|
return redirect("/user")
|
||||||
|
else:
|
||||||
|
flash("You are not authorized to access this page", "warning")
|
||||||
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/suggest")
|
@app.route("/suggest")
|
||||||
def suggest():
|
def suggest():
|
||||||
|
|
||||||
return json.dumps(search.suggest(request.args.get("prefix")))
|
return json.dumps(search.suggest(request.args.get("prefix")))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/document/<doc_id>")
|
@app.route("/document/<doc_id>")
|
||||||
def document(doc_id):
|
def document(doc_id):
|
||||||
|
|
||||||
doc = search.get_doc(doc_id)["_source"]
|
doc = search.get_doc(doc_id)["_source"]
|
||||||
directory = storage.dirs()[doc["directory"]]
|
directory = storage.dirs()[doc["directory"]]
|
||||||
|
|
||||||
@ -106,7 +180,6 @@ def document(doc_id):
|
|||||||
|
|
||||||
@app.route("/dl/<doc_id>")
|
@app.route("/dl/<doc_id>")
|
||||||
def file(doc_id):
|
def file(doc_id):
|
||||||
|
|
||||||
doc = search.get_doc(doc_id)["_source"]
|
doc = search.get_doc(doc_id)["_source"]
|
||||||
directory = storage.dirs()[doc["directory"]]
|
directory = storage.dirs()[doc["directory"]]
|
||||||
|
|
||||||
@ -121,7 +194,6 @@ def file(doc_id):
|
|||||||
|
|
||||||
@app.route("/file/<doc_id>")
|
@app.route("/file/<doc_id>")
|
||||||
def download(doc_id):
|
def download(doc_id):
|
||||||
|
|
||||||
doc = search.get_doc(doc_id)["_source"]
|
doc = search.get_doc(doc_id)["_source"]
|
||||||
directory = storage.dirs()[doc["directory"]]
|
directory = storage.dirs()[doc["directory"]]
|
||||||
extension = "" if doc["extension"] is None or doc["extension"] == "" else "." + doc["extension"]
|
extension = "" if doc["extension"] is None or doc["extension"] == "" else "." + doc["extension"]
|
||||||
@ -135,7 +207,6 @@ def download(doc_id):
|
|||||||
|
|
||||||
@app.route("/thumb/<doc_id>")
|
@app.route("/thumb/<doc_id>")
|
||||||
def thumb(doc_id):
|
def thumb(doc_id):
|
||||||
|
|
||||||
doc = search.get_doc(doc_id)
|
doc = search.get_doc(doc_id)
|
||||||
|
|
||||||
if doc is not None:
|
if doc is not None:
|
||||||
@ -145,14 +216,12 @@ def thumb(doc_id):
|
|||||||
if os.path.isfile(tn_path):
|
if os.path.isfile(tn_path):
|
||||||
return send_file(tn_path)
|
return send_file(tn_path)
|
||||||
else:
|
else:
|
||||||
print("tn not found")
|
|
||||||
default_thumbnail = BytesIO()
|
default_thumbnail = BytesIO()
|
||||||
Image.new("RGB", (255, 128), (0, 0, 0)).save(default_thumbnail, "JPEG")
|
Image.new("RGB", (255, 128), (0, 0, 0)).save(default_thumbnail, "JPEG")
|
||||||
default_thumbnail.seek(0)
|
default_thumbnail.seek(0)
|
||||||
return send_file(default_thumbnail, "image/jpeg")
|
return send_file(default_thumbnail, "image/jpeg")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print("doc is none")
|
|
||||||
default_thumbnail = BytesIO()
|
default_thumbnail = BytesIO()
|
||||||
Image.new("RGB", (255, 128), (0, 0, 0)).save(default_thumbnail, "JPEG")
|
Image.new("RGB", (255, 128), (0, 0, 0)).save(default_thumbnail, "JPEG")
|
||||||
default_thumbnail.seek(0)
|
default_thumbnail.seek(0)
|
||||||
@ -161,11 +230,14 @@ def thumb(doc_id):
|
|||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def search_page():
|
def search_page():
|
||||||
|
|
||||||
mime_map = search.get_mime_map()
|
mime_map = search.get_mime_map()
|
||||||
mime_map.append({"id": "any", "text": "Any"})
|
mime_map.append({"id": "any", "text": "All"})
|
||||||
|
|
||||||
return render_template("search.html", directories=storage.dirs(), mime_map=mime_map)
|
directories = [storage.dirs()[x] for x in get_allowed_dirs(session["username"] if "username" in session else None)]
|
||||||
|
|
||||||
|
return render_template("search.html",
|
||||||
|
directories=directories,
|
||||||
|
mime_map=mime_map)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/list")
|
@app.route("/list")
|
||||||
@ -173,9 +245,19 @@ def search_liste_page():
|
|||||||
return render_template("searchList.html")
|
return render_template("searchList.html")
|
||||||
|
|
||||||
|
|
||||||
|
def get_allowed_dirs(username):
|
||||||
|
if config.allow_guests:
|
||||||
|
return [x for x in storage.dirs() if x.enabled]
|
||||||
|
else:
|
||||||
|
if username:
|
||||||
|
user = storage.users()[username]
|
||||||
|
return [x for x in storage.dirs() if storage.dirs()[x].enabled and x in user.readable_directories]
|
||||||
|
else:
|
||||||
|
return list()
|
||||||
|
|
||||||
|
|
||||||
@app.route("/search", methods=['POST'])
|
@app.route("/search", methods=['POST'])
|
||||||
def search_route():
|
def search_route():
|
||||||
|
|
||||||
query = request.json["q"]
|
query = request.json["q"]
|
||||||
query = "" if query is None else query
|
query = "" if query is None else query
|
||||||
|
|
||||||
@ -186,19 +268,8 @@ def search_route():
|
|||||||
directories = request.json["directories"]
|
directories = request.json["directories"]
|
||||||
|
|
||||||
# Remove disabled & non-existing directories
|
# Remove disabled & non-existing directories
|
||||||
for search_directory in directories:
|
allowed_dirs = get_allowed_dirs(session["username"] if "username" in session else None)
|
||||||
directory_exists = False
|
directories = [x for x in directories if x in allowed_dirs]
|
||||||
|
|
||||||
for dir_id in storage.dirs():
|
|
||||||
if search_directory == dir_id:
|
|
||||||
directory_exists = True
|
|
||||||
|
|
||||||
if not storage.dirs()[dir_id].enabled:
|
|
||||||
directories.remove(search_directory)
|
|
||||||
break
|
|
||||||
|
|
||||||
if not directory_exists:
|
|
||||||
directories.remove(search_directory)
|
|
||||||
|
|
||||||
path = request.json["path"]
|
path = request.json["path"]
|
||||||
|
|
||||||
@ -209,7 +280,6 @@ def search_route():
|
|||||||
|
|
||||||
@app.route("/scroll")
|
@app.route("/scroll")
|
||||||
def scroll_route():
|
def scroll_route():
|
||||||
|
|
||||||
scroll_id = request.args.get("scroll_id")
|
scroll_id = request.args.get("scroll_id")
|
||||||
|
|
||||||
page = search.scroll(scroll_id)
|
page = search.scroll(scroll_id)
|
||||||
@ -219,184 +289,237 @@ def scroll_route():
|
|||||||
|
|
||||||
@app.route("/directory")
|
@app.route("/directory")
|
||||||
def dir_list():
|
def dir_list():
|
||||||
|
if "admin" in session and session["admin"]:
|
||||||
return render_template("directory.html", directories=storage.dirs())
|
return render_template("directory.html", directories=storage.dirs())
|
||||||
|
else:
|
||||||
|
flash("You are not authorized to access this page", "warning")
|
||||||
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/directory/add")
|
@app.route("/directory/add")
|
||||||
def directory_add():
|
def directory_add():
|
||||||
|
if "admin" in session and session["admin"]:
|
||||||
|
path = request.args.get("path")
|
||||||
|
name = request.args.get("name")
|
||||||
|
|
||||||
path = request.args.get("path")
|
if path is not None and name is not None:
|
||||||
name = request.args.get("name")
|
d = Directory(path, True, [], name)
|
||||||
|
|
||||||
if path is not None and name is not None:
|
try:
|
||||||
d = Directory(path, True, [], name)
|
d.set_default_options()
|
||||||
|
storage.save_directory(d)
|
||||||
|
flash("<strong>Directory created</strong>", "success")
|
||||||
|
except DuplicateDirectoryException:
|
||||||
|
flash("<strong>The directory couldn't be created</strong> Make sure to chose a unique name",
|
||||||
|
"danger")
|
||||||
|
|
||||||
try:
|
return redirect("/directory")
|
||||||
d.set_default_options()
|
else:
|
||||||
storage.save_directory(d)
|
flash("You are not authorized to access this page", "warning")
|
||||||
flash("<strong>Created directory</strong>", "success")
|
return redirect("/")
|
||||||
except DuplicateDirectoryException:
|
|
||||||
flash("<strong>Couldn't create directory</strong> Make sure that the path is unique", "danger")
|
|
||||||
|
|
||||||
return redirect("/directory")
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/directory/<int:dir_id>")
|
@app.route("/directory/<int:dir_id>")
|
||||||
def directory_manage(dir_id):
|
def directory_manage(dir_id):
|
||||||
|
if "admin" in session and session["admin"]:
|
||||||
|
directory = storage.dirs()[dir_id]
|
||||||
|
tn_size = get_dir_size("static/thumbnails/" + str(dir_id))
|
||||||
|
tn_size_formatted = humanfriendly.format_size(tn_size)
|
||||||
|
|
||||||
directory = storage.dirs()[dir_id]
|
return render_template("directory_manage.html", directory=directory, tn_size=tn_size,
|
||||||
tn_size = get_dir_size("static/thumbnails/" + str(dir_id))
|
tn_size_formatted=tn_size_formatted)
|
||||||
tn_size_formatted = humanfriendly.format_size(tn_size)
|
else:
|
||||||
|
flash("You are not authorized to access this page", "warning")
|
||||||
return render_template("directory_manage.html", directory=directory, tn_size=tn_size,
|
return redirect("/")
|
||||||
tn_size_formatted=tn_size_formatted)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/directory/<int:dir_id>/update")
|
@app.route("/directory/<int:dir_id>/update")
|
||||||
def directory_update(dir_id):
|
def directory_update(dir_id):
|
||||||
|
if "admin" in session and session["admin"]:
|
||||||
|
directory = storage.dirs()[dir_id]
|
||||||
|
|
||||||
directory = storage.dirs()[dir_id]
|
name = request.args.get("name")
|
||||||
|
name = directory.name if name is None else name
|
||||||
|
|
||||||
name = request.args.get("name")
|
enabled = request.args.get("enabled")
|
||||||
name = directory.name if name is None else name
|
enabled = directory.enabled if enabled is None else int(enabled)
|
||||||
|
|
||||||
enabled = request.args.get("enabled")
|
path = request.args.get("path")
|
||||||
enabled = directory.enabled if enabled is None else int(enabled)
|
path = directory.path if path is None else path
|
||||||
|
|
||||||
path = request.args.get("path")
|
# Only name and enabled status can be updated
|
||||||
path = directory.path if path is None else path
|
updated_dir = Directory(path, enabled, directory.options, name)
|
||||||
|
updated_dir.id = dir_id
|
||||||
|
|
||||||
# Only name and enabled status can be updated
|
try:
|
||||||
updated_dir = Directory(path, enabled, directory.options, name)
|
storage.update_directory(updated_dir)
|
||||||
updated_dir.id = dir_id
|
flash("<strong>Updated directory</strong>", "success")
|
||||||
|
|
||||||
try:
|
except DuplicateDirectoryException:
|
||||||
storage.update_directory(updated_dir)
|
flash("<strong>The directory couldn't be updated</strong> Make the that the path is unique",
|
||||||
flash("<strong>Updated directory</strong>", "success")
|
"danger")
|
||||||
|
|
||||||
except DuplicateDirectoryException:
|
return redirect("/directory/" + str(dir_id))
|
||||||
flash("<strong>Couldn't update directory</strong> Make sure that the path is unique", "danger")
|
else:
|
||||||
|
flash("You are not authorized to access this page", "warning")
|
||||||
return redirect("/directory/" + str(dir_id))
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/directory/<int:dir_id>/update_opt")
|
@app.route("/directory/<int:dir_id>/update_opt")
|
||||||
def directory_update_opt(dir_id):
|
def directory_update_opt(dir_id):
|
||||||
|
if "admin" in session and session["admin"]:
|
||||||
|
opt_id = request.args.get("id")
|
||||||
|
opt_key = request.args.get("key")
|
||||||
|
opt_value = request.args.get("value")
|
||||||
|
|
||||||
opt_id = request.args.get("id")
|
storage.update_option(Option(opt_key, opt_value, dir_id, opt_id))
|
||||||
opt_key = request.args.get("key")
|
|
||||||
opt_value = request.args.get("value")
|
|
||||||
|
|
||||||
storage.update_option(Option(opt_key, opt_value, dir_id, opt_id))
|
return redirect("/directory/" + str(dir_id))
|
||||||
|
else:
|
||||||
return redirect("/directory/" + str(dir_id))
|
flash("You are not authorized to access this page", "warning")
|
||||||
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/directory/<int:dir_id>/del")
|
@app.route("/directory/<int:dir_id>/del")
|
||||||
def directory_del(dir_id):
|
def directory_del(dir_id):
|
||||||
|
if "admin" in session and session["admin"]:
|
||||||
|
search.delete_directory(dir_id)
|
||||||
|
if os.path.exists("static/thumbnails/" + str(dir_id)):
|
||||||
|
shutil.rmtree("static/thumbnails/" + str(dir_id))
|
||||||
|
|
||||||
search.delete_directory(dir_id)
|
storage.remove_directory(dir_id)
|
||||||
if os.path.exists("static/thumbnails/" + str(dir_id)):
|
flash("<strong>Deleted folder</strong>", "success")
|
||||||
shutil.rmtree("static/thumbnails/" + str(dir_id))
|
|
||||||
|
|
||||||
storage.remove_directory(dir_id)
|
return redirect("/directory")
|
||||||
flash("<strong>Deleted directory</strong>", "success")
|
else:
|
||||||
|
flash("You are not authorized to access this page", "warning")
|
||||||
return redirect("/directory")
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/directory/<int:dir_id>/reset")
|
@app.route("/directory/<int:dir_id>/reset")
|
||||||
def directory_reset(dir_id):
|
def directory_reset(dir_id):
|
||||||
directory = storage.dirs()[dir_id]
|
if "admin" in session and session["admin"]:
|
||||||
|
directory = storage.dirs()[dir_id]
|
||||||
|
|
||||||
for opt in directory.options:
|
for opt in directory.options:
|
||||||
storage.del_option(opt.id)
|
storage.del_option(opt.id)
|
||||||
|
|
||||||
directory.set_default_options()
|
directory.set_default_options()
|
||||||
|
|
||||||
for opt in directory.options:
|
for opt in directory.options:
|
||||||
opt.dir_id = dir_id
|
opt.dir_id = dir_id
|
||||||
storage.save_option(opt)
|
storage.save_option(opt)
|
||||||
|
|
||||||
storage.dir_cache_outdated = True
|
storage.dir_cache_outdated = True
|
||||||
|
|
||||||
search.delete_directory(dir_id)
|
search.delete_directory(dir_id)
|
||||||
|
|
||||||
flash("<strong>Reset directory options to default settings</strong>", "success")
|
flash("<strong>Reset directory options</strong>", "success")
|
||||||
return redirect("directory/" + str(dir_id))
|
return redirect("directory/" + str(dir_id))
|
||||||
|
else:
|
||||||
|
flash("You are not authorized to access this page", "warning")
|
||||||
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/task")
|
@app.route("/task")
|
||||||
def task():
|
def task():
|
||||||
|
if "admin" in session and session["admin"]:
|
||||||
return render_template("task.html", tasks=storage.tasks(), directories=storage.dirs(),
|
return render_template("task.html", tasks=storage.tasks(), directories=storage.dirs(),
|
||||||
task_list=json.dumps(list(storage.tasks().keys())))
|
task_list=json.dumps(list(storage.tasks().keys())))
|
||||||
# return render_template("task.html", tasks=storage.tasks(), directories=storage.dirs())
|
else:
|
||||||
|
flash("You are not authorized to access this page", "warning")
|
||||||
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/task/current")
|
@app.route("/task/current")
|
||||||
def get_current_task():
|
def get_current_task():
|
||||||
|
if "admin" in session and session["admin"]:
|
||||||
|
|
||||||
if tm and tm.current_task:
|
if tm and tm.current_task:
|
||||||
return tm.current_task.to_json()
|
return tm.current_task.to_json()
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
else:
|
else:
|
||||||
return ""
|
flash("You are not authorized to access this page", "warning")
|
||||||
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/task/add")
|
@app.route("/task/add")
|
||||||
def task_add():
|
def task_add():
|
||||||
type = request.args.get("type")
|
if "admin" in session and session["admin"]:
|
||||||
directory = request.args.get("directory")
|
task_type = request.args.get("type")
|
||||||
|
directory = request.args.get("directory")
|
||||||
|
|
||||||
storage.save_task(Task(type, directory))
|
if task_type not in ("1", "2"):
|
||||||
|
flash("Please choose a task type", "danger")
|
||||||
|
return redirect("/task")
|
||||||
|
|
||||||
return redirect("/task")
|
if directory.isdigit() and int(directory) in storage.dirs():
|
||||||
|
storage.save_task(Task(task_type, directory))
|
||||||
|
else:
|
||||||
|
flash("You must choose a directory", "danger")
|
||||||
|
|
||||||
|
return redirect("/task")
|
||||||
|
else:
|
||||||
|
flash("You are not authorized to access this page", "warning")
|
||||||
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/task/<int:task_id>/del")
|
@app.route("/task/<int:task_id>/del")
|
||||||
def task_del(task_id):
|
def task_del(task_id):
|
||||||
storage.del_task(task_id)
|
if "admin" in session and session["admin"]:
|
||||||
|
storage.del_task(task_id)
|
||||||
|
|
||||||
if tm.current_task is not None and task_id == tm.current_task.task.id:
|
if tm.current_task is not None and task_id == tm.current_task.task.id:
|
||||||
tm.cancel_task()
|
tm.cancel_task()
|
||||||
|
|
||||||
return redirect("/task")
|
return redirect("/task")
|
||||||
|
else:
|
||||||
|
flash("You are not authorized to access this page", "warning")
|
||||||
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/reset_es")
|
@app.route("/reset_es")
|
||||||
def reset_es():
|
def reset_es():
|
||||||
|
if "admin" in session and session["admin"]:
|
||||||
|
flash("Elasticsearch index has been reset. Modifications made in <b>config.py</b> have been applied.",
|
||||||
|
"success")
|
||||||
|
|
||||||
flash("Elasticsearch index has been reset. Modifications made in <b>config.py</b> have been applied.", "success")
|
tm.indexer.init()
|
||||||
|
if os.path.exists("static/thumbnails"):
|
||||||
|
shutil.rmtree("static/thumbnails")
|
||||||
|
|
||||||
tm.indexer.init()
|
return redirect("/dashboard")
|
||||||
if os.path.exists("static/thumbnails"):
|
else:
|
||||||
shutil.rmtree("static/thumbnails")
|
flash("You are not authorized to access this page", "warning")
|
||||||
|
return redirect("/")
|
||||||
return redirect("/dashboard")
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/dashboard")
|
@app.route("/dashboard")
|
||||||
def dashboard():
|
def dashboard():
|
||||||
|
if "admin" in session and session["admin"]:
|
||||||
|
tn_sizes = {}
|
||||||
|
tn_size_total = 0
|
||||||
|
for directory in storage.dirs():
|
||||||
|
tn_size = get_dir_size("static/thumbnails/" + str(directory))
|
||||||
|
tn_size_formatted = humanfriendly.format_size(tn_size)
|
||||||
|
|
||||||
tn_sizes = {}
|
tn_sizes[directory] = tn_size_formatted
|
||||||
tn_size_total = 0
|
tn_size_total += tn_size
|
||||||
for directory in storage.dirs():
|
|
||||||
tn_size = get_dir_size("static/thumbnails/" + str(directory))
|
|
||||||
tn_size_formatted = humanfriendly.format_size(tn_size)
|
|
||||||
|
|
||||||
tn_sizes[directory] = tn_size_formatted
|
tn_size_total_formatted = humanfriendly.format_size(tn_size_total)
|
||||||
tn_size_total += tn_size
|
|
||||||
|
|
||||||
tn_size_total_formatted = humanfriendly.format_size(tn_size_total)
|
return render_template("dashboard.html", version=config.VERSION, tn_sizes=tn_sizes,
|
||||||
|
tn_size_total=tn_size_total_formatted,
|
||||||
|
doc_size=humanfriendly.format_size(search.get_doc_size()),
|
||||||
|
doc_count=search.get_doc_count(),
|
||||||
|
db_path=config.db_path,
|
||||||
|
elasticsearch_url=config.elasticsearch_url,
|
||||||
|
index_size=humanfriendly.format_size(search.get_index_size()))
|
||||||
|
|
||||||
return render_template("dashboard.html", version=config.VERSION, tn_sizes=tn_sizes,
|
else:
|
||||||
tn_size_total=tn_size_total_formatted,
|
flash("You are not authorized to access this page", "warning")
|
||||||
doc_size=humanfriendly.format_size(search.get_doc_size()),
|
return redirect("/")
|
||||||
doc_count=search.get_doc_count(),
|
|
||||||
db_path=config.db_path,
|
|
||||||
elasticsearch_url=config.elasticsearch_url,
|
|
||||||
index_size=humanfriendly.format_size(search.get_index_size()))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
4
static/css/bootstrap.min.css
vendored
4
static/css/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
@ -173,8 +173,8 @@ function gifOver(thumbnail, documentId) {
|
|||||||
|
|
||||||
function downloadPopover(element, documentId) {
|
function downloadPopover(element, documentId) {
|
||||||
element.setAttribute("data-content",
|
element.setAttribute("data-content",
|
||||||
'<a class="btn btn-sm btn-primary" href="/dl/'+ documentId +'"><i class="fas fa-download"></i> Download</a>' +
|
'<a class="btn btn-sm btn-primary" href="/dl/'+ documentId +'"><i class="fas fa-download"></i> Télécharger</a>' +
|
||||||
'<a class="btn btn-sm btn-success" style="margin-left:3px;" href="/file/'+ documentId + '" target="_blank"><i class="fas fa-eye"></i> View</a>');
|
'<a class="btn btn-sm btn-success" style="margin-left:3px;" href="/file/'+ documentId + '" target="_blank"><i class="fas fa-eye"></i> Consulter</a>');
|
||||||
element.setAttribute("data-toggle", "popover");
|
element.setAttribute("data-toggle", "popover");
|
||||||
element.addEventListener("mouseover", function() {
|
element.addEventListener("mouseover", function() {
|
||||||
element.focus();
|
element.focus();
|
||||||
@ -592,4 +592,4 @@ document.getElementById("pathBar").addEventListener("keyup", function () {
|
|||||||
searchQueued = true;
|
searchQueued = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
window.setInterval(search, 150);
|
window.setInterval(search, 300);
|
20
storage.py
20
storage.py
@ -7,6 +7,9 @@ import config
|
|||||||
|
|
||||||
class CheckSumCalculator:
|
class CheckSumCalculator:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def checksum(self, string: str):
|
def checksum(self, string: str):
|
||||||
return flask_bcrypt.generate_password_hash(string, config.bcrypt_rounds)
|
return flask_bcrypt.generate_password_hash(string, config.bcrypt_rounds)
|
||||||
|
|
||||||
@ -28,6 +31,7 @@ class User:
|
|||||||
self.username = username
|
self.username = username
|
||||||
self.hashed_password = hashed_password
|
self.hashed_password = hashed_password
|
||||||
self.admin = admin
|
self.admin = admin
|
||||||
|
self.readable_directories = set()
|
||||||
|
|
||||||
|
|
||||||
class Option:
|
class Option:
|
||||||
@ -224,7 +228,15 @@ class LocalStorage:
|
|||||||
db_users = c.fetchall()
|
db_users = c.fetchall()
|
||||||
|
|
||||||
for db_user in db_users:
|
for db_user in db_users:
|
||||||
self.cached_users[db_user[0]] = User(db_user[0], b"", bool(db_user[1]))
|
user = User(db_user[0], b"", bool(db_user[1]))
|
||||||
|
|
||||||
|
c.execute("SELECT username, directory_id FROM User_canRead_Directory WHERE username=?", (db_user[0],))
|
||||||
|
db_accesses = c.fetchall()
|
||||||
|
|
||||||
|
for db_access in db_accesses:
|
||||||
|
user.readable_directories.add(db_access[1])
|
||||||
|
|
||||||
|
self.cached_users[db_user[0]] = user
|
||||||
|
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
@ -256,6 +268,12 @@ class LocalStorage:
|
|||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
c.execute("UPDATE User SET is_admin=? WHERE username=?", (user.admin, user.username))
|
c.execute("UPDATE User SET is_admin=? WHERE username=?", (user.admin, user.username))
|
||||||
|
|
||||||
|
c.execute("DELETE FROM User_canRead_Directory WHERE username=?", (user.username, ))
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
for access in user.readable_directories:
|
||||||
|
c.execute("INSERT INTO User_canRead_Directory VALUES (?,?)", (user.username, access))
|
||||||
|
|
||||||
c.close()
|
c.close()
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">FSE Information</div>
|
<div class="card-header">Global information</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<table class="info-table table-hover table-striped">
|
<table class="info-table table-hover table-striped">
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -26,7 +26,7 @@
|
|||||||
<td><pre>{{ doc_size }}</pre></td>
|
<td><pre>{{ doc_size }}</pre></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Total index size</th>
|
<th>total index size</th>
|
||||||
<td><pre>{{ index_size }}</pre></td>
|
<td><pre>{{ index_size }}</pre></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@ -52,7 +52,7 @@
|
|||||||
<div class="card-header">Actions</div>
|
<div class="card-header">Actions</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<button class="btn btn-danger" onclick="resetAll()">
|
<button class="btn btn-danger" onclick="resetAll()">
|
||||||
<i class="fas fa-exclamation-triangle"></i> Reset elasticsearch index
|
<i class="fas fa-exclamation-triangle"></i> Reset Elasticsearch
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
{% set active_page = "directory" %}
|
{% set active_page = "directory" %}
|
||||||
|
|
||||||
{% block title %}An Excellent title{% endblock title %}
|
{% block title %}Directory list{% endblock title %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|
||||||
{# Add directory form #}
|
{# Add directory form #}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">An excellent form</div>
|
<div class="card-header">Add a directory</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|
||||||
<form method="GET" action="/directory/add">
|
<form method="GET" action="/directory/add">
|
||||||
@ -18,7 +18,7 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="text" class="form-control" placeholder="Absolute path" name="path">
|
<input type="text" class="form-control" placeholder="Absolute path" name="path">
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-success"><i class="fas fa-plus"></i> Add Directory</button>
|
<button type="submit" class="btn btn-success"><i class="fas fa-plus"></i> Add directory</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -26,16 +26,15 @@
|
|||||||
|
|
||||||
{# List of directories #}
|
{# List of directories #}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">An excellent list</div>
|
<div class="card-header">Directory list</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|
||||||
<table class="info-table table-hover table-striped">
|
<table class="info-table table-hover table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Display Name</th>
|
<th>Nom</th>
|
||||||
<th>Path</th>
|
<th>Chemin</th>
|
||||||
<th>Enabled</th>
|
<th>Activé</th>
|
||||||
<th>Last indexed</th>
|
|
||||||
<th>Action</th>
|
<th>Action</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -45,8 +44,7 @@
|
|||||||
<td>{{ directories[dir].name }}</td>
|
<td>{{ directories[dir].name }}</td>
|
||||||
<td style="word-break: break-all"><pre>{{ directories[dir].path }}</pre></td>
|
<td style="word-break: break-all"><pre>{{ directories[dir].path }}</pre></td>
|
||||||
<td><i class="far {{ "fa-check-square" if directories[dir].enabled else "fa-square" }}"></i></td>
|
<td><i class="far {{ "fa-check-square" if directories[dir].enabled else "fa-square" }}"></i></td>
|
||||||
<td>2018-02-21</td>
|
<td><a href="directory/{{ dir }}" class="btn btn-primary"><i class="fas fa-cog"></i> </a> Manage</td>
|
||||||
<td><a href="directory/{{ dir }}" class="btn btn-primary"><i class="fas fa-cog"></i> Manage</a> </td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
{% set active_page = "directory" %}
|
{% set active_page = "directory" %}
|
||||||
|
|
||||||
{% block title %}An excellent title{% endblock title %}
|
{% block title %}{{ directory.name }}{% endblock title %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
|
||||||
@ -75,7 +75,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr onclick="modifyPath()">
|
<tr onclick="modifyPath()">
|
||||||
<th style="width: 20%">Path</th>Task
|
<th style="width: 20%">Path</th>
|
||||||
<td>
|
<td>
|
||||||
<pre id="path" title="Click to update">{{ directory.path }}</pre>
|
<pre id="path" title="Click to update">{{ directory.path }}</pre>
|
||||||
</td>
|
</td>
|
||||||
@ -84,7 +84,8 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th style="width: 20%">Enabled</th>
|
<th style="width: 20%">Enabled</th>
|
||||||
<td>
|
<td>
|
||||||
<form action="/directory/{{ directory.id }}/update" style="display: inline;margin-left: 6px;">
|
<form action="/directory/{{ directory.id }}/update"
|
||||||
|
style="display: inline;margin-left: 6px;">
|
||||||
<input type="hidden" name="enabled" value="{{ "0" if directory.enabled else "1" }}">
|
<input type="hidden" name="enabled" value="{{ "0" if directory.enabled else "1" }}">
|
||||||
<button class="btn btn-sm {{ "btn-danger" if directory.enabled else "btn-success" }}">
|
<button class="btn btn-sm {{ "btn-danger" if directory.enabled else "btn-success" }}">
|
||||||
<i class="far {{ "fa-check-square" if directory.enabled else "fa-square" }}"></i>
|
<i class="far {{ "fa-check-square" if directory.enabled else "fa-square" }}"></i>
|
||||||
@ -96,7 +97,9 @@
|
|||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>Thumbnail cache size</th>
|
<th>Thumbnail cache size</th>
|
||||||
<td><pre>{{ tn_size_formatted }} ({{ tn_size }} bytes)</pre></td>
|
<td>
|
||||||
|
<pre>{{ tn_size_formatted }} ({{ tn_size }} bytes)</pre>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@ -112,7 +115,7 @@
|
|||||||
<form action="/task/add" class="p-2">
|
<form action="/task/add" class="p-2">
|
||||||
<input type="hidden" value="1" name="type">
|
<input type="hidden" value="1" name="type">
|
||||||
<input type="hidden" value="{{ directory.id }}" name="directory">
|
<input type="hidden" value="{{ directory.id }}" name="directory">
|
||||||
<button class="btn btn-primary" href="/task/" value="Generate thumbnails">
|
<button class="btn btn-primary" href="/task/">
|
||||||
<i class="fas fa-book"></i> Generate index
|
<i class="fas fa-book"></i> Generate index
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@ -120,7 +123,7 @@
|
|||||||
<form action="/task/add" class="p-2">
|
<form action="/task/add" class="p-2">
|
||||||
<input type="hidden" value="2" name="type">
|
<input type="hidden" value="2" name="type">
|
||||||
<input type="hidden" value="{{ directory.id }}" name="directory">
|
<input type="hidden" value="{{ directory.id }}" name="directory">
|
||||||
<button class="btn btn-primary" href="/task/" value="Generate thumbnails">
|
<button class="btn btn-primary" href="/task/">
|
||||||
<i class="far fa-images"></i> Generate thumbnails
|
<i class="far fa-images"></i> Generate thumbnails
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@ -140,7 +143,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">Options <a href="#" style="float:right">Learn more <i class="fas fa-external-link-alt"></i></a></div>
|
<div class="card-header">Options <a href="#" style="float:right">Learn more <i
|
||||||
|
class="fas fa-external-link-alt"></i></a></div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<table class="info-table table-striped table-hover">
|
<table class="info-table table-striped table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
@ -154,7 +158,9 @@
|
|||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td style="width: 30%"><span>{{ option.key }}</span></td>
|
<td style="width: 30%"><span>{{ option.key }}</span></td>
|
||||||
<td onclick="modifyVal({{ option.id }}, '{{ option.key }}')" title="Click to update"><pre id="val-{{ option.id }}">{{ option.value }}</pre></td>
|
<td onclick="modifyVal({{ option.id }}, '{{ option.key }}')" title="Click to update">
|
||||||
|
<pre id="val-{{ option.id }}">{{ option.value }}</pre>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<h3>Raw json</h3>
|
<h3>Raw JSON</h3>
|
||||||
<textarea class="form-control" style="min-height: 100px" readonly>{{ doc | tojson }}</textarea>
|
<textarea class="form-control" style="min-height: 100px" readonly>{{ doc | tojson }}</textarea>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
@ -1,71 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Title</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.fit:hover {
|
|
||||||
-webkit-transform:scale(2.5);
|
|
||||||
-moz-transform:scale(2.5);
|
|
||||||
-ms-transform:scale(2.5);
|
|
||||||
-o-transform:scale(2.5);
|
|
||||||
transform:scale(2.5);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.fit {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-container{
|
|
||||||
width: 305px;
|
|
||||||
height: 300px;
|
|
||||||
background-color: #ccc;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.doc-container {
|
|
||||||
height: 330px;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doc-caption {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="photos">
|
|
||||||
|
|
||||||
{% for doc in docs %}
|
|
||||||
{% if doc.type == "audio" %}
|
|
||||||
<div class="image-container">
|
|
||||||
<audio controls class="fit">
|
|
||||||
<!--<source src="files/{{doc.doc_id}}">-->
|
|
||||||
</audio>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<a href="/files/{{doc.doc_id}}">
|
|
||||||
<div class="doc-container">
|
|
||||||
<div class="image-container">
|
|
||||||
<img class="fit" src="/thumbs/{{doc.doc_id}}">
|
|
||||||
</div>
|
|
||||||
<span class="doc-caption" style="font-size: 8pt">{{doc.name}}</span>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
|
|
||||||
|
|
||||||
</html>
|
|
||||||
|
|
@ -34,11 +34,13 @@
|
|||||||
<label for="directories" >Search in directories</label>
|
<label for="directories" >Search in directories</label>
|
||||||
|
|
||||||
<select class="custom-select" id="directories" multiple size="6">
|
<select class="custom-select" id="directories" multiple size="6">
|
||||||
{% for dir_id in directories %}
|
{% if directories | length > 0%}
|
||||||
{% if directories[dir_id].enabled %}
|
{% for dir in directories %}
|
||||||
<option selected value="{{ directories[dir_id].id }}">{{ directories[dir_id].name }}</option>
|
<option selected value="{{ dir.id }}">{{ dir.name }}</option>
|
||||||
{% endif %}
|
{% endfor %}
|
||||||
{% endfor %}
|
{% else %}
|
||||||
|
<option disabled>There are no active directories which you have access to</option>
|
||||||
|
{% endif %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
{% set active_page = "task" %}
|
{% set active_page = "task" %}
|
||||||
|
|
||||||
{% block title %}An excellent title{% endblock title %}
|
{% block title %}Tasks{% endblock title %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
|
||||||
@ -49,7 +49,7 @@
|
|||||||
<select title="Select task type" class="form-control" id="type" name="type">
|
<select title="Select task type" class="form-control" id="type" name="type">
|
||||||
<option hidden>Create task...</option>
|
<option hidden>Create task...</option>
|
||||||
<option value="1">Indexing</option>
|
<option value="1">Indexing</option>
|
||||||
<option value="2">Thumbnail Generation</option>
|
<option value="2">Thumnail Generation</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select title="Select directory" class="form-control" id="directory" name="directory" >
|
<select title="Select directory" class="form-control" id="directory" name="directory" >
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
|
|
||||||
{% set active_page = "user" %}
|
{% set active_page = "user" %}
|
||||||
|
{% block title %}User list{% endblock title %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
|
||||||
<div class="container">
|
<div class="container"> <div class="card">
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">Create user</div>
|
<div class="card-header">Create user</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|
||||||
|
{% if not admin_account_present %}
|
||||||
|
<p>This page is unlocked because there are no admin accounts</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<form method="POST" action="/user/add">
|
<form method="POST" action="/user/add">
|
||||||
|
|
||||||
<div class="input-group form-group">
|
<div class="input-group form-group">
|
||||||
@ -24,7 +28,7 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="password" class="form-control" placeholder="Password" name="password">
|
<input type="password" class="form-control" placeholder="Password" name="password">
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-success"><i class="fas fa-plus"></i> Add User</button>
|
<button type="submit" class="btn btn-success"><i class="fas fa-plus"></i> Add user</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -39,12 +43,10 @@
|
|||||||
<th>User</th>
|
<th>User</th>
|
||||||
<th>Admin</th>
|
<th>Admin</th>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr> </thead>
|
||||||
</thead>
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for user in users %}
|
{% for user in users %} <tr>
|
||||||
<tr>
|
<td style="width: 80%;">{% if session["username"] == user %}<b>{{ user }}{% else %}{{ user }}{% endif %}</b></td>
|
||||||
<td>{{ user }}</td>
|
|
||||||
<td><i class="far {{ "fa-check-square" if users[user].admin else "fa-square" }}"></i></td>
|
<td><i class="far {{ "fa-check-square" if users[user].admin else "fa-square" }}"></i></td>
|
||||||
<td><a href="/user/{{ user }}" class="btn btn-primary">Manage</a></td>
|
<td><a href="/user/{{ user }}" class="btn btn-primary">Manage</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -1,33 +1,71 @@
|
|||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
|
|
||||||
|
{% set active_page = "user" %}
|
||||||
|
{% block title %}Gestion utilisateur{% endblock title %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">Directory permissions</div>
|
<div class="card-header">Manage permissions of <strong>{{ user.username }}</strong></div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h5>Admin: </h5>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<form action="/user/{{ user.username }}/set_admin" style="display: inline;margin-left: 6px;">
|
||||||
|
<input type="hidden" name="admin" value="{{ "0" if user.admin else "1" }}">
|
||||||
|
<button class="btn btn-sm {{ "btn-danger" if user.admin else "btn-success" }}">
|
||||||
|
<i class="far {{ "fa-check-square" if user.admin else "fa-square" }}"></i>
|
||||||
|
{{ "Remove admin" if user.admin else "Set admin" }}
|
||||||
|
</button>
|
||||||
|
<input type="hidden" name="dir_id" value="{{ dir_id }}">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
<table class="info-table table-hover table-striped">
|
<table class="info-table table-hover table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Directory</th>
|
<th>Directory</th>
|
||||||
<th>Search access</th>
|
<th>Access</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for user in users %}
|
{% for dir_id in directories %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ user }}</td>
|
<td>{{ directories[dir_id].name }}</td>
|
||||||
<td><i class="far {{ "fa-check-square" if users[user].admin else "fa-square" }}"></i></td>
|
<td><form action="/user/{{ user.username }}/set_access" style="display: inline;margin-left: 6px;">
|
||||||
<td><a href="/user/{{ user }}" class="btn btn-primary">Manage</a></td>
|
<input type="hidden" name="access" value="{{ "0" if dir_id in user.readable_directories else "1" }}">
|
||||||
|
<button class="btn btn-sm {{ "btn-danger" if dir_id in user.readable_directories else "btn-success" }}">
|
||||||
|
<i class="far {{ "fa-check-square" if dir_id in user.readable_directories else "fa-square" }}"></i>
|
||||||
|
{{ "Disable" if dir_id in user.readable_directories else "Enable" }}
|
||||||
|
</button>
|
||||||
|
<input type="hidden" name="dir_id" value="{{ dir_id }}">
|
||||||
|
</form></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<button class="btn btn-danger" onclick="userDelete()">Delete account</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function userDelete() {
|
||||||
|
if (confirm("Are you sure?")) {
|
||||||
|
window.location = "/user/{{ user.username }}/del"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock body %}
|
{% endblock body %}
|
@ -12,7 +12,7 @@ class MimeGuesserTest(TestCase):
|
|||||||
guesser = ContentMimeGuesser()
|
guesser = ContentMimeGuesser()
|
||||||
|
|
||||||
self.assertEqual("text/x-shellscript", guesser.guess_mime(dir_name + "/test_folder/test_utf8.sh"))
|
self.assertEqual("text/x-shellscript", guesser.guess_mime(dir_name + "/test_folder/test_utf8.sh"))
|
||||||
self.assertEqual("text/plain", guesser.guess_mime(dir_name + "/test_folder/more_books.json"))
|
self.assertTrue(guesser.guess_mime(dir_name + "/test_folder/more_books.json") in ["application/json", "text/plain"])
|
||||||
self.assertEqual("application/java-archive", guesser.guess_mime(dir_name + "/test_folder/post.jar"))
|
self.assertEqual("application/java-archive", guesser.guess_mime(dir_name + "/test_folder/post.jar"))
|
||||||
self.assertEqual("image/jpeg", guesser.guess_mime(dir_name + "/test_folder/sample_1.jpg"))
|
self.assertEqual("image/jpeg", guesser.guess_mime(dir_name + "/test_folder/sample_1.jpg"))
|
||||||
|
|
||||||
@ -20,7 +20,8 @@ class MimeGuesserTest(TestCase):
|
|||||||
|
|
||||||
guesser = ExtensionMimeGuesser()
|
guesser = ExtensionMimeGuesser()
|
||||||
|
|
||||||
self.assertEqual("text/x-sh", guesser.guess_mime(dir_name + "/test_folder/test_utf8.sh"))
|
self.assertTrue(guesser.guess_mime(dir_name + "/test_folder/test_utf8.sh") in ["text/x-sh", "application/x-sh"])
|
||||||
self.assertEqual("application/json", guesser.guess_mime(dir_name + "/test_folder/more_books.json"))
|
self.assertEqual("application/json", guesser.guess_mime(dir_name + "/test_folder/more_books.json"))
|
||||||
self.assertEqual("application/java-archive", guesser.guess_mime(dir_name + "/test_folder/post.jar"))
|
self.assertTrue(guesser.guess_mime(dir_name + "/test_folder/post.jar")
|
||||||
|
in ["application/java-archive", "application/x-java-archive"])
|
||||||
self.assertEqual("image/jpeg", guesser.guess_mime(dir_name + "/test_folder/sample_1.jpg"))
|
self.assertEqual("image/jpeg", guesser.guess_mime(dir_name + "/test_folder/sample_1.jpg"))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user