meerschaum 3.0.0rc3__py3-none-any.whl → 3.0.0rc7__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/_internal/arguments/_parser.py +14 -2
- meerschaum/_internal/cli/__init__.py +6 -0
- meerschaum/_internal/cli/daemons.py +103 -0
- meerschaum/_internal/cli/entry.py +220 -0
- meerschaum/_internal/cli/workers.py +434 -0
- meerschaum/_internal/docs/index.py +1 -2
- meerschaum/_internal/entry.py +44 -8
- meerschaum/_internal/shell/Shell.py +113 -19
- meerschaum/_internal/shell/__init__.py +4 -1
- meerschaum/_internal/static.py +3 -1
- meerschaum/_internal/term/TermPageHandler.py +1 -2
- meerschaum/_internal/term/__init__.py +40 -6
- meerschaum/_internal/term/tools.py +33 -8
- meerschaum/actions/__init__.py +6 -4
- meerschaum/actions/api.py +39 -11
- meerschaum/actions/attach.py +1 -0
- meerschaum/actions/delete.py +4 -2
- meerschaum/actions/edit.py +27 -8
- meerschaum/actions/login.py +8 -8
- meerschaum/actions/register.py +13 -7
- meerschaum/actions/reload.py +22 -5
- meerschaum/actions/restart.py +14 -0
- meerschaum/actions/show.py +69 -4
- meerschaum/actions/start.py +135 -14
- meerschaum/actions/stop.py +36 -3
- meerschaum/actions/sync.py +6 -1
- meerschaum/api/__init__.py +35 -13
- meerschaum/api/_events.py +7 -2
- meerschaum/api/_oauth2.py +47 -4
- meerschaum/api/dash/callbacks/dashboard.py +103 -97
- meerschaum/api/dash/callbacks/jobs.py +3 -2
- meerschaum/api/dash/callbacks/login.py +10 -1
- meerschaum/api/dash/callbacks/pipes.py +136 -57
- meerschaum/api/dash/callbacks/register.py +9 -2
- meerschaum/api/dash/callbacks/tokens.py +2 -1
- meerschaum/api/dash/components.py +6 -7
- meerschaum/api/dash/keys.py +17 -1
- meerschaum/api/dash/pages/login.py +2 -2
- meerschaum/api/dash/pages/pipes.py +14 -4
- meerschaum/api/dash/pipes.py +186 -65
- meerschaum/api/dash/tokens.py +1 -1
- meerschaum/api/dash/webterm.py +14 -6
- meerschaum/api/models/_pipes.py +7 -1
- meerschaum/api/resources/static/js/terminado.js +3 -0
- meerschaum/api/resources/static/js/xterm-addon-unicode11.js +2 -0
- meerschaum/api/resources/templates/termpage.html +1 -0
- meerschaum/api/routes/_jobs.py +23 -11
- meerschaum/api/routes/_login.py +73 -5
- meerschaum/api/routes/_pipes.py +6 -4
- meerschaum/api/routes/_webterm.py +3 -3
- meerschaum/config/__init__.py +60 -13
- meerschaum/config/_default.py +89 -61
- meerschaum/config/_edit.py +10 -8
- meerschaum/config/_formatting.py +2 -0
- meerschaum/config/_patch.py +4 -2
- meerschaum/config/_paths.py +127 -12
- meerschaum/config/_read_config.py +20 -10
- meerschaum/config/_version.py +1 -1
- meerschaum/config/environment.py +262 -0
- meerschaum/config/stack/__init__.py +7 -5
- meerschaum/connectors/_Connector.py +1 -2
- meerschaum/connectors/__init__.py +37 -2
- meerschaum/connectors/api/_APIConnector.py +1 -1
- meerschaum/connectors/api/_jobs.py +11 -0
- meerschaum/connectors/api/_pipes.py +7 -1
- meerschaum/connectors/instance/_plugins.py +9 -1
- meerschaum/connectors/instance/_tokens.py +20 -3
- meerschaum/connectors/instance/_users.py +8 -1
- meerschaum/connectors/parse.py +1 -1
- meerschaum/connectors/sql/_create_engine.py +3 -0
- meerschaum/connectors/sql/_pipes.py +98 -79
- meerschaum/connectors/sql/_users.py +8 -1
- meerschaum/connectors/sql/tables/__init__.py +20 -3
- meerschaum/connectors/valkey/_ValkeyConnector.py +3 -3
- meerschaum/connectors/valkey/_pipes.py +7 -5
- meerschaum/core/Pipe/__init__.py +62 -72
- meerschaum/core/Pipe/_attributes.py +66 -90
- meerschaum/core/Pipe/_cache.py +555 -0
- meerschaum/core/Pipe/_clear.py +0 -11
- meerschaum/core/Pipe/_data.py +0 -50
- meerschaum/core/Pipe/_deduplicate.py +0 -13
- meerschaum/core/Pipe/_delete.py +12 -21
- meerschaum/core/Pipe/_drop.py +11 -23
- meerschaum/core/Pipe/_dtypes.py +1 -1
- meerschaum/core/Pipe/_index.py +8 -14
- meerschaum/core/Pipe/_sync.py +12 -18
- meerschaum/core/Plugin/_Plugin.py +7 -1
- meerschaum/core/Token/_Token.py +1 -1
- meerschaum/core/User/_User.py +1 -2
- meerschaum/jobs/_Executor.py +88 -4
- meerschaum/jobs/_Job.py +135 -35
- meerschaum/jobs/systemd.py +7 -2
- meerschaum/plugins/__init__.py +277 -81
- meerschaum/utils/_get_pipes.py +30 -4
- meerschaum/utils/daemon/Daemon.py +195 -41
- meerschaum/utils/daemon/FileDescriptorInterceptor.py +0 -1
- meerschaum/utils/daemon/RotatingFile.py +63 -36
- meerschaum/utils/daemon/StdinFile.py +53 -13
- meerschaum/utils/daemon/__init__.py +18 -5
- meerschaum/utils/daemon/_names.py +6 -3
- meerschaum/utils/debug.py +34 -4
- meerschaum/utils/dtypes/__init__.py +5 -1
- meerschaum/utils/formatting/__init__.py +4 -1
- meerschaum/utils/formatting/_jobs.py +1 -1
- meerschaum/utils/formatting/_pipes.py +47 -46
- meerschaum/utils/formatting/_pprint.py +1 -0
- meerschaum/utils/formatting/_shell.py +16 -6
- meerschaum/utils/misc.py +18 -38
- meerschaum/utils/packages/__init__.py +15 -13
- meerschaum/utils/packages/_packages.py +1 -0
- meerschaum/utils/pipes.py +39 -7
- meerschaum/utils/process.py +1 -1
- meerschaum/utils/prompt.py +171 -144
- meerschaum/utils/sql.py +12 -2
- meerschaum/utils/threading.py +42 -0
- meerschaum/utils/venv/__init__.py +2 -0
- meerschaum/utils/warnings.py +19 -13
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/METADATA +3 -1
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/RECORD +125 -119
- meerschaum/config/_environment.py +0 -145
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/WHEEL +0 -0
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/entry_points.txt +0 -0
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/licenses/LICENSE +0 -0
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/licenses/NOTICE +0 -0
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/top_level.txt +0 -0
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/zip-safe +0 -0
meerschaum/api/__init__.py
CHANGED
@@ -21,7 +21,6 @@ from meerschaum.config._paths import API_UVICORN_CONFIG_PATH, API_UVICORN_RESOUR
|
|
21
21
|
from meerschaum.plugins import _api_plugins
|
22
22
|
from meerschaum.utils.warnings import warn, dprint
|
23
23
|
from meerschaum.utils.threading import RLock
|
24
|
-
from meerschaum.utils.misc import is_pipe_registered
|
25
24
|
from meerschaum.connectors.parse import parse_instance_keys
|
26
25
|
|
27
26
|
from meerschaum import __version__ as version
|
@@ -67,16 +66,13 @@ from meerschaum.api._exceptions import APIPermissionError
|
|
67
66
|
uvicorn_config_path = API_UVICORN_RESOURCES_PATH / SERVER_ID / 'config.json'
|
68
67
|
|
69
68
|
uvicorn_config = None
|
70
|
-
sys_config = get_config('
|
71
|
-
permissions_config = get_config('
|
69
|
+
sys_config = get_config('api')
|
70
|
+
permissions_config = get_config('api', 'permissions')
|
72
71
|
|
73
72
|
def get_uvicorn_config() -> Dict[str, Any]:
|
74
73
|
"""Read the Uvicorn configuration JSON and return a dictionary."""
|
75
74
|
global uvicorn_config
|
76
75
|
import json
|
77
|
-
runtime = os.environ.get(STATIC_CONFIG['environment']['runtime'], None)
|
78
|
-
if runtime == 'api':
|
79
|
-
return get_config('system', 'api', 'uvicorn')
|
80
76
|
_uvicorn_config = uvicorn_config
|
81
77
|
with _locks['uvicorn_config']:
|
82
78
|
if uvicorn_config is None:
|
@@ -85,6 +81,8 @@ def get_uvicorn_config() -> Dict[str, Any]:
|
|
85
81
|
uvicorn_config = json.load(f)
|
86
82
|
_uvicorn_config = uvicorn_config
|
87
83
|
except Exception:
|
84
|
+
import traceback
|
85
|
+
traceback.print_exc()
|
88
86
|
_uvicorn_config = sys_config.get('uvicorn', None)
|
89
87
|
|
90
88
|
if _uvicorn_config is None:
|
@@ -102,6 +100,10 @@ production = get_uvicorn_config().get('production', False)
|
|
102
100
|
_include_dash = (not no_dash)
|
103
101
|
_include_webterm = (not no_webterm) and _include_dash
|
104
102
|
docs_enabled = not production or sys_config.get('endpoints', {}).get('docs_in_production', True)
|
103
|
+
webterm_port = (
|
104
|
+
get_uvicorn_config().get('webterm_port', None)
|
105
|
+
or mrsm.get_config('api', 'webterm', 'port')
|
106
|
+
)
|
105
107
|
|
106
108
|
default_instance_keys = None
|
107
109
|
_instance_connectors = defaultdict(lambda: None)
|
@@ -129,7 +131,7 @@ def get_api_connector(instance_keys: Optional[str] = None):
|
|
129
131
|
)
|
130
132
|
found_match: bool = False
|
131
133
|
for allowed_keys_pattern in allowed_instance_keys:
|
132
|
-
if fnmatch(instance_keys, allowed_keys_pattern):
|
134
|
+
if fnmatch(str(instance_keys), allowed_keys_pattern):
|
133
135
|
found_match = True
|
134
136
|
break
|
135
137
|
if not found_match:
|
@@ -141,7 +143,9 @@ def get_api_connector(instance_keys: Optional[str] = None):
|
|
141
143
|
if _instance_connectors[instance_keys] is None:
|
142
144
|
try:
|
143
145
|
is_valid_connector = True
|
144
|
-
|
146
|
+
instance_connector = parse_instance_keys(instance_keys, debug=debug)
|
147
|
+
instance_connector._cache_connector = get_cache_connector()
|
148
|
+
_instance_connectors[instance_keys] = instance_connector
|
145
149
|
except Exception:
|
146
150
|
is_valid_connector = False
|
147
151
|
|
@@ -168,7 +172,7 @@ def get_cache_connector(connector_keys: Optional[str] = None):
|
|
168
172
|
return None
|
169
173
|
|
170
174
|
connector_keys = connector_keys or get_config(
|
171
|
-
'
|
175
|
+
'api', 'cache', 'connector',
|
172
176
|
warn=False,
|
173
177
|
)
|
174
178
|
if connector_keys is None:
|
@@ -196,7 +200,11 @@ def pipes(instance_keys: Optional[str] = None, refresh: bool = False) -> PipesDi
|
|
196
200
|
with _locks['pipes-' + instance_keys]:
|
197
201
|
pipes = _instance_pipes[instance_keys]
|
198
202
|
if pipes is None or refresh:
|
199
|
-
pipes = _get_pipes(
|
203
|
+
pipes = _get_pipes(
|
204
|
+
mrsm_instance=instance_keys,
|
205
|
+
cache=True,
|
206
|
+
cache_connector_keys=get_cache_connector(),
|
207
|
+
)
|
200
208
|
_instance_pipes[instance_keys] = pipes
|
201
209
|
return pipes
|
202
210
|
|
@@ -218,9 +226,23 @@ def get_pipe(
|
|
218
226
|
detail="Unable to serve any pipes with connector keys `mrsm` over the API.",
|
219
227
|
)
|
220
228
|
|
221
|
-
|
222
|
-
if
|
223
|
-
|
229
|
+
pipes_dict = pipes(instance_keys)
|
230
|
+
if (
|
231
|
+
not refresh
|
232
|
+
and connector_keys in pipes_dict
|
233
|
+
and metric_key in pipes_dict[connector_keys]
|
234
|
+
and location_key in pipes_dict[connector_keys][metric_key]
|
235
|
+
):
|
236
|
+
return pipes_dict[connector_keys][metric_key][location_key]
|
237
|
+
|
238
|
+
pipe = mrsm.Pipe(
|
239
|
+
connector_keys,
|
240
|
+
metric_key,
|
241
|
+
location_key,
|
242
|
+
mrsm_instance=instance_keys,
|
243
|
+
cache=True,
|
244
|
+
cache_connector_keys=get_cache_connector(),
|
245
|
+
)
|
224
246
|
return pipe
|
225
247
|
|
226
248
|
|
meerschaum/api/_events.py
CHANGED
@@ -8,13 +8,13 @@ Declare FastAPI events in this module (startup, shutdown, etc.).
|
|
8
8
|
|
9
9
|
import sys
|
10
10
|
import os
|
11
|
-
import time
|
12
11
|
from meerschaum.api import (
|
13
12
|
app,
|
14
13
|
get_api_connector,
|
15
14
|
get_cache_connector,
|
16
15
|
get_uvicorn_config,
|
17
16
|
debug,
|
17
|
+
webterm_port,
|
18
18
|
no_dash,
|
19
19
|
_include_dash,
|
20
20
|
_include_webterm,
|
@@ -75,7 +75,7 @@ async def startup():
|
|
75
75
|
try:
|
76
76
|
if _include_webterm:
|
77
77
|
from meerschaum.api.dash.webterm import start_webterm
|
78
|
-
start_webterm()
|
78
|
+
start_webterm(webterm_port=webterm_port)
|
79
79
|
|
80
80
|
connected = retry_connect(
|
81
81
|
get_api_connector(),
|
@@ -98,6 +98,11 @@ async def startup():
|
|
98
98
|
await shutdown()
|
99
99
|
os._exit(1)
|
100
100
|
|
101
|
+
conn = get_api_connector()
|
102
|
+
if conn.type == 'sql':
|
103
|
+
from meerschaum.connectors.sql.tables import get_tables
|
104
|
+
_ = get_tables(conn, refresh=True, create=True, debug=debug)
|
105
|
+
|
101
106
|
start_check_jobs_thread()
|
102
107
|
|
103
108
|
|
meerschaum/api/_oauth2.py
CHANGED
@@ -21,6 +21,7 @@ from meerschaum.core import User, Token
|
|
21
21
|
fastapi, starlette = attempt_import('fastapi', 'starlette', lazy=False, check_update=CHECK_UPDATE)
|
22
22
|
fastapi_responses = attempt_import('fastapi.responses', lazy=False, check_update=CHECK_UPDATE)
|
23
23
|
fastapi_login = attempt_import('fastapi_login', check_update=CHECK_UPDATE)
|
24
|
+
jose_jwt, jose_exceptions = attempt_import('jose.jwt', 'jose.exceptions', lazy=False, check_update=CHECK_UPDATE)
|
24
25
|
from fastapi import Depends, HTTPException, Request
|
25
26
|
from starlette import status
|
26
27
|
|
@@ -91,6 +92,7 @@ async def load_user_or_token(
|
|
91
92
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
92
93
|
detail="Not authenticated.",
|
93
94
|
)
|
95
|
+
|
94
96
|
authorization = authorization.replace('Basic ', '').replace('Bearer ', '')
|
95
97
|
if not authorization.startswith('mrsm-key:'):
|
96
98
|
if not users:
|
@@ -98,12 +100,15 @@ async def load_user_or_token(
|
|
98
100
|
status=status.HTTP_401_UNAUTHORIZED,
|
99
101
|
detail="Users not authenticated for this endpoint.",
|
100
102
|
)
|
103
|
+
|
101
104
|
return await manager(request)
|
105
|
+
|
102
106
|
if not tokens:
|
103
107
|
raise HTTPException(
|
104
108
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
105
109
|
detail="Tokens not authenticated for this endpoint.",
|
106
110
|
)
|
111
|
+
|
107
112
|
return get_token_from_authorization(authorization)
|
108
113
|
|
109
114
|
|
@@ -112,6 +117,7 @@ def ScopedAuth(scopes: List[str]):
|
|
112
117
|
Dependency factory for authenticating with either a user session or a scoped token.
|
113
118
|
"""
|
114
119
|
async def _authenticate(
|
120
|
+
request: Request,
|
115
121
|
user_or_token: Union[User, Token, None] = Depends(
|
116
122
|
load_user_or_token,
|
117
123
|
),
|
@@ -126,12 +132,45 @@ def ScopedAuth(scopes: List[str]):
|
|
126
132
|
headers={"WWW-Authenticate": "Basic"},
|
127
133
|
)
|
128
134
|
|
129
|
-
|
130
|
-
|
135
|
+
authorization = request.headers.get('authorization', request.headers.get('Authorization', None))
|
136
|
+
is_long_lived = authorization and 'mrsm-key:' in authorization
|
137
|
+
|
138
|
+
current_scopes = []
|
139
|
+
### For long-lived API tokens, always hit the database.
|
140
|
+
if is_long_lived:
|
141
|
+
current_scopes = user_or_token.get_scopes(refresh=True, debug=debug)
|
142
|
+
|
143
|
+
### For JWTs, trust the scopes in the token.
|
144
|
+
else:
|
145
|
+
if not authorization:
|
146
|
+
# This should be caught by `load_user_or_token` but we can be safe.
|
147
|
+
raise HTTPException(
|
148
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
149
|
+
detail="Not authenticated.",
|
150
|
+
)
|
151
|
+
|
152
|
+
scheme, _, token_str = authorization.partition(' ')
|
153
|
+
if not token_str or scheme.lower() != 'bearer':
|
154
|
+
raise HTTPException(
|
155
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
156
|
+
detail="Unsupported authentication scheme.",
|
157
|
+
)
|
158
|
+
|
159
|
+
try:
|
160
|
+
payload = jose_jwt.decode(token_str, SECRET, algorithms=['HS256'])
|
161
|
+
current_scopes = payload.get('scopes', [])
|
162
|
+
except jose_exceptions.JWTError:
|
163
|
+
raise HTTPException(
|
164
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
165
|
+
detail="Invalid access token.",
|
166
|
+
headers={"WWW-Authenticate": "Bearer"},
|
167
|
+
)
|
168
|
+
|
169
|
+
if '*' in current_scopes:
|
131
170
|
return user_or_token
|
132
171
|
|
133
172
|
for scope in scopes:
|
134
|
-
if scope not in
|
173
|
+
if scope not in current_scopes:
|
135
174
|
raise HTTPException(
|
136
175
|
status_code=status.HTTP_403_FORBIDDEN,
|
137
176
|
detail=f"Missing required scope: '{scope}'",
|
@@ -159,4 +198,8 @@ def generate_secret_key() -> bytes:
|
|
159
198
|
|
160
199
|
|
161
200
|
SECRET = generate_secret_key()
|
162
|
-
manager = LoginManager(
|
201
|
+
manager = LoginManager(
|
202
|
+
SECRET,
|
203
|
+
token_url=endpoints['login'],
|
204
|
+
scopes=STATIC_CONFIG['tokens']['scopes'],
|
205
|
+
)
|
@@ -498,13 +498,9 @@ def update_flags(input_flags_dropdown_values, n_clicks, input_flags_texts):
|
|
498
498
|
|
499
499
|
@dash_app.callback(
|
500
500
|
Output('connector-keys-dropdown', 'options'),
|
501
|
-
Output('connector-keys-dropdown', 'value'),
|
502
501
|
Output('metric-keys-dropdown', 'options'),
|
503
|
-
Output('metric-keys-dropdown', 'value'),
|
504
502
|
Output('location-keys-dropdown', 'options'),
|
505
|
-
Output('location-keys-dropdown', 'value'),
|
506
503
|
Output('tags-dropdown', 'options'),
|
507
|
-
Output('tags-dropdown', 'value'),
|
508
504
|
Output('instance-select', 'value'),
|
509
505
|
Output('instance-alert-div', 'children'),
|
510
506
|
Input('connector-keys-dropdown', 'value'),
|
@@ -546,15 +542,6 @@ def update_keys_options(
|
|
546
542
|
except Exception as e:
|
547
543
|
instance_alerts += [alert_from_success_tuple((False, str(e)))]
|
548
544
|
|
549
|
-
### Update the keys filters.
|
550
|
-
if connector_keys is None:
|
551
|
-
connector_keys = []
|
552
|
-
if metric_keys is None:
|
553
|
-
metric_keys = []
|
554
|
-
if location_keys is None:
|
555
|
-
location_keys = []
|
556
|
-
if tags is None:
|
557
|
-
tags = []
|
558
545
|
num_filter = 0
|
559
546
|
if connector_keys:
|
560
547
|
num_filter += 1
|
@@ -565,10 +552,6 @@ def update_keys_options(
|
|
565
552
|
if tags:
|
566
553
|
num_filter += 1
|
567
554
|
|
568
|
-
_ck_filter = connector_keys
|
569
|
-
_mk_filter = metric_keys
|
570
|
-
_lk_filter = location_keys
|
571
|
-
_tags_filter = tags
|
572
555
|
_ck_alone = (connector_keys and num_filter == 1) or instance_click
|
573
556
|
_mk_alone = (metric_keys and num_filter == 1) or instance_click
|
574
557
|
_lk_alone = (location_keys and num_filter == 1) or instance_click
|
@@ -581,101 +564,95 @@ def update_keys_options(
|
|
581
564
|
_keys = fetch_pipes_keys(
|
582
565
|
'registered',
|
583
566
|
get_web_connector(ctx.states),
|
584
|
-
connector_keys=
|
585
|
-
metric_keys=
|
586
|
-
location_keys=
|
587
|
-
tags=
|
567
|
+
connector_keys=connector_keys,
|
568
|
+
metric_keys=metric_keys,
|
569
|
+
location_keys=location_keys,
|
570
|
+
tags=tags,
|
588
571
|
)
|
589
572
|
_tags_pipes = mrsm.get_pipes(
|
590
|
-
connector_keys=
|
591
|
-
metric_keys=
|
592
|
-
location_keys=
|
593
|
-
tags=
|
573
|
+
connector_keys=connector_keys,
|
574
|
+
metric_keys=metric_keys,
|
575
|
+
location_keys=location_keys,
|
576
|
+
tags=tags,
|
594
577
|
instance=get_web_connector(ctx.states),
|
595
578
|
as_tags_dict=True,
|
596
579
|
)
|
597
580
|
_all_tags = list(
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
581
|
+
set(
|
582
|
+
mrsm.get_pipes(
|
583
|
+
instance=get_web_connector(ctx.states),
|
584
|
+
as_tags_dict=True,
|
585
|
+
)
|
586
|
+
).union(tags or [])
|
602
587
|
) if _tags_alone else []
|
603
588
|
except Exception as e:
|
604
589
|
instance_alerts += [alert_from_success_tuple((False, str(e)))]
|
605
|
-
_all_keys, _keys = [], []
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
590
|
+
_all_keys, _all_tags, _keys = [], [], []
|
591
|
+
|
592
|
+
connectors_options = sorted(
|
593
|
+
list(
|
594
|
+
set(
|
595
|
+
keys_tuple[0] for keys_tuple in (_all_keys if _ck_alone else _keys)
|
596
|
+
).union(set(connector_keys or []))
|
597
|
+
),
|
598
|
+
key=(lambda x: str(x).lower()),
|
599
|
+
)
|
600
|
+
metrics_options = sorted(
|
601
|
+
list(
|
602
|
+
set(
|
603
|
+
keys_tuple[1] for keys_tuple in (_all_keys if _mk_alone else _keys)
|
604
|
+
).union(set(metric_keys or []))
|
605
|
+
),
|
606
|
+
key=(lambda x: str(x).lower()),
|
607
|
+
)
|
608
|
+
locations_options = sorted(
|
609
|
+
list(
|
610
|
+
set(
|
611
|
+
(
|
612
|
+
str(keys_tuple[2])
|
613
|
+
for keys_tuple in (_all_keys if _lk_alone else _keys)
|
614
|
+
)
|
615
|
+
).union(set((str(_lk) for _lk in (location_keys or []))))
|
616
|
+
),
|
617
|
+
key=(lambda x: str(x).lower()),
|
618
|
+
)
|
619
|
+
|
620
|
+
tags_options = sorted(
|
621
|
+
list(
|
622
|
+
set(
|
623
|
+
(_all_tags if _tags_alone else _tags_pipes)
|
624
|
+
).union(set(tags or []))
|
625
|
+
),
|
626
|
+
key=(lambda x: str(x).lower()),
|
627
|
+
)
|
629
628
|
|
630
|
-
_connectors_options.sort(key=lambda x: str(x).lower())
|
631
|
-
_metrics_options.sort(key=lambda x: str(x).lower())
|
632
|
-
_locations_options.sort(key=lambda x: str(x).lower())
|
633
|
-
_tags_options.sort(key=lambda x: str(x).lower())
|
634
|
-
connector_keys = [
|
635
|
-
ck
|
636
|
-
for ck in connector_keys
|
637
|
-
if ck in [
|
638
|
-
_ck['value']
|
639
|
-
for _ck in _connectors_options
|
640
|
-
]
|
641
|
-
]
|
642
|
-
metric_keys = [
|
643
|
-
mk
|
644
|
-
for mk in metric_keys
|
645
|
-
if mk in [
|
646
|
-
_mk['value']
|
647
|
-
for _mk in _metrics_options
|
648
|
-
]
|
649
|
-
]
|
650
|
-
location_keys = [
|
651
|
-
lk
|
652
|
-
for lk in location_keys
|
653
|
-
if lk in [
|
654
|
-
_lk['value']
|
655
|
-
for _lk in _locations_options
|
656
|
-
]
|
657
|
-
]
|
658
|
-
tags = [
|
659
|
-
tag
|
660
|
-
for tag in tags
|
661
|
-
if tag in [
|
662
|
-
_tag['value']
|
663
|
-
for _tag in _tags_options
|
664
|
-
]
|
665
|
-
]
|
666
629
|
return (
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
_locations_options,
|
672
|
-
location_keys,
|
673
|
-
_tags_options,
|
674
|
-
tags,
|
630
|
+
connectors_options,
|
631
|
+
metrics_options,
|
632
|
+
locations_options,
|
633
|
+
tags_options,
|
675
634
|
(instance_keys if update_instance_keys else dash.no_update),
|
676
635
|
instance_alerts,
|
677
636
|
)
|
678
637
|
|
638
|
+
|
639
|
+
@dash_app.callback(
|
640
|
+
Output('connector-keys-dropdown', 'value'),
|
641
|
+
Output('metric-keys-dropdown', 'value'),
|
642
|
+
Output('location-keys-dropdown', 'value'),
|
643
|
+
Output('tags-dropdown', 'value'),
|
644
|
+
Input('clear-all-keys-button', 'n_clicks'),
|
645
|
+
prevent_initial_call=True,
|
646
|
+
)
|
647
|
+
def clear_all_keys_button_click(n_clicks):
|
648
|
+
"""
|
649
|
+
Clear the keys dropdowns when the `Clear all` button is clicked.
|
650
|
+
"""
|
651
|
+
if not n_clicks:
|
652
|
+
raise PreventUpdate
|
653
|
+
|
654
|
+
return [], [], [], []
|
655
|
+
|
679
656
|
dash_app.clientside_callback(
|
680
657
|
"""
|
681
658
|
function(
|
@@ -1216,3 +1193,32 @@ def toggle_pages_offcanvas(n_clicks: Optional[int], is_open: bool):
|
|
1216
1193
|
if n_clicks:
|
1217
1194
|
return not is_open, pages_children
|
1218
1195
|
return is_open, pages_children
|
1196
|
+
|
1197
|
+
|
1198
|
+
@dash_app.callback(
|
1199
|
+
Output({'type': 'calculate-rowcount-div', 'index': MATCH}, 'children'),
|
1200
|
+
Input({'type': 'calculate-rowcount-button', 'index': MATCH}, 'n_clicks'),
|
1201
|
+
prevent_initial_call=True,
|
1202
|
+
)
|
1203
|
+
def calculate_rowcount_button_click(n_clicks: int):
|
1204
|
+
"""
|
1205
|
+
Calculate the rowcount for the pipe.
|
1206
|
+
"""
|
1207
|
+
if not n_clicks:
|
1208
|
+
raise PreventUpdate
|
1209
|
+
|
1210
|
+
triggered = dash.callback_context.triggered
|
1211
|
+
if triggered[0]['value'] is None:
|
1212
|
+
raise PreventUpdate
|
1213
|
+
|
1214
|
+
pipe = pipe_from_ctx(triggered, 'n_clicks')
|
1215
|
+
if pipe is None:
|
1216
|
+
raise PreventUpdate
|
1217
|
+
|
1218
|
+
try:
|
1219
|
+
rowcount = pipe.get_rowcount(debug=debug)
|
1220
|
+
return f"{rowcount:,}"
|
1221
|
+
except Exception as e:
|
1222
|
+
return (
|
1223
|
+
alert_from_success_tuple((False, f"Failed to calculate row count: {e}"))
|
1224
|
+
)
|
@@ -13,6 +13,7 @@ import time
|
|
13
13
|
import traceback
|
14
14
|
from datetime import datetime, timezone
|
15
15
|
|
16
|
+
import meerschaum as mrsm
|
16
17
|
from meerschaum.jobs import get_jobs
|
17
18
|
from meerschaum.utils.typing import Optional, Dict, Any
|
18
19
|
from meerschaum.api import CHECK_UPDATE
|
@@ -136,12 +137,12 @@ def manage_job_button_click(
|
|
136
137
|
old_status = job.status
|
137
138
|
try:
|
138
139
|
success, msg = manage_functions[manage_job_action]()
|
139
|
-
except Exception
|
140
|
+
except Exception:
|
140
141
|
success, msg = False, traceback.format_exc()
|
141
142
|
|
142
143
|
### Wait for a status change before building the elements.
|
143
144
|
timeout_seconds = 1.0
|
144
|
-
check_interval_seconds =
|
145
|
+
check_interval_seconds = mrsm.get_config('system', 'cli', 'refresh_seconds')
|
145
146
|
begin = time.perf_counter()
|
146
147
|
while (time.perf_counter() - begin) < timeout_seconds:
|
147
148
|
if job.status != old_status:
|
@@ -16,11 +16,14 @@ from meerschaum.utils.typing import Optional
|
|
16
16
|
dash = attempt_import('dash', lazy=False, check_update=CHECK_UPDATE)
|
17
17
|
from dash.exceptions import PreventUpdate
|
18
18
|
from dash.dependencies import Input, Output, State
|
19
|
+
|
19
20
|
from meerschaum.api.dash import dash_app, debug, pipes, _get_pipes
|
20
21
|
from meerschaum.api.dash.sessions import set_session
|
21
22
|
from meerschaum.api.dash.connectors import get_web_connector
|
22
23
|
from meerschaum.api.routes._login import login
|
23
24
|
from meerschaum.api.dash.components import alert_from_success_tuple
|
25
|
+
from meerschaum.api._oauth2 import CustomOAuth2PasswordRequestForm
|
26
|
+
from meerschaum._internal.static import STATIC_CONFIG
|
24
27
|
from fastapi_login.exceptions import InvalidCredentialsException
|
25
28
|
from fastapi.exceptions import HTTPException
|
26
29
|
dbc = attempt_import('dash_bootstrap_components', lazy=False, check_update=CHECK_UPDATE)
|
@@ -73,7 +76,13 @@ def login_button_click(
|
|
73
76
|
raise PreventUpdate
|
74
77
|
|
75
78
|
try:
|
76
|
-
|
79
|
+
form = CustomOAuth2PasswordRequestForm(
|
80
|
+
grant_type='password',
|
81
|
+
username=username,
|
82
|
+
password=password,
|
83
|
+
scope=' '.join(STATIC_CONFIG['tokens']['scopes'])
|
84
|
+
)
|
85
|
+
_ = login(form)
|
77
86
|
session_id = str(uuid.uuid4())
|
78
87
|
session_data = {
|
79
88
|
'session-id': session_id,
|