mirror of
https://github.com/janeczku/calibre-web
synced 2024-11-28 04:19:59 +00:00
Started implement server side filechooser
This commit is contained in:
parent
983e3b2274
commit
2508c1abb2
86
cps/admin.py
86
cps/admin.py
@ -5,7 +5,7 @@
|
||||
# andy29485, idalin, Kyosfonica, wuqi, Kennyl, lemmsh,
|
||||
# falgh1, grunjol, csitko, ytils, xybydy, trasba, vrabe,
|
||||
# ruben-herold, marblepebble, JackED42, SiphonSquirrel,
|
||||
# apetresc, nanu-c, mutschler
|
||||
# apetresc, nanu-c, mutschler, GammaC0de, vuolter
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -26,6 +26,7 @@ import re
|
||||
import base64
|
||||
import json
|
||||
import time
|
||||
import operator
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from babel import Locale as LC
|
||||
@ -520,6 +521,89 @@ def list_restriction(res_type):
|
||||
return response
|
||||
|
||||
|
||||
@admi.route("/ajax/pathchooser/", endpoint="pathchooser")
|
||||
@admi.route("/ajax/filechooser/", endpoint="filechooser")
|
||||
@login_required
|
||||
@admin_required
|
||||
def pathchooser():
|
||||
browse_for = "folder" if request.endpoint == "admin.pathchooser" else "file"
|
||||
path = os.path.normpath(request.args.get('path', ""))
|
||||
|
||||
if os.path.isfile(path):
|
||||
oldfile = path
|
||||
path = os.path.dirname(path)
|
||||
else:
|
||||
oldfile = ""
|
||||
|
||||
abs = False
|
||||
|
||||
if os.path.isdir(path):
|
||||
if os.path.isabs(path):
|
||||
cwd = os.path.realpath(path)
|
||||
abs = True
|
||||
else:
|
||||
cwd = os.path.relpath(path)
|
||||
else:
|
||||
cwd = os.getcwd()
|
||||
|
||||
cwd = os.path.normpath(os.path.realpath(cwd))
|
||||
parentdir = os.path.dirname(cwd)
|
||||
if not abs:
|
||||
if os.path.realpath(cwd) == os.path.realpath("/"):
|
||||
cwd = os.path.relpath(cwd)
|
||||
else:
|
||||
cwd = os.path.relpath(cwd) + os.path.sep
|
||||
parentdir = os.path.relpath(parentdir) + os.path.sep
|
||||
|
||||
if os.path.realpath(cwd) == os.path.realpath("/"):
|
||||
parentdir = ""
|
||||
|
||||
try:
|
||||
folders = os.listdir(cwd)
|
||||
except Exception:
|
||||
folders = []
|
||||
|
||||
files = []
|
||||
locale = get_locale()
|
||||
for f in folders:
|
||||
try:
|
||||
data = {"name": f, "fullpath": os.path.join(cwd, f)}
|
||||
data["sort"] = data["fullpath"].lower()
|
||||
data["modified"] = format_datetime(datetime.fromtimestamp(int(os.path.getmtime(os.path.join(cwd, f)))),
|
||||
format='short', locale=locale)
|
||||
data["ext"] = os.path.splitext(f)[1]
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
if os.path.isfile(os.path.join(cwd, f)):
|
||||
data["type"] = "file"
|
||||
data["size"] = os.path.getsize(os.path.join(cwd, f))
|
||||
|
||||
power = 0
|
||||
while (data["size"] >> 10) > 0.3:
|
||||
power += 1
|
||||
data["size"] >>= 10
|
||||
units = ("", "K", "M", "G", "T")
|
||||
data["size"] = str(data["size"]) + " " + units[power] + "Byte"
|
||||
else:
|
||||
data["type"] = "dir"
|
||||
data["size"] = ""
|
||||
|
||||
files.append(data)
|
||||
|
||||
files = sorted(files, key=operator.itemgetter("type", "sort"))
|
||||
|
||||
context = {
|
||||
"cwd": cwd,
|
||||
"files": files,
|
||||
"parentdir": parentdir,
|
||||
"type": browse_for,
|
||||
"oldfile": oldfile,
|
||||
"absolute": abs,
|
||||
}
|
||||
return json.dumps(context)
|
||||
|
||||
|
||||
@admi.route("/config", methods=["GET", "POST"])
|
||||
@unconfigured
|
||||
def basic_configuration():
|
||||
|
@ -37,7 +37,7 @@ from flask_login import login_required
|
||||
from . import logger, gdriveutils, config, ub, calibre_db
|
||||
from .web import admin_required
|
||||
|
||||
gdrive = Blueprint('gdrive', __name__)
|
||||
gdrive = Blueprint('gdrive', __name__, url_prefix='/gdrive')
|
||||
log = logger.create()
|
||||
|
||||
try:
|
||||
@ -50,7 +50,7 @@ current_milli_time = lambda: int(round(time() * 1000))
|
||||
gdrive_watch_callback_token = 'target=calibreweb-watch_files'
|
||||
|
||||
|
||||
@gdrive.route("/gdrive/authenticate")
|
||||
@gdrive.route("/authenticate")
|
||||
@login_required
|
||||
@admin_required
|
||||
def authenticate_google_drive():
|
||||
@ -63,7 +63,7 @@ def authenticate_google_drive():
|
||||
return redirect(authUrl)
|
||||
|
||||
|
||||
@gdrive.route("/gdrive/callback")
|
||||
@gdrive.route("/callback")
|
||||
def google_drive_callback():
|
||||
auth_code = request.args.get('code')
|
||||
if not auth_code:
|
||||
@ -77,19 +77,14 @@ def google_drive_callback():
|
||||
return redirect(url_for('admin.configuration'))
|
||||
|
||||
|
||||
@gdrive.route("/gdrive/watch/subscribe")
|
||||
@gdrive.route("/watch/subscribe")
|
||||
@login_required
|
||||
@admin_required
|
||||
def watch_gdrive():
|
||||
if not config.config_google_drive_watch_changes_response:
|
||||
with open(gdriveutils.CLIENT_SECRETS, 'r') as settings:
|
||||
filedata = json.load(settings)
|
||||
# ToDo: Easier: rstrip('/')
|
||||
if filedata['web']['redirect_uris'][0].endswith('/'):
|
||||
filedata['web']['redirect_uris'][0] = filedata['web']['redirect_uris'][0][:-((len('/gdrive/callback')+1))]
|
||||
else:
|
||||
filedata['web']['redirect_uris'][0] = filedata['web']['redirect_uris'][0][:-(len('/gdrive/callback'))]
|
||||
address = '%s/gdrive/watch/callback' % filedata['web']['redirect_uris'][0]
|
||||
address = filedata['web']['redirect_uris'][0].rstrip('/').replace('/gdrive/callback', '/gdrive/watch/callback')
|
||||
notification_id = str(uuid4())
|
||||
try:
|
||||
result = gdriveutils.watchChange(gdriveutils.Gdrive.Instance().drive, notification_id,
|
||||
@ -99,14 +94,15 @@ def watch_gdrive():
|
||||
except HttpError as e:
|
||||
reason=json.loads(e.content)['error']['errors'][0]
|
||||
if reason['reason'] == u'push.webhookUrlUnauthorized':
|
||||
flash(_(u'Callback domain is not verified, please follow steps to verify domain in google developer console'), category="error")
|
||||
flash(_(u'Callback domain is not verified, '
|
||||
u'please follow steps to verify domain in google developer console'), category="error")
|
||||
else:
|
||||
flash(reason['message'], category="error")
|
||||
|
||||
return redirect(url_for('admin.configuration'))
|
||||
|
||||
|
||||
@gdrive.route("/gdrive/watch/revoke")
|
||||
@gdrive.route("/watch/revoke")
|
||||
@login_required
|
||||
@admin_required
|
||||
def revoke_watch_gdrive():
|
||||
@ -122,14 +118,14 @@ def revoke_watch_gdrive():
|
||||
return redirect(url_for('admin.configuration'))
|
||||
|
||||
|
||||
@gdrive.route("/gdrive/watch/callback", methods=['GET', 'POST'])
|
||||
@gdrive.route("/watch/callback", methods=['GET', 'POST'])
|
||||
def on_received_watch_confirmation():
|
||||
if not config.config_google_drive_watch_changes_response:
|
||||
return ''
|
||||
if request.headers.get('X-Goog-Channel-Token') != gdrive_watch_callback_token \
|
||||
or request.headers.get('X-Goog-Resource-State') != 'change' \
|
||||
or not request.data:
|
||||
return redirect(url_for('admin.configuration'))
|
||||
return '' # redirect(url_for('admin.configuration'))
|
||||
|
||||
log.debug('%r', request.headers)
|
||||
log.debug('%r', request.data)
|
||||
|
@ -213,6 +213,45 @@ $(function() {
|
||||
});
|
||||
}
|
||||
|
||||
function fillFileTable(path, type) {
|
||||
if (type === "dir") {
|
||||
var request_path = "/../../ajax/pathchooser/";
|
||||
} else {
|
||||
var request_path = "/../../ajax/filechooser/";
|
||||
}
|
||||
$.ajax({
|
||||
dataType: "json",
|
||||
data: {
|
||||
path: path,
|
||||
},
|
||||
url: window.location.pathname + request_path,
|
||||
success: function success(data) {
|
||||
$("#file_table > tbody > tr").each(function () {
|
||||
if ($(this).attr("id") !== "parent") {
|
||||
$(this).closest("tr").remove();
|
||||
}
|
||||
});
|
||||
if (data.parentdir !== "") {
|
||||
$("#parent").removeClass('hidden')
|
||||
} else {
|
||||
$("#parent").addClass('hidden')
|
||||
}
|
||||
// console.log(data);
|
||||
data.files.forEach(function(entry) {
|
||||
if(entry.type === "dir") {
|
||||
var type = "<span class=\"glyphicon glyphicon-folder-close\"></span>";
|
||||
} else {
|
||||
var type = "";
|
||||
}
|
||||
$("<tr class=\"tr-clickable\" data-type=\"" + entry.type + "\" data-path=\"" +
|
||||
entry.fullpath + "\"><td>" + type + "</td><td>" + entry.name + "</td><td>" +
|
||||
entry.size + "</td></tr>").appendTo($("#file_table"));
|
||||
});
|
||||
},
|
||||
timeout: 2000
|
||||
});
|
||||
}
|
||||
|
||||
$(".discover .row").isotope({
|
||||
// options
|
||||
itemSelector : ".book",
|
||||
@ -402,6 +441,81 @@ $(function() {
|
||||
$("#config_delete_kobo_token").show();
|
||||
});
|
||||
|
||||
|
||||
|
||||
$("#fileModal").on("show.bs.modal", function(e) {
|
||||
//get data-id attribute of the clicked element and store in button
|
||||
//var submit = true;
|
||||
//var cwd = "{{oldfile|default(cwd, True)|abspath|replace('\\', '\\\\')}}";
|
||||
//var isabsolute = true;
|
||||
fillFileTable("","dir");
|
||||
});
|
||||
|
||||
//(".tr-clickable").on("click",
|
||||
$(document).on("click", ".tr-clickable", function() {
|
||||
var path = this.attributes['data-path'].value;
|
||||
var type = this.attributes['data-type'].value;
|
||||
fillFileTable(path, type);
|
||||
});
|
||||
|
||||
|
||||
/*{% if type == 'folder' %} {# browsing for folder #}
|
||||
var abspath = "{{url_for('app.pathchooser') + '?path=' + cwd|abspath|quote_plus}}";
|
||||
var relpath = "{{url_for('app.pathchooser') + '?path=' + cwd|relpath|quote_plus}}";
|
||||
{% else %} {# browsing for file #}
|
||||
var abspath = "{{url_for('app.filechooser') + '?path=' + oldfile|default(cwd, True)|abspath|quote_plus}}";
|
||||
var relpath = "{{url_for('app.filechooser') + '?path=' + oldfile|default(cwd, True)|relpath|quote_plus}}";
|
||||
{% endif %}*/
|
||||
/*document.addEventListener("readystatechange", function(event) {
|
||||
if (this.readyState === "complete") {
|
||||
document.getElementById("tbody").style.height = (window.innerHeight - 25) + "px";
|
||||
window.onresize = function (event) {
|
||||
document.getElementById("tbody").style.height = (window.innerHeight - 25) + "px";
|
||||
};
|
||||
var clickables = document.getElementsByClassName("tr-clickable");
|
||||
for (var i = 0; i < clickables.length; i++) {
|
||||
clickables[i].onclick = (function () {
|
||||
var onclick = clickables[i].onclick;
|
||||
return function (e) {
|
||||
if (onclick != null && !onclick()) {
|
||||
return false
|
||||
}
|
||||
if (this.dataset.href !== undefined && this.dataset.href !== "#") {
|
||||
window.location.href = this.dataset.href;
|
||||
return false
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
})();
|
||||
}
|
||||
}
|
||||
});
|
||||
function updateParent()
|
||||
{
|
||||
if (window.top.SettingsUI !== undefined) {
|
||||
window.top.SettingsUI.prototype.pathchooserChanged(this);
|
||||
}
|
||||
}
|
||||
function setInvalid() {
|
||||
submit = false;
|
||||
cwd = "";
|
||||
updateParent();
|
||||
}
|
||||
function setValid() {
|
||||
submit = true;
|
||||
updateParent();
|
||||
}
|
||||
function setFile(fullpath, name)
|
||||
{
|
||||
cwd = fullpath;
|
||||
/*{*% if type == "file" %} {# browsing for file #}
|
||||
abspath = "{{url_for('app.filechooser')}}?path={{cwd|abspath|quote_plus}}" + encodeURIComponent(name);
|
||||
relpath = "{{url_for('app.filechooser')}}?path={{cwd|relpath|quote_plus}}" + encodeURIComponent(name);
|
||||
{% endif %}*/
|
||||
/*setValid();
|
||||
}*/
|
||||
|
||||
$("#btndeletetoken").click(function() {
|
||||
//get data-id attribute of the clicked element
|
||||
var pathname = document.getElementsByTagName("script"), src = pathname[pathname.length - 1].src;
|
||||
|
@ -384,7 +384,7 @@
|
||||
<div class="form-group input-group">
|
||||
<input type="text" class="form-control" id="config_converterpath" name="config_converterpath" value="{% if config.config_converterpath != None %}{{ config.config_converterpath }}{% endif %}" autocomplete="off">
|
||||
<span class="input-group-btn">
|
||||
<button type="button" id="converter_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
|
||||
<button type="button" id="converter_modal_path" data-toggle="modal" data-target="#fileModal" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@ -412,8 +412,6 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-sm-12">
|
||||
{% if not show_login_button %}
|
||||
<button type="submit" name="submit" class="btn btn-default">{{_('Save')}}</button>
|
||||
@ -428,6 +426,9 @@
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block modal %}
|
||||
{{ filechooser_modal() }}
|
||||
{% endblock %}
|
||||
{% block js %}
|
||||
<script type="text/javascript">
|
||||
$(document).on('change', '#config_use_google_drive', function() {
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% from 'modal_dialogs.html' import restrict_modal, delete_book %}
|
||||
{% from 'modal_dialogs.html' import restrict_modal, delete_book, filechooser_modal %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ g.user.locale }}">
|
||||
<head>
|
||||
|
@ -68,3 +68,36 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{% macro filechooser_modal() %}
|
||||
<div class="modal fade" id="fileModal" role="dialog" aria-labelledby="metafileLabel">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-info text-center">
|
||||
<span>{{_('Choose File Location')}}</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<table id="file_table" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{_('type')}}</th>
|
||||
<th>{{_('name')}}</th>
|
||||
<th>{{_('size')}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tbody">
|
||||
<tr class="tr-clickable hidden" id="parent" data-type="dir" data-path="..">
|
||||
<td><span class="glyphicon glyphicon-folder-close"></span></td>
|
||||
<td title="{{_('Parent Directory')}}"><span class="parentdir">..</span></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<input type="button" class="btn btn-primary" value="{{_('Select')}}" name="file_confirm" id="file_confirm" data-dismiss="modal">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{_('Cancel')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
Loading…
Reference in New Issue
Block a user