meerschaum 2.1.7__py3-none-any.whl → 2.2.0__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/__main__.py +1 -1
- meerschaum/_internal/arguments/_parser.py +3 -0
- meerschaum/_internal/entry.py +3 -2
- meerschaum/actions/install.py +7 -3
- meerschaum/actions/show.py +128 -42
- meerschaum/actions/sync.py +7 -3
- meerschaum/api/__init__.py +24 -14
- meerschaum/api/_oauth2.py +4 -4
- meerschaum/api/dash/callbacks/dashboard.py +93 -23
- meerschaum/api/dash/callbacks/jobs.py +55 -3
- meerschaum/api/dash/jobs.py +34 -8
- meerschaum/api/dash/keys.py +1 -1
- meerschaum/api/dash/pages/dashboard.py +14 -4
- meerschaum/api/dash/pipes.py +137 -26
- meerschaum/api/dash/plugins.py +25 -9
- meerschaum/api/resources/static/js/xterm.js +1 -1
- meerschaum/api/resources/templates/termpage.html +3 -0
- meerschaum/api/routes/_login.py +5 -4
- meerschaum/api/routes/_plugins.py +6 -3
- meerschaum/config/_dash.py +11 -0
- meerschaum/config/_default.py +3 -1
- meerschaum/config/_jobs.py +13 -4
- meerschaum/config/_paths.py +2 -0
- meerschaum/config/_sync.py +2 -3
- meerschaum/config/_version.py +1 -1
- meerschaum/config/stack/__init__.py +6 -7
- meerschaum/config/stack/grafana/__init__.py +1 -1
- meerschaum/config/static/__init__.py +4 -1
- meerschaum/connectors/__init__.py +2 -0
- meerschaum/connectors/api/_plugins.py +2 -1
- meerschaum/connectors/sql/SQLConnector.py +4 -2
- meerschaum/connectors/sql/_create_engine.py +9 -9
- meerschaum/connectors/sql/_instance.py +3 -1
- meerschaum/connectors/sql/_pipes.py +54 -38
- meerschaum/connectors/sql/_plugins.py +0 -2
- meerschaum/connectors/sql/_sql.py +7 -9
- meerschaum/core/User/_User.py +158 -16
- meerschaum/core/User/__init__.py +1 -1
- meerschaum/plugins/_Plugin.py +12 -3
- meerschaum/plugins/__init__.py +23 -1
- meerschaum/utils/daemon/Daemon.py +89 -36
- meerschaum/utils/daemon/FileDescriptorInterceptor.py +140 -0
- meerschaum/utils/daemon/RotatingFile.py +130 -14
- meerschaum/utils/daemon/__init__.py +3 -0
- meerschaum/utils/dtypes/__init__.py +9 -5
- meerschaum/utils/packages/__init__.py +21 -5
- meerschaum/utils/packages/_packages.py +18 -20
- meerschaum/utils/process.py +13 -10
- meerschaum/utils/schedule.py +276 -30
- meerschaum/utils/threading.py +1 -0
- meerschaum/utils/typing.py +1 -1
- {meerschaum-2.1.7.dist-info → meerschaum-2.2.0.dist-info}/METADATA +59 -62
- {meerschaum-2.1.7.dist-info → meerschaum-2.2.0.dist-info}/RECORD +59 -57
- {meerschaum-2.1.7.dist-info → meerschaum-2.2.0.dist-info}/WHEEL +1 -1
- {meerschaum-2.1.7.dist-info → meerschaum-2.2.0.dist-info}/LICENSE +0 -0
- {meerschaum-2.1.7.dist-info → meerschaum-2.2.0.dist-info}/NOTICE +0 -0
- {meerschaum-2.1.7.dist-info → meerschaum-2.2.0.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.1.7.dist-info → meerschaum-2.2.0.dist-info}/top_level.txt +0 -0
- {meerschaum-2.1.7.dist-info → meerschaum-2.2.0.dist-info}/zip-safe +0 -0
meerschaum/__main__.py
CHANGED
@@ -346,6 +346,9 @@ groups['misc'].add_argument(
|
|
346
346
|
groups['misc'].add_argument(
|
347
347
|
'--nopretty', action="store_true", help="Print elements without 'pretty' formatting"
|
348
348
|
)
|
349
|
+
groups['misc'].add_argument(
|
350
|
+
'--skip-deps', action="store_true", help="Skip dependencies when installing plugins.",
|
351
|
+
)
|
349
352
|
groups['misc'].add_argument(
|
350
353
|
'-P', '--params', type=string_to_dict, help=(
|
351
354
|
"Parameters dictionary in JSON format or simple format. " +
|
meerschaum/_internal/entry.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
#! /usr/bin/env python
|
2
2
|
# -*- coding: utf-8 -*-
|
3
3
|
# vim:fenc=utf-8
|
4
|
+
# type: ignore
|
4
5
|
|
5
6
|
"""
|
6
7
|
The entry point for launching Meerschaum actions.
|
@@ -48,7 +49,7 @@ def entry(sysargs: Optional[List[str]] = None) -> SuccessTuple:
|
|
48
49
|
|
49
50
|
if args.get('schedule', None):
|
50
51
|
from meerschaum.utils.schedule import schedule_function
|
51
|
-
return schedule_function(entry_with_args,
|
52
|
+
return schedule_function(entry_with_args, **args)
|
52
53
|
return entry_with_args(**args)
|
53
54
|
|
54
55
|
|
@@ -61,7 +62,7 @@ def entry_with_args(
|
|
61
62
|
"""
|
62
63
|
import sys
|
63
64
|
from meerschaum.plugins import Plugin
|
64
|
-
from meerschaum.actions import
|
65
|
+
from meerschaum.actions import get_action, get_main_action_name
|
65
66
|
from meerschaum._internal.arguments import remove_leading_action
|
66
67
|
from meerschaum.utils.venv import Venv, active_venvs, deactivate_venv
|
67
68
|
if kw.get('trace', None):
|
meerschaum/actions/install.py
CHANGED
@@ -54,6 +54,7 @@ def _complete_install(
|
|
54
54
|
def _install_plugins(
|
55
55
|
action: Optional[List[str]] = None,
|
56
56
|
repository: Optional[str] = None,
|
57
|
+
skip_deps: bool = False,
|
57
58
|
force: bool = False,
|
58
59
|
debug: bool = False,
|
59
60
|
**kw: Any
|
@@ -87,11 +88,14 @@ def _install_plugins(
|
|
87
88
|
|
88
89
|
repo_connector = parse_repo_keys(repository)
|
89
90
|
|
90
|
-
successes = {}
|
91
91
|
for name in action:
|
92
92
|
info(f"Installing plugin '{name}' from Meerschaum repository '{repo_connector}'...")
|
93
|
-
success, msg = repo_connector.install_plugin(
|
94
|
-
|
93
|
+
success, msg = repo_connector.install_plugin(
|
94
|
+
name,
|
95
|
+
force = force,
|
96
|
+
skip_deps = skip_deps,
|
97
|
+
debug = debug,
|
98
|
+
)
|
95
99
|
print_tuple((success, msg))
|
96
100
|
|
97
101
|
reload_plugins(debug=debug)
|
meerschaum/actions/show.py
CHANGED
@@ -41,6 +41,7 @@ def show(
|
|
41
41
|
'jobs' : _show_jobs,
|
42
42
|
'logs' : _show_logs,
|
43
43
|
'tags' : _show_tags,
|
44
|
+
'schedules' : _show_schedules,
|
44
45
|
}
|
45
46
|
return choose_subaction(action, show_options, **kw)
|
46
47
|
|
@@ -577,6 +578,7 @@ def _show_logs(
|
|
577
578
|
`show logs myjob myotherjob`
|
578
579
|
"""
|
579
580
|
import os, pathlib, random, asyncio
|
581
|
+
from datetime import datetime, timezone
|
580
582
|
from meerschaum.utils.packages import attempt_import, import_rich
|
581
583
|
from meerschaum.utils.daemon import get_filtered_daemons, Daemon
|
582
584
|
from meerschaum.utils.warnings import warn, info
|
@@ -587,72 +589,106 @@ def _show_logs(
|
|
587
589
|
if not ANSI:
|
588
590
|
info = print
|
589
591
|
colors = get_config('jobs', 'logs', 'colors')
|
592
|
+
timestamp_format = get_config('jobs', 'logs', 'timestamps', 'format')
|
593
|
+
follow_timestamp_format = get_config('jobs', 'logs', 'timestamps', 'follow_format')
|
590
594
|
daemons = get_filtered_daemons(action)
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
+
now = datetime.now(timezone.utc)
|
596
|
+
now_str = now.strftime(timestamp_format)
|
597
|
+
now_follow_str = now.strftime(follow_timestamp_format)
|
598
|
+
|
599
|
+
def build_buffer_spaces(daemons) -> Dict[str, str]:
|
600
|
+
max_len_id = max(len(d.daemon_id) for d in daemons) if daemons else 0
|
601
|
+
buffer_len = max(
|
602
|
+
get_config('jobs', 'logs', 'min_buffer_len'),
|
603
|
+
max_len_id
|
604
|
+
)
|
595
605
|
return {
|
596
|
-
d.daemon_id: ''.join([' ' for i in range(
|
606
|
+
d.daemon_id: ''.join([' ' for i in range(buffer_len - len(d.daemon_id))])
|
597
607
|
for d in daemons
|
598
608
|
}
|
599
609
|
|
600
|
-
def
|
610
|
+
def build_job_colors(daemons, _old_job_colors = None) -> Dict[str, str]:
|
601
611
|
return {d.daemon_id: colors[i % len(colors)] for i, d in enumerate(daemons)}
|
602
612
|
|
603
|
-
|
604
|
-
|
613
|
+
buffer_spaces = build_buffer_spaces(daemons)
|
614
|
+
job_colors = build_job_colors(daemons)
|
605
615
|
|
606
|
-
def
|
607
|
-
nonlocal
|
608
|
-
if daemon_id not in
|
616
|
+
def get_buffer_spaces(daemon_id):
|
617
|
+
nonlocal buffer_spaces, daemons
|
618
|
+
if daemon_id not in buffer_spaces:
|
609
619
|
d = Daemon(daemon_id=daemon_id)
|
610
620
|
if d not in daemons:
|
611
621
|
daemons = get_filtered_daemons(action)
|
612
|
-
|
613
|
-
return
|
622
|
+
buffer_spaces = build_buffer_spaces(daemons)
|
623
|
+
return buffer_spaces[daemon_id] or ' '
|
614
624
|
|
615
|
-
def
|
616
|
-
nonlocal
|
617
|
-
if daemon_id not in
|
625
|
+
def get_job_colors(daemon_id):
|
626
|
+
nonlocal job_colors, daemons
|
627
|
+
if daemon_id not in job_colors:
|
618
628
|
d = Daemon(daemon_id=daemon_id)
|
619
629
|
if d not in daemons:
|
620
630
|
daemons = get_filtered_daemons(action)
|
621
|
-
|
622
|
-
return
|
631
|
+
job_colors = build_job_colors(daemons)
|
632
|
+
return job_colors[daemon_id]
|
623
633
|
|
624
|
-
def
|
625
|
-
|
634
|
+
def follow_pretty_print():
|
635
|
+
watchfiles = attempt_import('watchfiles')
|
626
636
|
rich = import_rich()
|
627
637
|
rich_text = attempt_import('rich.text')
|
628
|
-
|
638
|
+
watch_daemon_ids = {d.daemon_id: d for d in daemons}
|
629
639
|
info("Watching log files...")
|
630
640
|
|
631
|
-
|
641
|
+
previous_line_timestamp = None
|
642
|
+
def print_job_line(daemon, line):
|
643
|
+
nonlocal previous_line_timestamp
|
644
|
+
date_prefix_str = line[:len(now_str)]
|
645
|
+
try:
|
646
|
+
line_timestamp = datetime.strptime(date_prefix_str, timestamp_format)
|
647
|
+
previous_line_timestamp = line_timestamp
|
648
|
+
except Exception as e:
|
649
|
+
line_timestamp = None
|
650
|
+
if line_timestamp:
|
651
|
+
line = line[(len(now_str) + 3):]
|
652
|
+
else:
|
653
|
+
line_timestamp = previous_line_timestamp
|
654
|
+
|
655
|
+
if len(line) == 0 or line == '\n':
|
656
|
+
return
|
657
|
+
|
632
658
|
text = rich_text.Text(daemon.daemon_id)
|
633
|
-
|
634
|
-
|
635
|
-
+ (
|
659
|
+
line_prefix = (
|
660
|
+
get_buffer_spaces(daemon.daemon_id)
|
661
|
+
+ (line_timestamp.strftime(follow_timestamp_format) if line_timestamp else '')
|
662
|
+
+ ' | '
|
636
663
|
)
|
664
|
+
text.append(line_prefix + (line[:-1] if line[-1] == '\n' else line))
|
637
665
|
if ANSI:
|
638
666
|
text.stylize(
|
639
|
-
|
667
|
+
get_job_colors(daemon.daemon_id),
|
640
668
|
0,
|
641
|
-
len(daemon.daemon_id) + len(
|
669
|
+
len(daemon.daemon_id) + len(line_prefix)
|
642
670
|
)
|
643
671
|
get_console().print(text)
|
644
672
|
|
645
673
|
|
646
|
-
def
|
674
|
+
def print_log_lines(daemon):
|
647
675
|
for line in daemon.readlines():
|
648
|
-
|
676
|
+
print_job_line(daemon, line)
|
649
677
|
|
650
|
-
def
|
678
|
+
def seek_back_offset(d) -> bool:
|
651
679
|
if d.log_offset_path.exists():
|
652
680
|
d.log_offset_path.unlink()
|
653
681
|
|
654
682
|
latest_subfile_path = d.rotating_log.get_latest_subfile_path()
|
655
683
|
latest_subfile_index = d.rotating_log.get_latest_subfile_index()
|
684
|
+
|
685
|
+
### Sometimes the latest file is empty.
|
686
|
+
if os.stat(latest_subfile_path).st_size == 0 and latest_subfile_index > 0:
|
687
|
+
latest_subfile_index -= 1
|
688
|
+
latest_subfile_path = d.rotating_log.get_subfile_path_from_index(
|
689
|
+
latest_subfile_index
|
690
|
+
)
|
691
|
+
|
656
692
|
with open(latest_subfile_path, 'r', encoding='utf-8') as f:
|
657
693
|
latest_lines = f.readlines()
|
658
694
|
|
@@ -672,12 +708,12 @@ def _show_logs(
|
|
672
708
|
for d in daemons
|
673
709
|
}
|
674
710
|
for d in daemons:
|
675
|
-
|
676
|
-
|
711
|
+
seek_back_offset(d)
|
712
|
+
print_log_lines(d)
|
677
713
|
|
678
714
|
_quit = False
|
679
|
-
|
680
|
-
|
715
|
+
def watch_logs():
|
716
|
+
for changes in watchfiles.watch(LOGS_RESOURCES_PATH):
|
681
717
|
if _quit:
|
682
718
|
return
|
683
719
|
for change in changes:
|
@@ -688,7 +724,7 @@ def _show_logs(
|
|
688
724
|
if not file_path.exists():
|
689
725
|
continue
|
690
726
|
daemon_id = file_path.name.split('.log')[0]
|
691
|
-
if daemon_id not in
|
727
|
+
if daemon_id not in watch_daemon_ids and action:
|
692
728
|
continue
|
693
729
|
try:
|
694
730
|
daemon = Daemon(daemon_id=daemon_id)
|
@@ -697,22 +733,21 @@ def _show_logs(
|
|
697
733
|
warn(f"Seeing new logs for non-existent job '{daemon_id}'.", stack=False)
|
698
734
|
|
699
735
|
if daemon is not None:
|
700
|
-
|
736
|
+
print_log_lines(daemon)
|
701
737
|
|
702
|
-
loop = asyncio.new_event_loop()
|
703
738
|
try:
|
704
|
-
|
705
|
-
except KeyboardInterrupt:
|
739
|
+
watch_logs()
|
740
|
+
except KeyboardInterrupt as ki:
|
706
741
|
_quit = True
|
707
742
|
|
708
|
-
def
|
743
|
+
def print_nopretty_log_text():
|
709
744
|
for d in daemons:
|
710
745
|
log_text = d.log_text
|
711
746
|
print(d.daemon_id)
|
712
747
|
print(log_text)
|
713
748
|
|
714
|
-
|
715
|
-
|
749
|
+
print_log_text = follow_pretty_print if not nopretty else print_nopretty_log_text
|
750
|
+
print_log_text()
|
716
751
|
|
717
752
|
return True, "Success"
|
718
753
|
|
@@ -817,6 +852,57 @@ def _show_tags(
|
|
817
852
|
return True, "Success"
|
818
853
|
|
819
854
|
|
855
|
+
def _show_schedules(
|
856
|
+
action: Optional[List[str]] = None,
|
857
|
+
nopretty: bool = False,
|
858
|
+
**kwargs: Any
|
859
|
+
) -> SuccessTuple:
|
860
|
+
"""
|
861
|
+
Print the upcoming timestamps according to the given schedule.
|
862
|
+
|
863
|
+
Examples:
|
864
|
+
show schedule 'daily starting 00:00'
|
865
|
+
show schedule 'every 12 hours and mon-fri starting 2024-01-01'
|
866
|
+
"""
|
867
|
+
from meerschaum.utils.schedule import parse_schedule
|
868
|
+
from meerschaum.utils.misc import is_int
|
869
|
+
from meerschaum.utils.formatting import print_options
|
870
|
+
if not action:
|
871
|
+
return False, "Provide a schedule to be parsed."
|
872
|
+
schedule = action[0]
|
873
|
+
default_num_timestamps = 5
|
874
|
+
num_timestamps_str = action[1] if len(action) >= 2 else str(default_num_timestamps)
|
875
|
+
num_timestamps = (
|
876
|
+
int(num_timestamps_str)
|
877
|
+
if is_int(num_timestamps_str)
|
878
|
+
else default_num_timestamps
|
879
|
+
)
|
880
|
+
try:
|
881
|
+
trigger = parse_schedule(schedule)
|
882
|
+
except ValueError as e:
|
883
|
+
return False, str(e)
|
884
|
+
|
885
|
+
next_datetimes = []
|
886
|
+
for _ in range(num_timestamps):
|
887
|
+
try:
|
888
|
+
next_dt = trigger.next()
|
889
|
+
next_datetimes.append(next_dt)
|
890
|
+
except Exception as e:
|
891
|
+
break
|
892
|
+
|
893
|
+
print_options(
|
894
|
+
next_datetimes,
|
895
|
+
num_cols = 1,
|
896
|
+
nopretty = nopretty,
|
897
|
+
header = (
|
898
|
+
f"Next {min(num_timestamps, len(next_datetimes))} timestamps "
|
899
|
+
+ f"for schedule '{schedule}':"
|
900
|
+
),
|
901
|
+
)
|
902
|
+
|
903
|
+
return True, "Success"
|
904
|
+
|
905
|
+
|
820
906
|
|
821
907
|
### NOTE: This must be the final statement of the module.
|
822
908
|
### Any subactions added below these lines will not
|
meerschaum/actions/sync.py
CHANGED
@@ -453,16 +453,20 @@ def _wrap_pipe(
|
|
453
453
|
return False, msg
|
454
454
|
return True, "Success"
|
455
455
|
|
456
|
-
|
456
|
+
pre_hook_results, post_hook_results = [], []
|
457
457
|
def apply_hooks(is_pre_sync: bool):
|
458
458
|
_sync_hooks = (_pre_sync_hooks if is_pre_sync else _post_sync_hooks)
|
459
|
+
_hook_results = (pre_hook_results if is_pre_sync else post_hook_results)
|
459
460
|
for module_name, sync_hooks in _sync_hooks.items():
|
460
461
|
plugin_name = module_name.split('.')[-1] if module_name.startswith('plugins.') else None
|
461
462
|
for sync_hook in sync_hooks:
|
462
463
|
hook_result = pool.apply_async(call_sync_hook, (plugin_name, sync_hook))
|
463
|
-
|
464
|
+
_hook_results.append(hook_result)
|
464
465
|
|
465
466
|
apply_hooks(True)
|
467
|
+
for hook_result in pre_hook_results:
|
468
|
+
hook_success, hook_msg = hook_result.get()
|
469
|
+
mrsm.pprint((hook_success, hook_msg))
|
466
470
|
|
467
471
|
try:
|
468
472
|
with Venv(get_connector_plugin(pipe.connector), debug=debug):
|
@@ -480,7 +484,7 @@ def _wrap_pipe(
|
|
480
484
|
'sync_complete_timestamp': datetime.now(timezone.utc),
|
481
485
|
})
|
482
486
|
apply_hooks(False)
|
483
|
-
for hook_result in
|
487
|
+
for hook_result in post_hook_results:
|
484
488
|
hook_success, hook_msg = hook_result.get()
|
485
489
|
mrsm.pprint((hook_success, hook_msg))
|
486
490
|
|
meerschaum/api/__init__.py
CHANGED
@@ -30,25 +30,35 @@ _locks = {'pipes': RLock(), 'connector': RLock(), 'uvicorn_config': RLock()}
|
|
30
30
|
CHECK_UPDATE = os.environ.get(STATIC_CONFIG['environment']['runtime'], None) != 'docker'
|
31
31
|
|
32
32
|
endpoints = STATIC_CONFIG['api']['endpoints']
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
33
|
+
|
34
|
+
(
|
35
|
+
fastapi,
|
36
|
+
aiofiles,
|
37
|
+
starlette_responses,
|
38
|
+
multipart,
|
39
|
+
packaging_version,
|
40
|
+
) = attempt_import(
|
41
|
+
'fastapi',
|
42
|
+
'aiofiles',
|
43
|
+
'starlette.responses',
|
44
|
+
'multipart',
|
45
|
+
'packaging.version',
|
46
|
+
lazy = False,
|
47
|
+
check_update = CHECK_UPDATE,
|
40
48
|
)
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
49
|
+
(
|
50
|
+
typing_extensions,
|
51
|
+
uvicorn_workers,
|
52
|
+
) = attempt_import(
|
53
|
+
'typing_extensions',
|
54
|
+
'uvicorn.workers',
|
55
|
+
lazy = False,
|
56
|
+
check_update = CHECK_UPDATE,
|
57
|
+
venv = None,
|
45
58
|
)
|
46
|
-
python_multipart = attempt_import('multipart', lazy=False, check_update=CHECK_UPDATE)
|
47
|
-
packaging_version = attempt_import('packaging.version', check_update=CHECK_UPDATE)
|
48
59
|
from meerschaum.api._chain import check_allow_chaining, DISALLOW_CHAINING_MESSAGE
|
49
60
|
uvicorn_config_path = API_UVICORN_RESOURCES_PATH / SERVER_ID / 'config.json'
|
50
61
|
|
51
|
-
uvicorn_workers = attempt_import('uvicorn.workers', venv=None, check_update=CHECK_UPDATE)
|
52
62
|
uvicorn_config = None
|
53
63
|
sys_config = get_config('system', 'api')
|
54
64
|
permissions_config = get_config('system', 'api', 'permissions')
|
meerschaum/api/_oauth2.py
CHANGED
@@ -7,11 +7,11 @@ Define JWT authorization here.
|
|
7
7
|
"""
|
8
8
|
|
9
9
|
import os
|
10
|
-
from meerschaum.api import app, endpoints
|
10
|
+
from meerschaum.api import app, endpoints, CHECK_UPDATE
|
11
11
|
from meerschaum.utils.packages import attempt_import
|
12
|
-
fastapi = attempt_import('fastapi', lazy=False)
|
13
|
-
fastapi_responses = attempt_import('fastapi.responses', lazy=False)
|
14
|
-
fastapi_login = attempt_import('fastapi_login')
|
12
|
+
fastapi = attempt_import('fastapi', lazy=False, check_update=CHECK_UPDATE)
|
13
|
+
fastapi_responses = attempt_import('fastapi.responses', lazy=False, check_update=CHECK_UPDATE)
|
14
|
+
fastapi_login = attempt_import('fastapi_login', check_update=CHECK_UPDATE)
|
15
15
|
|
16
16
|
LoginManager = fastapi_login.LoginManager
|
17
17
|
def generate_secret_key() -> str:
|
@@ -440,18 +440,13 @@ def update_flags(input_flags_dropdown_values, n_clicks, input_flags_texts):
|
|
440
440
|
className = 'input-text',
|
441
441
|
)
|
442
442
|
|
443
|
+
remove_index = trigger_dict['index'] if trigger_type == 'input-flags-remove-button' else None
|
443
444
|
rows = [
|
444
445
|
build_row(i, val, val_text)
|
445
446
|
for i, (val, val_text) in enumerate(zip(input_flags_dropdown_values, input_flags_texts))
|
447
|
+
if i != remove_index
|
446
448
|
]
|
447
449
|
|
448
|
-
if trigger_type == 'input-flags-remove-button':
|
449
|
-
remove_index = trigger_dict['index']
|
450
|
-
try:
|
451
|
-
del rows[remove_index]
|
452
|
-
except IndexError:
|
453
|
-
pass
|
454
|
-
|
455
450
|
if not rows or input_flags_dropdown_values[-1]:
|
456
451
|
rows.append(build_row(len(rows), None, None))
|
457
452
|
|
@@ -488,11 +483,11 @@ def update_keys_options(
|
|
488
483
|
"""
|
489
484
|
ctx = dash.callback_context
|
490
485
|
trigger = ctx.triggered[0]['prop_id'].split('.')[0]
|
486
|
+
instance_click = trigger == 'instance-select'
|
491
487
|
|
492
488
|
### Update the instance first.
|
493
489
|
update_instance_keys = False
|
494
490
|
if not instance_keys:
|
495
|
-
# instance_keys = get_config('meerschaum', 'web_instance')
|
496
491
|
instance_keys = str(get_api_connector())
|
497
492
|
update_instance_keys = True
|
498
493
|
instance_alerts = []
|
@@ -516,20 +511,23 @@ def update_keys_options(
|
|
516
511
|
if location_keys:
|
517
512
|
num_filter += 1
|
518
513
|
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
_ck_alone = connector_keys and num_filter == 1
|
523
|
-
_mk_alone = metric_keys and num_filter == 1
|
524
|
-
_lk_alone = location_keys and num_filter == 1
|
514
|
+
_ck_filter = connector_keys
|
515
|
+
_mk_filter = metric_keys
|
516
|
+
_lk_filter = location_keys
|
517
|
+
_ck_alone = (connector_keys and num_filter == 1) or instance_click
|
518
|
+
_mk_alone = (metric_keys and num_filter == 1) or instance_click
|
519
|
+
_lk_alone = (location_keys and num_filter == 1) or instance_click
|
525
520
|
|
526
521
|
from meerschaum.utils import fetch_pipes_keys
|
527
522
|
|
528
523
|
try:
|
529
524
|
_all_keys = fetch_pipes_keys('registered', get_web_connector(ctx.states))
|
530
525
|
_keys = fetch_pipes_keys(
|
531
|
-
'registered',
|
532
|
-
|
526
|
+
'registered',
|
527
|
+
get_web_connector(ctx.states),
|
528
|
+
connector_keys = _ck_filter,
|
529
|
+
metric_keys = _mk_filter,
|
530
|
+
location_keys = _lk_filter,
|
533
531
|
)
|
534
532
|
except Exception as e:
|
535
533
|
instance_alerts += [alert_from_success_tuple((False, str(e)))]
|
@@ -545,15 +543,39 @@ def update_keys_options(
|
|
545
543
|
k = locals()[key_type]
|
546
544
|
if k not in _seen_keys[key_type]:
|
547
545
|
_k = 'None' if k in (None, '[None]', 'None', 'null') else k
|
548
|
-
options.append({'label'
|
546
|
+
options.append({'label': _k, 'value': _k})
|
549
547
|
_seen_keys[key_type].add(k)
|
550
548
|
|
551
549
|
add_options(_connectors_options, _all_keys if _ck_alone else _keys, 'ck')
|
552
550
|
add_options(_metrics_options, _all_keys if _mk_alone else _keys, 'mk')
|
553
551
|
add_options(_locations_options, _all_keys if _lk_alone else _keys, 'lk')
|
554
|
-
|
555
|
-
|
556
|
-
|
552
|
+
_connectors_options.sort(key=lambda x: str(x).lower())
|
553
|
+
_metrics_options.sort(key=lambda x: str(x).lower())
|
554
|
+
_locations_options.sort(key=lambda x: str(x).lower())
|
555
|
+
connector_keys = [
|
556
|
+
ck
|
557
|
+
for ck in connector_keys
|
558
|
+
if ck in [
|
559
|
+
_ck['value']
|
560
|
+
for _ck in _connectors_options
|
561
|
+
]
|
562
|
+
]
|
563
|
+
metric_keys = [
|
564
|
+
mk
|
565
|
+
for mk in metric_keys
|
566
|
+
if mk in [
|
567
|
+
_mk['value']
|
568
|
+
for _mk in _metrics_options
|
569
|
+
]
|
570
|
+
]
|
571
|
+
location_keys = [
|
572
|
+
lk
|
573
|
+
for lk in location_keys
|
574
|
+
if lk in [
|
575
|
+
_lk['value']
|
576
|
+
for _lk in _locations_options
|
577
|
+
]
|
578
|
+
]
|
557
579
|
_connectors_datalist = [html.Option(value=o['value']) for o in _connectors_options]
|
558
580
|
_metrics_datalist = [html.Option(value=o['value']) for o in _metrics_options]
|
559
581
|
_locations_datalist = [html.Option(value=o['value']) for o in _locations_options]
|
@@ -680,6 +702,9 @@ dash_app.clientside_callback(
|
|
680
702
|
Input({'type': 'pipe-download-csv-button', 'index': ALL}, 'n_clicks'),
|
681
703
|
)
|
682
704
|
def download_pipe_csv(n_clicks):
|
705
|
+
"""
|
706
|
+
Download the most recent chunk as a CSV file.
|
707
|
+
"""
|
683
708
|
if not n_clicks:
|
684
709
|
raise PreventUpdate
|
685
710
|
ctx = dash.callback_context.triggered
|
@@ -688,11 +713,11 @@ def download_pipe_csv(n_clicks):
|
|
688
713
|
pipe = pipe_from_ctx(ctx, 'n_clicks')
|
689
714
|
if pipe is None:
|
690
715
|
raise PreventUpdate
|
691
|
-
filename = str(pipe.target) + '.csv'
|
692
716
|
bounds = pipe.get_chunk_bounds(bounded=True, debug=debug)
|
693
|
-
begin,
|
717
|
+
begin, end = bounds[-1]
|
718
|
+
filename = str(pipe.target) + f" {begin} - {end}.csv"
|
694
719
|
try:
|
695
|
-
df = pipe.get_data(begin=begin, end=
|
720
|
+
df = pipe.get_data(begin=begin, end=end, debug=debug)
|
696
721
|
except Exception as e:
|
697
722
|
df = None
|
698
723
|
if df is not None:
|
@@ -818,6 +843,51 @@ def sync_documents_click(n_clicks, sync_editor_text):
|
|
818
843
|
return alert_from_success_tuple((success, msg))
|
819
844
|
|
820
845
|
|
846
|
+
dash_app.clientside_callback(
|
847
|
+
"""
|
848
|
+
function(n_clicks_arr, url){
|
849
|
+
display_block = {"display": "block"};
|
850
|
+
|
851
|
+
var clicked = false;
|
852
|
+
for (var i = 0; i < n_clicks_arr.length; i++){
|
853
|
+
if (n_clicks_arr[i]){
|
854
|
+
clicked = true;
|
855
|
+
break;
|
856
|
+
}
|
857
|
+
}
|
858
|
+
if (!clicked){ return dash_clientside.no_update; }
|
859
|
+
|
860
|
+
const triggered_id = dash_clientside.callback_context.triggered_id;
|
861
|
+
const action = triggered_id["action"];
|
862
|
+
const pipe_meta = JSON.parse(triggered_id["index"]);
|
863
|
+
|
864
|
+
iframe = document.getElementById('webterm-iframe');
|
865
|
+
if (!iframe){ return dash_clientside.no_update; }
|
866
|
+
var location = pipe_meta.location;
|
867
|
+
if (!pipe_meta.location){
|
868
|
+
location = "None";
|
869
|
+
}
|
870
|
+
|
871
|
+
iframe.contentWindow.postMessage(
|
872
|
+
{
|
873
|
+
action: action,
|
874
|
+
subaction: "pipes",
|
875
|
+
connector_keys: [pipe_meta.connector],
|
876
|
+
metric_keys: [pipe_meta.metric],
|
877
|
+
location_keys: [location],
|
878
|
+
instance: pipe_meta.instance,
|
879
|
+
},
|
880
|
+
url
|
881
|
+
);
|
882
|
+
dash_clientside.set_props("webterm-div", {"style": display_block});
|
883
|
+
return [];
|
884
|
+
}
|
885
|
+
""",
|
886
|
+
Output('content-div-right', 'children'),
|
887
|
+
Input({'type': 'manage-pipe-button', 'index': ALL, 'action': ALL}, 'n_clicks'),
|
888
|
+
State('location', 'href'),
|
889
|
+
)
|
890
|
+
|
821
891
|
@dash_app.callback(
|
822
892
|
Output("navbar-collapse", "is_open"),
|
823
893
|
[Input("navbar-toggler", "n_clicks")],
|