ansys-solutions-dash-super-components 0.2.dev7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. ansys/solutions/dash_super_components/__init__.py +44 -0
  2. ansys/solutions/dash_super_components/assets/__init__.py +20 -0
  3. ansys/solutions/dash_super_components/assets/dashAgGridComponentFunctions.js +71 -0
  4. ansys/solutions/dash_super_components/authenticator.py +277 -0
  5. ansys/solutions/dash_super_components/dual_input_range_slider.py +397 -0
  6. ansys/solutions/dash_super_components/folder_selector.py +830 -0
  7. ansys/solutions/dash_super_components/input_form.py +908 -0
  8. ansys/solutions/dash_super_components/input_row_array.py +368 -0
  9. ansys/solutions/dash_super_components/logs_supervisor.py +887 -0
  10. ansys/solutions/dash_super_components/transaction_method_status_badge.py +366 -0
  11. ansys/solutions/dash_super_components/transaction_supervisor.py +587 -0
  12. ansys/solutions/dash_super_components/tree.py +396 -0
  13. ansys/solutions/dash_super_components/utils/__init__.py +18 -0
  14. ansys/solutions/dash_super_components/utils/aio_ids.py +83 -0
  15. ansys/solutions/dash_super_components/utils/api_requests.py +69 -0
  16. ansys/solutions/dash_super_components/utils/assets_server.py +52 -0
  17. ansys/solutions/dash_super_components/utils/config.py +77 -0
  18. ansys/solutions/dash_super_components/utils/logs_parser.py +151 -0
  19. ansys/solutions/dash_super_components/utils/path_validator.py +163 -0
  20. ansys/solutions/dash_super_components/utils/status_badge_properties.py +60 -0
  21. ansys/solutions/dash_super_components/utils/svg_icons.py +395 -0
  22. ansys_solutions_dash_super_components-0.2.dev7.dist-info/METADATA +169 -0
  23. ansys_solutions_dash_super_components-0.2.dev7.dist-info/RECORD +26 -0
  24. ansys_solutions_dash_super_components-0.2.dev7.dist-info/WHEEL +4 -0
  25. ansys_solutions_dash_super_components-0.2.dev7.dist-info/licenses/AUTHORS +12 -0
  26. ansys_solutions_dash_super_components-0.2.dev7.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,587 @@
