tg-prepare 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tg-prepare might be problematic. Click here for more details.
- tg_prepare-1.0.0.dist-info/METADATA +19 -0
- tg_prepare-1.0.0.dist-info/RECORD +46 -0
- tg_prepare-1.0.0.dist-info/WHEEL +5 -0
- tg_prepare-1.0.0.dist-info/entry_points.txt +3 -0
- tg_prepare-1.0.0.dist-info/licenses/LICENSE +202 -0
- tg_prepare-1.0.0.dist-info/top_level.txt +2 -0
- tgp_backend/__init__.py +18 -0
- tgp_backend/auth.py +71 -0
- tgp_backend/cli.py +22 -0
- tgp_backend/directories.py +85 -0
- tgp_backend/interfaces.py +3 -0
- tgp_backend/nextcloud.py +125 -0
- tgp_backend/project.py +267 -0
- tgp_backend/tgclient.py +92 -0
- tgp_backend/util.py +83 -0
- tgp_ui/__init__.py +0 -0
- tgp_ui/app.py +424 -0
- tgp_ui/static/css/bootstrap-icons.min.css +5 -0
- tgp_ui/static/css/bootstrap.min.css +7 -0
- tgp_ui/static/css/main.css +146 -0
- tgp_ui/static/css/simpleXML.css +67 -0
- tgp_ui/static/js/bootstrap.bundle.min.js +7 -0
- tgp_ui/static/js/jquery.min.js +2 -0
- tgp_ui/static/js/jstree.min.js +3 -0
- tgp_ui/static/js/main.js +454 -0
- tgp_ui/static/js/simpleXML.js +193 -0
- tgp_ui/templates/collection.html +194 -0
- tgp_ui/templates/empty.html +4 -0
- tgp_ui/templates/file_tree.html +39 -0
- tgp_ui/templates/file_upload.html +24 -0
- tgp_ui/templates/jsfiler.html +6 -0
- tgp_ui/templates/layout.html +59 -0
- tgp_ui/templates/layout2.html +99 -0
- tgp_ui/templates/macros.html +91 -0
- tgp_ui/templates/main.html +5 -0
- tgp_ui/templates/nxc_file_tree.html +33 -0
- tgp_ui/templates/nxc_login.html +25 -0
- tgp_ui/templates/nxc_tab.html +15 -0
- tgp_ui/templates/project.html +25 -0
- tgp_ui/templates/project_nxc.html +37 -0
- tgp_ui/templates/projects.html +73 -0
- tgp_ui/templates/publish.html +120 -0
- tgp_ui/templates/storage.html +49 -0
- tgp_ui/templates/tei_browser.html +14 -0
- tgp_ui/templates/tei_explorer.html +48 -0
- tgp_ui/templates/xpath_parser_modal_content.html +37 -0
tgp_ui/app.py
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Copyright (C) 2023-2025 TU-Dresden (ZIH)
|
|
3
|
+
# ralf.klammer@tu-dresden.de
|
|
4
|
+
# moritz.wilhelm@tu-dresden.de
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
import click # type: ignore
|
|
9
|
+
import os
|
|
10
|
+
import shutil
|
|
11
|
+
|
|
12
|
+
from json import loads
|
|
13
|
+
from flask import Flask, render_template, request, redirect, session # type: ignore
|
|
14
|
+
from flask import url_for as default_url_for # type: ignore
|
|
15
|
+
from flask_json import json_response, FlaskJSON # type: ignore
|
|
16
|
+
|
|
17
|
+
from tgp_backend.directories import generateList
|
|
18
|
+
from tgp_backend.project import Project, TGProject
|
|
19
|
+
|
|
20
|
+
from tgp_backend.auth import SecretKeyManager
|
|
21
|
+
from tgp_backend.nextcloud import Nextcloud
|
|
22
|
+
from tgp_backend.util import config
|
|
23
|
+
from tgp_backend.util import remove_empty_strings_from_dict
|
|
24
|
+
|
|
25
|
+
from tg_model.tei import TEIParser # type: ignore
|
|
26
|
+
|
|
27
|
+
log = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
base_params = {
|
|
30
|
+
"title": "TG Prepare",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
MAIN_PATH = config("main", "path", default="./projects")
|
|
34
|
+
|
|
35
|
+
app = Flask(__name__)
|
|
36
|
+
FlaskJSON(app)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
secret_manager = SecretKeyManager(MAIN_PATH)
|
|
40
|
+
app.secret_key = secret_manager.secret_key
|
|
41
|
+
|
|
42
|
+
# Additional security settings, if not in DEBUG mode
|
|
43
|
+
app.config.update(SESSION_COOKIE_NAME="tgp_nextcloud_session")
|
|
44
|
+
if config("log", "level", default="DEBUG") != "DEBUG":
|
|
45
|
+
app.config.update(
|
|
46
|
+
SESSION_COOKIE_SECURE=True, # Only allow cookies over HTTPS
|
|
47
|
+
SESSION_COOKIE_HTTPONLY=True, # Prevent JavaScript access to cookies
|
|
48
|
+
SESSION_COOKIE_SAMESITE="Strict", # Protect against CSRF attacks
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
app.jinja_env.globals.update(len=len, round=round)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# Function to retrieve the prefix from headers, used for reverse proxy setups
|
|
55
|
+
def get_prefix():
|
|
56
|
+
return request.headers.get(
|
|
57
|
+
"X-Forwarded-Prefix", request.headers.get("X-Script-Name", "")
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# Custom implementation of Flask's url_for to include the prefix
|
|
62
|
+
def url_for(*args, **kwargs):
|
|
63
|
+
"""Overrides Flask's url_for globally to include the prefix"""
|
|
64
|
+
return get_prefix() + default_url_for(*args, **kwargs)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# Context processor to inject the custom url_for into the Jinja2 templates
|
|
68
|
+
@app.context_processor
|
|
69
|
+
def inject_url_for():
|
|
70
|
+
return dict(url_for=url_for)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_projects():
|
|
74
|
+
projects = []
|
|
75
|
+
for sub in os.listdir(MAIN_PATH):
|
|
76
|
+
projectpath = "%s/%s" % (MAIN_PATH, sub)
|
|
77
|
+
if os.path.isdir(projectpath):
|
|
78
|
+
projects.append(Project(sub))
|
|
79
|
+
return projects
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
app.jinja_env.globals.update(title="TG Prepare", get_projects=get_projects)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _startup():
|
|
86
|
+
|
|
87
|
+
# Create the projects directory if it does not exist
|
|
88
|
+
if not os.path.exists(MAIN_PATH):
|
|
89
|
+
os.makedirs(MAIN_PATH)
|
|
90
|
+
|
|
91
|
+
logging.getLogger("zeep").setLevel(logging.INFO)
|
|
92
|
+
app.run(
|
|
93
|
+
host=config("main", "host", default="0.0.0.0"),
|
|
94
|
+
port=config("main", "port", default=8077),
|
|
95
|
+
debug=config("log", "level", default="DEBUG") == "DEBUG",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@click.command()
|
|
100
|
+
@click.option("--path", "-p", default=None)
|
|
101
|
+
def startup(path):
|
|
102
|
+
base_params["path"] = path if path else os.getcwd()
|
|
103
|
+
_startup()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# *****************
|
|
107
|
+
# VIEWS
|
|
108
|
+
# *****************
|
|
109
|
+
@app.route(
|
|
110
|
+
"/sidebar",
|
|
111
|
+
methods=["GET", "POST"],
|
|
112
|
+
)
|
|
113
|
+
def sidebar():
|
|
114
|
+
|
|
115
|
+
return render_template(
|
|
116
|
+
"layout2.html",
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@app.route(
|
|
121
|
+
"/xpath_parser_modal/<string:projectname>/<string:title>",
|
|
122
|
+
methods=["GET", "POST"],
|
|
123
|
+
)
|
|
124
|
+
def xpath_parser_modal(projectname=None, title=None):
|
|
125
|
+
project = Project(projectname)
|
|
126
|
+
collection = project.get_collection(title)
|
|
127
|
+
# collection_config = collection["config"]
|
|
128
|
+
collection_parser = collection["parser"]
|
|
129
|
+
|
|
130
|
+
return render_template(
|
|
131
|
+
"xpath_parser_modal_content.html",
|
|
132
|
+
# collection=collection_config,
|
|
133
|
+
collection_parser=collection_parser,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@app.route("/new_project", methods=["POST"])
|
|
138
|
+
def new_project():
|
|
139
|
+
project = Project(request.form.get("projectname"))
|
|
140
|
+
project.create()
|
|
141
|
+
return redirect(url_for("project", projectname=project.name))
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@app.route("/upload_files/<string:projectname>", methods=["POST"])
|
|
145
|
+
def upload_files(projectname=None):
|
|
146
|
+
Project(projectname).file_upload(request.files.getlist("files"))
|
|
147
|
+
|
|
148
|
+
return redirect(url_for("project", projectname=projectname))
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@app.route(
|
|
152
|
+
"/collection/<string:projectname>/<string:name>", methods=["GET", "POST"]
|
|
153
|
+
)
|
|
154
|
+
def collection(projectname, name):
|
|
155
|
+
project = Project(projectname)
|
|
156
|
+
collection = project.get_collection(name)
|
|
157
|
+
return render_template(
|
|
158
|
+
"collection.html",
|
|
159
|
+
project=project,
|
|
160
|
+
collection_title=name,
|
|
161
|
+
collection=collection["config"],
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@app.route("/project/<string:projectname>", methods=["GET", "POST"])
|
|
166
|
+
def project(projectname=None):
|
|
167
|
+
params = {
|
|
168
|
+
"sub_title": "Project: %s" % projectname,
|
|
169
|
+
"sub_description": "",
|
|
170
|
+
}
|
|
171
|
+
params.update(base_params)
|
|
172
|
+
|
|
173
|
+
return render_template(
|
|
174
|
+
"project.html",
|
|
175
|
+
user=session.get("user", "-"),
|
|
176
|
+
current_project=projectname,
|
|
177
|
+
project=Project(projectname),
|
|
178
|
+
tab=request.args.get("tab"),
|
|
179
|
+
**params,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@app.route("/", methods=["GET"])
|
|
184
|
+
@app.route("/project", methods=["GET", "POST"])
|
|
185
|
+
@app.route("/projects", methods=["GET", "POST"])
|
|
186
|
+
def projects():
|
|
187
|
+
params = {
|
|
188
|
+
"sub_title": "Projects",
|
|
189
|
+
"sub_description": "",
|
|
190
|
+
}
|
|
191
|
+
params.update(base_params)
|
|
192
|
+
|
|
193
|
+
projectname = request.form.get("projectname")
|
|
194
|
+
fullpath = f"{MAIN_PATH}/{projectname}"
|
|
195
|
+
|
|
196
|
+
if request.method == "POST":
|
|
197
|
+
if request.form.get("delete"):
|
|
198
|
+
# Delete the project
|
|
199
|
+
if fullpath.strip("/") == MAIN_PATH.strip("/"):
|
|
200
|
+
log.error(f"Cannot delete main path ({MAIN_PATH})!")
|
|
201
|
+
elif os.path.exists(fullpath):
|
|
202
|
+
shutil.rmtree(fullpath)
|
|
203
|
+
else:
|
|
204
|
+
log.warning("Project does not exist!")
|
|
205
|
+
|
|
206
|
+
# Get list of all existing projects/directories
|
|
207
|
+
projects = []
|
|
208
|
+
for sub in os.listdir(MAIN_PATH):
|
|
209
|
+
projectpath = "%s/%s" % (MAIN_PATH, sub)
|
|
210
|
+
if os.path.isdir(projectpath):
|
|
211
|
+
projects.append(Project(sub))
|
|
212
|
+
# projects.append({"name": sub, "fullpath": projectpath})
|
|
213
|
+
|
|
214
|
+
return render_template("projects.html", projects=projects, **params)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@app.route(
|
|
218
|
+
"/tei_explorer/<string:projectname>/<string:title>", methods=["GET"]
|
|
219
|
+
)
|
|
220
|
+
def tei_explorer(projectname, title):
|
|
221
|
+
project = Project(projectname)
|
|
222
|
+
dir_list_dict, file_list_dict = generateList(
|
|
223
|
+
project.get_subproject_inpath(title)
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
return render_template(
|
|
227
|
+
"tei_explorer.html",
|
|
228
|
+
dir_list=dir_list_dict,
|
|
229
|
+
file_list=file_list_dict,
|
|
230
|
+
collection_title=title,
|
|
231
|
+
project=project,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
# *****************
|
|
236
|
+
# JSON-REQUESTS
|
|
237
|
+
# *****************
|
|
238
|
+
@app.route("/load_tei_content", methods=["GET"])
|
|
239
|
+
def load_tei_content():
|
|
240
|
+
path = request.args.get("path")
|
|
241
|
+
log.debug("load_tei_content path: %s" % path)
|
|
242
|
+
_type = request.args.get("type")
|
|
243
|
+
if path is not None:
|
|
244
|
+
tei_parser = TEIParser(fullpath=path)
|
|
245
|
+
if _type == "header":
|
|
246
|
+
return json_response(
|
|
247
|
+
value="OK",
|
|
248
|
+
content=tei_parser.find(
|
|
249
|
+
"//teiHeader", node_as_text=True
|
|
250
|
+
).decode("utf-8"),
|
|
251
|
+
)
|
|
252
|
+
elif _type == "text":
|
|
253
|
+
return json_response(
|
|
254
|
+
value="OK",
|
|
255
|
+
content=tei_parser.find(".//text", node_as_text=True).decode(
|
|
256
|
+
"utf-8"
|
|
257
|
+
),
|
|
258
|
+
)
|
|
259
|
+
return json_response(value="Unknown type requested!")
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@app.route(
|
|
263
|
+
"/set_configs/<string:projectname>/<string:title>", methods=["POST"]
|
|
264
|
+
)
|
|
265
|
+
def save_collection(projectname, title):
|
|
266
|
+
project = Project(projectname)
|
|
267
|
+
collection = project.get_collection(title)
|
|
268
|
+
collection_config = collection["config"]
|
|
269
|
+
for _attrib in request.form:
|
|
270
|
+
attrib = _attrib.replace("[]", "")
|
|
271
|
+
if attrib in collection_config.multi_attribs:
|
|
272
|
+
value = [
|
|
273
|
+
remove_empty_strings_from_dict(loads(v))
|
|
274
|
+
for v in request.form.getlist(_attrib)
|
|
275
|
+
]
|
|
276
|
+
elif attrib in collection_config.xpath_or_value_attribs:
|
|
277
|
+
value = remove_empty_strings_from_dict(
|
|
278
|
+
loads(request.form.get(attrib))
|
|
279
|
+
)
|
|
280
|
+
else:
|
|
281
|
+
value = request.form.get(attrib)
|
|
282
|
+
setattr(collection_config, attrib, value)
|
|
283
|
+
collection_config.save()
|
|
284
|
+
return json_response(response="OK")
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
@app.route("/nextcloud_tab/", methods=["POST"])
|
|
288
|
+
def nextcloud_tab():
|
|
289
|
+
nextcloud = Nextcloud(**session)
|
|
290
|
+
return render_template(
|
|
291
|
+
"nxc_tab.html",
|
|
292
|
+
nextcloud=nextcloud if nextcloud.test_connection() else None,
|
|
293
|
+
user=session.get("username", "-"),
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
@app.route("/nextcloud_login/", methods=["POST"])
|
|
298
|
+
def nextcloud_login():
|
|
299
|
+
|
|
300
|
+
data = request.get_json()
|
|
301
|
+
login_data = data.get("data", [])
|
|
302
|
+
|
|
303
|
+
if Nextcloud(**login_data).test_connection():
|
|
304
|
+
for key in login_data:
|
|
305
|
+
session[key] = login_data[key]
|
|
306
|
+
|
|
307
|
+
return json_response(login_succes=True)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
@app.route("/nextcloud_logout", methods=["POST"])
|
|
311
|
+
def nextcloud_logout():
|
|
312
|
+
session.clear()
|
|
313
|
+
return json_response(response="OK")
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
@app.route("/nextcloud_download", methods=["POST"])
|
|
317
|
+
def nextcloud_download():
|
|
318
|
+
data = request.get_json()
|
|
319
|
+
projectname = data.get("projectname")
|
|
320
|
+
file_paths = data.get("file_paths", [])
|
|
321
|
+
nxt = Nextcloud(**session)
|
|
322
|
+
nxt.download_nxc_files(file_paths, projectname)
|
|
323
|
+
|
|
324
|
+
return json_response(response="OK")
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
@app.route("/clone_git_project/<string:projectname>", methods=["POST"])
|
|
328
|
+
def clone_git_project(projectname=None):
|
|
329
|
+
Project(projectname).clone_git_project(request.form.get("github_repo"))
|
|
330
|
+
|
|
331
|
+
return json_response(response="OK")
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
@app.route(
|
|
335
|
+
"/check_xpath/<string:projectname>/<string:collectionname>",
|
|
336
|
+
methods=["GET"],
|
|
337
|
+
)
|
|
338
|
+
def check_xpath(projectname, collectionname):
|
|
339
|
+
project = Project(projectname)
|
|
340
|
+
collection = project.get_collection(collectionname)
|
|
341
|
+
collection_config = collection["config"]
|
|
342
|
+
|
|
343
|
+
xpath = request.args.get("xpath")
|
|
344
|
+
|
|
345
|
+
results = []
|
|
346
|
+
|
|
347
|
+
for file in collection_config.elements:
|
|
348
|
+
tei_parser = TEIParser(fullpath=file["fullpath"])
|
|
349
|
+
result = tei_parser.find(xpath)
|
|
350
|
+
if result is not None:
|
|
351
|
+
results.append({"filename": file["filename"], "result": result})
|
|
352
|
+
|
|
353
|
+
return json_response(value="OK", results=results)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
@app.route("/set_configs/<string:projectname>", methods=["POST"])
|
|
357
|
+
def set_configs(projectname):
|
|
358
|
+
Project(projectname).init_configs(request.form.getlist("tei_directories"))
|
|
359
|
+
|
|
360
|
+
return json_response(response="OK")
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
@app.route(
|
|
364
|
+
"/publish_project/<string:projectname>/<string:instance>", methods=["POST"]
|
|
365
|
+
)
|
|
366
|
+
def publish_project(projectname, instance):
|
|
367
|
+
TGProject(projectname, instance).publish_tg_project()
|
|
368
|
+
return json_response(response="OK")
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
@app.route(
|
|
372
|
+
"/save_session_id/<string:projectname>/<string:instance>", methods=["POST"]
|
|
373
|
+
)
|
|
374
|
+
def save_session_id(projectname, instance):
|
|
375
|
+
TGProject(projectname, instance).tg_session_id = request.form.get(
|
|
376
|
+
"tg_auth_session_id"
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
return json_response(response="OK")
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
@app.route(
|
|
383
|
+
"/save_tg_project_id/<string:projectname>/<string:instance>",
|
|
384
|
+
methods=["POST"],
|
|
385
|
+
)
|
|
386
|
+
def save_tg_project_id(projectname, instance):
|
|
387
|
+
TGProject(projectname, instance).tg_project_id = request.form.get(
|
|
388
|
+
"tg_project_id"
|
|
389
|
+
)
|
|
390
|
+
return json_response(response="OK")
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
@app.route(
|
|
394
|
+
"/delete_tg_project_id/<string:projectname>/<string:instance>/<string:tg_project_id>",
|
|
395
|
+
methods=["POST"],
|
|
396
|
+
)
|
|
397
|
+
def delete_tg_project_id(projectname, instance, tg_project_id):
|
|
398
|
+
TGProject(projectname, instance).delete_tg_project(tg_project_id)
|
|
399
|
+
|
|
400
|
+
return json_response(response="OK")
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
@app.route(
|
|
404
|
+
"/get_tg_project_hits/<string:projectname>/<string:instance>/<string:tg_project_id>",
|
|
405
|
+
methods=["GET"],
|
|
406
|
+
)
|
|
407
|
+
def get_tg_project_hits(projectname, instance, tg_project_id):
|
|
408
|
+
hits = TGProject(projectname, instance).get_tg_project_hits(tg_project_id)
|
|
409
|
+
return json_response(response="OK", hits=hits)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
@app.route(
|
|
413
|
+
"/create_tg_project/<string:projectname>/<string:instance>",
|
|
414
|
+
methods=["POST"],
|
|
415
|
+
)
|
|
416
|
+
def create_tg_project(projectname, instance):
|
|
417
|
+
TGProject(projectname, instance).create_tg_project(
|
|
418
|
+
request.form.get("tg_projectname")
|
|
419
|
+
)
|
|
420
|
+
return json_response(response="OK")
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
if __name__ == "__main__":
|
|
424
|
+
startup()
|