tg-prepare 1.1.0__py3-none-any.whl → 2.1.0b1__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.1.0.dist-info → tg_prepare-2.1.0b1.dist-info}/METADATA +3 -3
- tg_prepare-2.1.0b1.dist-info/RECORD +54 -0
- {tg_prepare-1.1.0.dist-info → tg_prepare-2.1.0b1.dist-info}/WHEEL +1 -1
- tg_prepare-2.1.0b1.dist-info/projects/.secret_key +1 -0
- tgp_backend/__init__.py +3 -4
- tgp_backend/config.py +31 -0
- tgp_backend/directories.py +6 -8
- tgp_backend/nextcloud.py +40 -19
- tgp_backend/project.py +172 -45
- tgp_backend/session_manager.py +47 -0
- tgp_backend/util.py +73 -25
- tgp_ui/app.py +43 -335
- tgp_ui/routes/__init__.py +0 -0
- tgp_ui/routes/collection.py +272 -0
- tgp_ui/routes/data.py +228 -0
- tgp_ui/routes/project.py +102 -0
- tgp_ui/routes/publication.py +129 -0
- tgp_ui/routes/tabs.py +34 -0
- tgp_ui/routes/views.py +62 -0
- tgp_ui/static/css/navbar.css +92 -0
- tgp_ui/static/js/collectionManager.js +60 -0
- tgp_ui/static/js/fileManager.js +186 -0
- tgp_ui/static/js/main.js +32 -485
- tgp_ui/static/js/modalManager.js +105 -0
- tgp_ui/static/js/navbarManager.js +151 -0
- tgp_ui/static/js/projectManager.js +60 -0
- tgp_ui/static/js/require.js +5 -0
- tgp_ui/static/js/sidebarManager.js +32 -0
- tgp_ui/static/js/tabManager.js +79 -0
- tgp_ui/templates/layout.html +9 -48
- tgp_ui/templates/macros.html +79 -72
- tgp_ui/templates/project_main.html +36 -0
- tgp_ui/templates/project_navbar.html +81 -0
- tgp_ui/templates/{projects.html → projects_main.html} +13 -28
- tgp_ui/templates/tab_final_upload.html +29 -0
- tg_prepare-1.1.0.dist-info/RECORD +0 -39
- tgp_ui/templates/collection.html +0 -194
- tgp_ui/templates/file_upload.html +0 -24
- tgp_ui/templates/nxc_file_tree.html +0 -33
- tgp_ui/templates/project.html +0 -26
- tgp_ui/templates/storage.html +0 -49
- tgp_ui/templates/tei_explorer.html +0 -48
- tgp_ui/templates/xpath_parser_modal_content.html +0 -37
- {tg_prepare-1.1.0.dist-info → tg_prepare-2.1.0b1.dist-info}/entry_points.txt +0 -0
- {tg_prepare-1.1.0.dist-info → tg_prepare-2.1.0b1.dist-info}/licenses/LICENSE +0 -0
- {tg_prepare-1.1.0.dist-info → tg_prepare-2.1.0b1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,272 @@
|
|
|
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 os
|
|
9
|
+
|
|
10
|
+
from flask import Blueprint, render_template, request, send_file
|
|
11
|
+
from flask_json import json_response
|
|
12
|
+
|
|
13
|
+
from io import BytesIO
|
|
14
|
+
|
|
15
|
+
from tg_model.tei import TEIParser
|
|
16
|
+
|
|
17
|
+
from tgp_backend.directories import generateList
|
|
18
|
+
from tgp_backend.project import Project
|
|
19
|
+
from tgp_backend.util import parse_request_data
|
|
20
|
+
|
|
21
|
+
log = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
collection_routes = Blueprint("collection", __name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# ***TABS***
|
|
27
|
+
# **********
|
|
28
|
+
@collection_routes.route(
|
|
29
|
+
"/tab_select_directories/<string:projectname>", methods=["GET", "POST"]
|
|
30
|
+
)
|
|
31
|
+
def tab_select_directories(projectname):
|
|
32
|
+
|
|
33
|
+
project = Project(projectname)
|
|
34
|
+
if request.method == "POST":
|
|
35
|
+
project.project_config.update(
|
|
36
|
+
tei_directories=request.form.getlist("selected_folder")
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
tei_directories = [
|
|
40
|
+
sp["inpath"] for sp in project.project_config.get_subprojects()
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
return render_template(
|
|
44
|
+
"tabs/select_directories.html",
|
|
45
|
+
project=project,
|
|
46
|
+
selected_directories=tei_directories,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@collection_routes.route(
|
|
51
|
+
"/tab_manage_collections/<string:projectname>", methods=["GET"]
|
|
52
|
+
)
|
|
53
|
+
def tab_manage_collections(projectname):
|
|
54
|
+
|
|
55
|
+
project = Project(projectname)
|
|
56
|
+
|
|
57
|
+
return render_template(
|
|
58
|
+
"tabs/manage_collections.html",
|
|
59
|
+
project=project,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@collection_routes.route(
|
|
64
|
+
"/tab_validate_metadata/<string:projectname>", methods=["GET"]
|
|
65
|
+
)
|
|
66
|
+
@collection_routes.route(
|
|
67
|
+
"/tab_validate_metadata/<string:projectname>/<string:refresh>",
|
|
68
|
+
methods=["GET"],
|
|
69
|
+
)
|
|
70
|
+
def tab_validate_metadata(projectname, refresh=False):
|
|
71
|
+
|
|
72
|
+
project = Project(projectname)
|
|
73
|
+
return render_template(
|
|
74
|
+
"tabs/validate_metadata.html",
|
|
75
|
+
projectname=projectname,
|
|
76
|
+
validation_results=project.get_validation_results(refresh=refresh),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# ***OTHER***
|
|
81
|
+
# **********
|
|
82
|
+
@collection_routes.route(
|
|
83
|
+
"/load_collection/<string:projectname>/<string:collectionname>",
|
|
84
|
+
methods=["GET"],
|
|
85
|
+
)
|
|
86
|
+
def load_collection(projectname, collectionname):
|
|
87
|
+
collection = None
|
|
88
|
+
project = Project(projectname)
|
|
89
|
+
collection = None
|
|
90
|
+
if collectionname:
|
|
91
|
+
collection = project.get_collection(collectionname)
|
|
92
|
+
|
|
93
|
+
return render_template(
|
|
94
|
+
"details/manage_collection.html",
|
|
95
|
+
collectionname=collectionname,
|
|
96
|
+
collection=collection["config"],
|
|
97
|
+
project=project,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@collection_routes.route(
|
|
102
|
+
"/save_collection_attributes/<string:projectname>/<string:collectionname>",
|
|
103
|
+
methods=["POST"],
|
|
104
|
+
)
|
|
105
|
+
def save_collection_attributes(projectname, collectionname):
|
|
106
|
+
project = Project(projectname)
|
|
107
|
+
collection = project.get_collection(collectionname)
|
|
108
|
+
collection_config = collection["config"]
|
|
109
|
+
|
|
110
|
+
multi_attribs = ["collector", "rights_holder"]
|
|
111
|
+
for multi_attrib in multi_attribs:
|
|
112
|
+
elements = []
|
|
113
|
+
for name, url in zip(
|
|
114
|
+
request.form.getlist(f"{multi_attrib}_name"),
|
|
115
|
+
request.form.getlist(f"{multi_attrib}_url"),
|
|
116
|
+
):
|
|
117
|
+
elements.append({"name": name, "url": url})
|
|
118
|
+
setattr(collection_config, multi_attrib, elements)
|
|
119
|
+
|
|
120
|
+
for attrib in ["short_title", "long_title"]:
|
|
121
|
+
value = request.form.get(attrib)
|
|
122
|
+
setattr(collection_config, attrib, value)
|
|
123
|
+
|
|
124
|
+
collection_config.save()
|
|
125
|
+
return load_collection(
|
|
126
|
+
projectname=projectname, collectionname=collectionname
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@collection_routes.route(
|
|
131
|
+
"/save_collection_classifications/<string:projectname>/<string:collectionname>",
|
|
132
|
+
methods=["POST"],
|
|
133
|
+
)
|
|
134
|
+
def save_collection_classifications(projectname, collectionname):
|
|
135
|
+
project = Project(projectname)
|
|
136
|
+
collection = project.get_collection(collectionname)
|
|
137
|
+
collection_config = collection["config"]
|
|
138
|
+
|
|
139
|
+
# Save basic classifications...
|
|
140
|
+
collection_config.basic_classifications = parse_request_data(
|
|
141
|
+
request, "basic_classifications"
|
|
142
|
+
)
|
|
143
|
+
# ...and GND subjects
|
|
144
|
+
collection_config.gnd_subjects = parse_request_data(
|
|
145
|
+
request, "gnd_subjects"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
collection_config.save()
|
|
149
|
+
return load_collection(
|
|
150
|
+
projectname=projectname, collectionname=collectionname
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@collection_routes.route(
|
|
155
|
+
"/save_collection_metadata/<string:projectname>/<string:collectionname>",
|
|
156
|
+
methods=["POST"],
|
|
157
|
+
)
|
|
158
|
+
def save_collection_metadata(projectname, collectionname):
|
|
159
|
+
project = Project(projectname)
|
|
160
|
+
collection = project.get_collection(collectionname)
|
|
161
|
+
collection_config = collection["config"]
|
|
162
|
+
for attrib in request.form:
|
|
163
|
+
if "xpath" in attrib:
|
|
164
|
+
key = attrib.replace("_xpath", "")
|
|
165
|
+
type_ = "xpath"
|
|
166
|
+
elif "value" in attrib:
|
|
167
|
+
key = attrib.replace("_value", "")
|
|
168
|
+
type_ = "value"
|
|
169
|
+
if key in collection_config.xpath_or_value_attribs:
|
|
170
|
+
cc_attrib = getattr(collection_config, key)
|
|
171
|
+
cc_attrib[type_] = request.form.get(attrib)
|
|
172
|
+
else:
|
|
173
|
+
log.warning(
|
|
174
|
+
f"Attribute '{attrib}' not found in collection config."
|
|
175
|
+
)
|
|
176
|
+
collection_config.save()
|
|
177
|
+
return load_collection(
|
|
178
|
+
projectname=projectname, collectionname=collectionname
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@collection_routes.route(
|
|
183
|
+
"/modal/tei_explorer/<string:projectname>/<string:collectionname>",
|
|
184
|
+
methods=["GET", "POST"],
|
|
185
|
+
)
|
|
186
|
+
def modal_tei_explorer(projectname, collectionname):
|
|
187
|
+
project = Project(projectname)
|
|
188
|
+
# collection = project.get_collection(collectionname)
|
|
189
|
+
dir_list_dict, file_list_dict = generateList(
|
|
190
|
+
project.get_subproject_inpath(collectionname)
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
xpath = request.args.get("xpath", "")
|
|
194
|
+
|
|
195
|
+
return render_template(
|
|
196
|
+
"modal/tei_explorer.html",
|
|
197
|
+
projectname=projectname,
|
|
198
|
+
collectionname=collectionname,
|
|
199
|
+
dir_list=dir_list_dict,
|
|
200
|
+
file_list=file_list_dict,
|
|
201
|
+
xpath=xpath,
|
|
202
|
+
project=project,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@collection_routes.route("/load_tei_content", methods=["GET"])
|
|
207
|
+
def load_tei_content():
|
|
208
|
+
path = request.args.get("path")
|
|
209
|
+
log.debug("load_tei_content path: %s" % path)
|
|
210
|
+
_type = request.args.get("type")
|
|
211
|
+
if path is not None:
|
|
212
|
+
tei_parser = TEIParser(fullpath=path)
|
|
213
|
+
if _type == "header":
|
|
214
|
+
return json_response(
|
|
215
|
+
value="OK",
|
|
216
|
+
content=tei_parser.find(
|
|
217
|
+
"//teiHeader", node_as_text=True
|
|
218
|
+
).decode("utf-8"),
|
|
219
|
+
)
|
|
220
|
+
elif _type == "text":
|
|
221
|
+
return json_response(
|
|
222
|
+
value="OK",
|
|
223
|
+
content=tei_parser.find(".//text", node_as_text=True).decode(
|
|
224
|
+
"utf-8"
|
|
225
|
+
),
|
|
226
|
+
)
|
|
227
|
+
return json_response(value="Unknown type requested!")
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
@collection_routes.route(
|
|
231
|
+
"/modal/xpath_parser/<string:projectname>/<string:collectionname>",
|
|
232
|
+
methods=["GET", "POST"],
|
|
233
|
+
)
|
|
234
|
+
def modal_xpath_parser(
|
|
235
|
+
projectname,
|
|
236
|
+
collectionname,
|
|
237
|
+
):
|
|
238
|
+
project = Project(projectname)
|
|
239
|
+
collection = project.get_collection(
|
|
240
|
+
collectionname,
|
|
241
|
+
)
|
|
242
|
+
collection_parser = collection["parser"]
|
|
243
|
+
|
|
244
|
+
xpath = request.args.get("xpath")
|
|
245
|
+
if not xpath:
|
|
246
|
+
xpath = request.form.get("xpath", "")
|
|
247
|
+
|
|
248
|
+
return render_template(
|
|
249
|
+
"modal/xpath_parser.html",
|
|
250
|
+
xpath=xpath,
|
|
251
|
+
projectname=projectname,
|
|
252
|
+
collectionname=collectionname,
|
|
253
|
+
collection_parser=collection_parser,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@collection_routes.route("/export_tsv/<projectname>/<collectionname>")
|
|
258
|
+
def export_tsv(projectname, collectionname):
|
|
259
|
+
project = Project(projectname)
|
|
260
|
+
collection = project.get_collection(
|
|
261
|
+
collectionname,
|
|
262
|
+
)
|
|
263
|
+
filepath = collection["modeler"].export()
|
|
264
|
+
if os.path.isfile(filepath):
|
|
265
|
+
with open(filepath, "rb") as file:
|
|
266
|
+
file_content = BytesIO(file.read())
|
|
267
|
+
|
|
268
|
+
return send_file(
|
|
269
|
+
file_content,
|
|
270
|
+
download_name=os.path.basename(filepath),
|
|
271
|
+
as_attachment=True,
|
|
272
|
+
)
|
tgp_ui/routes/data.py
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
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 os
|
|
9
|
+
import shutil
|
|
10
|
+
|
|
11
|
+
from flask import Blueprint, render_template, request, send_file, session
|
|
12
|
+
from flask_json import json_response
|
|
13
|
+
|
|
14
|
+
from io import BytesIO
|
|
15
|
+
|
|
16
|
+
from tgp_backend.config import MAIN_PATH
|
|
17
|
+
from tgp_backend.nextcloud import Nextcloud
|
|
18
|
+
from tgp_backend.project import Project
|
|
19
|
+
from tgp_backend.session_manager import SessionManagerNextcloud
|
|
20
|
+
|
|
21
|
+
log = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
data_routes = Blueprint("data", __name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# ***TABS***
|
|
27
|
+
# **********
|
|
28
|
+
@data_routes.route("/tab-import-data/<string:projectname>", methods=["GET"])
|
|
29
|
+
def tab_import_data(projectname=None, project=None, error_message=None):
|
|
30
|
+
if not project:
|
|
31
|
+
if not projectname:
|
|
32
|
+
raise ValueError("Project name must be provided.")
|
|
33
|
+
else:
|
|
34
|
+
project = Project(projectname)
|
|
35
|
+
return render_template(
|
|
36
|
+
"tabs/import_data.html",
|
|
37
|
+
project=project,
|
|
38
|
+
error_message=error_message,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# ***DIRECT UPLOAD***
|
|
43
|
+
# *******************
|
|
44
|
+
@data_routes.route("/upload_files/<string:projectname>", methods=["POST"])
|
|
45
|
+
def upload_files(projectname=None):
|
|
46
|
+
project = Project(projectname)
|
|
47
|
+
project.file_upload(request.files.getlist("files"))
|
|
48
|
+
|
|
49
|
+
return tab_import_data(project=project)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# ***GITHUB***
|
|
53
|
+
# ************
|
|
54
|
+
@data_routes.route("/modal/github/<projectname>", methods=["GET", "POST"])
|
|
55
|
+
def modal_github(projectname):
|
|
56
|
+
"""
|
|
57
|
+
Render the Github modal.
|
|
58
|
+
"""
|
|
59
|
+
project = Project(projectname)
|
|
60
|
+
return render_template(
|
|
61
|
+
"modal/github_modal.html",
|
|
62
|
+
project=project,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@data_routes.route("/clone_git_project/<string:projectname>", methods=["POST"])
|
|
67
|
+
def clone_git_project(projectname=None):
|
|
68
|
+
project = Project(projectname)
|
|
69
|
+
project.clone_git_project(request.form.get("github_repo"))
|
|
70
|
+
|
|
71
|
+
return tab_import_data(project=project)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# ***NEXTCLOUD***
|
|
75
|
+
# ***************
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def render_nextcloud_tree(projectname, items):
|
|
79
|
+
"""
|
|
80
|
+
Render the Nextcloud tree view.
|
|
81
|
+
"""
|
|
82
|
+
# items = items or nc.nxc_list_files_and_folders()
|
|
83
|
+
project = Project(projectname)
|
|
84
|
+
return render_template(
|
|
85
|
+
"modal/file_explorer_nextcloud.html",
|
|
86
|
+
project=project,
|
|
87
|
+
items=items,
|
|
88
|
+
show_checkbox=True,
|
|
89
|
+
show_all_folders=True,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def render_nextcloud_login(projectname, message=None):
|
|
94
|
+
"""
|
|
95
|
+
Render the Nextcloud login form.
|
|
96
|
+
"""
|
|
97
|
+
return render_template(
|
|
98
|
+
"modal/nextcloud_login.html", projectname=projectname, message=message
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@data_routes.route("/modal/nextcloud/<projectname>", methods=["GET", "POST"])
|
|
103
|
+
@data_routes.route(
|
|
104
|
+
"/modal/nextcloud/<projectname>/<logout>", methods=["GET", "POST"]
|
|
105
|
+
)
|
|
106
|
+
def modal_nextcloud(projectname, form_type=None, logout=False):
|
|
107
|
+
|
|
108
|
+
nxc_session_manager = SessionManagerNextcloud(session)
|
|
109
|
+
|
|
110
|
+
if logout:
|
|
111
|
+
# If the user wants to log out, delete the session credentials
|
|
112
|
+
nxc_session_manager.delete_nextcloud_credentials()
|
|
113
|
+
return render_nextcloud_login(projectname, message="You logged out.")
|
|
114
|
+
|
|
115
|
+
nc = Nextcloud(**nxc_session_manager.get_credentials())
|
|
116
|
+
if nc.test_connection():
|
|
117
|
+
# An active Nextcloud session exists, so we can use it to render
|
|
118
|
+
# the tree view
|
|
119
|
+
return render_nextcloud_tree(
|
|
120
|
+
projectname, nc.nxc_list_files_and_folders()
|
|
121
|
+
)
|
|
122
|
+
elif nxc_session_manager.request_has_valid_credentials(request):
|
|
123
|
+
# If credentials are provided,
|
|
124
|
+
# Recreate a Nextcloud instance and test the connection
|
|
125
|
+
nxc_session_manager.save_credentials(request.form)
|
|
126
|
+
nc = Nextcloud(**nxc_session_manager.get_credentials())
|
|
127
|
+
if nc.test_connection():
|
|
128
|
+
return render_nextcloud_tree(
|
|
129
|
+
projectname, nc.nxc_list_files_and_folders()
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
else:
|
|
133
|
+
return render_nextcloud_login(
|
|
134
|
+
projectname,
|
|
135
|
+
message="Login failed. Please check your credentials.",
|
|
136
|
+
)
|
|
137
|
+
else:
|
|
138
|
+
# If no valid credentials are provided, render the login form
|
|
139
|
+
log.info("No valid Nextcloud credentials found, showing login form.")
|
|
140
|
+
# This will clear any previous session data
|
|
141
|
+
nxc_session_manager.delete_nextcloud_credentials()
|
|
142
|
+
return render_nextcloud_login(projectname)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@data_routes.route("/download_nextcloud/<projectname>", methods=["POST"])
|
|
146
|
+
def download_nextcloud(projectname):
|
|
147
|
+
|
|
148
|
+
nxc_session_manager = SessionManagerNextcloud(session)
|
|
149
|
+
|
|
150
|
+
selected_option = request.form.getlist("selected_folder")
|
|
151
|
+
|
|
152
|
+
if selected_option:
|
|
153
|
+
nc = Nextcloud(**nxc_session_manager.get_credentials())
|
|
154
|
+
nc.download_nxc_files(selected_option, projectname=projectname)
|
|
155
|
+
|
|
156
|
+
return json_response(
|
|
157
|
+
status=200,
|
|
158
|
+
message="Downloaded.",
|
|
159
|
+
)
|
|
160
|
+
else:
|
|
161
|
+
return json_response(
|
|
162
|
+
status=400, message="No folder selected for download."
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# ***OTHER***
|
|
167
|
+
# *******************
|
|
168
|
+
@data_routes.route("/image/<path:filepath>")
|
|
169
|
+
def serve_image(filepath):
|
|
170
|
+
filepath = "/" + filepath
|
|
171
|
+
if os.path.isfile(filepath):
|
|
172
|
+
return send_file(filepath)
|
|
173
|
+
return "", 404
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@data_routes.route("/file/<path:filepath>")
|
|
177
|
+
def serve_file(filepath):
|
|
178
|
+
|
|
179
|
+
filepath = "/" + filepath
|
|
180
|
+
|
|
181
|
+
if os.path.isfile(filepath):
|
|
182
|
+
with open(filepath, "rb") as file:
|
|
183
|
+
file_content = BytesIO(file.read())
|
|
184
|
+
|
|
185
|
+
return send_file(
|
|
186
|
+
file_content,
|
|
187
|
+
download_name=os.path.basename(filepath),
|
|
188
|
+
as_attachment=True,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@data_routes.route("/api/delete-folder", methods=["DELETE"])
|
|
193
|
+
def delete_folder():
|
|
194
|
+
error_message = None # Variable für die Fehlermeldung
|
|
195
|
+
try:
|
|
196
|
+
path = request.form.get("path")
|
|
197
|
+
projectname = request.form.get("projectname")
|
|
198
|
+
|
|
199
|
+
if not path:
|
|
200
|
+
error_message = "No path specified"
|
|
201
|
+
elif not path.startswith(MAIN_PATH):
|
|
202
|
+
# Security check to ensure path is within allowed directory
|
|
203
|
+
error_message = "Invalid path"
|
|
204
|
+
elif not os.path.exists(path):
|
|
205
|
+
# Check if path exists
|
|
206
|
+
error_message = "Path not found"
|
|
207
|
+
else:
|
|
208
|
+
# Check if the path is a file or a folder
|
|
209
|
+
if os.path.isfile(path):
|
|
210
|
+
# Delete the file
|
|
211
|
+
os.remove(path)
|
|
212
|
+
log.info(f"File deleted: {path}")
|
|
213
|
+
elif os.path.isdir(path):
|
|
214
|
+
# Delete the folder and its contents
|
|
215
|
+
shutil.rmtree(path)
|
|
216
|
+
log.info(f"Folder deleted: {path}")
|
|
217
|
+
else:
|
|
218
|
+
error_message = "Path is neither a file nor a folder"
|
|
219
|
+
|
|
220
|
+
except Exception as e:
|
|
221
|
+
log.error(f"Error deleting path: {e}")
|
|
222
|
+
error_message = f"An error occurred: {str(e)}"
|
|
223
|
+
|
|
224
|
+
# Am Ende einmal `tab_import_data` aufrufen
|
|
225
|
+
return tab_import_data(
|
|
226
|
+
projectname=projectname,
|
|
227
|
+
error_message=error_message,
|
|
228
|
+
)
|
tgp_ui/routes/project.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
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 os
|
|
9
|
+
import shutil
|
|
10
|
+
|
|
11
|
+
from flask import Blueprint, redirect, render_template, request
|
|
12
|
+
from flask import url_for as default_url_for
|
|
13
|
+
from flask_json import json_response
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
from tgp_backend.config import MAIN_PATH
|
|
17
|
+
from tgp_backend.project import Project
|
|
18
|
+
|
|
19
|
+
from .views import overview
|
|
20
|
+
|
|
21
|
+
log = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
project_routes = Blueprint("project", __name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_prefix():
|
|
27
|
+
return request.headers.get(
|
|
28
|
+
"X-Forwarded-Prefix", request.headers.get("X-Script-Name", "")
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def url_for(*args, **kwargs):
|
|
33
|
+
"""Overrides Flask's url_for globally to include the prefix"""
|
|
34
|
+
return get_prefix() + default_url_for(*args, **kwargs)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@project_routes.route("/new_project", methods=["POST"])
|
|
38
|
+
def new_project():
|
|
39
|
+
project = Project(request.form.get("projectname"))
|
|
40
|
+
project.create()
|
|
41
|
+
return redirect(url_for("views.project_view", projectname=project.path))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@project_routes.route("/delete_project/<string:projectname>", methods=["POST"])
|
|
45
|
+
def delete_project(projectname):
|
|
46
|
+
|
|
47
|
+
# projectname = request.form.get("projectname")
|
|
48
|
+
fullpath = f"{MAIN_PATH}/{projectname}"
|
|
49
|
+
|
|
50
|
+
# Delete the project
|
|
51
|
+
if fullpath.strip("/") == MAIN_PATH.strip("/"):
|
|
52
|
+
log.error(f"Cannot delete main path ({MAIN_PATH})!")
|
|
53
|
+
elif os.path.exists(fullpath):
|
|
54
|
+
shutil.rmtree(fullpath)
|
|
55
|
+
else:
|
|
56
|
+
log.warning("Project does not exist!")
|
|
57
|
+
|
|
58
|
+
return overview()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@project_routes.route(
|
|
62
|
+
"/modal_delete_project/<string:projectname>", methods=["POST"]
|
|
63
|
+
)
|
|
64
|
+
def modal_delete_project(projectname):
|
|
65
|
+
return render_template(
|
|
66
|
+
"modal/delete_project.html",
|
|
67
|
+
projectname=projectname,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@project_routes.route(
|
|
72
|
+
"/tab-edit-project/<string:projectname>", methods=["GET", "POST"]
|
|
73
|
+
)
|
|
74
|
+
def tab_edit_project(projectname):
|
|
75
|
+
project = Project(projectname)
|
|
76
|
+
if request.method == "POST":
|
|
77
|
+
collectors = []
|
|
78
|
+
for name, url in zip(
|
|
79
|
+
request.form.getlist("collector_name"),
|
|
80
|
+
request.form.getlist("collector_url"),
|
|
81
|
+
):
|
|
82
|
+
collectors.append({"name": name, "url": url})
|
|
83
|
+
|
|
84
|
+
project.update(
|
|
85
|
+
title=request.form["title"],
|
|
86
|
+
description=request.form["description"],
|
|
87
|
+
collectors=collectors,
|
|
88
|
+
avatar=request.files["avatar"],
|
|
89
|
+
xslt=request.files.get("xslt"),
|
|
90
|
+
)
|
|
91
|
+
return render_template(
|
|
92
|
+
"tabs/edit_project.html",
|
|
93
|
+
project=project,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@project_routes.route("/delete-xslt/<string:projectname>", methods=["DELETE"])
|
|
98
|
+
def delete_xslt(projectname):
|
|
99
|
+
project = Project(projectname)
|
|
100
|
+
log.debug(f"Deleting XSLT for project {projectname}")
|
|
101
|
+
project.clear_xslt()
|
|
102
|
+
return json_response("OK")
|