meerschaum 2.9.5__py3-none-any.whl → 3.0.0rc2__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.
- meerschaum/__init__.py +5 -2
- meerschaum/_internal/__init__.py +1 -0
- meerschaum/_internal/arguments/_parse_arguments.py +4 -4
- meerschaum/_internal/arguments/_parser.py +19 -2
- meerschaum/_internal/docs/index.py +49 -2
- meerschaum/_internal/entry.py +6 -6
- meerschaum/_internal/shell/Shell.py +1 -1
- meerschaum/_internal/static.py +356 -0
- meerschaum/actions/api.py +12 -2
- meerschaum/actions/bootstrap.py +7 -7
- meerschaum/actions/edit.py +142 -18
- meerschaum/actions/register.py +137 -6
- meerschaum/actions/show.py +117 -29
- meerschaum/actions/stop.py +4 -1
- meerschaum/actions/sync.py +1 -1
- meerschaum/actions/tag.py +9 -8
- meerschaum/actions/verify.py +5 -8
- meerschaum/api/__init__.py +11 -3
- meerschaum/api/_events.py +39 -2
- meerschaum/api/_oauth2.py +118 -8
- meerschaum/api/_tokens.py +102 -0
- meerschaum/api/dash/__init__.py +0 -3
- meerschaum/api/dash/callbacks/custom.py +2 -2
- meerschaum/api/dash/callbacks/dashboard.py +103 -19
- meerschaum/api/dash/callbacks/plugins.py +0 -1
- meerschaum/api/dash/callbacks/register.py +1 -1
- meerschaum/api/dash/callbacks/settings/__init__.py +1 -0
- meerschaum/api/dash/callbacks/settings/password_reset.py +2 -2
- meerschaum/api/dash/callbacks/settings/tokens.py +388 -0
- meerschaum/api/dash/components.py +30 -8
- meerschaum/api/dash/keys.py +19 -93
- meerschaum/api/dash/pages/dashboard.py +1 -20
- meerschaum/api/dash/pages/settings/__init__.py +1 -0
- meerschaum/api/dash/pages/settings/password_reset.py +1 -1
- meerschaum/api/dash/pages/settings/tokens.py +55 -0
- meerschaum/api/dash/pipes.py +94 -59
- meerschaum/api/dash/sessions.py +12 -0
- meerschaum/api/dash/tokens.py +606 -0
- meerschaum/api/dash/websockets.py +1 -1
- meerschaum/api/dash/webterm.py +4 -0
- meerschaum/api/models/__init__.py +23 -3
- meerschaum/api/models/_actions.py +22 -0
- meerschaum/api/models/_pipes.py +85 -7
- meerschaum/api/models/_tokens.py +81 -0
- meerschaum/api/resources/templates/termpage.html +12 -0
- meerschaum/api/routes/__init__.py +1 -0
- meerschaum/api/routes/_actions.py +3 -4
- meerschaum/api/routes/_connectors.py +3 -7
- meerschaum/api/routes/_jobs.py +14 -35
- meerschaum/api/routes/_login.py +49 -12
- meerschaum/api/routes/_misc.py +5 -10
- meerschaum/api/routes/_pipes.py +173 -140
- meerschaum/api/routes/_plugins.py +38 -28
- meerschaum/api/routes/_tokens.py +236 -0
- meerschaum/api/routes/_users.py +47 -35
- meerschaum/api/routes/_version.py +3 -3
- meerschaum/config/__init__.py +43 -20
- meerschaum/config/_default.py +43 -6
- meerschaum/config/_edit.py +28 -24
- meerschaum/config/_environment.py +1 -1
- meerschaum/config/_patch.py +6 -6
- meerschaum/config/_paths.py +5 -1
- meerschaum/config/_read_config.py +65 -34
- meerschaum/config/_sync.py +6 -3
- meerschaum/config/_version.py +1 -1
- meerschaum/config/stack/__init__.py +31 -11
- meerschaum/config/static.py +18 -0
- meerschaum/connectors/_Connector.py +10 -4
- meerschaum/connectors/__init__.py +4 -20
- meerschaum/connectors/api/_APIConnector.py +34 -6
- meerschaum/connectors/api/_actions.py +2 -2
- meerschaum/connectors/api/_jobs.py +1 -1
- meerschaum/connectors/api/_login.py +33 -7
- meerschaum/connectors/api/_misc.py +2 -2
- meerschaum/connectors/api/_pipes.py +16 -31
- meerschaum/connectors/api/_plugins.py +2 -2
- meerschaum/connectors/api/_request.py +1 -1
- meerschaum/connectors/api/_tokens.py +146 -0
- meerschaum/connectors/api/_users.py +70 -58
- meerschaum/connectors/instance/_InstanceConnector.py +83 -0
- meerschaum/connectors/instance/__init__.py +10 -0
- meerschaum/connectors/instance/_pipes.py +442 -0
- meerschaum/connectors/instance/_plugins.py +151 -0
- meerschaum/connectors/instance/_tokens.py +296 -0
- meerschaum/connectors/instance/_users.py +181 -0
- meerschaum/connectors/parse.py +4 -1
- meerschaum/connectors/sql/_SQLConnector.py +8 -5
- meerschaum/connectors/sql/_cli.py +12 -11
- meerschaum/connectors/sql/_create_engine.py +9 -168
- meerschaum/connectors/sql/_fetch.py +2 -18
- meerschaum/connectors/sql/_pipes.py +156 -190
- meerschaum/connectors/sql/_plugins.py +29 -0
- meerschaum/connectors/sql/_sql.py +46 -21
- meerschaum/connectors/sql/_users.py +29 -2
- meerschaum/connectors/sql/tables/__init__.py +1 -1
- meerschaum/connectors/valkey/_ValkeyConnector.py +2 -4
- meerschaum/connectors/valkey/_pipes.py +53 -26
- meerschaum/connectors/valkey/_plugins.py +2 -26
- meerschaum/core/Pipe/__init__.py +59 -19
- meerschaum/core/Pipe/_attributes.py +412 -90
- meerschaum/core/Pipe/_bootstrap.py +54 -24
- meerschaum/core/Pipe/_data.py +96 -18
- meerschaum/core/Pipe/_dtypes.py +48 -18
- meerschaum/core/Pipe/_edit.py +14 -4
- meerschaum/core/Pipe/_fetch.py +1 -1
- meerschaum/core/Pipe/_show.py +5 -5
- meerschaum/core/Pipe/_sync.py +118 -193
- meerschaum/core/Pipe/_verify.py +4 -4
- meerschaum/{plugins → core/Plugin}/_Plugin.py +9 -11
- meerschaum/core/Plugin/__init__.py +1 -1
- meerschaum/core/Token/_Token.py +220 -0
- meerschaum/core/Token/__init__.py +12 -0
- meerschaum/core/User/_User.py +34 -8
- meerschaum/core/User/__init__.py +9 -1
- meerschaum/core/__init__.py +1 -0
- meerschaum/jobs/_Job.py +3 -2
- meerschaum/jobs/__init__.py +3 -2
- meerschaum/jobs/systemd.py +1 -1
- meerschaum/models/__init__.py +35 -0
- meerschaum/models/pipes.py +247 -0
- meerschaum/models/tokens.py +38 -0
- meerschaum/models/users.py +26 -0
- meerschaum/plugins/__init__.py +22 -7
- meerschaum/plugins/bootstrap.py +2 -1
- meerschaum/utils/_get_pipes.py +68 -27
- meerschaum/utils/daemon/Daemon.py +2 -1
- meerschaum/utils/daemon/__init__.py +30 -2
- meerschaum/utils/dataframe.py +473 -81
- meerschaum/utils/debug.py +15 -15
- meerschaum/utils/dtypes/__init__.py +473 -34
- meerschaum/utils/dtypes/sql.py +368 -28
- meerschaum/utils/formatting/__init__.py +1 -1
- meerschaum/utils/formatting/_pipes.py +5 -4
- meerschaum/utils/formatting/_shell.py +11 -9
- meerschaum/utils/misc.py +246 -148
- meerschaum/utils/packages/__init__.py +10 -27
- meerschaum/utils/packages/_packages.py +41 -34
- meerschaum/utils/pipes.py +181 -0
- meerschaum/utils/process.py +1 -1
- meerschaum/utils/prompt.py +3 -1
- meerschaum/utils/schedule.py +2 -1
- meerschaum/utils/sql.py +121 -44
- meerschaum/utils/typing.py +1 -4
- meerschaum/utils/venv/_Venv.py +2 -2
- meerschaum/utils/venv/__init__.py +5 -7
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc2.dist-info}/METADATA +92 -96
- meerschaum-3.0.0rc2.dist-info/RECORD +283 -0
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc2.dist-info}/WHEEL +1 -1
- meerschaum-3.0.0rc2.dist-info/licenses/NOTICE +2 -0
- meerschaum/api/models/_interfaces.py +0 -15
- meerschaum/api/models/_locations.py +0 -15
- meerschaum/api/models/_metrics.py +0 -15
- meerschaum/config/static/__init__.py +0 -186
- meerschaum-2.9.5.dist-info/RECORD +0 -263
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc2.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc2.dist-info}/licenses/LICENSE +0 -0
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc2.dist-info}/top_level.txt +0 -0
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc2.dist-info}/zip-safe +0 -0
@@ -0,0 +1,388 @@
|
|
1
|
+
#! /usr/bin/env python3
|
2
|
+
# vim:fenc=utf-8
|
3
|
+
|
4
|
+
"""
|
5
|
+
Define the callbacks for the tokens page.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import json
|
9
|
+
from typing import Optional, List, Dict, Any, Tuple
|
10
|
+
from datetime import datetime, timezone
|
11
|
+
|
12
|
+
import dash
|
13
|
+
from dash.dependencies import Input, Output, State, MATCH, ALL
|
14
|
+
from dash.exceptions import PreventUpdate
|
15
|
+
import dash_bootstrap_components as dbc
|
16
|
+
import dash.html as html
|
17
|
+
import dash.dcc as dcc
|
18
|
+
|
19
|
+
import meerschaum as mrsm
|
20
|
+
from meerschaum.api import get_api_connector, debug
|
21
|
+
from meerschaum.api.dash import dash_app
|
22
|
+
from meerschaum.api.dash.sessions import get_user_from_session
|
23
|
+
from meerschaum.api.dash.components import alert_from_success_tuple, build_cards_grid
|
24
|
+
from meerschaum.api.dash.tokens import (
|
25
|
+
get_tokens_cards,
|
26
|
+
get_tokens_table,
|
27
|
+
build_tokens_register_input_modal,
|
28
|
+
build_tokens_register_output_modal,
|
29
|
+
)
|
30
|
+
from meerschaum._internal.static import STATIC_CONFIG
|
31
|
+
from meerschaum.utils.daemon import get_new_daemon_name
|
32
|
+
from meerschaum.core import Token
|
33
|
+
|
34
|
+
|
35
|
+
@dash_app.callback(
|
36
|
+
Output('tokens-output-div', 'children'),
|
37
|
+
Output('tokens-register-input-modal', 'children'),
|
38
|
+
Output('tokens-alert-div', 'children'),
|
39
|
+
Input('tokens-refresh-button', 'n_clicks'),
|
40
|
+
State('session-store', 'data'),
|
41
|
+
)
|
42
|
+
def refresh_tokens_button_click(
|
43
|
+
n_clicks: Optional[int],
|
44
|
+
session_data: Optional[Dict[str, Any]] = None,
|
45
|
+
):
|
46
|
+
"""
|
47
|
+
Build the tokens cards on load or refresh.
|
48
|
+
"""
|
49
|
+
session_id = (session_data or {}).get('session-id', None)
|
50
|
+
tokens_table, alerts = get_tokens_table(session_id)
|
51
|
+
if not tokens_table:
|
52
|
+
return (
|
53
|
+
[
|
54
|
+
html.H4('No tokens registered.'),
|
55
|
+
html.P('Click the `+` button to register a new token.'),
|
56
|
+
],
|
57
|
+
alerts
|
58
|
+
)
|
59
|
+
|
60
|
+
return tokens_table, build_tokens_register_input_modal(), alerts
|
61
|
+
|
62
|
+
|
63
|
+
@dash_app.callback(
|
64
|
+
Output('tokens-register-input-modal', 'is_open'),
|
65
|
+
Input('tokens-create-button', 'n_clicks'),
|
66
|
+
prevent_initial_call=True,
|
67
|
+
)
|
68
|
+
def create_tokens_button_click(n_clicks: Optional[int]):
|
69
|
+
"""
|
70
|
+
Open the tokens registration modal when the plus button is clicked.
|
71
|
+
"""
|
72
|
+
if not n_clicks:
|
73
|
+
raise PreventUpdate
|
74
|
+
|
75
|
+
return True
|
76
|
+
|
77
|
+
|
78
|
+
@dash_app.callback(
|
79
|
+
Output("tokens-scopes-checklist-div", 'style'),
|
80
|
+
Input("tokens-toggle-scopes-switch", 'value'),
|
81
|
+
prevent_initial_call=True,
|
82
|
+
)
|
83
|
+
def toggle_token_scopes_checklist(value: bool):
|
84
|
+
"""
|
85
|
+
Toggle the scopes checklist.
|
86
|
+
"""
|
87
|
+
return {'display': 'none'} if value else {}
|
88
|
+
|
89
|
+
|
90
|
+
@dash_app.callback(
|
91
|
+
Output('tokens-scopes-checklist', 'value'),
|
92
|
+
Output('tokens-deselect-scopes-button', 'children'),
|
93
|
+
Input('tokens-deselect-scopes-button', 'n_clicks'),
|
94
|
+
State('tokens-deselect-scopes-button', 'children'),
|
95
|
+
prevent_initial_call=True,
|
96
|
+
)
|
97
|
+
def deselect_scopes_click(n_clicks: Optional[int], name: str):
|
98
|
+
"""
|
99
|
+
Set the value of the scopes checklist to an empty list.
|
100
|
+
"""
|
101
|
+
if not n_clicks:
|
102
|
+
raise PreventUpdate
|
103
|
+
|
104
|
+
new_name = 'Select all' if name == 'Deselect all' else 'Deselect all'
|
105
|
+
value = (
|
106
|
+
[]
|
107
|
+
if name == 'Deselect all'
|
108
|
+
else list(STATIC_CONFIG['tokens']['scopes'])
|
109
|
+
)
|
110
|
+
|
111
|
+
return value, new_name
|
112
|
+
|
113
|
+
|
114
|
+
@dash_app.callback(
|
115
|
+
Output({'type': 'tokens-scopes-checklist', 'index': MATCH}, 'value'),
|
116
|
+
Output({'type': 'tokens-deselect-scopes-button', 'index': MATCH}, 'children'),
|
117
|
+
Input({'type': 'tokens-deselect-scopes-button', 'index': MATCH}, 'n_clicks'),
|
118
|
+
State({'type': 'tokens-deselect-scopes-button', 'index': MATCH}, 'children'),
|
119
|
+
prevent_initial_call=True,
|
120
|
+
)
|
121
|
+
def edit_token_deselect_scopes_click(n_clicks: Optional[int], name: str):
|
122
|
+
"""
|
123
|
+
Set the value of the scopes checklist to an empty list.
|
124
|
+
"""
|
125
|
+
if not n_clicks:
|
126
|
+
raise PreventUpdate
|
127
|
+
|
128
|
+
new_name = 'Select all' if name == 'Deselect all' else 'Deselect all'
|
129
|
+
value = (
|
130
|
+
[]
|
131
|
+
if name == 'Deselect all'
|
132
|
+
else list(STATIC_CONFIG['tokens']['scopes'])
|
133
|
+
)
|
134
|
+
|
135
|
+
return value, new_name
|
136
|
+
|
137
|
+
|
138
|
+
@dash_app.callback(
|
139
|
+
Output('tokens-register-input-modal', 'is_open'),
|
140
|
+
Output('tokens-register-output-modal', 'is_open'),
|
141
|
+
Output('tokens-register-output-modal', 'children'),
|
142
|
+
Input('tokens-register-button', 'n_clicks'),
|
143
|
+
State('tokens-name-input', 'value'),
|
144
|
+
State('tokens-scopes-checklist', 'value'),
|
145
|
+
State('tokens-expiration-datepickersingle', 'date'),
|
146
|
+
State('session-store', 'data'),
|
147
|
+
prevent_initial_call=True,
|
148
|
+
)
|
149
|
+
def register_token_click(
|
150
|
+
n_clicks: Optional[int],
|
151
|
+
name: str,
|
152
|
+
scopes: List[str],
|
153
|
+
expiration: Optional[datetime] = None,
|
154
|
+
session_data: Optional[Dict[str, Any]] = None,
|
155
|
+
):
|
156
|
+
"""
|
157
|
+
Register the token.
|
158
|
+
"""
|
159
|
+
if not n_clicks:
|
160
|
+
raise PreventUpdate
|
161
|
+
|
162
|
+
session_id = (session_data or {}).get('session-id', None)
|
163
|
+
token = Token(
|
164
|
+
label=(name or None),
|
165
|
+
user=get_user_from_session(session_id),
|
166
|
+
expiration=(datetime.fromisoformat(f"{expiration}T00:00:00Z") if expiration is not None else None),
|
167
|
+
)
|
168
|
+
return False, True, build_tokens_register_output_modal(token)
|
169
|
+
|
170
|
+
|
171
|
+
@dash_app.callback(
|
172
|
+
Output("tokens-refresh-button", "n_clicks"),
|
173
|
+
Input("tokens-register-output-modal", "is_open"),
|
174
|
+
Input({'type': 'tokens-edit-modal', 'index': ALL}, 'is_open'),
|
175
|
+
Input({'type': 'tokens-invalidate-modal', 'index': ALL}, 'is_open'),
|
176
|
+
Input({'type': 'tokens-delete-modal', 'index': ALL}, 'is_open'),
|
177
|
+
State("tokens-refresh-button", "n_clicks"),
|
178
|
+
prevent_initial_call=True,
|
179
|
+
)
|
180
|
+
def register_token_modal_close_refresh(
|
181
|
+
register_is_open: bool,
|
182
|
+
edit_is_open_list,
|
183
|
+
invalidate_is_open_list,
|
184
|
+
delete_is_open_list,
|
185
|
+
n_clicks: int,
|
186
|
+
):
|
187
|
+
"""
|
188
|
+
Refresh the cards when the registration, edit, invalidate, or delete modals changes visibility.
|
189
|
+
"""
|
190
|
+
if any(
|
191
|
+
edit_is_open_list
|
192
|
+
+ invalidate_is_open_list
|
193
|
+
+ delete_is_open_list
|
194
|
+
):
|
195
|
+
raise PreventUpdate
|
196
|
+
|
197
|
+
return (n_clicks or 0) + 1
|
198
|
+
|
199
|
+
|
200
|
+
@dash_app.callback(
|
201
|
+
Output('tokens-register-clipboard', 'content'),
|
202
|
+
Output('tokens-register-clipboard', 'n_clicks'),
|
203
|
+
Output('tokens-register-copy-button', 'children'),
|
204
|
+
Input('tokens-register-copy-button', 'n_clicks'),
|
205
|
+
State('tokens-register-clipboard', 'n_clicks'),
|
206
|
+
State('token-id-pre', 'children'),
|
207
|
+
State('token-secret-pre', 'children'),
|
208
|
+
prevent_initial_call=True,
|
209
|
+
)
|
210
|
+
def copy_token_button_click(
|
211
|
+
n_clicks: int,
|
212
|
+
clipboard_n_clicks: Optional[int],
|
213
|
+
token_id: str,
|
214
|
+
token_secret: str,
|
215
|
+
) -> Tuple[str, int, str]:
|
216
|
+
"""
|
217
|
+
Copy the token's ID and secret to the clipboard.
|
218
|
+
"""
|
219
|
+
if not n_clicks:
|
220
|
+
raise PreventUpdate
|
221
|
+
return (
|
222
|
+
f"Client ID: {token_id}\nClient Secret: {token_secret}",
|
223
|
+
(clipboard_n_clicks or 0) + 1,
|
224
|
+
"Copied!",
|
225
|
+
)
|
226
|
+
|
227
|
+
|
228
|
+
@dash_app.callback(
|
229
|
+
Output('tokens-close-register-output-modal-button', 'disabled'),
|
230
|
+
Output('tokens-register-output-modal', 'backdrop'),
|
231
|
+
Input('tokens-register-clipboard', 'n_clicks'),
|
232
|
+
prevent_initial_call=True,
|
233
|
+
)
|
234
|
+
def enable_close_button(n_clicks):
|
235
|
+
"""
|
236
|
+
Enable the close button once the token has been copied.
|
237
|
+
"""
|
238
|
+
if not n_clicks:
|
239
|
+
raise PreventUpdate
|
240
|
+
return False, True
|
241
|
+
|
242
|
+
|
243
|
+
@dash_app.callback(
|
244
|
+
Output('tokens-register-output-modal', 'is_open'),
|
245
|
+
Input('tokens-close-register-output-modal-button', 'n_clicks'),
|
246
|
+
prevent_initial_call=True,
|
247
|
+
)
|
248
|
+
def close_register_output_modal(n_clicks: int) -> bool:
|
249
|
+
"""
|
250
|
+
Close the register output modal when the Close button is clicked.
|
251
|
+
"""
|
252
|
+
if not n_clicks:
|
253
|
+
raise PreventUpdate
|
254
|
+
return False
|
255
|
+
|
256
|
+
|
257
|
+
@dash_app.callback(
|
258
|
+
Output({'type': 'tokens-edit-modal', 'index': MATCH}, 'is_open'),
|
259
|
+
Input({'type': 'tokens-edit-button', 'index': MATCH}, 'n_clicks'),
|
260
|
+
prevent_initial_call=True,
|
261
|
+
)
|
262
|
+
def edit_token_button_click(n_clicks: int):
|
263
|
+
if not n_clicks:
|
264
|
+
raise PreventUpdate
|
265
|
+
return True
|
266
|
+
|
267
|
+
|
268
|
+
@dash_app.callback(
|
269
|
+
Output({'type': 'tokens-edit-modal', 'index': MATCH}, 'is_open'),
|
270
|
+
Output({'type': 'tokens-edit-alerts-div', 'index': MATCH}, 'children'),
|
271
|
+
Input({'type': 'tokens-edit-submit-button', 'index': MATCH}, 'n_clicks'),
|
272
|
+
State({'type': 'tokens-expiration-datepickersingle', 'index': MATCH}, 'date'),
|
273
|
+
State({'type': 'tokens-scopes-checklist', 'index': MATCH}, 'value'),
|
274
|
+
State({'type': 'tokens-name-input', 'index': MATCH}, 'value'),
|
275
|
+
prevent_initial_call=True,
|
276
|
+
)
|
277
|
+
def edit_token_submit_button_click(
|
278
|
+
n_clicks: int,
|
279
|
+
expiration: Optional[datetime],
|
280
|
+
scopes: List[str],
|
281
|
+
label: str,
|
282
|
+
):
|
283
|
+
if not n_clicks:
|
284
|
+
raise PreventUpdate
|
285
|
+
|
286
|
+
ctx = dash.callback_context.triggered
|
287
|
+
if ctx[0]['value'] is None:
|
288
|
+
raise PreventUpdate
|
289
|
+
|
290
|
+
component_dict = json.loads(ctx[0]['prop_id'].split('.' + 'n_clicks')[0])
|
291
|
+
token_id = component_dict['index']
|
292
|
+
|
293
|
+
token = Token(
|
294
|
+
id=token_id,
|
295
|
+
label=label,
|
296
|
+
expiration=(datetime.fromisoformat(f"{expiration}T00:00:00Z") if expiration is not None else None),
|
297
|
+
scopes=scopes,
|
298
|
+
instance=get_api_connector(),
|
299
|
+
)
|
300
|
+
|
301
|
+
success, msg = token.edit(debug=debug)
|
302
|
+
if not success:
|
303
|
+
return dash.no_update, alert_from_success_tuple((success, msg))
|
304
|
+
|
305
|
+
return False, dash.no_update
|
306
|
+
|
307
|
+
|
308
|
+
@dash_app.callback(
|
309
|
+
Output({'type': 'tokens-invalidate-modal', 'index': MATCH}, 'is_open'),
|
310
|
+
Input({'type': 'tokens-invalidate-button', 'index': MATCH}, 'n_clicks'),
|
311
|
+
prevent_initial_call=True,
|
312
|
+
)
|
313
|
+
def invalidate_token_click(n_clicks: int):
|
314
|
+
if not n_clicks:
|
315
|
+
raise PreventUpdate
|
316
|
+
return True
|
317
|
+
|
318
|
+
|
319
|
+
@dash_app.callback(
|
320
|
+
Output({'type': 'tokens-delete-modal', 'index': MATCH}, 'is_open'),
|
321
|
+
Input({'type': 'tokens-delete-button', 'index': MATCH}, 'n_clicks'),
|
322
|
+
prevent_initial_call=True,
|
323
|
+
)
|
324
|
+
def invalidate_token_click(n_clicks: int):
|
325
|
+
if not n_clicks:
|
326
|
+
raise PreventUpdate
|
327
|
+
return True
|
328
|
+
|
329
|
+
|
330
|
+
|
331
|
+
@dash_app.callback(
|
332
|
+
Output({'type': 'tokens-edit-modal', 'index': MATCH}, 'is_open'),
|
333
|
+
Output({'type': 'tokens-invalidate-modal', 'index': MATCH}, 'is_open'),
|
334
|
+
Output({'type': 'tokens-invalidate-alerts-div', 'index': MATCH}, 'children'),
|
335
|
+
Input({'type': 'tokens-invalidate-confirm-button', 'index': MATCH}, 'n_clicks'),
|
336
|
+
prevent_initial_call=True,
|
337
|
+
)
|
338
|
+
def invalidate_token_confirm_click(n_clicks: int):
|
339
|
+
if not n_clicks:
|
340
|
+
raise PreventUpdate
|
341
|
+
|
342
|
+
ctx = dash.callback_context.triggered
|
343
|
+
if ctx[0]['value'] is None:
|
344
|
+
raise PreventUpdate
|
345
|
+
|
346
|
+
component_dict = json.loads(ctx[0]['prop_id'].split('.' + 'n_clicks')[0])
|
347
|
+
token_id = component_dict['index']
|
348
|
+
|
349
|
+
token = Token(
|
350
|
+
id=token_id,
|
351
|
+
instance=get_api_connector(),
|
352
|
+
)
|
353
|
+
|
354
|
+
success, msg = token.invalidate(debug=debug)
|
355
|
+
if not success:
|
356
|
+
return dash.no_update, dash.no_update, alert_from_success_tuple((success, msg))
|
357
|
+
|
358
|
+
return False, False, dash.no_update
|
359
|
+
|
360
|
+
|
361
|
+
@dash_app.callback(
|
362
|
+
Output({'type': 'tokens-edit-modal', 'index': MATCH}, 'is_open'),
|
363
|
+
Output({'type': 'tokens-delete-modal', 'index': MATCH}, 'is_open'),
|
364
|
+
Output({'type': 'tokens-delete-alerts-div', 'index': MATCH}, 'children'),
|
365
|
+
Input({'type': 'tokens-delete-confirm-button', 'index': MATCH}, 'n_clicks'),
|
366
|
+
prevent_initial_call=True,
|
367
|
+
)
|
368
|
+
def delete_token_confirm_click(n_clicks: int):
|
369
|
+
if not n_clicks:
|
370
|
+
raise PreventUpdate
|
371
|
+
|
372
|
+
ctx = dash.callback_context.triggered
|
373
|
+
if ctx[0]['value'] is None:
|
374
|
+
raise PreventUpdate
|
375
|
+
|
376
|
+
component_dict = json.loads(ctx[0]['prop_id'].split('.' + 'n_clicks')[0])
|
377
|
+
token_id = component_dict['index']
|
378
|
+
|
379
|
+
token = Token(
|
380
|
+
id=token_id,
|
381
|
+
instance=get_api_connector(),
|
382
|
+
)
|
383
|
+
|
384
|
+
success, msg = token.delete(debug=debug)
|
385
|
+
if not success:
|
386
|
+
return dash.no_update, dash.no_update, alert_from_success_tuple((success, msg))
|
387
|
+
|
388
|
+
return False, False, []
|
@@ -10,7 +10,7 @@ from __future__ import annotations
|
|
10
10
|
|
11
11
|
from meerschaum.utils.packages import attempt_import, import_dcc, import_html
|
12
12
|
from meerschaum.utils.typing import SuccessTuple, List
|
13
|
-
from meerschaum.
|
13
|
+
from meerschaum._internal.static import STATIC_CONFIG
|
14
14
|
from meerschaum.utils.misc import remove_ansi
|
15
15
|
from meerschaum._internal.shell.Shell import get_shell_intro
|
16
16
|
from meerschaum.api import endpoints, CHECK_UPDATE, docs_enabled
|
@@ -267,6 +267,26 @@ def build_pages_offcanvas_children():
|
|
267
267
|
|
268
268
|
plugins_accordion_items = []
|
269
269
|
for page_group, pages_dicts in _plugin_endpoints_to_pages.items():
|
270
|
+
if len(pages_dicts) == 1:
|
271
|
+
page_href, page_dict = list(pages_dicts.items())[0]
|
272
|
+
if page_dict['page_key'].lower() == page_group.lower():
|
273
|
+
pages_listgroup_items.append(
|
274
|
+
dbc.ListGroupItem(
|
275
|
+
dbc.Button(
|
276
|
+
' '.join([word.capitalize() for word in page_dict['page_key'].split(' ')]),
|
277
|
+
style={
|
278
|
+
'width': '100%',
|
279
|
+
'text-align': 'left',
|
280
|
+
'text-decoration': 'none',
|
281
|
+
'padding-left': '0',
|
282
|
+
},
|
283
|
+
href=page_href,
|
284
|
+
color='dark',
|
285
|
+
)
|
286
|
+
)
|
287
|
+
)
|
288
|
+
continue
|
289
|
+
|
270
290
|
plugin_listgroup_items = [
|
271
291
|
dbc.ListGroupItem(
|
272
292
|
dbc.Button(
|
@@ -294,13 +314,15 @@ def build_pages_offcanvas_children():
|
|
294
314
|
class_name='pages-offcanvas-accordion',
|
295
315
|
)
|
296
316
|
plugins_accordion_items.append(plugin_accordion_item)
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
317
|
+
|
318
|
+
if plugins_accordion_items:
|
319
|
+
plugins_accordion = dbc.Accordion(
|
320
|
+
plugins_accordion_items,
|
321
|
+
start_collapsed=True,
|
322
|
+
flush=True,
|
323
|
+
always_open=True,
|
324
|
+
)
|
325
|
+
pages_listgroup_items.append(plugins_accordion)
|
304
326
|
|
305
327
|
pages_children = dbc.Card(
|
306
328
|
dbc.ListGroup(
|
meerschaum/api/dash/keys.py
CHANGED
@@ -16,13 +16,10 @@ dbc = attempt_import('dash_bootstrap_components', lazy=False, check_update=CHECK
|
|
16
16
|
html, dcc = import_html(check_update=CHECK_UPDATE), import_dcc(check_update=CHECK_UPDATE)
|
17
17
|
|
18
18
|
placeholders = {
|
19
|
-
'ck'
|
20
|
-
'mk'
|
21
|
-
'lk'
|
22
|
-
'
|
23
|
-
'Additional search parameters. ' +
|
24
|
-
'Simple dictionary format or JSON accepted.'
|
25
|
-
),
|
19
|
+
'ck': 'Connectors',
|
20
|
+
'mk': 'Metrics',
|
21
|
+
'lk': 'Locations',
|
22
|
+
'tags': 'Tags',
|
26
23
|
}
|
27
24
|
widths = {
|
28
25
|
'flags' : {'size' : 12},
|
@@ -212,11 +209,25 @@ dropdown_keys_row = dbc.Row(
|
|
212
209
|
),
|
213
210
|
] ### end of filters row children
|
214
211
|
)
|
212
|
+
tags_dropdown = html.Div(
|
213
|
+
dcc.Dropdown(
|
214
|
+
id='tags-dropdown',
|
215
|
+
options=[],
|
216
|
+
placeholder=placeholders['tags'],
|
217
|
+
multi=True,
|
218
|
+
searchable=True,
|
219
|
+
),
|
220
|
+
className="dbc_dark",
|
221
|
+
id="tags-dropdown-div",
|
222
|
+
)
|
223
|
+
|
215
224
|
dropdown_tab_content = html.Div([
|
216
225
|
dbc.Card(
|
217
226
|
dbc.CardBody(
|
218
227
|
[
|
219
228
|
dropdown_keys_row,
|
229
|
+
html.Br(),
|
230
|
+
tags_dropdown,
|
220
231
|
], ### end of card children
|
221
232
|
className='card-text',
|
222
233
|
)
|
@@ -232,94 +243,9 @@ dropdown_tab_content = html.Div([
|
|
232
243
|
),
|
233
244
|
])
|
234
245
|
|
235
|
-
text_tab_content = dbc.Card(
|
236
|
-
dbc.CardBody(
|
237
|
-
[
|
238
|
-
dbc.Row(
|
239
|
-
[
|
240
|
-
dbc.Col(html.Div(className='dbc_dark', children=[
|
241
|
-
dbc.InputGroup(
|
242
|
-
[
|
243
|
-
dbc.Button(
|
244
|
-
'Clear',
|
245
|
-
id='clear-connector-keys-input-button',
|
246
|
-
color='link',
|
247
|
-
size='sm',
|
248
|
-
),
|
249
|
-
dbc.Input(
|
250
|
-
id='connector-keys-input',
|
251
|
-
placeholder=placeholders['ck'],
|
252
|
-
type='text',
|
253
|
-
value='',
|
254
|
-
list='connector-keys-list',
|
255
|
-
className='dbc_dark'
|
256
|
-
),
|
257
|
-
],
|
258
|
-
size=input_group_sizes['ck'],
|
259
|
-
)]),
|
260
|
-
width=4,
|
261
|
-
),
|
262
|
-
dbc.Col(
|
263
|
-
dbc.InputGroup(
|
264
|
-
[
|
265
|
-
dbc.Button(
|
266
|
-
'Clear',
|
267
|
-
id='clear-metric-keys-input-button',
|
268
|
-
color='link',
|
269
|
-
size='sm',
|
270
|
-
),
|
271
|
-
dbc.Input(
|
272
|
-
id='metric-keys-input',
|
273
|
-
placeholder=placeholders['mk'],
|
274
|
-
type='text',
|
275
|
-
value='',
|
276
|
-
list='metric-keys-list',
|
277
|
-
),
|
278
|
-
],
|
279
|
-
size=input_group_sizes['mk'],
|
280
|
-
),
|
281
|
-
width=4,
|
282
|
-
),
|
283
|
-
dbc.Col(
|
284
|
-
dbc.InputGroup(
|
285
|
-
[
|
286
|
-
dbc.Button(
|
287
|
-
'Clear',
|
288
|
-
id='clear-location-keys-input-button',
|
289
|
-
color='link',
|
290
|
-
size='sm',
|
291
|
-
),
|
292
|
-
dbc.Input(
|
293
|
-
id='location-keys-input',
|
294
|
-
placeholder=placeholders['lk'],
|
295
|
-
type='text',
|
296
|
-
value='',
|
297
|
-
list='location-keys-list',
|
298
|
-
),
|
299
|
-
],
|
300
|
-
size=input_group_sizes['lk'],
|
301
|
-
),
|
302
|
-
width=4,
|
303
|
-
),
|
304
|
-
]
|
305
|
-
),
|
306
|
-
html.Br(),
|
307
|
-
dbc.Row(
|
308
|
-
dbc.Col(
|
309
|
-
dbc.InputGroup(
|
310
|
-
[
|
311
|
-
search_parameters_editor,
|
312
|
-
],
|
313
|
-
size=input_group_sizes['params'],
|
314
|
-
)
|
315
|
-
)
|
316
|
-
),
|
317
|
-
]
|
318
|
-
)
|
319
|
-
)
|
320
|
-
|
321
246
|
keys_lists_content = html.Div([
|
322
247
|
html.Datalist(id='connector-keys-list'),
|
323
248
|
html.Datalist(id='metric-keys-list'),
|
324
249
|
html.Datalist(id='location-keys-list'),
|
250
|
+
html.Datalist(id='tags-list'),
|
325
251
|
], hidden=True)
|
@@ -6,7 +6,6 @@
|
|
6
6
|
The main dashboard layout.
|
7
7
|
"""
|
8
8
|
|
9
|
-
import uuid
|
10
9
|
from meerschaum.config import __doc__ as doc, get_config
|
11
10
|
from meerschaum.utils.misc import get_connector_labels
|
12
11
|
from meerschaum.utils.packages import attempt_import, import_html, import_dcc, import_pandas
|
@@ -43,7 +42,6 @@ from meerschaum.api.dash.components import (
|
|
43
42
|
)
|
44
43
|
from meerschaum.api.dash.keys import (
|
45
44
|
keys_lists_content,
|
46
|
-
text_tab_content,
|
47
45
|
dropdown_tab_content,
|
48
46
|
)
|
49
47
|
|
@@ -62,24 +60,7 @@ layout = html.Div(
|
|
62
60
|
children=[
|
63
61
|
dbc.Col(
|
64
62
|
children=[
|
65
|
-
|
66
|
-
id='pipes-filter-tabs',
|
67
|
-
children=[
|
68
|
-
dbc.Tab(
|
69
|
-
dropdown_tab_content,
|
70
|
-
label='Filters',
|
71
|
-
id='pipes-filter-dropdown-tab',
|
72
|
-
tab_id='dropdown',
|
73
|
-
),
|
74
|
-
dbc.Tab(
|
75
|
-
text_tab_content,
|
76
|
-
label='Text',
|
77
|
-
id='pipes-filter-input-tab',
|
78
|
-
tab_id='input',
|
79
|
-
tab_style={"display": "none"},
|
80
|
-
),
|
81
|
-
]
|
82
|
-
),
|
63
|
+
dropdown_tab_content,
|
83
64
|
html.Br(),
|
84
65
|
bottom_buttons_content,
|
85
66
|
test_button,
|
@@ -14,7 +14,7 @@ from meerschaum.plugins import web_page
|
|
14
14
|
@web_page('password-reset', login_required=True, page_group='Settings')
|
15
15
|
def page_layout():
|
16
16
|
"""
|
17
|
-
Return the layout for
|
17
|
+
Return the layout for the password reset page.
|
18
18
|
"""
|
19
19
|
return dbc.Container([
|
20
20
|
html.Br(),
|
@@ -0,0 +1,55 @@
|
|
1
|
+
#! /usr/bin/env python3
|
2
|
+
# vim:fenc=utf-8
|
3
|
+
|
4
|
+
"""
|
5
|
+
Define the tokens page layout.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import dash_bootstrap_components as dbc
|
9
|
+
import dash.html as html
|
10
|
+
import dash.dcc as dcc
|
11
|
+
from meerschaum.plugins import web_page
|
12
|
+
from meerschaum._internal.static import STATIC_CONFIG
|
13
|
+
|
14
|
+
|
15
|
+
@web_page('tokens', login_required=True, page_group='Settings')
|
16
|
+
def page_layout():
|
17
|
+
"""
|
18
|
+
Return the layout for the tokens page.
|
19
|
+
"""
|
20
|
+
return dbc.Container([
|
21
|
+
html.Br(),
|
22
|
+
html.H3('Tokens'),
|
23
|
+
html.Div(id="tokens-alert-div"),
|
24
|
+
dbc.Modal(
|
25
|
+
id="tokens-register-input-modal",
|
26
|
+
size='lg',
|
27
|
+
is_open=False,
|
28
|
+
),
|
29
|
+
dbc.Modal(
|
30
|
+
id="tokens-register-output-modal",
|
31
|
+
size='lg',
|
32
|
+
is_open=False,
|
33
|
+
backdrop='static',
|
34
|
+
),
|
35
|
+
html.Div(id='tokens-register-output-modal-div'),
|
36
|
+
html.Div(
|
37
|
+
[
|
38
|
+
dbc.Button(
|
39
|
+
"⟳",
|
40
|
+
color='black',
|
41
|
+
size='sm',
|
42
|
+
id='tokens-refresh-button',
|
43
|
+
),
|
44
|
+
dbc.Button(
|
45
|
+
html.B('+'),
|
46
|
+
color='black',
|
47
|
+
size='sm',
|
48
|
+
id='tokens-create-button',
|
49
|
+
),
|
50
|
+
],
|
51
|
+
id='tokens-controls-div',
|
52
|
+
style={'text-align': 'right'},
|
53
|
+
),
|
54
|
+
html.Div(id='tokens-output-div'),
|
55
|
+
])
|