flask-admin-tabler 2026.2.1__tar.gz
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.
- flask_admin_tabler-2026.2.1/LICENSE +21 -0
- flask_admin_tabler-2026.2.1/PKG-INFO +111 -0
- flask_admin_tabler-2026.2.1/README.md +61 -0
- flask_admin_tabler-2026.2.1/flask_admin_tabler/__init__.py +3 -0
- flask_admin_tabler-2026.2.1/flask_admin_tabler/static/admin/css/tabler/admin.css +74 -0
- flask_admin_tabler-2026.2.1/flask_admin_tabler/static/admin/css/tabler/rediscli.css +48 -0
- flask_admin_tabler-2026.2.1/flask_admin_tabler/templates/tabler/admin/actions.html +38 -0
- flask_admin_tabler-2026.2.1/flask_admin_tabler/templates/tabler/admin/base.html +115 -0
- flask_admin_tabler-2026.2.1/flask_admin_tabler/templates/tabler/admin/file/form.html +9 -0
- flask_admin_tabler-2026.2.1/flask_admin_tabler/templates/tabler/admin/file/list.html +195 -0
- flask_admin_tabler-2026.2.1/flask_admin_tabler/templates/tabler/admin/file/modals/form.html +19 -0
- flask_admin_tabler-2026.2.1/flask_admin_tabler/templates/tabler/admin/index.html +4 -0
- flask_admin_tabler-2026.2.1/flask_admin_tabler/templates/tabler/admin/layout.html +103 -0
- flask_admin_tabler-2026.2.1/flask_admin_tabler/templates/tabler/admin/lib.html +293 -0
- flask_admin_tabler-2026.2.1/flask_admin_tabler/templates/tabler/admin/master.html +1 -0
- flask_admin_tabler-2026.2.1/flask_admin_tabler/templates/tabler/admin/model/create.html +30 -0
- flask_admin_tabler-2026.2.1/flask_admin_tabler/templates/tabler/admin/model/details.html +53 -0
- flask_admin_tabler-2026.2.1/flask_admin_tabler/templates/tabler/admin/model/edit.html +40 -0
- flask_admin_tabler-2026.2.1/flask_admin_tabler/templates/tabler/admin/model/inline_field_list.html +15 -0
- flask_admin_tabler-2026.2.1/flask_admin_tabler/templates/tabler/admin/model/inline_form.html +4 -0
- flask_admin_tabler-2026.2.1/flask_admin_tabler/templates/tabler/admin/model/inline_list_base.html +45 -0
- flask_admin_tabler-2026.2.1/flask_admin_tabler/templates/tabler/admin/model/layout.html +110 -0
- flask_admin_tabler-2026.2.1/flask_admin_tabler/templates/tabler/admin/model/list.html +198 -0
- flask_admin_tabler-2026.2.1/flask_admin_tabler/templates/tabler/admin/model/modals/create.html +26 -0
- flask_admin_tabler-2026.2.1/flask_admin_tabler/templates/tabler/admin/model/modals/details.html +40 -0
- flask_admin_tabler-2026.2.1/flask_admin_tabler/templates/tabler/admin/model/modals/edit.html +27 -0
- flask_admin_tabler-2026.2.1/flask_admin_tabler/templates/tabler/admin/model/row_actions.html +38 -0
- flask_admin_tabler-2026.2.1/flask_admin_tabler/templates/tabler/admin/rediscli/console.html +27 -0
- flask_admin_tabler-2026.2.1/flask_admin_tabler/templates/tabler/admin/rediscli/response.html +32 -0
- flask_admin_tabler-2026.2.1/flask_admin_tabler/templates/tabler/admin/static.html +3 -0
- flask_admin_tabler-2026.2.1/flask_admin_tabler/theme.py +55 -0
- flask_admin_tabler-2026.2.1/pyproject.toml +86 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mario Hernandez
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flask-admin-tabler
|
|
3
|
+
Version: 2026.2.1
|
|
4
|
+
Summary: Tabler UI theme for Flask-Admin
|
|
5
|
+
License: MIT License
|
|
6
|
+
|
|
7
|
+
Copyright (c) 2026 Mario Hernandez
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all
|
|
17
|
+
copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
+
SOFTWARE.
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Keywords: flask,flask-admin,tabler,admin-interface,theme,template
|
|
28
|
+
Requires-Python: >=3.10,<4.0
|
|
29
|
+
Classifier: Development Status :: 4 - Beta
|
|
30
|
+
Classifier: Environment :: Web Environment
|
|
31
|
+
Classifier: Intended Audience :: Developers
|
|
32
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
33
|
+
Classifier: Operating System :: OS Independent
|
|
34
|
+
Classifier: Programming Language :: Python
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
40
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
41
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
42
|
+
Provides-Extra: dev
|
|
43
|
+
Requires-Dist: black ; extra == "dev"
|
|
44
|
+
Requires-Dist: flask-admin (>=2.0.2)
|
|
45
|
+
Requires-Dist: isort ; extra == "dev"
|
|
46
|
+
Requires-Dist: pre-commit ; extra == "dev"
|
|
47
|
+
Project-URL: Source, https://github.com/mariofix/flask-admin-tablerui
|
|
48
|
+
Description-Content-Type: text/markdown
|
|
49
|
+
|
|
50
|
+
# flask-admin-tabler
|
|
51
|
+
|
|
52
|
+
A [Tabler UI](https://tabler.io/) theme for [Flask-Admin](https://github.com/pallets-eco/flask-admin).
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
```shell
|
|
57
|
+
pip install flask-admin-tabler
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Usage
|
|
61
|
+
|
|
62
|
+
Call `theme.init_app(app)` **before** creating the `Admin` instance so that the
|
|
63
|
+
Tabler templates are registered with higher priority than Flask-Admin's default
|
|
64
|
+
Bootstrap ones.
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from flask import Flask
|
|
68
|
+
from flask_admin import Admin
|
|
69
|
+
from flask_admin_tabler import TablerTheme
|
|
70
|
+
|
|
71
|
+
app = Flask(__name__)
|
|
72
|
+
app.config["SECRET_KEY"] = "change-me"
|
|
73
|
+
|
|
74
|
+
theme = TablerTheme()
|
|
75
|
+
theme.init_app(app) # must come before Admin(app, ...)
|
|
76
|
+
|
|
77
|
+
admin = Admin(app, name="My App", theme=theme)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
That's it — navigating to `/admin/` will now render the Tabler UI instead of
|
|
81
|
+
the default Bootswatch theme.
|
|
82
|
+
|
|
83
|
+
A fully runnable example (including a SQLAlchemy model and sample data) is
|
|
84
|
+
available in [`examples/quickstart.py`](examples/quickstart.py):
|
|
85
|
+
|
|
86
|
+
```shell
|
|
87
|
+
pip install flask-admin-tabler flask-sqlalchemy
|
|
88
|
+
python examples/quickstart.py
|
|
89
|
+
# open http://127.0.0.1:5000/admin/
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## How it works
|
|
93
|
+
|
|
94
|
+
`TablerTheme.init_app(app)` registers a Flask blueprint named
|
|
95
|
+
`flask_admin_tabler` that:
|
|
96
|
+
|
|
97
|
+
1. **Templates** — exposes `flask_admin_tabler/templates/tabler/` as a template
|
|
98
|
+
folder. Because this blueprint is registered *before* Flask-Admin's admin
|
|
99
|
+
blueprint, Flask resolves `admin/base.html` (and all other admin templates)
|
|
100
|
+
from here first.
|
|
101
|
+
2. **Static files** — serves the small amount of theme-specific CSS (e.g.
|
|
102
|
+
`admin/css/tabler/admin.css`) at `/static/flask_admin_tabler/`.
|
|
103
|
+
|
|
104
|
+
Tabler's core CSS and JS are loaded from the jsDelivr CDN — no local copies
|
|
105
|
+
needed.
|
|
106
|
+
|
|
107
|
+
## Requirements
|
|
108
|
+
|
|
109
|
+
- Python ≥ 3.10
|
|
110
|
+
- flask-admin ≥ 2.0.2
|
|
111
|
+
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# flask-admin-tabler
|
|
2
|
+
|
|
3
|
+
A [Tabler UI](https://tabler.io/) theme for [Flask-Admin](https://github.com/pallets-eco/flask-admin).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```shell
|
|
8
|
+
pip install flask-admin-tabler
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
Call `theme.init_app(app)` **before** creating the `Admin` instance so that the
|
|
14
|
+
Tabler templates are registered with higher priority than Flask-Admin's default
|
|
15
|
+
Bootstrap ones.
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from flask import Flask
|
|
19
|
+
from flask_admin import Admin
|
|
20
|
+
from flask_admin_tabler import TablerTheme
|
|
21
|
+
|
|
22
|
+
app = Flask(__name__)
|
|
23
|
+
app.config["SECRET_KEY"] = "change-me"
|
|
24
|
+
|
|
25
|
+
theme = TablerTheme()
|
|
26
|
+
theme.init_app(app) # must come before Admin(app, ...)
|
|
27
|
+
|
|
28
|
+
admin = Admin(app, name="My App", theme=theme)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
That's it — navigating to `/admin/` will now render the Tabler UI instead of
|
|
32
|
+
the default Bootswatch theme.
|
|
33
|
+
|
|
34
|
+
A fully runnable example (including a SQLAlchemy model and sample data) is
|
|
35
|
+
available in [`examples/quickstart.py`](examples/quickstart.py):
|
|
36
|
+
|
|
37
|
+
```shell
|
|
38
|
+
pip install flask-admin-tabler flask-sqlalchemy
|
|
39
|
+
python examples/quickstart.py
|
|
40
|
+
# open http://127.0.0.1:5000/admin/
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## How it works
|
|
44
|
+
|
|
45
|
+
`TablerTheme.init_app(app)` registers a Flask blueprint named
|
|
46
|
+
`flask_admin_tabler` that:
|
|
47
|
+
|
|
48
|
+
1. **Templates** — exposes `flask_admin_tabler/templates/tabler/` as a template
|
|
49
|
+
folder. Because this blueprint is registered *before* Flask-Admin's admin
|
|
50
|
+
blueprint, Flask resolves `admin/base.html` (and all other admin templates)
|
|
51
|
+
from here first.
|
|
52
|
+
2. **Static files** — serves the small amount of theme-specific CSS (e.g.
|
|
53
|
+
`admin/css/tabler/admin.css`) at `/static/flask_admin_tabler/`.
|
|
54
|
+
|
|
55
|
+
Tabler's core CSS and JS are loaded from the jsDelivr CDN — no local copies
|
|
56
|
+
needed.
|
|
57
|
+
|
|
58
|
+
## Requirements
|
|
59
|
+
|
|
60
|
+
- Python ≥ 3.10
|
|
61
|
+
- flask-admin ≥ 2.0.2
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/* Flask-Admin Tabler theme custom styles */
|
|
2
|
+
|
|
3
|
+
/* List view action buttons */
|
|
4
|
+
td.list-buttons-column {
|
|
5
|
+
white-space: nowrap;
|
|
6
|
+
width: 1%;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
a.icon,
|
|
10
|
+
form.icon {
|
|
11
|
+
display: inline-block;
|
|
12
|
+
padding: 0 2px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
form.icon button {
|
|
16
|
+
background: none;
|
|
17
|
+
border: none;
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
padding: 0;
|
|
20
|
+
color: inherit;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
form.icon button:hover,
|
|
24
|
+
a.icon:hover {
|
|
25
|
+
color: var(--tblr-primary);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* Inline form fields */
|
|
29
|
+
.inline-field-list .inline-field {
|
|
30
|
+
margin-bottom: 1rem;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Filter form table */
|
|
34
|
+
table.filters {
|
|
35
|
+
width: 100%;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* Model list checkbox column */
|
|
39
|
+
.list-checkbox-column {
|
|
40
|
+
width: 1%;
|
|
41
|
+
white-space: nowrap;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* Submit row spacing */
|
|
45
|
+
.submit-row {
|
|
46
|
+
display: flex;
|
|
47
|
+
gap: 0.5rem;
|
|
48
|
+
flex-wrap: wrap;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* Admin form fieldset */
|
|
52
|
+
.admin-form fieldset {
|
|
53
|
+
border: none;
|
|
54
|
+
padding: 0;
|
|
55
|
+
margin: 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Select2 adjustments for Tabler */
|
|
59
|
+
.select2-container .select2-selection--single {
|
|
60
|
+
height: calc(1.5em + 0.75rem + 2px);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.select2-container--default .select2-selection--single .select2-selection__rendered {
|
|
64
|
+
line-height: calc(1.5em + 0.75rem);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.select2-container--default .select2-selection--single .select2-selection__arrow {
|
|
68
|
+
height: calc(1.5em + 0.75rem);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* Searchable table filter */
|
|
72
|
+
tr.hidden-by-filter {
|
|
73
|
+
display: none;
|
|
74
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
.console {
|
|
2
|
+
position: relative;
|
|
3
|
+
width: 100%;
|
|
4
|
+
min-height: 400px;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.console-container {
|
|
8
|
+
border-radius: 4px;
|
|
9
|
+
position: absolute;
|
|
10
|
+
border: 1px solid #d4d4d4;
|
|
11
|
+
padding: 2px;
|
|
12
|
+
overflow: scroll;
|
|
13
|
+
top: 2px;
|
|
14
|
+
left: 2px;
|
|
15
|
+
right: 2px;
|
|
16
|
+
bottom: 5em;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.console-line {
|
|
20
|
+
position: absolute;
|
|
21
|
+
left: 2px;
|
|
22
|
+
right: 2px;
|
|
23
|
+
bottom: 2px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.console-line input {
|
|
27
|
+
width: 100%;
|
|
28
|
+
box-sizing: border-box;
|
|
29
|
+
-moz-box-sizing: border-box;
|
|
30
|
+
-webkit-box-sizing: border-box;
|
|
31
|
+
height: 2em;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.console .cmd {
|
|
35
|
+
background-color: #f5f5f5;
|
|
36
|
+
padding: 2px;
|
|
37
|
+
margin: 1px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.console .response {
|
|
41
|
+
background-color: #f0f0f0;
|
|
42
|
+
padding: 2px;
|
|
43
|
+
margin: 1px;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.console .error {
|
|
47
|
+
color: red;
|
|
48
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{% import 'admin/static.html' as admin_static with context %}
|
|
2
|
+
|
|
3
|
+
{% macro dropdown(actions, btn_class='nav-link dropdown-toggle') -%}
|
|
4
|
+
<a class="{{ btn_class }}" data-bs-toggle="dropdown" href="javascript:void(0)" role="button" aria-haspopup="true"
|
|
5
|
+
aria-expanded="false">{{ _gettext('With selected') }}</a>
|
|
6
|
+
<div class="dropdown-menu">
|
|
7
|
+
{% for p in actions %}
|
|
8
|
+
<a class="dropdown-item" href="javascript:void(0)"
|
|
9
|
+
onclick="return modelActions.execute('{{ p[0] }}');">{{ _gettext(p[1]) }}</a>
|
|
10
|
+
{% endfor %}
|
|
11
|
+
</div>
|
|
12
|
+
{% endmacro %}
|
|
13
|
+
|
|
14
|
+
{% macro form(actions, url) %}
|
|
15
|
+
{% if actions %}
|
|
16
|
+
<form id="action_form" action="{{ url }}" method="POST" class="d-none">
|
|
17
|
+
{% if action_form.csrf_token is defined and action_form.csrf_token %}
|
|
18
|
+
{{ action_form.csrf_token }}
|
|
19
|
+
{% elif csrf_token is defined and csrf_token %}
|
|
20
|
+
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
|
21
|
+
{% endif %}
|
|
22
|
+
{% if return_url is defined and return_url %}
|
|
23
|
+
{{ action_form.url(value=return_url) }}
|
|
24
|
+
{% else %}
|
|
25
|
+
{{ action_form.url() }}
|
|
26
|
+
{% endif %}
|
|
27
|
+
{{ action_form.action() }}
|
|
28
|
+
</form>
|
|
29
|
+
{% endif %}
|
|
30
|
+
{% endmacro %}
|
|
31
|
+
|
|
32
|
+
{% macro script(message, actions, actions_confirmation) %}
|
|
33
|
+
{% if actions %}
|
|
34
|
+
<div id="actions-confirmation-data" class="d-none">{{ actions_confirmation|tojson|safe }}</div>
|
|
35
|
+
<div id="message-data" class="d-none">{{ message|tojson|safe }}</div>
|
|
36
|
+
<script {{ admin_csp_nonce_attribute }} src="{{ admin_static.url(filename='admin/js/actions.js', v='1.0.0') }}"></script>
|
|
37
|
+
{% endif %}
|
|
38
|
+
{% endmacro %}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
{% import 'admin/layout.html' as layout with context -%}
|
|
2
|
+
{% import 'admin/static.html' as admin_static with context %}
|
|
3
|
+
<!DOCTYPE html>
|
|
4
|
+
<html lang="en">
|
|
5
|
+
<head>
|
|
6
|
+
<title>{% block title %}{% if admin_view.category %}{{ admin_view.category }} - {% endif %}{{ admin_view.name }} - {{ admin_view.admin.name }}{% endblock %}</title>
|
|
7
|
+
{% block head_meta %}
|
|
8
|
+
<meta charset="UTF-8">
|
|
9
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
10
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
11
|
+
<meta name="description" content="">
|
|
12
|
+
<meta name="author" content="">
|
|
13
|
+
{% endblock %}
|
|
14
|
+
{% block head_css %}
|
|
15
|
+
<link href="https://cdn.jsdelivr.net/npm/@tabler/core@1.4.0/dist/css/tabler.min.css" rel="stylesheet" {{ admin_csp_nonce_attribute }}>
|
|
16
|
+
{% if admin_view.admin.theme.tabler_icons %}
|
|
17
|
+
<link href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@3.31.0/dist/tabler-icons.min.css" rel="stylesheet" {{ admin_csp_nonce_attribute }}>
|
|
18
|
+
{% endif %}
|
|
19
|
+
<link href="{{ url_for('flask_admin_tabler.static', filename='admin/css/tabler/admin.css') }}" rel="stylesheet" {{ admin_csp_nonce_attribute }}>
|
|
20
|
+
{% if admin_view.extra_css %}
|
|
21
|
+
{% for css_url in admin_view.extra_css %}
|
|
22
|
+
<link href="{{ css_url }}" rel="stylesheet" {{ admin_csp_nonce_attribute }}>
|
|
23
|
+
{% endfor %}
|
|
24
|
+
{% endif %}
|
|
25
|
+
{% endblock %}
|
|
26
|
+
{% block head %}
|
|
27
|
+
{% endblock %}
|
|
28
|
+
{% block head_tail %}
|
|
29
|
+
{% endblock %}
|
|
30
|
+
</head>
|
|
31
|
+
<body class="antialiased">
|
|
32
|
+
<div class="page">
|
|
33
|
+
{% block page_body %}
|
|
34
|
+
<!-- Sticky top navbar -->
|
|
35
|
+
<header class="navbar navbar-expand-md sticky-top d-print-none">
|
|
36
|
+
<div class="container-xl">
|
|
37
|
+
{% block brand %}
|
|
38
|
+
<a class="navbar-brand navbar-brand-autodark" href="{{ admin_view.admin.url }}">{{ admin_view.admin.name }}</a>
|
|
39
|
+
{% endblock %}
|
|
40
|
+
<!-- Toggle button for smaller screens -->
|
|
41
|
+
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar-menu"
|
|
42
|
+
aria-controls="navbar-menu" aria-expanded="false" aria-label="Toggle navigation">
|
|
43
|
+
<span class="navbar-toggler-icon"></span>
|
|
44
|
+
</button>
|
|
45
|
+
<!-- Collapsible menu -->
|
|
46
|
+
<div class="collapse navbar-collapse" id="navbar-menu">
|
|
47
|
+
{% block main_menu %}
|
|
48
|
+
<ul class="navbar-nav me-auto">
|
|
49
|
+
{{ layout.menu() }}
|
|
50
|
+
</ul>
|
|
51
|
+
{% endblock %}
|
|
52
|
+
{% block menu_links %}
|
|
53
|
+
<ul class="navbar-nav ms-auto">
|
|
54
|
+
{{ layout.menu_links() }}
|
|
55
|
+
</ul>
|
|
56
|
+
{% endblock %}
|
|
57
|
+
{% block access_control %}
|
|
58
|
+
{% endblock %}
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</header>
|
|
62
|
+
|
|
63
|
+
<!-- Page wrapper -->
|
|
64
|
+
<div class="page-wrapper">
|
|
65
|
+
<!-- Page header -->
|
|
66
|
+
<div class="page-header d-print-none">
|
|
67
|
+
<div class="container-xl">
|
|
68
|
+
<div class="row g-2 align-items-center">
|
|
69
|
+
<div class="col">
|
|
70
|
+
{% block page_title %}
|
|
71
|
+
<h2 class="page-title">
|
|
72
|
+
{{ layout.menu_icon(admin_view.menu) }}
|
|
73
|
+
{{ admin_view.name }}
|
|
74
|
+
</h2>
|
|
75
|
+
{% endblock %}
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<!-- Page body -->
|
|
82
|
+
<div class="page-body">
|
|
83
|
+
<div class="container-xl">
|
|
84
|
+
{% block messages %}
|
|
85
|
+
{{ layout.messages() }}
|
|
86
|
+
{% endblock %}
|
|
87
|
+
|
|
88
|
+
{# store the jinja2 context for form_rules rendering logic #}
|
|
89
|
+
{% set render_ctx = h.resolve_ctx() %}
|
|
90
|
+
|
|
91
|
+
{% block body %}{% endblock %}
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
{% endblock %}
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
{% block tail_js %}
|
|
99
|
+
<script {{ admin_csp_nonce_attribute }} src="{{ admin_static.url(filename='vendor/jquery.min.js', v='3.5.1') }}" type="text/javascript"></script>
|
|
100
|
+
<script {{ admin_csp_nonce_attribute }} src="https://cdn.jsdelivr.net/npm/@tabler/core@1.4.0/dist/js/tabler.min.js" type="text/javascript"></script>
|
|
101
|
+
<script {{ admin_csp_nonce_attribute }} src="{{ admin_static.url(filename='vendor/moment.min.js', v='2.9.4') }}" type="text/javascript"></script>
|
|
102
|
+
<script {{ admin_csp_nonce_attribute }} src="{{ admin_static.url(filename='vendor/select2/select2.min.js', v='4.2.1') }}"
|
|
103
|
+
type="text/javascript"></script>
|
|
104
|
+
<script {{ admin_csp_nonce_attribute }} src="{{ admin_static.url(filename='admin/js/helpers.js', v='1.0.0') }}" type="text/javascript"></script>
|
|
105
|
+
{% if admin_view.extra_js %}
|
|
106
|
+
{% for js_url in admin_view.extra_js %}
|
|
107
|
+
<script {{ admin_csp_nonce_attribute }} src="{{ js_url }}" type="text/javascript"></script>
|
|
108
|
+
{% endfor %}
|
|
109
|
+
{% endif %}
|
|
110
|
+
{% endblock %}
|
|
111
|
+
|
|
112
|
+
{% block tail %}
|
|
113
|
+
{% endblock %}
|
|
114
|
+
</body>
|
|
115
|
+
</html>
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
{% extends 'admin/master.html' %}
|
|
2
|
+
{% import 'admin/lib.html' as lib with context %}
|
|
3
|
+
{% import 'admin/actions.html' as actionslib with context %}
|
|
4
|
+
|
|
5
|
+
{% block body %}
|
|
6
|
+
{% block breadcrums %}
|
|
7
|
+
<nav aria-label="breadcrumb">
|
|
8
|
+
<ol class="breadcrumb">
|
|
9
|
+
<li class="breadcrumb-item">
|
|
10
|
+
<a href="{{ get_dir_url('.index_view', path=None) }}">{{ _gettext('Root') }}</a>
|
|
11
|
+
</li>
|
|
12
|
+
{% for name, path in breadcrumbs[:-1] %}
|
|
13
|
+
<li class="breadcrumb-item">
|
|
14
|
+
<a href="{{ get_dir_url('.index_view', path=path) }}">{{ name }}</a>
|
|
15
|
+
</li>
|
|
16
|
+
{% endfor %}
|
|
17
|
+
{% if breadcrumbs %}
|
|
18
|
+
<li class="breadcrumb-item">
|
|
19
|
+
<a href="{{ get_dir_url('.index_view', path=breadcrumbs[-1][1]) }}">{{ breadcrumbs[-1][0] }}</a>
|
|
20
|
+
</li>
|
|
21
|
+
{% endif %}
|
|
22
|
+
</ol>
|
|
23
|
+
</nav>
|
|
24
|
+
{% endblock %}
|
|
25
|
+
|
|
26
|
+
{% block file_list_table %}
|
|
27
|
+
<div class="table-responsive">
|
|
28
|
+
<table class="table table-vcenter table-striped table-bordered model-list">
|
|
29
|
+
<thead>
|
|
30
|
+
<tr>
|
|
31
|
+
{% block list_header scoped %}
|
|
32
|
+
{% if actions %}
|
|
33
|
+
<th class="list-checkbox-column">
|
|
34
|
+
<input type="checkbox" name="rowtoggle" class="action-rowtoggle" />
|
|
35
|
+
</th>
|
|
36
|
+
{% endif %}
|
|
37
|
+
<th class=""> </th>
|
|
38
|
+
{% for column in admin_view.column_list %}
|
|
39
|
+
<th>
|
|
40
|
+
{% if admin_view.is_column_sortable(column) %}
|
|
41
|
+
{% if sort_column == column %}
|
|
42
|
+
<a href="{{ sort_url(column, dir_path, True) }}" title="{{ _gettext('Sort by %(name)s', name=column) }}">
|
|
43
|
+
{{ admin_view.column_label(column) }}
|
|
44
|
+
{% if sort_desc %}
|
|
45
|
+
<span class="fa fa-chevron-up"></span>
|
|
46
|
+
{% else %}
|
|
47
|
+
<span class="fa fa-chevron-down"></span>
|
|
48
|
+
{% endif %}
|
|
49
|
+
</a>
|
|
50
|
+
{% else %}
|
|
51
|
+
<a href="{{ sort_url(column, dir_path) }}" title="{{ _gettext('Sort by %(name)s', name=column) }}">{{ admin_view.column_label(column) }}</a>
|
|
52
|
+
{% endif %}
|
|
53
|
+
{% else %}
|
|
54
|
+
{{ _gettext(admin_view.column_label(column)) }}
|
|
55
|
+
{% endif %}
|
|
56
|
+
</th>
|
|
57
|
+
{% endfor %}
|
|
58
|
+
{% endblock %}
|
|
59
|
+
</tr>
|
|
60
|
+
</thead>
|
|
61
|
+
{% for name, path, is_dir, size, date in items %}
|
|
62
|
+
<tr>
|
|
63
|
+
{% block list_row scoped %}
|
|
64
|
+
{% if actions %}
|
|
65
|
+
<td>
|
|
66
|
+
{% if not is_dir %}
|
|
67
|
+
<input type="checkbox" name="rowid" class="action-checkbox" value="{{ path }}" />
|
|
68
|
+
{% endif %}
|
|
69
|
+
</td>
|
|
70
|
+
{% endif %}
|
|
71
|
+
<td>
|
|
72
|
+
{% block list_row_actions scoped %}
|
|
73
|
+
{% if admin_view.can_rename and path and name != '..' %}
|
|
74
|
+
{%- if admin_view.rename_modal -%}
|
|
75
|
+
{{ lib.add_modal_button(url=get_url('.rename', path=path, modal=True),
|
|
76
|
+
title=_gettext('Rename File'),
|
|
77
|
+
content='<i class="fa fa-pencil"></i>') }}
|
|
78
|
+
{% else %}
|
|
79
|
+
<a class="icon" href="{{ get_url('.rename', path=path) }}" title="{{ _gettext('Rename File') }}">
|
|
80
|
+
<i class="fa fa-pencil"></i>
|
|
81
|
+
</a>
|
|
82
|
+
{%- endif -%}
|
|
83
|
+
{% endif %}
|
|
84
|
+
{%- if admin_view.can_delete and path -%}
|
|
85
|
+
{% if is_dir %}
|
|
86
|
+
{% if name != '..' and admin_view.can_delete_dirs %}
|
|
87
|
+
<form class="icon" method="POST" action="{{ get_url('.delete') }}">
|
|
88
|
+
{{ delete_form.path(value=path) }}
|
|
89
|
+
{% if delete_form.csrf_token is defined and delete_form.csrf_token %}
|
|
90
|
+
{{ delete_form.csrf_token }}
|
|
91
|
+
{% endif %}
|
|
92
|
+
<button onclick="return confirm('{{ _gettext('Are you sure you want to delete \\\'%(name)s\\\' recursively?', name=name) }}')">
|
|
93
|
+
<i class="fa fa-times"></i>
|
|
94
|
+
</button>
|
|
95
|
+
</form>
|
|
96
|
+
{% endif %}
|
|
97
|
+
{% else %}
|
|
98
|
+
<form class="icon" method="POST" action="{{ get_url('.delete') }}">
|
|
99
|
+
{{ delete_form.path(value=path) }}
|
|
100
|
+
{% if delete_form.csrf_token is defined and delete_form.csrf_token %}
|
|
101
|
+
{{ delete_form.csrf_token }}
|
|
102
|
+
{% endif %}
|
|
103
|
+
<button onclick="return confirm('{{ _gettext('Are you sure you want to delete \\\'%(name)s\\\'?', name=name) }}')">
|
|
104
|
+
<i class="fa fa-trash"></i>
|
|
105
|
+
</button>
|
|
106
|
+
</form>
|
|
107
|
+
{% endif %}
|
|
108
|
+
{%- endif -%}
|
|
109
|
+
{% endblock %}
|
|
110
|
+
</td>
|
|
111
|
+
{% if is_dir %}
|
|
112
|
+
<td colspan="2">
|
|
113
|
+
<a href="{{ get_dir_url('.index_view', path)|safe }}">
|
|
114
|
+
<i class="fa fa-folder-o"></i> <span>{{ name }}</span>
|
|
115
|
+
</a>
|
|
116
|
+
</td>
|
|
117
|
+
{% else %}
|
|
118
|
+
<td>
|
|
119
|
+
{% if admin_view.can_download %}
|
|
120
|
+
{%- if admin_view.edit_modal and admin_view.is_file_editable(path) -%}
|
|
121
|
+
{{ lib.add_modal_button(url=get_file_url(path, modal=True)|safe,
|
|
122
|
+
btn_class='', content=name) }}
|
|
123
|
+
{% else %}
|
|
124
|
+
<a href="{{ get_file_url(path)|safe }}">{{ name }}</a>
|
|
125
|
+
{%- endif -%}
|
|
126
|
+
{% else %}
|
|
127
|
+
{{ name }}
|
|
128
|
+
{% endif %}
|
|
129
|
+
</td>
|
|
130
|
+
{% if admin_view.is_column_visible('size') %}
|
|
131
|
+
<td>
|
|
132
|
+
{{ size|filesizeformat }}
|
|
133
|
+
</td>
|
|
134
|
+
{% endif %}
|
|
135
|
+
{% endif %}
|
|
136
|
+
{% if admin_view.is_column_visible('date') %}
|
|
137
|
+
<td>
|
|
138
|
+
{{ timestamp_format(date) }}
|
|
139
|
+
</td>
|
|
140
|
+
{% endif %}
|
|
141
|
+
{% endblock %}
|
|
142
|
+
</tr>
|
|
143
|
+
{% endfor %}
|
|
144
|
+
</table>
|
|
145
|
+
</div>
|
|
146
|
+
{% endblock %}
|
|
147
|
+
{% block toolbar %}
|
|
148
|
+
<div class="btn-toolbar">
|
|
149
|
+
{% if admin_view.can_upload %}
|
|
150
|
+
<div class="btn-group">
|
|
151
|
+
{%- if admin_view.upload_modal -%}
|
|
152
|
+
{{ lib.add_modal_button(url=get_dir_url('.upload', path=dir_path, modal=True),
|
|
153
|
+
btn_class="btn btn-secondary",
|
|
154
|
+
content=_gettext('Upload File')) }}
|
|
155
|
+
{% else %}
|
|
156
|
+
<a class="btn btn-secondary" href="{{ get_dir_url('.upload', path=dir_path) }}">{{ _gettext('Upload File') }}</a>
|
|
157
|
+
{%- endif -%}
|
|
158
|
+
</div>
|
|
159
|
+
{% endif %}
|
|
160
|
+
{% if admin_view.can_mkdir %}
|
|
161
|
+
<div class="ms-1">
|
|
162
|
+
{%- if admin_view.mkdir_modal -%}
|
|
163
|
+
{{ lib.add_modal_button(url=get_dir_url('.mkdir', path=dir_path, modal=True),
|
|
164
|
+
btn_class="btn btn-secondary",
|
|
165
|
+
content=_gettext('Create Directory')) }}
|
|
166
|
+
{% else %}
|
|
167
|
+
<a class="btn btn-secondary" href="{{ get_dir_url('.mkdir', path=dir_path) }}">{{ _gettext('Create Directory') }}</a>
|
|
168
|
+
{%- endif -%}
|
|
169
|
+
</div>
|
|
170
|
+
{% endif %}
|
|
171
|
+
{% if actions %}
|
|
172
|
+
<div class="ms-1">
|
|
173
|
+
{{ actionslib.dropdown(actions, 'dropdown-toggle btn btn-secondary') }}
|
|
174
|
+
</div>
|
|
175
|
+
{% endif %}
|
|
176
|
+
</div>
|
|
177
|
+
{% endblock %}
|
|
178
|
+
|
|
179
|
+
{% block actions %}
|
|
180
|
+
{{ actionslib.form(actions, get_url('.action_view')) }}
|
|
181
|
+
{% endblock %}
|
|
182
|
+
|
|
183
|
+
{%- if admin_view.rename_modal or admin_view.mkdir_modal
|
|
184
|
+
or admin_view.upload_modal or admin_view.edit_modal -%}
|
|
185
|
+
{{ lib.add_modal_window() }}
|
|
186
|
+
{%- endif -%}
|
|
187
|
+
{% endblock %}
|
|
188
|
+
|
|
189
|
+
{% block tail %}
|
|
190
|
+
{{ super() }}
|
|
191
|
+
{{ actionslib.script(_gettext('Please select at least one file.'),
|
|
192
|
+
actions,
|
|
193
|
+
actions_confirmation) }}
|
|
194
|
+
<script {{ admin_csp_nonce_attribute }} src="{{ admin_static.url(filename='admin/js/bs4_modal.js', v='1.0.0') }}"></script>
|
|
195
|
+
{% endblock %}
|