meerschaum 3.0.0rc4__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 +2 -2
- meerschaum/api/_oauth2.py +47 -4
- meerschaum/api/dash/callbacks/dashboard.py +29 -0
- meerschaum/api/dash/callbacks/jobs.py +3 -2
- meerschaum/api/dash/callbacks/login.py +10 -1
- meerschaum/api/dash/callbacks/register.py +9 -2
- meerschaum/api/dash/pages/login.py +2 -2
- meerschaum/api/dash/pipes.py +72 -36
- 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 +93 -79
- meerschaum/connectors/sql/_users.py +8 -1
- meerschaum/connectors/valkey/_ValkeyConnector.py +3 -3
- meerschaum/connectors/valkey/_pipes.py +7 -5
- meerschaum/core/Pipe/__init__.py +45 -71
- 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/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/_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 +33 -5
- 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.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/METADATA +3 -1
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/RECORD +116 -110
- meerschaum/config/_environment.py +0 -145
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/WHEEL +0 -0
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/entry_points.txt +0 -0
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/licenses/LICENSE +0 -0
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/licenses/NOTICE +0 -0
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/top_level.txt +0 -0
- {meerschaum-3.0.0rc4.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(),
|
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
|
+
)
|
@@ -1193,3 +1193,32 @@ def toggle_pages_offcanvas(n_clicks: Optional[int], is_open: bool):
|
|
1193
1193
|
if n_clicks:
|
1194
1194
|
return not is_open, pages_children
|
1195
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,
|
@@ -15,6 +15,7 @@ from dash.exceptions import PreventUpdate
|
|
15
15
|
from meerschaum.core import User
|
16
16
|
from meerschaum._internal.static import STATIC_CONFIG
|
17
17
|
from meerschaum.utils.packages import attempt_import
|
18
|
+
from meerschaum.api._oauth2 import CustomOAuth2PasswordRequestForm
|
18
19
|
dash = attempt_import('dash', check_update=CHECK_UPDATE)
|
19
20
|
from fastapi.exceptions import HTTPException
|
20
21
|
|
@@ -97,10 +98,16 @@ def register_button_click(
|
|
97
98
|
form_class += ' is-invalid'
|
98
99
|
return {}, form_class, dash.no_update
|
99
100
|
try:
|
100
|
-
|
101
|
+
form = CustomOAuth2PasswordRequestForm(
|
102
|
+
grant_type='password',
|
103
|
+
username=username,
|
104
|
+
password=password,
|
105
|
+
scope=' '.join(STATIC_CONFIG['tokens']['scopes'])
|
106
|
+
)
|
107
|
+
_ = login(form)
|
101
108
|
session_data = {'session-id': str(uuid.uuid4())}
|
102
109
|
set_session(session_data['session-id'], {'username': username})
|
103
|
-
except HTTPException
|
110
|
+
except HTTPException:
|
104
111
|
form_class += ' is-invalid'
|
105
112
|
session_data = None
|
106
113
|
return session_data, form_class, (dash.no_update if not session_data else endpoints['dash'])
|
@@ -49,11 +49,11 @@ registration_div = html.Div(
|
|
49
49
|
html.Code('mrsm register user newuser -i sql:main', className='codeblock'),
|
50
50
|
),
|
51
51
|
dcc.Markdown("""
|
52
|
-
To enable online registration, open the `
|
52
|
+
To enable online registration, open the `api` configuration file and""" +
|
53
53
|
""" set the permissions to `true`:"""
|
54
54
|
),
|
55
55
|
html.Pre(
|
56
|
-
html.Code('mrsm edit config
|
56
|
+
html.Code('mrsm edit config api', className='codeblock'),
|
57
57
|
),
|
58
58
|
html.Br(),
|
59
59
|
dcc.Markdown('The settings file should look something like this:'),
|
meerschaum/api/dash/pipes.py
CHANGED
@@ -377,48 +377,48 @@ def accordion_items_from_pipe(
|
|
377
377
|
html.Th(
|
378
378
|
html.Span(
|
379
379
|
"Key",
|
380
|
-
id={'type': 'key-table-header', '
|
380
|
+
id={'type': 'key-table-header', 'index': pipe_meta_str},
|
381
381
|
style={"textDecoration": "underline", "cursor": "pointer"},
|
382
382
|
),
|
383
383
|
),
|
384
384
|
html.Th(
|
385
385
|
html.Span(
|
386
386
|
"Column",
|
387
|
-
id={'type': 'column-table-header', '
|
387
|
+
id={'type': 'column-table-header', 'index': pipe_meta_str},
|
388
388
|
style={"textDecoration": "underline", "cursor": "pointer"},
|
389
389
|
),
|
390
390
|
),
|
391
391
|
html.Th(
|
392
392
|
html.Span(
|
393
393
|
"Index",
|
394
|
-
id={'type': 'index-table-header', '
|
394
|
+
id={'type': 'index-table-header', 'index': pipe_meta_str},
|
395
395
|
style={"textDecoration": "underline", "cursor": "pointer"},
|
396
396
|
),
|
397
397
|
),
|
398
398
|
html.Th(
|
399
399
|
html.Span(
|
400
400
|
"Is Composite",
|
401
|
-
id={'type': 'is-composite-table-header', '
|
401
|
+
id={'type': 'is-composite-table-header', 'index': pipe_meta_str},
|
402
402
|
style={"textDecoration": "underline", "cursor": "pointer"},
|
403
403
|
),
|
404
404
|
),
|
405
405
|
dbc.Tooltip(
|
406
406
|
"Unique reference name for the index "
|
407
407
|
"(e.g. `datetime` for the range axis)",
|
408
|
-
target={'type': 'key-table-header', '
|
408
|
+
target={'type': 'key-table-header', 'index': pipe_meta_str},
|
409
409
|
),
|
410
410
|
dbc.Tooltip(
|
411
411
|
"The actual column (field name) in the target dataset.",
|
412
|
-
target={'type': 'column-table-header', '
|
412
|
+
target={'type': 'column-table-header', 'index': pipe_meta_str},
|
413
413
|
),
|
414
414
|
dbc.Tooltip(
|
415
415
|
"The name of the index created on the given columns.",
|
416
|
-
target={'type': 'index-table-header', '
|
416
|
+
target={'type': 'index-table-header', 'index': pipe_meta_str},
|
417
417
|
),
|
418
418
|
dbc.Tooltip(
|
419
419
|
"Whether the column is used in the composite primary key "
|
420
420
|
"to determine updates.",
|
421
|
-
target={'type': 'is-composite-table-header', '
|
421
|
+
target={'type': 'is-composite-table-header', 'index': pipe_meta_str},
|
422
422
|
),
|
423
423
|
]
|
424
424
|
)
|
@@ -483,9 +483,14 @@ def accordion_items_from_pipe(
|
|
483
483
|
])
|
484
484
|
)
|
485
485
|
|
486
|
-
items_bodies['overview'] =
|
487
|
-
|
488
|
-
|
486
|
+
items_bodies['overview'] = html.Div(
|
487
|
+
dbc.Table(
|
488
|
+
overview_header + [html.Tbody(overview_rows)],
|
489
|
+
bordered=False,
|
490
|
+
hover=True,
|
491
|
+
striped=False,
|
492
|
+
),
|
493
|
+
style={'overflowX': 'auto'},
|
489
494
|
)
|
490
495
|
|
491
496
|
if 'stats' in active_items:
|
@@ -497,17 +502,13 @@ def accordion_items_from_pipe(
|
|
497
502
|
(newest_time - oldest_time) if newest_time is not None and oldest_time is not None
|
498
503
|
else None
|
499
504
|
)
|
500
|
-
rowcount = pipe.get_rowcount(debug=debug)
|
501
505
|
except Exception:
|
502
506
|
oldest_time = None
|
503
507
|
newest_time = None
|
504
508
|
interval = None
|
505
|
-
rowcount = None
|
506
509
|
|
507
510
|
stats_rows = []
|
508
|
-
if
|
509
|
-
stats_rows.append(html.Tr([html.Td("Row Count"), html.Td(f"{rowcount:,}")]))
|
510
|
-
if interval is not None:
|
511
|
+
if interval is not None and not isinstance(interval, int):
|
511
512
|
stats_rows.append(
|
512
513
|
html.Tr([html.Td("Timespan"), html.Td(humanfriendly.format_timespan(interval))])
|
513
514
|
)
|
@@ -516,7 +517,39 @@ def accordion_items_from_pipe(
|
|
516
517
|
if newest_time is not None:
|
517
518
|
stats_rows.append(html.Tr([html.Td("Newest time"), html.Td(str(newest_time))]))
|
518
519
|
|
519
|
-
|
520
|
+
precision = pipe.precision
|
521
|
+
if precision:
|
522
|
+
stats_rows.append(
|
523
|
+
html.Tr([
|
524
|
+
html.Td("Precision"),
|
525
|
+
html.Td(str(precision.get('interval', 1)) + ' ' + str(precision.get('unit', 'unit')))
|
526
|
+
])
|
527
|
+
)
|
528
|
+
|
529
|
+
stats_rows.append(
|
530
|
+
html.Tr([
|
531
|
+
html.Td("Row count"),
|
532
|
+
html.Td(
|
533
|
+
html.Div(
|
534
|
+
dbc.Button(
|
535
|
+
"Calculate",
|
536
|
+
color='link',
|
537
|
+
size='sm',
|
538
|
+
style={'text-decoration': 'none'},
|
539
|
+
id={'type': 'calculate-rowcount-button', 'index': pipe_meta_str},
|
540
|
+
)
|
541
|
+
if pipe.exists(debug=debug)
|
542
|
+
else '0'
|
543
|
+
),
|
544
|
+
id={'type': 'calculate-rowcount-div', 'index': pipe_meta_str},
|
545
|
+
)
|
546
|
+
])
|
547
|
+
)
|
548
|
+
|
549
|
+
items_bodies['stats'] = html.Div(
|
550
|
+
dbc.Table(stats_header + [html.Tbody(stats_rows)], hover=True),
|
551
|
+
style={'overflowX': 'auto'},
|
552
|
+
)
|
520
553
|
|
521
554
|
if 'columns' in active_items:
|
522
555
|
try:
|
@@ -546,7 +579,7 @@ def accordion_items_from_pipe(
|
|
546
579
|
mode='norm',
|
547
580
|
tabSize=4,
|
548
581
|
theme='twilight',
|
549
|
-
id={'type': 'parameters-editor', 'index':
|
582
|
+
id={'type': 'parameters-editor', 'index': pipe_meta_str},
|
550
583
|
width='100%',
|
551
584
|
height='500px',
|
552
585
|
readOnly=False,
|
@@ -558,19 +591,19 @@ def accordion_items_from_pipe(
|
|
558
591
|
)
|
559
592
|
update_parameters_button = dbc.Button(
|
560
593
|
"Update",
|
561
|
-
id={'type': 'update-parameters-button', 'index':
|
594
|
+
id={'type': 'update-parameters-button', 'index': pipe_meta_str},
|
562
595
|
)
|
563
596
|
|
564
597
|
as_yaml_button = dbc.Button(
|
565
598
|
"YAML",
|
566
|
-
id={'type': 'parameters-as-yaml-button', 'index':
|
599
|
+
id={'type': 'parameters-as-yaml-button', 'index': pipe_meta_str},
|
567
600
|
color='link',
|
568
601
|
size='sm',
|
569
602
|
style={'text-decoration': 'none'},
|
570
603
|
)
|
571
604
|
as_json_button = dbc.Button(
|
572
605
|
"JSON",
|
573
|
-
id={'type': 'parameters-as-json-button', 'index':
|
606
|
+
id={'type': 'parameters-as-json-button', 'index': pipe_meta_str},
|
574
607
|
color='link',
|
575
608
|
size='sm',
|
576
609
|
style={'text-decoration': 'none', 'margin-left': '10px'},
|
@@ -596,7 +629,7 @@ def accordion_items_from_pipe(
|
|
596
629
|
html.Div(
|
597
630
|
id={
|
598
631
|
'type': 'update-parameters-success-div',
|
599
|
-
'index':
|
632
|
+
'index': pipe_meta_str,
|
600
633
|
}
|
601
634
|
)
|
602
635
|
],
|
@@ -640,7 +673,7 @@ def accordion_items_from_pipe(
|
|
640
673
|
mode='sql',
|
641
674
|
tabSize=4,
|
642
675
|
theme='twilight',
|
643
|
-
id={'type': 'sql-editor', 'index':
|
676
|
+
id={'type': 'sql-editor', 'index': pipe_meta_str},
|
644
677
|
width='100%',
|
645
678
|
height='500px',
|
646
679
|
readOnly=False,
|
@@ -652,7 +685,7 @@ def accordion_items_from_pipe(
|
|
652
685
|
)
|
653
686
|
update_sql_button = dbc.Button(
|
654
687
|
"Update",
|
655
|
-
id={'type': 'update-sql-button', 'index':
|
688
|
+
id={'type': 'update-sql-button', 'index': pipe_meta_str},
|
656
689
|
)
|
657
690
|
items_bodies['sql'] = html.Div([
|
658
691
|
sql_editor,
|
@@ -661,7 +694,7 @@ def accordion_items_from_pipe(
|
|
661
694
|
dbc.Col([update_sql_button], width=2),
|
662
695
|
dbc.Col([
|
663
696
|
html.Div(
|
664
|
-
id={'type': 'update-sql-success-div', 'index':
|
697
|
+
id={'type': 'update-sql-success-div', 'index': pipe_meta_str}
|
665
698
|
)
|
666
699
|
],
|
667
700
|
width=True,
|
@@ -683,7 +716,7 @@ def accordion_items_from_pipe(
|
|
683
716
|
mode='norm',
|
684
717
|
tabSize=4,
|
685
718
|
theme='twilight',
|
686
|
-
id={'type': 'query-editor', 'index':
|
719
|
+
id={'type': 'query-editor', 'index': pipe_meta_str},
|
687
720
|
width='100%',
|
688
721
|
height='200px',
|
689
722
|
readOnly=False,
|
@@ -695,17 +728,17 @@ def accordion_items_from_pipe(
|
|
695
728
|
)
|
696
729
|
query_data_button = dbc.Button(
|
697
730
|
"Query",
|
698
|
-
id={'type': 'query-data-button', 'index':
|
731
|
+
id={'type': 'query-data-button', 'index': pipe_meta_str},
|
699
732
|
)
|
700
733
|
|
701
734
|
begin_end_input_group = dbc.InputGroup(
|
702
735
|
[
|
703
736
|
dbc.Input(
|
704
|
-
id={'type': 'query-data-begin-input', 'index':
|
737
|
+
id={'type': 'query-data-begin-input', 'index': pipe_meta_str},
|
705
738
|
placeholder="Begin",
|
706
739
|
),
|
707
740
|
dbc.Input(
|
708
|
-
id={'type': 'query-data-end-input', 'index':
|
741
|
+
id={'type': 'query-data-end-input', 'index': pipe_meta_str},
|
709
742
|
placeholder="End",
|
710
743
|
),
|
711
744
|
],
|
@@ -719,9 +752,12 @@ def accordion_items_from_pipe(
|
|
719
752
|
value=10,
|
720
753
|
step=1,
|
721
754
|
placeholder="Limit",
|
722
|
-
id={'type': 'limit-input', 'index':
|
755
|
+
id={'type': 'limit-input', 'index': pipe_meta_str},
|
756
|
+
)
|
757
|
+
query_result_div = html.Div(
|
758
|
+
id={'type': 'query-result-div', 'index': pipe_meta_str},
|
759
|
+
style={'overflowX': 'auto'},
|
723
760
|
)
|
724
|
-
query_result_div = html.Div(id={'type': 'query-result-div', 'index': json.dumps(pipe.meta)})
|
725
761
|
|
726
762
|
items_bodies['query-data'] = html.Div([
|
727
763
|
query_editor,
|
@@ -746,7 +782,7 @@ def accordion_items_from_pipe(
|
|
746
782
|
mode = 'norm',
|
747
783
|
tabSize = 4,
|
748
784
|
theme = 'twilight',
|
749
|
-
id = {'type': 'sync-editor', 'index':
|
785
|
+
id = {'type': 'sync-editor', 'index': pipe_meta_str},
|
750
786
|
width = '100%',
|
751
787
|
height = '500px',
|
752
788
|
readOnly = False,
|
@@ -759,14 +795,14 @@ def accordion_items_from_pipe(
|
|
759
795
|
|
760
796
|
sync_as_json_button = dbc.Button(
|
761
797
|
"JSON",
|
762
|
-
id={'type': 'sync-as-json-button', 'index':
|
798
|
+
id={'type': 'sync-as-json-button', 'index': pipe_meta_str},
|
763
799
|
color='link',
|
764
800
|
size='sm',
|
765
801
|
style={'text-decoration': 'none', 'margin-left': '10px'},
|
766
802
|
)
|
767
803
|
sync_as_lines_button = dbc.Button(
|
768
804
|
"Lines",
|
769
|
-
id={'type': 'sync-as-lines-button', 'index':
|
805
|
+
id={'type': 'sync-as-lines-button', 'index': pipe_meta_str},
|
770
806
|
color='link',
|
771
807
|
size='sm',
|
772
808
|
style={'text-decoration': 'none', 'margin-left': '10px'},
|
@@ -774,9 +810,9 @@ def accordion_items_from_pipe(
|
|
774
810
|
|
775
811
|
update_sync_button = dbc.Button(
|
776
812
|
"Sync",
|
777
|
-
id = {'type': 'update-sync-button', 'index':
|
813
|
+
id = {'type': 'update-sync-button', 'index': pipe_meta_str},
|
778
814
|
)
|
779
|
-
sync_success_div = html.Div(id={'type': 'sync-success-div', 'index':
|
815
|
+
sync_success_div = html.Div(id={'type': 'sync-success-div', 'index': pipe_meta_str})
|
780
816
|
items_bodies['sync-data'] = html.Div([
|
781
817
|
sync_editor,
|
782
818
|
html.Br(),
|