1
+ # Copyright (C) 2026 ANSYS, Inc. and/or its affiliates.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+
18
+ """Provides a Dash component for tracking the status of SAF transaction method."""
19
+
20
+ from datetime import datetime, timedelta
21
+ import time
22
+ from typing import Any
23
+ import uuid
24
+
25
+ try:
26
+ # dash >=3.2.0
27
+ from dash import NoUpdate
28
+ except ImportError:
29
+ # dash >=2.18.2, <3.2.0
30
+ from dash._callback import NoUpdate # pyright: ignore[reportPrivateImportUsage]
31
+
32
+ try:
33
+ # dash-extensions < 2.0.5
34
+ from dash_extensions.enrich import _Wildcard # pyright: ignore[reportAttributeAccessIssue]
35
+ except ImportError:
36
+ # dash-extensions >= 2.0.5
37
+ from dash_extensions.enrich import (
38
+ Wildcard as _Wildcard, # pyright: ignore[reportAttributeAccessIssue]
39
+ )
40
+ from dash_extensions.enrich import (
41
+ MATCH,
42
+ Input,
43
+ Output,
44
+ State,
45
+ callback,
46
+ dcc,
47
+ html,
48
+ no_update,
49
+ )
50
+ import dash_mantine_components as dmc
51
+
52
+ from ansys.solutions.dash_super_components.utils.aio_ids import AIOIds
53
+ from ansys.solutions.dash_super_components.utils.api_requests import GlowAPIRequest
54
+ from ansys.solutions.dash_super_components.utils.svg_icons import IconNames, create_base64_svg_src
55
+
56
+ TRANSACTION_STATUS_BADGE_PROPERTIES = {
57
+ "run-required": {
58
+ "color": "gray",
59
+ },
60
+ "running": {
61
+ "color": "indigo",
62
+ },
63
+ "completed": {
64
+ "color": "lime",
65
+ },
66
+ "failed": {
67
+ "color": "red",
68
+ },
69
+ }
70
+
71
+
72
+ class TransactionSupervisor(html.Div):
73
+ """
74
+ A Dash component for monitoring the status of a SAF GLOW transaction method.
75
+
76
+ TransactionSupervisor is based on the Dash All-in-One (AIO) component pattern. It polls
77
+ the GLOW API to track method status in real time and displays elapsed time, started time,
78
+ and a color-coded status badge.
79
+
80
+ .. warning::
81
+
82
+ This component requires a SAF-based solution with access to the GLOW API. It must
83
+ be instantiated inside a callback (not directly in the layout) because it needs the
84
+ project URL from ``DashClient``.
85
+
86
+ To activate monitoring from a callback use::
87
+
88
+ TransactionSupervisor.ids.activate_supervision(aio_id) # Store; set to True
89
+
90
+ Parameters
91
+ ----------
92
+ url : str
93
+ The URL of the GLOW API server.
94
+ step_name : str
95
+ The name of the GLOW step (StepModel) that contains the method.
96
+ method_name : str
97
+ The name of the GLOW transaction method whose status is being monitored.
98
+ title : str, optional
99
+ Title displayed in the monitoring card.
100
+ show : bool, optional
101
+ Whether to show the monitoring card. Default is ``True``.
102
+ aio_id : str, optional
103
+ Unique identifier for this component instance. A UUID is generated when
104
+ not provided.
105
+ width : str or int, optional
106
+ Width of the monitoring card. Default is ``400``.
107
+ fontsize : str, optional
108
+ Font size of the text in the card. Default is ``'14px'``.
109
+ title_fontsize : str, optional
110
+ Font size of the title in the card. Default is ``'16px'``.
111
+ orientation : str, optional
112
+ Layout orientation of the card (``'horizontal'`` or ``'vertical'``).
113
+ Default is ``'horizontal'``.
114
+ """
115
+
116
+ class TransactionSupervisorIds(AIOIds):
117
+ """Provides IDs for the subcomponents of :class:`TransactionSupervisor`."""
118
+
119
+ @classmethod
120
+ def _monitoring_data(cls, aio_id: str | _Wildcard) -> dict[str, Any]:
121
+ """Return the ID for the monitoring data store.
122
+
123
+ Parameters
124
+ ----------
125
+ aio_id : str or _Wildcard
126
+ The component's unique identifier.
127
+
128
+ Returns
129
+ -------
130
+ dict[str, Any]
131
+ The unique ID dictionary for the monitoring data store subcomponent.
132
+ """
133
+ return cls.make_id_dict("transaction-supervisor", "monitoring-data", aio_id)
134
+
135
+ @classmethod
136
+ def _loader(cls, aio_id: str | _Wildcard) -> dict[str, Any]:
137
+ """Return the ID for the loader store.
138
+
139
+ Parameters
140
+ ----------
141
+ aio_id : str or _Wildcard
142
+ The component's unique identifier.
143
+
144
+ Returns
145
+ -------
146
+ dict[str, Any]
147
+ The unique ID dictionary for the loader store subcomponent.
148
+ """
149
+ return cls.make_id_dict("transaction-supervisor", "loader", aio_id)
150
+
151
+ @classmethod
152
+ def _interval(cls, aio_id: str | _Wildcard) -> dict[str, Any]:
153
+ """Return the ID for the interval component.
154
+
155
+ Parameters
156
+ ----------
157
+ aio_id : str or _Wildcard
158
+ The component's unique identifier.
159
+
160
+ Returns
161
+ -------
162
+ dict[str, Any]
163
+ The unique ID dictionary for the interval subcomponent.
164
+ """
165
+ return cls.make_id_dict("transaction-supervisor", "interval", aio_id)
166
+
167
+ @classmethod
168
+ def _container(cls, aio_id: str | _Wildcard) -> dict[str, Any]:
169
+ """Return the ID for the container component.
170
+
171
+ Parameters
172
+ ----------
173
+ aio_id : str or _Wildcard
174
+ The component's unique identifier.
175
+
176
+ Returns
177
+ -------
178
+ dict[str, Any]
179
+ The unique ID dictionary for the container subcomponent.
180
+ """
181
+ return cls.make_id_dict("transaction-supervisor", "container", aio_id)
182
+
183
+ @classmethod
184
+ def activate_supervision(cls, aio_id: str | _Wildcard) -> dict[str, Any]:
185
+ """Return the ID for the activate supervision store.
186
+
187
+ Parameters
188
+ ----------
189
+ aio_id : str or _Wildcard
190
+ The component's unique identifier.
191
+
192
+ Returns
193
+ -------
194
+ dict[str, Any]
195
+ The unique ID dictionary for the activate supervision store subcomponent.
196
+ """
197
+ return cls.make_id_dict("transaction-supervisor", "activate-supervision", aio_id)
198
+
199
+ @classmethod
200
+ def transaction_status_badge(cls, aio_id: str | _Wildcard) -> dict[str, Any]:
201
+ """Return the ID for the transaction status badge.
202
+
203
+ Parameters
204
+ ----------
205
+ aio_id : str or _Wildcard
206
+ The component's unique identifier.
207
+
208
+ Returns
209
+ -------
210
+ dict[str, Any]
211
+ The unique ID dictionary for the transaction status badge subcomponent.
212
+ """
213
+ return cls.make_id_dict("transaction-supervisor", "transaction-status-badge", aio_id)
214
+
215
+ @classmethod
216
+ def started_time_label(cls, aio_id: str | _Wildcard) -> dict[str, Any]:
217
+ """Return the ID for the started time label.
218
+
219
+ Parameters
220
+ ----------
221
+ aio_id : str or _Wildcard
222
+ The component's unique identifier.
223
+
224
+ Returns
225
+ -------
226
+ dict[str, Any]
227
+ The unique ID dictionary for the started time label subcomponent.
228
+ """
229
+ return cls.make_id_dict("transaction-supervisor", "started-time-label", aio_id)
230
+
231
+ @classmethod
232
+ def elapsed_time_label(cls, aio_id: str | _Wildcard) -> dict[str, Any]:
233
+ """Return the ID for the elapsed time label.
234
+
235
+ Parameters
236
+ ----------
237
+ aio_id : str or _Wildcard
238
+ The component's unique identifier.
239
+
240
+ Returns
241
+ -------
242
+ dict[str, Any]
243
+ The unique ID dictionary for the elapsed time label subcomponent.
244
+ """
245
+ return cls.make_id_dict("transaction-supervisor", "elapsed-time-label", aio_id)
246
+
247
+ @classmethod
248
+ def switch(cls, aio_id: str | _Wildcard) -> dict[str, Any]:
249
+ """Return the ID for the switch component.
250
+
251
+ Parameters
252
+ ----------
253
+ aio_id : str or _Wildcard
254
+ The component's unique identifier.
255
+
256
+ Returns
257
+ -------
258
+ dict[str, Any]
259
+ The unique ID dictionary for the switch subcomponent.
260
+ """
261
+ return cls.make_id_dict("transaction-supervisor", "switch", aio_id)
262
+
263
+ ids = TransactionSupervisorIds
264
+
265
+ def __init__(
266
+ self,
267
+ url: str,
268
+ step_name: str,
269
+ method_name: str,
270
+ title: str = "Transaction Supervisor",
271
+ show: bool = True,
272
+ aio_id: str | None = None,
273
+ width: str | int = 400,
274
+ fontsize: str = "14px",
275
+ title_fontsize: str = "16px",
276
+ orientation: str = "horizontal",
277
+ ):
278
+ self._url = url
279
+ self._step_name = step_name
280
+ self._method_name = method_name
281
+ self._title = title
282
+ self._show = show
283
+ self._width = width
284
+ self._fontsize = fontsize
285
+ self._orientation = orientation
286
+ self._title_fontsize = title_fontsize
287
+ self.aio_id = aio_id if aio_id is not None else str(uuid.uuid4())
288
+
289
+ super().__init__(
290
+ [
291
+ html.Div(
292
+ [
293
+ dcc.Store(
294
+ id=self.ids._monitoring_data(self.aio_id),
295
+ storage_type="session",
296
+ data={
297
+ "url": url,
298
+ "step_name": self._step_name,
299
+ "method_name": self._method_name,
300
+ },
301
+ ),
302
+ dcc.Store(
303
+ id=self.ids.activate_supervision(self.aio_id),
304
+ storage_type="session",
305
+ data=False,
306
+ ),
307
+ dcc.Interval(
308
+ id=self.ids._interval(self.aio_id),
309
+ interval=1000,
310
+ n_intervals=0,
311
+ disabled=True,
312
+ ),
313
+ dmc.Card(
314
+ children=[
315
+ dmc.CardSection(
316
+ dmc.Group(
317
+ children=[
318
+ html.Div(
319
+ self._title,
320
+ style={
321
+ "display": "inline-block",
322
+ "fontSize": self._title_fontsize,
323
+ },
324
+ ),
325
+ dcc.Loading(
326
+ id=self.ids._loader(self.aio_id),
327
+ display="hide",
328
+ ),
329
+ dmc.HoverCard(
330
+ children=[
331
+ dmc.HoverCardTarget(
332
+ dmc.Switch(
333
+ id=self.ids.switch(
334
+ self.aio_id,
335
+ ),
336
+ checked=self._show,
337
+ offLabel=html.Img(
338
+ src=create_base64_svg_src(
339
+ IconNames.MDI_HIDE,
340
+ ),
341
+ ),
342
+ onLabel=html.Img(
343
+ src=create_base64_svg_src(
344
+ IconNames.MDI_SHOW,
345
+ color="#fff",
346
+ ),
347
+ ),
348
+ size="sm",
349
+ ),
350
+ ),
351
+ dmc.HoverCardDropdown(
352
+ dmc.Text(
353
+ "Show/Hide monitoring panel.",
354
+ size="sm",
355
+ ),
356
+ ),
357
+ ],
358
+ withArrow=True,
359
+ shadow="md",
360
+ ),
361
+ ],
362
+ justify="space-around",
363
+ ),
364
+ withBorder=True,
365
+ inheritPadding=True,
366
+ py="xs",
367
+ ),
368
+ html.Div(
369
+ dmc.Grid(
370
+ children=[
371
+ dmc.GridCol(
372
+ html.Div(
373
+ [
374
+ html.Div(
375
+ "Transaction method status :",
376
+ style={
377
+ "display": "inline-block",
378
+ "fontSize": self._fontsize,
379
+ "marginTop": "5px",
380
+ },
381
+ ),
382
+ html.Div(
383
+ dmc.Badge(
384
+ id=self.ids.transaction_status_badge(
385
+ self.aio_id,
386
+ ),
387
+ style={
388
+ "fontSize": self._fontsize,
389
+ },
390
+ ),
391
+ style={
392
+ "marginLeft": "5px",
393
+ "display": "inline-block",
394
+ },
395
+ ),
396
+ ],
397
+ style={"display": "inline-block"},
398
+ ),
399
+ span=(
400
+ "content"
401
+ if self._orientation == "horizontal"
402
+ else 12
403
+ ),
404
+ ),
405
+ dmc.GridCol(
406
+ html.Div(
407
+ [
408
+ html.Div(
409
+ "Started time :",
410
+ style={
411
+ "display": "inline-block",
412
+ "fontSize": self._fontsize,
413
+ "marginTop": "5px",
414
+ },
415
+ ),
416
+ html.Div(
417
+ id=self.ids.started_time_label(
418
+ self.aio_id,
419
+ ),
420
+ style={
421
+ "display": "inline-block",
422
+ "fontSize": self._fontsize,
423
+ "marginTop": "5px",
424
+ "marginLeft": "5px",
425
+ },
426
+ ),
427
+ ],
428
+ style={"display": "inline-block"},
429
+ ),
430
+ span=(
431
+ "content"
432
+ if self._orientation == "horizontal"
433
+ else 12
434
+ ),
435
+ ),
436
+ dmc.GridCol(
437
+ html.Div(
438
+ [
439
+ html.Div(
440
+ "Elapsed time :",
441
+ style={
442
+ "display": "inline-block",
443
+ "fontSize": self._fontsize,
444
+ "marginTop": "5px",
445
+ },
446
+ ),
447
+ html.Div(
448
+ id=self.ids.elapsed_time_label(
449
+ self.aio_id,
450
+ ),
451
+ style={
452
+ "display": "inline-block",
453
+ "fontSize": self._fontsize,
454
+ "marginTop": "5px",
455
+ "marginLeft": "5px",
456
+ },
457
+ ),
458
+ ],
459
+ style={"display": "inline-block"},
460
+ ),
461
+ span=(
462
+ "content"
463
+ if self._orientation == "horizontal"
464
+ else 12
465
+ ),
466
+ ),
467
+ ],
468
+ gutter="xs",
469
+ ),
470
+ id=self.ids._container(self.aio_id),
471
+ style={
472
+ "display": "inline-block",
473
+ },
474
+ ),
475
+ ],
476
+ withBorder=True,
477
+ shadow="sm",
478
+ radius="md",
479
+ w=self._width,
480
+ ),
481
+ ],
482
+ ),
483
+ ],
484
+ )
485
+
486
+ @staticmethod
487
+ @callback(
488
+ Output(ids._container(MATCH), "style"),
489
+ Input(ids.switch(MATCH), "checked"),
490
+ )
491
+ def show_supervision_card(
492
+ checked: bool,
493
+ ) -> dict[str, Any]:
494
+ """Hide or show the content of the supervision card."""
495
+ return {"display": "inline-block"} if checked else {"display": "none"}
496
+
497
+ @staticmethod
498
+ @callback(
499
+ Output(ids._interval(MATCH), "disabled"),
500
+ Output(ids._monitoring_data(MATCH), "data", allow_duplicate=True),
501
+ Output(ids._loader(MATCH), "display"),
502
+ Input(ids.activate_supervision(MATCH), "data"),
503
+ State(ids._monitoring_data(MATCH), "data"),
504
+ prevent_initial_call=True,
505
+ )
506
+ def start_stop_supervision(
507
+ supervision_active: bool,
508
+ data: dict[str, str],
509
+ ) -> tuple[bool, dict[str, str] | NoUpdate, str]:
510
+ """Start the monitoring of the transaction method."""
511
+ if not supervision_active:
512
+ return True, no_update, "hide"
513
+
514
+ # Reset timing data for a new transaction run
515
+ data["started_time"] = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())
516
+ data["elapsed_time"] = str(timedelta(0))
517
+ # set the loader display to show
518
+ loader_display = "show"
519
+
520
+ return not supervision_active, data, loader_display
521
+
522
+ @staticmethod
523
+ @callback(
524
+ Output(ids.started_time_label(MATCH), "children", allow_duplicate=True),
525
+ Output(ids.elapsed_time_label(MATCH), "children", allow_duplicate=True),
526
+ Input(ids._monitoring_data(MATCH), "data"),
527
+ )
528
+ def update_time_labels(
529
+ data: dict[str, str],
530
+ ) -> tuple[str, str]:
531
+ """Update the started time and elapsed time labels."""
532
+ started_time = data.get("started_time", "")
533
+ elapsed_time = data.get("elapsed_time", str(timedelta(0)))
534
+ return started_time, elapsed_time
535
+
536
+ @staticmethod
537
+ @callback(
538
+ Output(ids.transaction_status_badge(MATCH), "children"),
539
+ Output(ids.transaction_status_badge(MATCH), "color"),
540
+ Output(ids.activate_supervision(MATCH), "data", allow_duplicate=True),
541
+ Output(ids._monitoring_data(MATCH), "data", allow_duplicate=True),
542
+ Input(ids._interval(MATCH), "n_intervals"),
543
+ State(ids._monitoring_data(MATCH), "data"),
544
+ State(ids.activate_supervision(MATCH), "data"),
545
+ )
546
+ def update_supervision_card(
547
+ n_intervals: int,
548
+ data: dict[str, str],
549
+ supervision_active: bool,
550
+ ) -> tuple[str, str, bool | NoUpdate, dict[str, str] | NoUpdate]:
551
+ """Update the supervision data and stop supervision when transaction has finished."""
552
+ glow_api_request = GlowAPIRequest(data["url"])
553
+ method_status = glow_api_request.get_transaction_method_status(
554
+ data["step_name"],
555
+ data["method_name"],
556
+ )
557
+ color = TRANSACTION_STATUS_BADGE_PROPERTIES[method_status]["color"]
558
+
559
+ if not supervision_active:
560
+ return (
561
+ method_status,
562
+ color,
563
+ no_update,
564
+ no_update,
565
+ )
566
+
567
+ # Stop supervision if method has finished
568
+ supervisor_active = False if method_status.lower() in ["completed", "failed"] else no_update
569
+
570
+ current_time = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())
571
+ started_time = data.get("started_time", "")
572
+ if not started_time:
573
+ # no started_time found in data (e.g. first time page load when method still "running")
574
+ elapsed_time = str(timedelta(0))
575
+ else:
576
+ elapsed_time = str(
577
+ datetime.strptime(current_time, "%Y-%m-%d %H:%M:%S")
578
+ - datetime.strptime(started_time, "%Y-%m-%d %H:%M:%S"),
579
+ )
580
+ data["elapsed_time"] = elapsed_time
581
+
582
+ return (
583
+ method_status,
584
+ color,
585
+ supervisor_active,
586
+ data,
587
+ )