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.
- meerschaum/__init__.py +5 -2
- meerschaum/_internal/__init__.py +1 -0
- meerschaum/_internal/arguments/_parse_arguments.py +4 -4
- meerschaum/_internal/arguments/_parser.py +17 -1
- meerschaum/_internal/entry.py +6 -6
- meerschaum/_internal/shell/Shell.py +1 -1
- meerschaum/_internal/static.py +372 -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/api/__init__.py +9 -2
- meerschaum/api/_events.py +39 -2
- meerschaum/api/_oauth2.py +118 -8
- meerschaum/api/_tokens.py +102 -0
- meerschaum/api/dash/__init__.py +0 -1
- meerschaum/api/dash/callbacks/custom.py +2 -2
- meerschaum/api/dash/callbacks/dashboard.py +133 -18
- 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 +156 -58
- 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/static/css/dash.css +16 -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 +134 -111
- 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 +32 -5
- 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 +24 -5
- 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 +15 -14
- 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 +6 -154
- meerschaum/connectors/sql/_fetch.py +2 -18
- meerschaum/connectors/sql/_pipes.py +42 -31
- meerschaum/connectors/sql/_plugins.py +29 -0
- meerschaum/connectors/sql/_sql.py +9 -2
- 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 +9 -10
- meerschaum/connectors/valkey/_plugins.py +2 -26
- meerschaum/core/Pipe/__init__.py +31 -14
- meerschaum/core/Pipe/_attributes.py +156 -58
- meerschaum/core/Pipe/_bootstrap.py +54 -24
- meerschaum/core/Pipe/_data.py +41 -1
- meerschaum/core/Pipe/_dtypes.py +29 -14
- meerschaum/core/Pipe/_edit.py +12 -4
- meerschaum/core/Pipe/_show.py +5 -5
- meerschaum/core/Pipe/_sync.py +48 -53
- meerschaum/core/Pipe/_verify.py +1 -1
- meerschaum/{plugins → core/Plugin}/_Plugin.py +9 -11
- meerschaum/core/Plugin/__init__.py +1 -1
- meerschaum/core/Token/_Token.py +221 -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 +96 -15
- meerschaum/utils/dtypes/__init__.py +93 -21
- meerschaum/utils/dtypes/sql.py +44 -0
- 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 +237 -80
- meerschaum/utils/packages/__init__.py +3 -6
- meerschaum/utils/packages/_packages.py +34 -32
- meerschaum/utils/pipes.py +181 -0
- meerschaum/utils/process.py +1 -1
- meerschaum/utils/prompt.py +3 -1
- meerschaum/utils/schedule.py +1 -0
- meerschaum/utils/sql.py +115 -39
- meerschaum/utils/typing.py +1 -4
- meerschaum/utils/venv/_Venv.py +2 -2
- meerschaum/utils/venv/__init__.py +5 -7
- {meerschaum-2.9.4.dist-info → meerschaum-3.0.0rc1.dist-info}/METADATA +88 -80
- meerschaum-3.0.0rc1.dist-info/RECORD +282 -0
- {meerschaum-2.9.4.dist-info → meerschaum-3.0.0rc1.dist-info}/WHEEL +1 -1
- 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.4.dist-info/RECORD +0 -263
- {meerschaum-2.9.4.dist-info → meerschaum-3.0.0rc1.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.9.4.dist-info → meerschaum-3.0.0rc1.dist-info}/licenses/LICENSE +0 -0
- {meerschaum-2.9.4.dist-info → meerschaum-3.0.0rc1.dist-info}/top_level.txt +0 -0
- {meerschaum-2.9.4.dist-info → meerschaum-3.0.0rc1.dist-info}/zip-safe +0 -0
@@ -0,0 +1,606 @@
|
|
1
|
+
#! /usr/bin/env python3
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
# vim:fenc=utf-8
|
4
|
+
|
5
|
+
"""
|
6
|
+
Dash utility functions for constructing tokens components.
|
7
|
+
"""
|
8
|
+
|
9
|
+
from __future__ import annotations
|
10
|
+
|
11
|
+
from datetime import datetime, timezone, timedelta
|
12
|
+
from typing import Any, List
|
13
|
+
|
14
|
+
import meerschaum as mrsm
|
15
|
+
from meerschaum.api import debug, CHECK_UPDATE, get_api_connector
|
16
|
+
from meerschaum.api.dash.connectors import get_web_connector
|
17
|
+
from meerschaum.api.dash.components import alert_from_success_tuple
|
18
|
+
from meerschaum.api.dash.sessions import get_user_from_session
|
19
|
+
from meerschaum.utils.typing import WebState, SuccessTuple, List, Tuple
|
20
|
+
from meerschaum.utils.packages import attempt_import, import_html, import_dcc
|
21
|
+
from meerschaum.utils.misc import interval_str, round_time
|
22
|
+
from meerschaum.utils.dtypes import value_is_null
|
23
|
+
from meerschaum._internal.static import STATIC_CONFIG
|
24
|
+
from meerschaum.core import Token
|
25
|
+
from meerschaum.utils.daemon import get_new_daemon_name
|
26
|
+
dcc, html = import_dcc(check_update=CHECK_UPDATE), import_html(check_update=CHECK_UPDATE)
|
27
|
+
dbc = attempt_import('dash_bootstrap_components', lazy=False, check_update=CHECK_UPDATE)
|
28
|
+
|
29
|
+
|
30
|
+
def get_tokens_table(session_id: Optional[str] = None) -> Tuple[dbc.Table, List[dbc.Alert]]:
|
31
|
+
"""
|
32
|
+
Return the main tokens table.
|
33
|
+
"""
|
34
|
+
conn = get_api_connector()
|
35
|
+
user = get_user_from_session(session_id) if session_id is not None else None
|
36
|
+
alerts = []
|
37
|
+
try:
|
38
|
+
tokens = conn.get_tokens(user=user, debug=debug)
|
39
|
+
except Exception as e:
|
40
|
+
tokens = []
|
41
|
+
alerts = [alert_from_success_tuple((False, f"Failed to fetch tokens from '{conn}':\n{e}"))]
|
42
|
+
|
43
|
+
if not tokens:
|
44
|
+
return tokens, alerts
|
45
|
+
|
46
|
+
table_header = [
|
47
|
+
html.Thead(
|
48
|
+
html.Tr([
|
49
|
+
html.Th("Label"),
|
50
|
+
html.Th("Client ID"),
|
51
|
+
html.Th("Created"),
|
52
|
+
html.Th("Expires in"),
|
53
|
+
html.Th("Is Valid"),
|
54
|
+
html.Th(""),
|
55
|
+
]),
|
56
|
+
),
|
57
|
+
]
|
58
|
+
|
59
|
+
rows = [
|
60
|
+
html.Tr([
|
61
|
+
html.Td(str(token.label)),
|
62
|
+
html.Td(html.Pre(str(token.id))),
|
63
|
+
html.Td(get_creation_string(token).replace('Created ', '')),
|
64
|
+
html.Td(get_expiration_string(token).replace('Expires in ', '')),
|
65
|
+
html.Td("✅" if token.is_valid else "❌"),
|
66
|
+
html.Td([
|
67
|
+
dbc.Button(
|
68
|
+
html.B("⠇"),
|
69
|
+
color='link',
|
70
|
+
size='sm',
|
71
|
+
id={'type': 'tokens-context-button', 'index': str(token.id)},
|
72
|
+
style={'text-decoration': 'none'},
|
73
|
+
),
|
74
|
+
build_manage_token_popover(token),
|
75
|
+
build_edit_token_modal(token),
|
76
|
+
build_invalidate_token_modal(token),
|
77
|
+
build_delete_token_modal(token),
|
78
|
+
]),
|
79
|
+
])
|
80
|
+
for token in tokens
|
81
|
+
]
|
82
|
+
|
83
|
+
table_body = [html.Tbody(rows)]
|
84
|
+
table = dbc.Table(table_header + table_body)
|
85
|
+
return table, alerts
|
86
|
+
|
87
|
+
|
88
|
+
def build_manage_token_popover(token: Token) -> dbc.Popover:
|
89
|
+
"""
|
90
|
+
Return the "Manage token" popover.
|
91
|
+
"""
|
92
|
+
return dbc.Popover(
|
93
|
+
[
|
94
|
+
dbc.PopoverHeader(["Manage token"]),
|
95
|
+
dbc.PopoverBody([
|
96
|
+
dbc.ButtonGroup(
|
97
|
+
([
|
98
|
+
dbc.Button(
|
99
|
+
"Edit",
|
100
|
+
outline=True,
|
101
|
+
color='light',
|
102
|
+
id={
|
103
|
+
'type': 'tokens-edit-button',
|
104
|
+
'index': str(token.id),
|
105
|
+
},
|
106
|
+
),
|
107
|
+
dbc.Button(
|
108
|
+
"Invalidate",
|
109
|
+
outline=True,
|
110
|
+
color='warning',
|
111
|
+
id={
|
112
|
+
'type': 'tokens-invalidate-button',
|
113
|
+
'index': str(token.id),
|
114
|
+
},
|
115
|
+
),
|
116
|
+
] if token.is_valid else []) + [
|
117
|
+
dbc.Button(
|
118
|
+
"Delete",
|
119
|
+
color='danger',
|
120
|
+
outline=True,
|
121
|
+
id={
|
122
|
+
'type': 'tokens-delete-button',
|
123
|
+
'index': str(token.id),
|
124
|
+
},
|
125
|
+
),
|
126
|
+
]),
|
127
|
+
]),
|
128
|
+
],
|
129
|
+
body=True,
|
130
|
+
trigger='legacy',
|
131
|
+
autohide=True,
|
132
|
+
id={'type': 'tokens-context-popover', 'index': str(token.id)},
|
133
|
+
target={'type': 'tokens-context-button', 'index': str(token.id)},
|
134
|
+
)
|
135
|
+
|
136
|
+
|
137
|
+
def build_edit_token_modal(token: Token) -> dbc.Modal:
|
138
|
+
"""
|
139
|
+
Return the Modal for editing the token.
|
140
|
+
"""
|
141
|
+
return dbc.Modal(
|
142
|
+
[
|
143
|
+
dbc.ModalHeader([
|
144
|
+
html.H4([
|
145
|
+
"Edit token ",
|
146
|
+
html.B(str(token.label))
|
147
|
+
]),
|
148
|
+
]),
|
149
|
+
dbc.ModalBody([
|
150
|
+
html.Div(id={'type': 'tokens-edit-alerts-div', 'index': str(token.id)}),
|
151
|
+
|
152
|
+
dbc.Form([
|
153
|
+
dbc.Row(
|
154
|
+
([
|
155
|
+
dbc.Label("Name", width='auto'),
|
156
|
+
dbc.Col(
|
157
|
+
[
|
158
|
+
dbc.Input(
|
159
|
+
placeholder="Enter token's label",
|
160
|
+
value=str(token.label),
|
161
|
+
id={
|
162
|
+
'type': 'tokens-name-input',
|
163
|
+
'index': str(token.id),
|
164
|
+
},
|
165
|
+
),
|
166
|
+
],
|
167
|
+
className="me-3",
|
168
|
+
),
|
169
|
+
dbc.Label("Expiration", width='auto'),
|
170
|
+
dbc.Col(
|
171
|
+
dcc.DatePickerSingle(
|
172
|
+
date=(
|
173
|
+
token.expiration.to_pydatetime()
|
174
|
+
if hasattr(token.expiration, 'to_pydatetime')
|
175
|
+
else token.expiration
|
176
|
+
),
|
177
|
+
clearable=True,
|
178
|
+
min_date_allowed=datetime.today().date(),
|
179
|
+
display_format="YYYY-MM-DD",
|
180
|
+
id={
|
181
|
+
'type': 'tokens-expiration-datepickersingle',
|
182
|
+
'index': str(token.id),
|
183
|
+
},
|
184
|
+
)
|
185
|
+
),
|
186
|
+
] if token.is_valid else []) + [
|
187
|
+
dbc.Col([
|
188
|
+
]),
|
189
|
+
],
|
190
|
+
className='g-2',
|
191
|
+
),
|
192
|
+
html.Br(),
|
193
|
+
dbc.Row([
|
194
|
+
]),
|
195
|
+
html.Div([
|
196
|
+
dbc.Button(
|
197
|
+
"Deselect all",
|
198
|
+
size='sm',
|
199
|
+
color='link',
|
200
|
+
id={
|
201
|
+
'type': "tokens-deselect-scopes-button",
|
202
|
+
'index': str(token.id),
|
203
|
+
},
|
204
|
+
style={'text-decoration': 'none'},
|
205
|
+
),
|
206
|
+
html.Br(),
|
207
|
+
dbc.Row([
|
208
|
+
dbc.Label("Scopes", width='auto'),
|
209
|
+
dbc.Col([
|
210
|
+
dbc.Checklist(
|
211
|
+
options=[
|
212
|
+
{"label": scope, "value": scope}
|
213
|
+
for scope in STATIC_CONFIG['tokens']['scopes']
|
214
|
+
],
|
215
|
+
value=token.scopes,
|
216
|
+
id={
|
217
|
+
'type': "tokens-scopes-checklist",
|
218
|
+
'index': str(token.id),
|
219
|
+
},
|
220
|
+
style={'columnCount': 3},
|
221
|
+
),
|
222
|
+
]),
|
223
|
+
]),
|
224
|
+
] if token.is_valid else [], id={
|
225
|
+
'type': 'tokens-scopes-checklist-div',
|
226
|
+
'index': str(token.id),
|
227
|
+
}),
|
228
|
+
]),
|
229
|
+
|
230
|
+
]),
|
231
|
+
dbc.ModalFooter(
|
232
|
+
[
|
233
|
+
html.Small(str(token.id)),
|
234
|
+
dbc.Button(
|
235
|
+
"Submit",
|
236
|
+
id={'type': 'tokens-edit-submit-button', 'index': str(token.id)},
|
237
|
+
),
|
238
|
+
],
|
239
|
+
className='d-flex justify-content-between',
|
240
|
+
),
|
241
|
+
],
|
242
|
+
size='lg',
|
243
|
+
is_open=False,
|
244
|
+
id={'type': 'tokens-edit-modal', 'index': str(token.id)},
|
245
|
+
)
|
246
|
+
|
247
|
+
|
248
|
+
def build_invalidate_token_modal(token: Token) -> dbc.Modal:
|
249
|
+
"""
|
250
|
+
Return the Invalidate token modal.
|
251
|
+
"""
|
252
|
+
return dbc.Modal(
|
253
|
+
[
|
254
|
+
dbc.ModalHeader([
|
255
|
+
html.H4([
|
256
|
+
"Invalidate token ",
|
257
|
+
html.B(token.label),
|
258
|
+
"?"
|
259
|
+
]),
|
260
|
+
]),
|
261
|
+
dbc.ModalBody([
|
262
|
+
html.Div(
|
263
|
+
id={
|
264
|
+
'type': 'tokens-invalidate-alerts-div',
|
265
|
+
'index': str(token.id)
|
266
|
+
},
|
267
|
+
),
|
268
|
+
html.P(
|
269
|
+
[
|
270
|
+
"Are you sure you want to invalidate token ",
|
271
|
+
html.B(token.label),
|
272
|
+
" (",
|
273
|
+
html.I(str(token.id)),
|
274
|
+
")?",
|
275
|
+
],
|
276
|
+
),
|
277
|
+
html.P([html.B("This action cannot be undone!")]),
|
278
|
+
]),
|
279
|
+
dbc.ModalFooter([
|
280
|
+
dbc.Button(
|
281
|
+
"Invalidate",
|
282
|
+
color='danger',
|
283
|
+
id={
|
284
|
+
'type': 'tokens-invalidate-confirm-button',
|
285
|
+
'index': str(token.id),
|
286
|
+
},
|
287
|
+
),
|
288
|
+
]),
|
289
|
+
],
|
290
|
+
id={
|
291
|
+
'type': 'tokens-invalidate-modal',
|
292
|
+
'index': str(token.id),
|
293
|
+
},
|
294
|
+
)
|
295
|
+
|
296
|
+
|
297
|
+
def build_delete_token_modal(token: Token) -> dbc.Modal:
|
298
|
+
"""
|
299
|
+
Return the delete token modal.
|
300
|
+
"""
|
301
|
+
return dbc.Modal(
|
302
|
+
[
|
303
|
+
dbc.ModalHeader([
|
304
|
+
html.H4([
|
305
|
+
"Delete token ",
|
306
|
+
html.B(token.label),
|
307
|
+
"?"
|
308
|
+
]),
|
309
|
+
]),
|
310
|
+
dbc.ModalBody([
|
311
|
+
html.Div(
|
312
|
+
id={
|
313
|
+
'type': 'tokens-delete-alerts-div',
|
314
|
+
'index': str(token.id)
|
315
|
+
},
|
316
|
+
),
|
317
|
+
html.P(
|
318
|
+
[
|
319
|
+
"Are you sure you want to delete token ",
|
320
|
+
html.B(token.label),
|
321
|
+
" (",
|
322
|
+
html.I(str(token.id)),
|
323
|
+
")?",
|
324
|
+
],
|
325
|
+
),
|
326
|
+
html.P([html.B("This action cannot be undone!")]),
|
327
|
+
]),
|
328
|
+
dbc.ModalFooter([
|
329
|
+
dbc.Button(
|
330
|
+
"Delete",
|
331
|
+
color='danger',
|
332
|
+
id={
|
333
|
+
'type': 'tokens-delete-confirm-button',
|
334
|
+
'index': str(token.id),
|
335
|
+
},
|
336
|
+
),
|
337
|
+
]),
|
338
|
+
],
|
339
|
+
id={
|
340
|
+
'type': 'tokens-delete-modal',
|
341
|
+
'index': str(token.id),
|
342
|
+
},
|
343
|
+
)
|
344
|
+
|
345
|
+
|
346
|
+
def get_tokens_cards(session_id: Optional[str] = None) -> Tuple[List[dbc.Card], List[dbc.Alert]]:
|
347
|
+
"""
|
348
|
+
Return the cards and alerts for tokens.
|
349
|
+
"""
|
350
|
+
cards, alerts = [], []
|
351
|
+
conn = get_api_connector()
|
352
|
+
user = get_user_from_session(session_id) if session_id is not None else None
|
353
|
+
try:
|
354
|
+
tokens = conn.get_tokens(user=user, debug=debug)
|
355
|
+
except Exception as e:
|
356
|
+
tokens = []
|
357
|
+
alerts = [alert_from_success_tuple((False, f"Failed to fetch tokens from '{conn}':\n{e}"))]
|
358
|
+
|
359
|
+
for token in tokens:
|
360
|
+
try:
|
361
|
+
cards.append(
|
362
|
+
dbc.Card([
|
363
|
+
dbc.CardHeader(
|
364
|
+
[
|
365
|
+
html.H5(token.label),
|
366
|
+
]
|
367
|
+
),
|
368
|
+
dbc.CardBody(
|
369
|
+
[
|
370
|
+
html.Code(str(token.id), style={'color': '#999999'}),
|
371
|
+
]
|
372
|
+
),
|
373
|
+
dbc.CardFooter(
|
374
|
+
[
|
375
|
+
html.P(
|
376
|
+
get_creation_string(token),
|
377
|
+
style={'color': '#999999'},
|
378
|
+
),
|
379
|
+
html.P(
|
380
|
+
get_expiration_string(token),
|
381
|
+
style={'color': '#999999'},
|
382
|
+
),
|
383
|
+
]
|
384
|
+
),
|
385
|
+
])
|
386
|
+
)
|
387
|
+
except Exception as e:
|
388
|
+
alerts.append(
|
389
|
+
alert_from_success_tuple((False, f"Failed to load metadata for token:\n{e}"))
|
390
|
+
)
|
391
|
+
|
392
|
+
return cards, alerts
|
393
|
+
|
394
|
+
|
395
|
+
def get_creation_string(token: mrsm.core.Token) -> str:
|
396
|
+
"""
|
397
|
+
Return the formatted string to represent the token's creation timestamp.
|
398
|
+
"""
|
399
|
+
creation = token.creation
|
400
|
+
if value_is_null(str(creation)):
|
401
|
+
return ''
|
402
|
+
now = datetime.now(timezone.utc)
|
403
|
+
return 'Created ' + interval_str(creation - now, round_unit=True)
|
404
|
+
|
405
|
+
|
406
|
+
def get_expiration_string(token: mrsm.core.Token) -> str:
|
407
|
+
"""
|
408
|
+
Return the formatted string to represent the token's expiration timestamp.
|
409
|
+
"""
|
410
|
+
expiration = token.expiration
|
411
|
+
if value_is_null(str(expiration)):
|
412
|
+
return 'Does not expire'
|
413
|
+
now = datetime.now(timezone.utc)
|
414
|
+
return 'Expires in ' + interval_str(expiration - now, round_unit=True)
|
415
|
+
|
416
|
+
|
417
|
+
def build_tokens_register_input_modal() -> dbc.Modal:
|
418
|
+
"""
|
419
|
+
Return the layout for the tokens register input modal.
|
420
|
+
"""
|
421
|
+
now = datetime.now(timezone.utc)
|
422
|
+
default_expiration_days = mrsm.get_config(
|
423
|
+
'system', 'api', 'tokens', 'default_expiration_days',
|
424
|
+
) or 366
|
425
|
+
default_expiration = round_time(
|
426
|
+
now + timedelta(days=default_expiration_days),
|
427
|
+
timedelta(days=1),
|
428
|
+
)
|
429
|
+
min_date_allowed = round_time(now + timedelta(days=1), timedelta(days=1))
|
430
|
+
|
431
|
+
return [
|
432
|
+
dbc.ModalHeader(html.H4("Register Token")),
|
433
|
+
dbc.ModalBody([
|
434
|
+
dbc.Form([
|
435
|
+
dbc.Row(
|
436
|
+
[
|
437
|
+
dbc.Label("Name", width='auto'),
|
438
|
+
dbc.Col(
|
439
|
+
[
|
440
|
+
dbc.Input(
|
441
|
+
placeholder="Enter token's label",
|
442
|
+
value=get_new_daemon_name(),
|
443
|
+
id='tokens-name-input'
|
444
|
+
),
|
445
|
+
],
|
446
|
+
className="me-3",
|
447
|
+
),
|
448
|
+
dbc.Label("Expiration", width='auto'),
|
449
|
+
dbc.Col(
|
450
|
+
dcc.DatePickerSingle(
|
451
|
+
date=default_expiration,
|
452
|
+
clearable=True,
|
453
|
+
min_date_allowed=min_date_allowed,
|
454
|
+
display_format="YYYY-MM-DD",
|
455
|
+
id='tokens-expiration-datepickersingle',
|
456
|
+
)
|
457
|
+
),
|
458
|
+
dbc.Col(
|
459
|
+
dbc.Switch(
|
460
|
+
id="tokens-toggle-scopes-switch",
|
461
|
+
label="Grant all scopes",
|
462
|
+
value=True,
|
463
|
+
),
|
464
|
+
className="me-3",
|
465
|
+
),
|
466
|
+
],
|
467
|
+
className='g-2',
|
468
|
+
),
|
469
|
+
html.Br(),
|
470
|
+
dbc.Row([
|
471
|
+
]),
|
472
|
+
html.Div([
|
473
|
+
dbc.Button(
|
474
|
+
"Deselect all",
|
475
|
+
size='sm',
|
476
|
+
color='link',
|
477
|
+
id="tokens-deselect-scopes-button",
|
478
|
+
style={'text-decoration': 'none'},
|
479
|
+
),
|
480
|
+
html.Br(),
|
481
|
+
dbc.Row([
|
482
|
+
dbc.Label("Scopes", width='auto'),
|
483
|
+
dbc.Col([
|
484
|
+
dbc.Checklist(
|
485
|
+
options=[
|
486
|
+
{"label": scope, "value": scope}
|
487
|
+
for scope in STATIC_CONFIG['tokens']['scopes']
|
488
|
+
],
|
489
|
+
value=list(STATIC_CONFIG['tokens']['scopes']),
|
490
|
+
id="tokens-scopes-checklist",
|
491
|
+
style={'columnCount': 3},
|
492
|
+
),
|
493
|
+
]),
|
494
|
+
]),
|
495
|
+
], id='tokens-scopes-checklist-div', style={'display': 'none'}),
|
496
|
+
]),
|
497
|
+
]),
|
498
|
+
dbc.ModalFooter([
|
499
|
+
dbc.Button('Register', id='tokens-register-button'),
|
500
|
+
]),
|
501
|
+
]
|
502
|
+
|
503
|
+
|
504
|
+
def build_register_table_from_token(token: Token) -> dbc.Table:
|
505
|
+
"""
|
506
|
+
Return a table with the token's metadata.
|
507
|
+
"""
|
508
|
+
table_header = [html.Thead(html.Tr([html.Th("Attribute"), html.Th("Value")]))]
|
509
|
+
table_header = []
|
510
|
+
pre_style = {'white-space': 'pre-wrap', 'word-break': 'break-all'}
|
511
|
+
rows = [
|
512
|
+
html.Tr([
|
513
|
+
html.Td(html.B("Client ID")),
|
514
|
+
html.Td(html.Pre(
|
515
|
+
str(token.id),
|
516
|
+
style=pre_style,
|
517
|
+
id='token-id-pre',
|
518
|
+
)),
|
519
|
+
]),
|
520
|
+
html.Tr([
|
521
|
+
html.Td(html.B("Client Secret")),
|
522
|
+
html.Td(html.Pre(
|
523
|
+
str(token.secret),
|
524
|
+
style=pre_style,
|
525
|
+
id='token-secret-pre',
|
526
|
+
)),
|
527
|
+
]),
|
528
|
+
html.Tr([
|
529
|
+
html.Td(html.B("API Key")),
|
530
|
+
html.Td(html.Pre(token.get_api_key(), style=pre_style)),
|
531
|
+
]),
|
532
|
+
html.Tr([
|
533
|
+
html.Td(html.B("Expiration")),
|
534
|
+
html.Td(
|
535
|
+
html.Pre(token.expiration.isoformat(), style=pre_style)
|
536
|
+
if token.expiration is not None
|
537
|
+
else "Does not expire"
|
538
|
+
),
|
539
|
+
]),
|
540
|
+
html.Tr([
|
541
|
+
html.Td(html.B("Scopes")),
|
542
|
+
html.Td(html.P(' '.join(token.scopes), style={'word-break': 'normal'})),
|
543
|
+
]),
|
544
|
+
html.Tr([
|
545
|
+
html.Td(html.B("User")),
|
546
|
+
html.Td(token.user.username if token.user is not None else ""),
|
547
|
+
]),
|
548
|
+
]
|
549
|
+
table_body = [html.Tbody(rows)]
|
550
|
+
table = dbc.Table(
|
551
|
+
table_header + table_body,
|
552
|
+
id='tokens-register-table',
|
553
|
+
)
|
554
|
+
return table
|
555
|
+
|
556
|
+
|
557
|
+
def build_tokens_register_output_modal(token: Token) -> List[Any]:
|
558
|
+
"""
|
559
|
+
Return the layout for the tokens register output modal.
|
560
|
+
"""
|
561
|
+
success, msg = token.register(debug=debug)
|
562
|
+
header_text = (
|
563
|
+
"Registered token "
|
564
|
+
if success
|
565
|
+
else "Failed to register token "
|
566
|
+
)
|
567
|
+
body_children = (
|
568
|
+
[
|
569
|
+
dbc.Stack(
|
570
|
+
[
|
571
|
+
html.Div([
|
572
|
+
html.P(html.B("Copy the token's details to dismiss.")),
|
573
|
+
html.P(html.I("The secret and API key will not be shown again.")),
|
574
|
+
]),
|
575
|
+
html.Div([
|
576
|
+
dbc.Button(
|
577
|
+
"Copy token details",
|
578
|
+
id='tokens-register-copy-button',
|
579
|
+
),
|
580
|
+
], className="ms-auto"),
|
581
|
+
html.Div([
|
582
|
+
dcc.Clipboard(id="tokens-register-clipboard"),
|
583
|
+
]),
|
584
|
+
],
|
585
|
+
direction='horizontal',
|
586
|
+
gap=2,
|
587
|
+
),
|
588
|
+
build_register_table_from_token(token),
|
589
|
+
]
|
590
|
+
if success
|
591
|
+
else alert_from_success_tuple((False, msg))
|
592
|
+
)
|
593
|
+
return [
|
594
|
+
dbc.ModalHeader(
|
595
|
+
html.H4([header_text, html.B(token.label)]),
|
596
|
+
close_button=False,
|
597
|
+
),
|
598
|
+
dbc.ModalBody(body_children),
|
599
|
+
dbc.ModalFooter([
|
600
|
+
dbc.Button(
|
601
|
+
"Close",
|
602
|
+
id='tokens-close-register-output-modal-button',
|
603
|
+
disabled=True,
|
604
|
+
),
|
605
|
+
]),
|
606
|
+
]
|
@@ -8,7 +8,7 @@ Functions for interacting via WebSockets.
|
|
8
8
|
|
9
9
|
import asyncio, sys
|
10
10
|
from meerschaum.api._websockets import websockets
|
11
|
-
from meerschaum.
|
11
|
+
from meerschaum._internal.static import STATIC_CONFIG
|
12
12
|
|
13
13
|
def ws_url_from_href(href: str) -> str:
|
14
14
|
"""
|
meerschaum/api/dash/webterm.py
CHANGED
@@ -31,6 +31,10 @@ def get_webterm(state: WebState) -> Tuple[Any, Any]:
|
|
31
31
|
"""
|
32
32
|
Start the webterm and return its iframe.
|
33
33
|
"""
|
34
|
+
from meerschaum.api import _include_webterm
|
35
|
+
if not _include_webterm:
|
36
|
+
return console_div, []
|
37
|
+
|
34
38
|
session_id = state['session-store.data'].get('session-id', None)
|
35
39
|
username = get_username_from_session(session_id)
|
36
40
|
if not is_session_authenticated(session_id):
|
@@ -6,6 +6,26 @@
|
|
6
6
|
Create and manipulate SQL tables with ORM
|
7
7
|
"""
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
import meerschaum as mrsm
|
10
|
+
import meerschaum.models
|
11
|
+
|
12
|
+
from meerschaum.api.models._pipes import (
|
13
|
+
FetchPipesKeysResponseModel,
|
14
|
+
SyncPipeRequestModel,
|
15
|
+
)
|
16
|
+
from meerschaum.api.models._actions import SuccessTupleResponseModel
|
17
|
+
from meerschaum.api.models._tokens import (
|
18
|
+
GetTokensResponseModel,
|
19
|
+
RegisterTokenResponseModel,
|
20
|
+
RegisterTokenRequestModel,
|
21
|
+
GetTokenResponseModel,
|
22
|
+
)
|
23
|
+
|
24
|
+
__all__ = (
|
25
|
+
'FetchPipesKeysResponseModel',
|
26
|
+
'SyncPipeRequestModel',
|
27
|
+
'SuccessTupleResponseModel',
|
28
|
+
'RegisterTokenResponseModel',
|
29
|
+
'RegisterTokenRequestModel',
|
30
|
+
'GetTokenResponseModel',
|
31
|
+
)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
#! /usr/bin/env python3
|
2
|
+
# vim:fenc=utf-8
|
3
|
+
|
4
|
+
"""
|
5
|
+
Define actions response models.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from typing import Tuple
|
9
|
+
from pydantic import RootModel, ConfigDict
|
10
|
+
from meerschaum.utils.typing import SuccessTuple
|
11
|
+
|
12
|
+
|
13
|
+
class SuccessTupleResponseModel(RootModel[SuccessTuple]):
|
14
|
+
"""
|
15
|
+
A response model for a tuple of a boolean and a string.
|
16
|
+
E.g. `[true, "Success"]`
|
17
|
+
"""
|
18
|
+
model_config = ConfigDict(
|
19
|
+
json_schema_extra={
|
20
|
+
'example': [True, "Success"],
|
21
|
+
},
|
22
|
+
)
|