laketower 0.5.1__py3-none-any.whl → 0.6.5__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 laketower might be problematic. Click here for more details.
- laketower/__about__.py +1 -1
- laketower/cli.py +269 -101
- laketower/config.py +96 -14
- laketower/static/datatables.bundle.js +27931 -0
- laketower/static/datatables.js +55 -0
- laketower/static/editor.bundle.js +27433 -0
- laketower/static/editor.js +74 -0
- laketower/static/vendor/bootstrap/bootstrap.bundle.min.js +7 -0
- laketower/static/vendor/bootstrap-icons/bootstrap-icons.min.css +5 -0
- laketower/static/vendor/bootstrap-icons/fonts/bootstrap-icons.woff +0 -0
- laketower/static/vendor/bootstrap-icons/fonts/bootstrap-icons.woff2 +0 -0
- laketower/static/vendor/datatables.net-bs5/dataTables.bootstrap5.css +610 -0
- laketower/static/vendor/datatables.net-columncontrol-bs5/columnControl.bootstrap5.min.css +1 -0
- laketower/static/vendor/halfmoon/halfmoon.min.css +22 -0
- laketower/static/vendor/halfmoon/halfmoon.modern.css +282 -0
- laketower/tables.py +218 -16
- laketower/templates/_base.html +99 -20
- laketower/templates/queries/view.html +50 -8
- laketower/templates/tables/_macros.html +3 -0
- laketower/templates/tables/history.html +6 -0
- laketower/templates/tables/import.html +71 -0
- laketower/templates/tables/index.html +6 -0
- laketower/templates/tables/query.html +53 -7
- laketower/templates/tables/statistics.html +10 -4
- laketower/templates/tables/view.html +48 -42
- laketower/web.py +253 -30
- {laketower-0.5.1.dist-info → laketower-0.6.5.dist-info}/METADATA +189 -5
- laketower-0.6.5.dist-info/RECORD +35 -0
- laketower-0.6.5.dist-info/entry_points.txt +2 -0
- laketower-0.5.1.dist-info/RECORD +0 -22
- laketower-0.5.1.dist-info/entry_points.txt +0 -2
- {laketower-0.5.1.dist-info → laketower-0.6.5.dist-info}/WHEEL +0 -0
- {laketower-0.5.1.dist-info → laketower-0.6.5.dist-info}/licenses/LICENSE +0 -0
laketower/templates/_base.html
CHANGED
|
@@ -4,12 +4,19 @@
|
|
|
4
4
|
<head>
|
|
5
5
|
<meta charset="utf-8">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
7
|
-
<title>
|
|
8
|
-
<link href="
|
|
9
|
-
<link href="
|
|
10
|
-
|
|
11
|
-
<link href="
|
|
12
|
-
|
|
7
|
+
<title>{{ app_metadata.app_name }}</title>
|
|
8
|
+
<link href="{{ url_for('static', path='/vendor/bootstrap-icons/bootstrap-icons.min.css') }}" rel="stylesheet">
|
|
9
|
+
<link href="{{ url_for('static', path='/vendor/halfmoon/halfmoon.min.css') }}" rel="stylesheet">
|
|
10
|
+
<link href="{{ url_for('static', path='/vendor/halfmoon/halfmoon.modern.css') }}" rel="stylesheet">
|
|
11
|
+
<link href="{{ url_for('static', path='/vendor/datatables.net-bs5/dataTables.bootstrap5.css') }}" rel="stylesheet">
|
|
12
|
+
<link href="{{ url_for('static', path='/vendor/datatables.net-columncontrol-bs5/columnControl.bootstrap5.min.css') }}" rel="stylesheet">
|
|
13
|
+
<style>
|
|
14
|
+
/* Only override overflow when DataTables is initialized */
|
|
15
|
+
.table-responsive:has(table.dataTable) {
|
|
16
|
+
overflow: visible !important;
|
|
17
|
+
}
|
|
18
|
+
</style>
|
|
19
|
+
{% block extra_styles %}{% endblock %}
|
|
13
20
|
</head>
|
|
14
21
|
|
|
15
22
|
<body class="ps-md-sbwidth">
|
|
@@ -17,7 +24,7 @@
|
|
|
17
24
|
<nav class="sidebar offcanvas-start offcanvas-md" tabindex="-1" id="sidebar">
|
|
18
25
|
<div class="offcanvas-header border-bottom">
|
|
19
26
|
<a class="sidebar-brand" href="/">
|
|
20
|
-
|
|
27
|
+
{{ app_metadata.app_name }}
|
|
21
28
|
</a>
|
|
22
29
|
<button
|
|
23
30
|
type="button"
|
|
@@ -28,8 +35,9 @@
|
|
|
28
35
|
>
|
|
29
36
|
</button>
|
|
30
37
|
</div>
|
|
31
|
-
<div class="offcanvas-body">
|
|
32
|
-
<ul class="sidebar-nav">
|
|
38
|
+
<div class="offcanvas-body d-flex flex-column">
|
|
39
|
+
<ul class="sidebar-nav flex-grow-1 overflow-auto">
|
|
40
|
+
{% if not request.app.state.config.settings.web.hide_tables %}
|
|
33
41
|
<li>
|
|
34
42
|
<h6 class="sidebar-header">Tables</h6>
|
|
35
43
|
</li>
|
|
@@ -38,7 +46,7 @@
|
|
|
38
46
|
{% set table_url = '/tables/' + table.name %}
|
|
39
47
|
<li class="nav-item">
|
|
40
48
|
<a
|
|
41
|
-
class="text-truncate nav-link{% if request.url.path.startswith(table_url) %} active{% endif %}"
|
|
49
|
+
class="text-truncate nav-link{% if request.url.path.startswith(table_url + '/') or request.url.path == table_url %} active{% endif %}"
|
|
42
50
|
href="{{ table_url }}"
|
|
43
51
|
aria-current="page"
|
|
44
52
|
>
|
|
@@ -49,6 +57,7 @@
|
|
|
49
57
|
{% endfor %}
|
|
50
58
|
|
|
51
59
|
<li><hr class="sidebar-divider"></li>
|
|
60
|
+
{% endif %}
|
|
52
61
|
|
|
53
62
|
<li>
|
|
54
63
|
<h6 class="sidebar-header">Queries</h6>
|
|
@@ -58,7 +67,7 @@
|
|
|
58
67
|
{% set query_url = '/queries/' + query.name + '/view' %}
|
|
59
68
|
<li class="nav-item">
|
|
60
69
|
<a
|
|
61
|
-
class="text-truncate nav-link{% if request.url.path.startswith(query_url) %} active{% endif %}"
|
|
70
|
+
class="text-truncate nav-link{% if request.url.path.startswith(query_url + '/') or request.url.path == query_url %} active{% endif %}"
|
|
62
71
|
href="{{ query_url }}"
|
|
63
72
|
aria-current="page"
|
|
64
73
|
>
|
|
@@ -68,6 +77,15 @@
|
|
|
68
77
|
</li>
|
|
69
78
|
{% endfor %}
|
|
70
79
|
</ul>
|
|
80
|
+
|
|
81
|
+
<div class="mt-auto pt-3 border-top flex-shrink-0">
|
|
82
|
+
<div class="px-3 d-block">
|
|
83
|
+
<button type="button" class="btn btn-link btn-sm text-muted text-decoration-none p-0" data-bs-toggle="modal" data-bs-target="#creditsModal">
|
|
84
|
+
<i class="bi-info-circle me-1"></i>
|
|
85
|
+
About
|
|
86
|
+
</button>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
71
89
|
</div>
|
|
72
90
|
</nav>
|
|
73
91
|
|
|
@@ -78,8 +96,8 @@
|
|
|
78
96
|
</button>
|
|
79
97
|
|
|
80
98
|
<a href="/" class="navbar-brand d-flex align-items-center d-md-none ms-auto ms-md-0">
|
|
81
|
-
|
|
82
|
-
|
|
99
|
+
{{ app_metadata.app_name }}
|
|
100
|
+
</a>
|
|
83
101
|
</div>
|
|
84
102
|
</nav>
|
|
85
103
|
</header>
|
|
@@ -90,14 +108,75 @@
|
|
|
90
108
|
</div>
|
|
91
109
|
</main>
|
|
92
110
|
|
|
93
|
-
<
|
|
111
|
+
<div class="modal fade" id="creditsModal" tabindex="-1" aria-labelledby="creditsModalLabel" aria-hidden="true">
|
|
112
|
+
<div class="modal-dialog modal-dialog-centered">
|
|
113
|
+
<div class="modal-content">
|
|
114
|
+
<div class="modal-header">
|
|
115
|
+
<h5 class="modal-title" id="creditsModalLabel">About</h5>
|
|
116
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
117
|
+
</div>
|
|
118
|
+
<div class="modal-body">
|
|
119
|
+
<div class="text-center mb-3">
|
|
120
|
+
<h4>{{ app_metadata.app_name }}</h4>
|
|
121
|
+
<p class="text-muted mb-0">Oversee your lakehouse</p>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<dl class="row mb-3">
|
|
125
|
+
<dt class="col-sm-4">Version</dt>
|
|
126
|
+
<dd class="col-sm-8">
|
|
127
|
+
<a href="https://github.com/datalpia/laketower/releases/tag/{{ app_metadata.app_version }}"
|
|
128
|
+
target="_blank"
|
|
129
|
+
class="text-decoration-none">
|
|
130
|
+
{{ app_metadata.app_version }}
|
|
131
|
+
<i class="bi-box-arrow-up-right ms-1" style="font-size: 0.8em;"></i>
|
|
132
|
+
</a>
|
|
133
|
+
</dd>
|
|
134
|
+
|
|
135
|
+
<dt class="col-sm-4">Repository</dt>
|
|
136
|
+
<dd class="col-sm-8">
|
|
137
|
+
<a href="https://github.com/datalpia/laketower"
|
|
138
|
+
target="_blank"
|
|
139
|
+
class="text-decoration-none">
|
|
140
|
+
github.com/datalpia/laketower
|
|
141
|
+
<i class="bi-box-arrow-up-right ms-1" style="font-size: 0.8em;"></i>
|
|
142
|
+
</a>
|
|
143
|
+
</dd>
|
|
144
|
+
|
|
145
|
+
<dt class="col-sm-4">Issue Tracker</dt>
|
|
146
|
+
<dd class="col-sm-8">
|
|
147
|
+
<a href="https://github.com/datalpia/laketower/issues"
|
|
148
|
+
target="_blank"
|
|
149
|
+
class="text-decoration-none">
|
|
150
|
+
Report an issue
|
|
151
|
+
<i class="bi-box-arrow-up-right ms-1" style="font-size: 0.8em;"></i>
|
|
152
|
+
</a>
|
|
153
|
+
</dd>
|
|
154
|
+
|
|
155
|
+
<dt class="col-sm-4">License</dt>
|
|
156
|
+
<dd class="col-sm-8">
|
|
157
|
+
<a href="https://github.com/datalpia/laketower/blob/main/LICENSE"
|
|
158
|
+
target="_blank"
|
|
159
|
+
class="text-decoration-none">
|
|
160
|
+
Apache License 2.0
|
|
161
|
+
<i class="bi-box-arrow-up-right ms-1" style="font-size: 0.8em;"></i>
|
|
162
|
+
</a>
|
|
163
|
+
</dd>
|
|
164
|
+
</dl>
|
|
165
|
+
|
|
166
|
+
<div class="text-center text-muted">
|
|
167
|
+
<small>Copyright © 2025 Romain Clement / Datalpia</small>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
<div class="modal-footer">
|
|
171
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
94
176
|
|
|
95
|
-
<script src="
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js"
|
|
99
|
-
integrity="sha384-0pUGZvbkm6XF6gxjEnlmuGrJXVbNuzT9qBBavbLwCsOGabYfZo0T0to5eqruptLy"
|
|
100
|
-
crossorigin="anonymous"></script>
|
|
177
|
+
<script src="{{ url_for('static', path='/vendor/bootstrap/bootstrap.bundle.min.js') }}"></script>
|
|
178
|
+
<script src="{{ url_for('static', path='/datatables.bundle.js') }}"></script>
|
|
179
|
+
{% block extra_scripts %}{% endblock %}
|
|
101
180
|
</body>
|
|
102
181
|
|
|
103
182
|
</html>
|
|
@@ -1,20 +1,37 @@
|
|
|
1
1
|
{% extends "_base.html" %}
|
|
2
2
|
|
|
3
|
+
{% block extra_styles %}
|
|
4
|
+
{% endblock %}
|
|
5
|
+
|
|
3
6
|
{% block body %}
|
|
4
7
|
<div class="row">
|
|
5
8
|
<div class="col">
|
|
6
9
|
<h2 class="mb-3">{{ query.title }}</h2>
|
|
7
10
|
|
|
11
|
+
{% if query.description %}
|
|
12
|
+
<div>
|
|
13
|
+
{{ query.description | render_markdown | safe }}
|
|
14
|
+
</div>
|
|
15
|
+
{% endif %}
|
|
16
|
+
|
|
8
17
|
<form action="{{ request.url.path }}" method="get">
|
|
9
|
-
|
|
10
|
-
|
|
18
|
+
{% if sql_params|length %}
|
|
19
|
+
<h3 class="mb-3">Parameters</h3>
|
|
20
|
+
{% for param_name, param_value in sql_params.items() %}
|
|
21
|
+
<div class="row mb-3">
|
|
22
|
+
<label for="param-{{ param_name }}" class="col-form-label col-sm-2">{{ param_name }}</label>
|
|
23
|
+
<div class="col-sm-4">
|
|
24
|
+
<input id="param-{{ param_name }}" class="form-control" name="{{ param_name }}" value="{{ param_value }}">
|
|
25
|
+
</div>
|
|
11
26
|
</div>
|
|
27
|
+
{% endfor %}
|
|
28
|
+
{% endif %}
|
|
12
29
|
|
|
13
30
|
<div class="mb-3">
|
|
14
31
|
<div class="d-flex justify-content-end">
|
|
15
32
|
<div class="row">
|
|
16
33
|
<div class="col">
|
|
17
|
-
<a href="/tables/query?sql={{ query.sql | urlencode }}" class="btn btn-secondary" type="button"
|
|
34
|
+
<a href="/tables/query?sql={{ query.sql | urlencode }}{% if request.query_params | length > 0 %}&{{ request.query_params.multi_items() | urlencode}}{% endif %}" class="btn btn-secondary" type="button">
|
|
18
35
|
<i class="bi-code" aria-hidden="true"></i> Edit SQL
|
|
19
36
|
</a>
|
|
20
37
|
</div>
|
|
@@ -34,20 +51,33 @@
|
|
|
34
51
|
{{ error.message }}
|
|
35
52
|
</div>
|
|
36
53
|
{% else %}
|
|
54
|
+
<h3>Results</h3>
|
|
55
|
+
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
56
|
+
<p>
|
|
57
|
+
<i class="{% if truncated_results %}bi-database-exclamation{% else %}bi-database-check{% endif %}" aria-hidden="true"></i>
|
|
58
|
+
{{ query_results.num_rows }} rows returned{% if truncated_results %} (truncated){% endif %}
|
|
59
|
+
<br>
|
|
60
|
+
<i class="bi-speedometer" aria-hidden="true"></i>
|
|
61
|
+
Query execution time: {{ execution_time_ms | round(2) }}ms
|
|
62
|
+
</p>
|
|
63
|
+
<a href="/tables/query/csv?sql={{ query.sql | urlencode }}" class="btn btn-outline-secondary btn-sm">
|
|
64
|
+
<i class="bi-download" aria-hidden="true"></i> Export CSV
|
|
65
|
+
</a>
|
|
66
|
+
</div>
|
|
37
67
|
<div class="table-responsive">
|
|
38
|
-
<table class="table table-sm table-bordered table-striped table-hover">
|
|
68
|
+
<table id="resultsTable" class="table table-sm table-bordered table-striped table-hover">
|
|
39
69
|
<thead>
|
|
40
70
|
<tr>
|
|
41
|
-
{% for column in query_results.
|
|
71
|
+
{% for column in query_results.column_names %}
|
|
42
72
|
<th>{{ column }}</th>
|
|
43
73
|
{% endfor %}
|
|
44
74
|
</tr>
|
|
45
75
|
</thead>
|
|
46
76
|
<tbody class="table-group-divider">
|
|
47
|
-
{% for row in query_results.
|
|
77
|
+
{% for row in query_results.to_pylist() %}
|
|
48
78
|
<tr>
|
|
49
|
-
{% for
|
|
50
|
-
<td>{{
|
|
79
|
+
{% for column in query_results.column_names %}
|
|
80
|
+
<td>{{ row[column] }}</td>
|
|
51
81
|
{% endfor %}
|
|
52
82
|
</tr>
|
|
53
83
|
{% endfor %}
|
|
@@ -57,4 +87,16 @@
|
|
|
57
87
|
{% endif %}
|
|
58
88
|
</div>
|
|
59
89
|
</div>
|
|
90
|
+
{% endblock %}
|
|
91
|
+
|
|
92
|
+
{% block extra_scripts %}
|
|
93
|
+
<script>
|
|
94
|
+
window.addEventListener("DOMContentLoaded", () => {
|
|
95
|
+
{% if not error %}
|
|
96
|
+
const arrowTypes = {{ query_results.schema.types | map('string') | list | tojson }}
|
|
97
|
+
const columnTypes = datatables.arrowTypesToDataTables(arrowTypes)
|
|
98
|
+
const dataTable = datatables.createDataTable('#resultsTable', {columnTypes})
|
|
99
|
+
{% endif %}
|
|
100
|
+
})
|
|
101
|
+
</script>
|
|
60
102
|
{% endblock %}
|
|
@@ -12,5 +12,8 @@
|
|
|
12
12
|
<li class="nav-item">
|
|
13
13
|
<a class="nav-link{% if current == 'history' %} active{% endif %}"{% if current == 'history' %} aria-current="true"{% endif %} href="/tables/{{ table_id }}/history">History</a>
|
|
14
14
|
</li>
|
|
15
|
+
<li class="nav-item">
|
|
16
|
+
<a class="nav-link{% if current == 'import' %} active{% endif %}"{% if current == 'import' %} aria-current="true"{% endif %} href="/tables/{{ table_id }}/import">Import</a>
|
|
17
|
+
</li>
|
|
15
18
|
</ul>
|
|
16
19
|
{%- endmacro %}
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
{% import 'tables/_macros.html' as table_macros %}
|
|
3
3
|
|
|
4
4
|
{% block body %}
|
|
5
|
+
{% if error %}
|
|
6
|
+
<div class="alert alert-danger" role="alert">
|
|
7
|
+
{{ error.message }}
|
|
8
|
+
</div>
|
|
9
|
+
{% else %}
|
|
5
10
|
{{ table_macros.table_nav(table_id, 'history') }}
|
|
6
11
|
|
|
7
12
|
<div class="row">
|
|
@@ -39,4 +44,5 @@
|
|
|
39
44
|
{% endfor %}
|
|
40
45
|
</div>
|
|
41
46
|
</div>
|
|
47
|
+
{% endif %}
|
|
42
48
|
{% endblock %}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{% extends "_base.html" %}
|
|
2
|
+
{% import 'tables/_macros.html' as table_macros %}
|
|
3
|
+
|
|
4
|
+
{% block body %}
|
|
5
|
+
{{ table_macros.table_nav(table_id, 'import') }}
|
|
6
|
+
|
|
7
|
+
{% if message %}
|
|
8
|
+
{% if message.type == 'success' %}{% set alert_type = 'success' %}
|
|
9
|
+
{% elif message.type == 'error' %}{% set alert_type = 'danger' %}
|
|
10
|
+
{% else %}{% set alert_type = 'primary' %}
|
|
11
|
+
{% endif %}
|
|
12
|
+
|
|
13
|
+
<div class="alert alert-{{ alert_type }}" role="alert">
|
|
14
|
+
{{ message.body }}
|
|
15
|
+
</div>
|
|
16
|
+
{% endif %}
|
|
17
|
+
|
|
18
|
+
<div class="row justify-content-center">
|
|
19
|
+
<div class="col-12 col-md-8 col-lg-4">
|
|
20
|
+
<form action="{{ request.url.path }}" method="post" enctype="multipart/form-data">
|
|
21
|
+
<div class="mb-3">
|
|
22
|
+
<label for="import-file-input" class="form-label">Input file</label>
|
|
23
|
+
<input id="import-file-input" class="form-control" name="input_file" type="file" accept=".csv" required>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div class="mb-3">
|
|
27
|
+
<label class="form-label">Mode</label>
|
|
28
|
+
|
|
29
|
+
<div class="form-check">
|
|
30
|
+
<input id="import-mode-append" class="form-check-input" name="mode" type="radio" value="append" checked>
|
|
31
|
+
<label for="import-mode-append" class="form-check-label">Append</label>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="form-check">
|
|
34
|
+
<input id="import-mode-overwrite" class="form-check-input" name="mode" type="radio" value="overwrite">
|
|
35
|
+
<label for="import-mode-overwrite" class="form-check-label">Overwrite</label>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div class="mb-3">
|
|
40
|
+
<label for="import-file-format" class="form-label">File format</label>
|
|
41
|
+
<select id="import-file-format" class="form-select" name="file_format">
|
|
42
|
+
<option value="csv" selected>CSV</option>
|
|
43
|
+
</select>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div class="mb-3">
|
|
47
|
+
<label for="import-delimiter" class="form-label">Delimiter</label>
|
|
48
|
+
<input id="import-delimiter" class="form-control" name="delimiter" value="," required>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div class="mb-3">
|
|
52
|
+
<label for="import-encoding" class="form-label">Encoding</label>
|
|
53
|
+
<select id="import-encoding" class="form-select" name="encoding">
|
|
54
|
+
<option value="utf-8" selected>UTF-8</option>
|
|
55
|
+
<option value="utf-16">UTF-16</option>
|
|
56
|
+
<option value="utf-32">UTF-32</option>
|
|
57
|
+
<option value="latin-1">Latin-1</option>
|
|
58
|
+
</select>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div class="mb-3">
|
|
62
|
+
<div class="d-flex justify-content-end">
|
|
63
|
+
<button type="submit" class="btn btn-primary">
|
|
64
|
+
<i class="bi-upload" aria-hidden="true"></i> Import Data
|
|
65
|
+
</button>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</form>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
{% endblock %}
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
{% import 'tables/_macros.html' as table_macros %}
|
|
3
3
|
|
|
4
4
|
{% block body %}
|
|
5
|
+
{% if error %}
|
|
6
|
+
<div class="alert alert-danger" role="alert">
|
|
7
|
+
{{ error.message }}
|
|
8
|
+
</div>
|
|
9
|
+
{% else %}
|
|
5
10
|
{{ table_macros.table_nav(table_id, 'overview') }}
|
|
6
11
|
|
|
7
12
|
<div class="row row-cols-1 row-cols-md-2 g-4">
|
|
@@ -81,4 +86,5 @@
|
|
|
81
86
|
</div>
|
|
82
87
|
</div>
|
|
83
88
|
</div>
|
|
89
|
+
{% endif %}
|
|
84
90
|
{% endblock %}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
{% extends "_base.html" %}
|
|
2
2
|
{% import 'tables/_macros.html' as table_macros %}
|
|
3
3
|
|
|
4
|
+
{% block extra_styles %}
|
|
5
|
+
{% endblock %}
|
|
6
|
+
|
|
4
7
|
{% block body %}
|
|
5
8
|
<div class="row">
|
|
6
9
|
<div class="col">
|
|
@@ -8,8 +11,20 @@
|
|
|
8
11
|
|
|
9
12
|
<form action="{{ request.url.path }}" method="get">
|
|
10
13
|
<div class="mb-3">
|
|
11
|
-
<textarea name="sql" rows="5" class="form-control">{{ sql_query }}</textarea>
|
|
14
|
+
<textarea id="sql-editor" name="sql" rows="5" class="form-control">{{ sql_query }}</textarea>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
{% if sql_params|length %}
|
|
18
|
+
<h3 class="mb-3">Parameters</h3>
|
|
19
|
+
{% for param_name, param_value in sql_params.items() %}
|
|
20
|
+
<div class="row mb-3">
|
|
21
|
+
<label for="param-{{ param_name }}" class="col-form-label col-sm-2">{{ param_name }}</label>
|
|
22
|
+
<div class="col-sm-4">
|
|
23
|
+
<input id="param-{{ param_name }}" class="form-control" name="{{ param_name }}" value="{{ param_value }}">
|
|
24
|
+
</div>
|
|
12
25
|
</div>
|
|
26
|
+
{% endfor %}
|
|
27
|
+
{% endif %}
|
|
13
28
|
|
|
14
29
|
<div class="mb-3">
|
|
15
30
|
<div class="d-flex justify-content-end">
|
|
@@ -25,20 +40,33 @@
|
|
|
25
40
|
{{ error.message }}
|
|
26
41
|
</div>
|
|
27
42
|
{% else %}
|
|
43
|
+
<h3>Results</h3>
|
|
44
|
+
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
45
|
+
<p>
|
|
46
|
+
<i class="{% if truncated_results %}bi-database-exclamation{% else %}bi-database-check{% endif %}" aria-hidden="true"></i>
|
|
47
|
+
{{ table_results.num_rows }} rows returned{% if truncated_results %} (truncated){% endif %}
|
|
48
|
+
<br>
|
|
49
|
+
<i class="bi-speedometer" aria-hidden="true"></i>
|
|
50
|
+
Query execution time: {{ execution_time_ms | round(2) }}ms
|
|
51
|
+
</p>
|
|
52
|
+
<a href="/tables/query/csv?sql={{ sql_query | urlencode }}" class="btn btn-outline-secondary btn-sm">
|
|
53
|
+
<i class="bi-download" aria-hidden="true"></i> Export CSV
|
|
54
|
+
</a>
|
|
55
|
+
</div>
|
|
28
56
|
<div class="table-responsive">
|
|
29
|
-
<table class="table table-sm table-bordered table-striped table-hover">
|
|
57
|
+
<table id="resultsTable" class="table table-sm table-bordered table-striped table-hover">
|
|
30
58
|
<thead>
|
|
31
59
|
<tr>
|
|
32
|
-
{% for column in table_results.
|
|
60
|
+
{% for column in table_results.column_names %}
|
|
33
61
|
<th>{{ column }}</th>
|
|
34
62
|
{% endfor %}
|
|
35
63
|
</tr>
|
|
36
64
|
</thead>
|
|
37
|
-
<tbody
|
|
38
|
-
{% for row in table_results.
|
|
65
|
+
<tbody>
|
|
66
|
+
{% for row in table_results.to_pylist() %}
|
|
39
67
|
<tr>
|
|
40
|
-
{% for
|
|
41
|
-
<td>{{
|
|
68
|
+
{% for column in table_results.column_names %}
|
|
69
|
+
<td>{{ row[column] }}</td>
|
|
42
70
|
{% endfor %}
|
|
43
71
|
</tr>
|
|
44
72
|
{% endfor %}
|
|
@@ -48,4 +76,22 @@
|
|
|
48
76
|
{% endif %}
|
|
49
77
|
</div>
|
|
50
78
|
</div>
|
|
79
|
+
{% endblock %}
|
|
80
|
+
|
|
81
|
+
{% block extra_scripts %}
|
|
82
|
+
<script src="{{ url_for('static', path='/editor.bundle.js') }}"></script>
|
|
83
|
+
<script>
|
|
84
|
+
window.addEventListener("DOMContentLoaded", () => {
|
|
85
|
+
const textArea = document.querySelector("textarea#sql-editor")
|
|
86
|
+
textArea.style.display = "none"
|
|
87
|
+
const sqlSchema = {{ sql_schema | tojson }}
|
|
88
|
+
const sqlEditor = editor.createEditor(textArea, { readOnly: false, dialect: 'duckdb', schema: sqlSchema})
|
|
89
|
+
|
|
90
|
+
{% if not error %}
|
|
91
|
+
const arrowTypes = {{ table_results.schema.types | map('string') | list | tojson }}
|
|
92
|
+
const columnTypes = datatables.arrowTypesToDataTables(arrowTypes)
|
|
93
|
+
const dataTable = datatables.createDataTable('#resultsTable', {columnTypes})
|
|
94
|
+
{% endif %}
|
|
95
|
+
})
|
|
96
|
+
</script>
|
|
51
97
|
{% endblock %}
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
{% import 'tables/_macros.html' as table_macros %}
|
|
3
3
|
|
|
4
4
|
{% block body %}
|
|
5
|
+
{% if error %}
|
|
6
|
+
<div class="alert alert-danger" role="alert">
|
|
7
|
+
{{ error.message }}
|
|
8
|
+
</div>
|
|
9
|
+
{% else %}
|
|
5
10
|
{{ table_macros.table_nav(table_id, 'statistics') }}
|
|
6
11
|
|
|
7
12
|
<div class="row">
|
|
@@ -10,16 +15,16 @@
|
|
|
10
15
|
<table class="table table-sm table-bordered table-striped table-hover">
|
|
11
16
|
<thead>
|
|
12
17
|
<tr>
|
|
13
|
-
{% for column in table_results.
|
|
18
|
+
{% for column in table_results.column_names %}
|
|
14
19
|
<th>{{ column }}</th>
|
|
15
20
|
{% endfor %}
|
|
16
21
|
</tr>
|
|
17
22
|
</thead>
|
|
18
23
|
<tbody class="table-group-divider">
|
|
19
|
-
{% for row in table_results.
|
|
24
|
+
{% for row in table_results.to_pylist() %}
|
|
20
25
|
<tr>
|
|
21
|
-
{% for
|
|
22
|
-
<td>{{
|
|
26
|
+
{% for column in table_results.column_names %}
|
|
27
|
+
<td>{{ row[column] }}</td>
|
|
23
28
|
{% endfor %}
|
|
24
29
|
</tr>
|
|
25
30
|
{% endfor %}
|
|
@@ -53,4 +58,5 @@
|
|
|
53
58
|
</div>
|
|
54
59
|
</div>
|
|
55
60
|
</div>
|
|
61
|
+
{% endif %}
|
|
56
62
|
{% endblock %}
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
{% import 'tables/_macros.html' as table_macros %}
|
|
3
3
|
|
|
4
4
|
{% block body %}
|
|
5
|
+
{% if error %}
|
|
6
|
+
<div class="alert alert-danger" role="alert">
|
|
7
|
+
{{ error.message }}
|
|
8
|
+
</div>
|
|
9
|
+
{% else %}
|
|
5
10
|
{{ table_macros.table_nav(table_id, 'view') }}
|
|
6
11
|
|
|
7
12
|
<div class="row">
|
|
@@ -10,7 +15,7 @@
|
|
|
10
15
|
<table class="table table-sm table-bordered table-striped table-hover">
|
|
11
16
|
<thead>
|
|
12
17
|
<tr>
|
|
13
|
-
{% for column in table_results.
|
|
18
|
+
{% for column in table_results.column_names %}
|
|
14
19
|
<th>
|
|
15
20
|
{{ column }}
|
|
16
21
|
{% if column == request.query_params.sort_asc %}
|
|
@@ -26,7 +31,7 @@
|
|
|
26
31
|
<i class="bi-arrow-down-up" aria-hidden="true"></i>
|
|
27
32
|
</a>
|
|
28
33
|
{% endif %}
|
|
29
|
-
{% set other_cols = table_results.
|
|
34
|
+
{% set other_cols = table_results.column_names | list | reject('equalto', column) | list %}
|
|
30
35
|
{% set cols_args = [] %}
|
|
31
36
|
{% for col in other_cols %}
|
|
32
37
|
{% set tmp = cols_args.append(('cols', col)) %}
|
|
@@ -39,58 +44,59 @@
|
|
|
39
44
|
</tr>
|
|
40
45
|
</thead>
|
|
41
46
|
<tbody class="table-group-divider">
|
|
42
|
-
{% for row in table_results.
|
|
47
|
+
{% for row in table_results.to_pylist() %}
|
|
43
48
|
<tr>
|
|
44
|
-
{% for
|
|
45
|
-
<td>{{
|
|
49
|
+
{% for column in table_results.column_names %}
|
|
50
|
+
<td>{{ row[column] }}</td>
|
|
46
51
|
{% endfor %}
|
|
47
52
|
</tr>
|
|
48
53
|
{% endfor %}
|
|
49
54
|
</tbody>
|
|
50
55
|
</table>
|
|
56
|
+
</div>
|
|
51
57
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
<div class="input-group">
|
|
62
|
-
<input id="limit-input" name="limit" type="number" class="form-control" min="1" max="10000" value="{{ request.query_params.limit or default_limit }}">
|
|
63
|
-
<button type="submit" class="btn btn-primary">Limit</button>
|
|
64
|
-
</div>
|
|
65
|
-
</form>
|
|
58
|
+
<div class="d-flex justify-content-between">
|
|
59
|
+
<div class="row">
|
|
60
|
+
<form class="col" action="{{ request.url.path }}" method="get">
|
|
61
|
+
{% for param_name, param_val in request.query_params.multi_items() %}
|
|
62
|
+
{% if param_name != 'limit' %}
|
|
63
|
+
<input type="hidden" name="{{ param_name }}" value="{{ param_val }}">
|
|
64
|
+
{% endif %}
|
|
65
|
+
{% endfor %}
|
|
66
66
|
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
{% endfor %}
|
|
67
|
+
<div class="input-group">
|
|
68
|
+
<input id="limit-input" name="limit" type="number" class="form-control" min="1" max="10000" value="{{ request.query_params.limit or default_limit }}">
|
|
69
|
+
<button type="submit" class="btn btn-primary">Limit</button>
|
|
70
|
+
</div>
|
|
71
|
+
</form>
|
|
73
72
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
min="0"
|
|
81
|
-
max="{{ table_metadata.version }}"
|
|
82
|
-
value="{{ request.query_params.version or table_metadata.version }}"
|
|
83
|
-
>
|
|
84
|
-
<button type="submit" class="btn btn-primary">Version</button>
|
|
85
|
-
</div>
|
|
86
|
-
</form>
|
|
87
|
-
</div>
|
|
73
|
+
<form class="col" ="{{ request.url.path }}" method="get">
|
|
74
|
+
{% for param_name, param_val in request.query_params.multi_items() %}
|
|
75
|
+
{% if param_name != 'version' %}
|
|
76
|
+
<input type="hidden" name="{{ param_name }}" value="{{ param_val }}">
|
|
77
|
+
{% endif %}
|
|
78
|
+
{% endfor %}
|
|
88
79
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
80
|
+
<div class="input-group">
|
|
81
|
+
<input
|
|
82
|
+
id="version-input"
|
|
83
|
+
name="version"
|
|
84
|
+
type="number"
|
|
85
|
+
class="form-control"
|
|
86
|
+
min="0"
|
|
87
|
+
max="{{ table_metadata.version }}"
|
|
88
|
+
value="{{ request.query_params.version or table_metadata.version }}"
|
|
89
|
+
>
|
|
90
|
+
<button type="submit" class="btn btn-primary">Version</button>
|
|
91
|
+
</div>
|
|
92
|
+
</form>
|
|
92
93
|
</div>
|
|
94
|
+
|
|
95
|
+
<a href="/tables/query?sql={{ sql_query | urlencode }}" class="btn btn-primary" role="button">
|
|
96
|
+
<i class="bi-code" aria-hidden="true"></i> SQL Query
|
|
97
|
+
</a>
|
|
93
98
|
</div>
|
|
94
99
|
</div>
|
|
95
100
|
</div>
|
|
101
|
+
{% endif %}
|
|
96
102
|
{% endblock %}
|