meerschaum 2.9.4__py3-none-any.whl → 3.0.0rc1__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.
Files changed (154) hide show
  1. meerschaum/__init__.py +5 -2
  2. meerschaum/_internal/__init__.py +1 -0
  3. meerschaum/_internal/arguments/_parse_arguments.py +4 -4
  4. meerschaum/_internal/arguments/_parser.py +17 -1
  5. meerschaum/_internal/entry.py +6 -6
  6. meerschaum/_internal/shell/Shell.py +1 -1
  7. meerschaum/_internal/static.py +372 -0
  8. meerschaum/actions/api.py +12 -2
  9. meerschaum/actions/bootstrap.py +7 -7
  10. meerschaum/actions/edit.py +142 -18
  11. meerschaum/actions/register.py +137 -6
  12. meerschaum/actions/show.py +117 -29
  13. meerschaum/actions/stop.py +4 -1
  14. meerschaum/actions/sync.py +1 -1
  15. meerschaum/actions/tag.py +9 -8
  16. meerschaum/api/__init__.py +9 -2
  17. meerschaum/api/_events.py +39 -2
  18. meerschaum/api/_oauth2.py +118 -8
  19. meerschaum/api/_tokens.py +102 -0
  20. meerschaum/api/dash/__init__.py +0 -1
  21. meerschaum/api/dash/callbacks/custom.py +2 -2
  22. meerschaum/api/dash/callbacks/dashboard.py +133 -18
  23. meerschaum/api/dash/callbacks/plugins.py +0 -1
  24. meerschaum/api/dash/callbacks/register.py +1 -1
  25. meerschaum/api/dash/callbacks/settings/__init__.py +1 -0
  26. meerschaum/api/dash/callbacks/settings/password_reset.py +2 -2
  27. meerschaum/api/dash/callbacks/settings/tokens.py +388 -0
  28. meerschaum/api/dash/components.py +30 -8
  29. meerschaum/api/dash/keys.py +19 -93
  30. meerschaum/api/dash/pages/dashboard.py +1 -20
  31. meerschaum/api/dash/pages/settings/__init__.py +1 -0
  32. meerschaum/api/dash/pages/settings/password_reset.py +1 -1
  33. meerschaum/api/dash/pages/settings/tokens.py +55 -0
  34. meerschaum/api/dash/pipes.py +156 -58
  35. meerschaum/api/dash/sessions.py +12 -0
  36. meerschaum/api/dash/tokens.py +606 -0
  37. meerschaum/api/dash/websockets.py +1 -1
  38. meerschaum/api/dash/webterm.py +4 -0
  39. meerschaum/api/models/__init__.py +23 -3
  40. meerschaum/api/models/_actions.py +22 -0
  41. meerschaum/api/models/_pipes.py +85 -7
  42. meerschaum/api/models/_tokens.py +81 -0
  43. meerschaum/api/resources/static/css/dash.css +16 -0
  44. meerschaum/api/resources/templates/termpage.html +12 -0
  45. meerschaum/api/routes/__init__.py +1 -0
  46. meerschaum/api/routes/_actions.py +3 -4
  47. meerschaum/api/routes/_connectors.py +3 -7
  48. meerschaum/api/routes/_jobs.py +14 -35
  49. meerschaum/api/routes/_login.py +49 -12
  50. meerschaum/api/routes/_misc.py +5 -10
  51. meerschaum/api/routes/_pipes.py +134 -111
  52. meerschaum/api/routes/_plugins.py +38 -28
  53. meerschaum/api/routes/_tokens.py +236 -0
  54. meerschaum/api/routes/_users.py +47 -35
  55. meerschaum/api/routes/_version.py +3 -3
  56. meerschaum/config/__init__.py +43 -20
  57. meerschaum/config/_default.py +32 -5
  58. meerschaum/config/_edit.py +28 -24
  59. meerschaum/config/_environment.py +1 -1
  60. meerschaum/config/_patch.py +6 -6
  61. meerschaum/config/_paths.py +5 -1
  62. meerschaum/config/_read_config.py +65 -34
  63. meerschaum/config/_sync.py +6 -3
  64. meerschaum/config/_version.py +1 -1
  65. meerschaum/config/stack/__init__.py +24 -5
  66. meerschaum/config/static.py +18 -0
  67. meerschaum/connectors/_Connector.py +10 -4
  68. meerschaum/connectors/__init__.py +4 -20
  69. meerschaum/connectors/api/_APIConnector.py +34 -6
  70. meerschaum/connectors/api/_actions.py +2 -2
  71. meerschaum/connectors/api/_jobs.py +1 -1
  72. meerschaum/connectors/api/_login.py +33 -7
  73. meerschaum/connectors/api/_misc.py +2 -2
  74. meerschaum/connectors/api/_pipes.py +15 -14
  75. meerschaum/connectors/api/_plugins.py +2 -2
  76. meerschaum/connectors/api/_request.py +1 -1
  77. meerschaum/connectors/api/_tokens.py +146 -0
  78. meerschaum/connectors/api/_users.py +70 -58
  79. meerschaum/connectors/instance/_InstanceConnector.py +83 -0
  80. meerschaum/connectors/instance/__init__.py +10 -0
  81. meerschaum/connectors/instance/_pipes.py +442 -0
  82. meerschaum/connectors/instance/_plugins.py +151 -0
  83. meerschaum/connectors/instance/_tokens.py +296 -0
  84. meerschaum/connectors/instance/_users.py +181 -0
  85. meerschaum/connectors/parse.py +4 -1
  86. meerschaum/connectors/sql/_SQLConnector.py +8 -5
  87. meerschaum/connectors/sql/_cli.py +12 -11
  88. meerschaum/connectors/sql/_create_engine.py +6 -154
  89. meerschaum/connectors/sql/_fetch.py +2 -18
  90. meerschaum/connectors/sql/_pipes.py +42 -31
  91. meerschaum/connectors/sql/_plugins.py +29 -0
  92. meerschaum/connectors/sql/_sql.py +9 -2
  93. meerschaum/connectors/sql/_users.py +29 -2
  94. meerschaum/connectors/sql/tables/__init__.py +1 -1
  95. meerschaum/connectors/valkey/_ValkeyConnector.py +2 -4
  96. meerschaum/connectors/valkey/_pipes.py +9 -10
  97. meerschaum/connectors/valkey/_plugins.py +2 -26
  98. meerschaum/core/Pipe/__init__.py +31 -14
  99. meerschaum/core/Pipe/_attributes.py +156 -58
  100. meerschaum/core/Pipe/_bootstrap.py +54 -24
  101. meerschaum/core/Pipe/_data.py +41 -1
  102. meerschaum/core/Pipe/_dtypes.py +29 -14
  103. meerschaum/core/Pipe/_edit.py +12 -4
  104. meerschaum/core/Pipe/_show.py +5 -5
  105. meerschaum/core/Pipe/_sync.py +48 -53
  106. meerschaum/core/Pipe/_verify.py +1 -1
  107. meerschaum/{plugins → core/Plugin}/_Plugin.py +9 -11
  108. meerschaum/core/Plugin/__init__.py +1 -1
  109. meerschaum/core/Token/_Token.py +221 -0
  110. meerschaum/core/Token/__init__.py +12 -0
  111. meerschaum/core/User/_User.py +34 -8
  112. meerschaum/core/User/__init__.py +9 -1
  113. meerschaum/core/__init__.py +1 -0
  114. meerschaum/jobs/_Job.py +3 -2
  115. meerschaum/jobs/__init__.py +3 -2
  116. meerschaum/jobs/systemd.py +1 -1
  117. meerschaum/models/__init__.py +35 -0
  118. meerschaum/models/pipes.py +247 -0
  119. meerschaum/models/tokens.py +38 -0
  120. meerschaum/models/users.py +26 -0
  121. meerschaum/plugins/__init__.py +22 -7
  122. meerschaum/plugins/bootstrap.py +2 -1
  123. meerschaum/utils/_get_pipes.py +68 -27
  124. meerschaum/utils/daemon/Daemon.py +2 -1
  125. meerschaum/utils/daemon/__init__.py +30 -2
  126. meerschaum/utils/dataframe.py +96 -15
  127. meerschaum/utils/dtypes/__init__.py +93 -21
  128. meerschaum/utils/dtypes/sql.py +44 -0
  129. meerschaum/utils/formatting/__init__.py +1 -1
  130. meerschaum/utils/formatting/_pipes.py +5 -4
  131. meerschaum/utils/formatting/_shell.py +11 -9
  132. meerschaum/utils/misc.py +237 -80
  133. meerschaum/utils/packages/__init__.py +3 -6
  134. meerschaum/utils/packages/_packages.py +34 -32
  135. meerschaum/utils/pipes.py +181 -0
  136. meerschaum/utils/process.py +1 -1
  137. meerschaum/utils/prompt.py +3 -1
  138. meerschaum/utils/schedule.py +1 -0
  139. meerschaum/utils/sql.py +115 -39
  140. meerschaum/utils/typing.py +1 -4
  141. meerschaum/utils/venv/_Venv.py +2 -2
  142. meerschaum/utils/venv/__init__.py +5 -7
  143. {meerschaum-2.9.4.dist-info → meerschaum-3.0.0rc1.dist-info}/METADATA +88 -80
  144. meerschaum-3.0.0rc1.dist-info/RECORD +282 -0
  145. {meerschaum-2.9.4.dist-info → meerschaum-3.0.0rc1.dist-info}/WHEEL +1 -1
  146. meerschaum/api/models/_interfaces.py +0 -15
  147. meerschaum/api/models/_locations.py +0 -15
  148. meerschaum/api/models/_metrics.py +0 -15
  149. meerschaum/config/static/__init__.py +0 -186
  150. meerschaum-2.9.4.dist-info/RECORD +0 -263
  151. {meerschaum-2.9.4.dist-info → meerschaum-3.0.0rc1.dist-info}/entry_points.txt +0 -0
  152. {meerschaum-2.9.4.dist-info → meerschaum-3.0.0rc1.dist-info}/licenses/LICENSE +0 -0
  153. {meerschaum-2.9.4.dist-info → meerschaum-3.0.0rc1.dist-info}/top_level.txt +0 -0
  154. {meerschaum-2.9.4.dist-info → meerschaum-3.0.0rc1.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.config.static import STATIC_CONFIG
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
- plugins_accordion = dbc.Accordion(
298
- plugins_accordion_items,
299
- start_collapsed=True,
300
- flush=True,
301
- always_open=True,
302
- )
303
- pages_listgroup_items.append(plugins_accordion)
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(
@@ -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' : 'Connectors',
20
- 'mk' : 'Metrics',
21
- 'lk' : 'Locations',
22
- 'params' : (
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
- dbc.Tabs(
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,
@@ -6,3 +6,4 @@ Define the layouts for the `settings` pages.
6
6
  """
7
7
 
8
8
  import meerschaum.api.dash.pages.settings.password_reset
9
+ import meerschaum.api.dash.pages.settings.tokens
@@ -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 this page.
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
+ ])