flowyml 1.3.0__py3-none-any.whl → 1.5.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.
- flowyml/core/execution_status.py +1 -0
- flowyml/core/executor.py +175 -3
- flowyml/core/observability.py +7 -7
- flowyml/core/resources.py +12 -12
- flowyml/core/retry_policy.py +2 -2
- flowyml/core/scheduler.py +9 -9
- flowyml/core/scheduler_config.py +2 -3
- flowyml/core/submission_result.py +4 -4
- flowyml/stacks/bridge.py +9 -9
- flowyml/stacks/plugins.py +2 -2
- flowyml/stacks/registry.py +21 -0
- flowyml/storage/materializers/base.py +33 -0
- flowyml/storage/metadata.py +3 -1042
- flowyml/storage/remote.py +590 -0
- flowyml/storage/sql.py +951 -0
- flowyml/ui/backend/dependencies.py +28 -0
- flowyml/ui/backend/main.py +4 -79
- flowyml/ui/backend/routers/assets.py +170 -9
- flowyml/ui/backend/routers/client.py +6 -6
- flowyml/ui/backend/routers/execution.py +2 -2
- flowyml/ui/backend/routers/experiments.py +53 -6
- flowyml/ui/backend/routers/metrics.py +23 -68
- flowyml/ui/backend/routers/pipelines.py +19 -10
- flowyml/ui/backend/routers/runs.py +287 -9
- flowyml/ui/backend/routers/schedules.py +5 -21
- flowyml/ui/backend/routers/stats.py +14 -0
- flowyml/ui/backend/routers/traces.py +37 -53
- flowyml/ui/backend/routers/websocket.py +121 -0
- flowyml/ui/frontend/dist/assets/index-CBUXOWze.css +1 -0
- flowyml/ui/frontend/dist/assets/index-DF8dJaFL.js +629 -0
- flowyml/ui/frontend/dist/index.html +2 -2
- flowyml/ui/frontend/package-lock.json +289 -0
- flowyml/ui/frontend/package.json +1 -0
- flowyml/ui/frontend/src/app/compare/page.jsx +213 -0
- flowyml/ui/frontend/src/app/experiments/compare/page.jsx +289 -0
- flowyml/ui/frontend/src/app/experiments/page.jsx +61 -1
- flowyml/ui/frontend/src/app/runs/[runId]/page.jsx +418 -203
- flowyml/ui/frontend/src/app/runs/page.jsx +64 -3
- flowyml/ui/frontend/src/app/settings/page.jsx +1 -1
- flowyml/ui/frontend/src/app/tokens/page.jsx +8 -6
- flowyml/ui/frontend/src/components/ArtifactViewer.jsx +159 -0
- flowyml/ui/frontend/src/components/NavigationTree.jsx +26 -9
- flowyml/ui/frontend/src/components/PipelineGraph.jsx +26 -24
- flowyml/ui/frontend/src/components/RunDetailsPanel.jsx +42 -14
- flowyml/ui/frontend/src/router/index.jsx +4 -0
- {flowyml-1.3.0.dist-info → flowyml-1.5.0.dist-info}/METADATA +3 -1
- {flowyml-1.3.0.dist-info → flowyml-1.5.0.dist-info}/RECORD +50 -42
- flowyml/ui/frontend/dist/assets/index-DcYwrn2j.css +0 -1
- flowyml/ui/frontend/dist/assets/index-Dlz_ygOL.js +0 -592
- {flowyml-1.3.0.dist-info → flowyml-1.5.0.dist-info}/WHEEL +0 -0
- {flowyml-1.3.0.dist-info → flowyml-1.5.0.dist-info}/entry_points.txt +0 -0
- {flowyml-1.3.0.dist-info → flowyml-1.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<meta charset="UTF-8" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>FlowyML</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-DF8dJaFL.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CBUXOWze.css">
|
|
10
10
|
</head>
|
|
11
11
|
|
|
12
12
|
<body>
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"react": "^18.2.0",
|
|
13
13
|
"react-dom": "^18.2.0",
|
|
14
14
|
"react-router-dom": "^6.22.0",
|
|
15
|
+
"react-syntax-highlighter": "^16.1.0",
|
|
15
16
|
"reactflow": "^11.11.4",
|
|
16
17
|
"recharts": "^2.12.0",
|
|
17
18
|
"tailwind-merge": "^2.6.0"
|
|
@@ -1548,6 +1549,21 @@
|
|
|
1548
1549
|
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
|
|
1549
1550
|
"version": "7946.0.16"
|
|
1550
1551
|
},
|
|
1552
|
+
"node_modules/@types/hast": {
|
|
1553
|
+
"dependencies": {
|
|
1554
|
+
"@types/unist": "*"
|
|
1555
|
+
},
|
|
1556
|
+
"integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
|
|
1557
|
+
"license": "MIT",
|
|
1558
|
+
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
|
|
1559
|
+
"version": "3.0.4"
|
|
1560
|
+
},
|
|
1561
|
+
"node_modules/@types/prismjs": {
|
|
1562
|
+
"integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==",
|
|
1563
|
+
"license": "MIT",
|
|
1564
|
+
"resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz",
|
|
1565
|
+
"version": "1.26.5"
|
|
1566
|
+
},
|
|
1551
1567
|
"node_modules/@types/prop-types": {
|
|
1552
1568
|
"devOptional": true,
|
|
1553
1569
|
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
|
@@ -1576,6 +1592,12 @@
|
|
|
1576
1592
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
|
|
1577
1593
|
"version": "18.3.7"
|
|
1578
1594
|
},
|
|
1595
|
+
"node_modules/@types/unist": {
|
|
1596
|
+
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
|
|
1597
|
+
"license": "MIT",
|
|
1598
|
+
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
|
|
1599
|
+
"version": "3.0.3"
|
|
1600
|
+
},
|
|
1579
1601
|
"node_modules/@vitejs/plugin-react": {
|
|
1580
1602
|
"dependencies": {
|
|
1581
1603
|
"@babel/core": "^7.28.0",
|
|
@@ -1764,6 +1786,36 @@
|
|
|
1764
1786
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001756.tgz",
|
|
1765
1787
|
"version": "1.0.30001756"
|
|
1766
1788
|
},
|
|
1789
|
+
"node_modules/character-entities": {
|
|
1790
|
+
"funding": {
|
|
1791
|
+
"type": "github",
|
|
1792
|
+
"url": "https://github.com/sponsors/wooorm"
|
|
1793
|
+
},
|
|
1794
|
+
"integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==",
|
|
1795
|
+
"license": "MIT",
|
|
1796
|
+
"resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
|
|
1797
|
+
"version": "2.0.2"
|
|
1798
|
+
},
|
|
1799
|
+
"node_modules/character-entities-legacy": {
|
|
1800
|
+
"funding": {
|
|
1801
|
+
"type": "github",
|
|
1802
|
+
"url": "https://github.com/sponsors/wooorm"
|
|
1803
|
+
},
|
|
1804
|
+
"integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
|
|
1805
|
+
"license": "MIT",
|
|
1806
|
+
"resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
|
|
1807
|
+
"version": "3.0.0"
|
|
1808
|
+
},
|
|
1809
|
+
"node_modules/character-reference-invalid": {
|
|
1810
|
+
"funding": {
|
|
1811
|
+
"type": "github",
|
|
1812
|
+
"url": "https://github.com/sponsors/wooorm"
|
|
1813
|
+
},
|
|
1814
|
+
"integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==",
|
|
1815
|
+
"license": "MIT",
|
|
1816
|
+
"resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz",
|
|
1817
|
+
"version": "2.0.1"
|
|
1818
|
+
},
|
|
1767
1819
|
"node_modules/chokidar": {
|
|
1768
1820
|
"dependencies": {
|
|
1769
1821
|
"anymatch": "~3.1.2",
|
|
@@ -1817,6 +1869,16 @@
|
|
|
1817
1869
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
|
1818
1870
|
"version": "2.1.1"
|
|
1819
1871
|
},
|
|
1872
|
+
"node_modules/comma-separated-tokens": {
|
|
1873
|
+
"funding": {
|
|
1874
|
+
"type": "github",
|
|
1875
|
+
"url": "https://github.com/sponsors/wooorm"
|
|
1876
|
+
},
|
|
1877
|
+
"integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
|
|
1878
|
+
"license": "MIT",
|
|
1879
|
+
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
|
|
1880
|
+
"version": "2.0.3"
|
|
1881
|
+
},
|
|
1820
1882
|
"node_modules/commander": {
|
|
1821
1883
|
"dev": true,
|
|
1822
1884
|
"engines": {
|
|
@@ -2084,6 +2146,19 @@
|
|
|
2084
2146
|
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
|
|
2085
2147
|
"version": "2.5.1"
|
|
2086
2148
|
},
|
|
2149
|
+
"node_modules/decode-named-character-reference": {
|
|
2150
|
+
"dependencies": {
|
|
2151
|
+
"character-entities": "^2.0.0"
|
|
2152
|
+
},
|
|
2153
|
+
"funding": {
|
|
2154
|
+
"type": "github",
|
|
2155
|
+
"url": "https://github.com/sponsors/wooorm"
|
|
2156
|
+
},
|
|
2157
|
+
"integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==",
|
|
2158
|
+
"license": "MIT",
|
|
2159
|
+
"resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz",
|
|
2160
|
+
"version": "1.2.0"
|
|
2161
|
+
},
|
|
2087
2162
|
"node_modules/didyoumean": {
|
|
2088
2163
|
"dev": true,
|
|
2089
2164
|
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
|
|
@@ -2219,6 +2294,19 @@
|
|
|
2219
2294
|
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
|
|
2220
2295
|
"version": "1.19.1"
|
|
2221
2296
|
},
|
|
2297
|
+
"node_modules/fault": {
|
|
2298
|
+
"dependencies": {
|
|
2299
|
+
"format": "^0.2.0"
|
|
2300
|
+
},
|
|
2301
|
+
"funding": {
|
|
2302
|
+
"type": "github",
|
|
2303
|
+
"url": "https://github.com/sponsors/wooorm"
|
|
2304
|
+
},
|
|
2305
|
+
"integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==",
|
|
2306
|
+
"license": "MIT",
|
|
2307
|
+
"resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz",
|
|
2308
|
+
"version": "1.0.4"
|
|
2309
|
+
},
|
|
2222
2310
|
"node_modules/fill-range": {
|
|
2223
2311
|
"dependencies": {
|
|
2224
2312
|
"to-regex-range": "^5.0.1"
|
|
@@ -2232,6 +2320,14 @@
|
|
|
2232
2320
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
|
2233
2321
|
"version": "7.1.1"
|
|
2234
2322
|
},
|
|
2323
|
+
"node_modules/format": {
|
|
2324
|
+
"engines": {
|
|
2325
|
+
"node": ">=0.4.x"
|
|
2326
|
+
},
|
|
2327
|
+
"integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==",
|
|
2328
|
+
"resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz",
|
|
2329
|
+
"version": "0.2.2"
|
|
2330
|
+
},
|
|
2235
2331
|
"node_modules/fraction.js": {
|
|
2236
2332
|
"dev": true,
|
|
2237
2333
|
"engines": {
|
|
@@ -2343,6 +2439,51 @@
|
|
|
2343
2439
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
|
2344
2440
|
"version": "2.0.2"
|
|
2345
2441
|
},
|
|
2442
|
+
"node_modules/hast-util-parse-selector": {
|
|
2443
|
+
"dependencies": {
|
|
2444
|
+
"@types/hast": "^3.0.0"
|
|
2445
|
+
},
|
|
2446
|
+
"funding": {
|
|
2447
|
+
"type": "opencollective",
|
|
2448
|
+
"url": "https://opencollective.com/unified"
|
|
2449
|
+
},
|
|
2450
|
+
"integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==",
|
|
2451
|
+
"license": "MIT",
|
|
2452
|
+
"resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz",
|
|
2453
|
+
"version": "4.0.0"
|
|
2454
|
+
},
|
|
2455
|
+
"node_modules/hastscript": {
|
|
2456
|
+
"dependencies": {
|
|
2457
|
+
"@types/hast": "^3.0.0",
|
|
2458
|
+
"comma-separated-tokens": "^2.0.0",
|
|
2459
|
+
"hast-util-parse-selector": "^4.0.0",
|
|
2460
|
+
"property-information": "^7.0.0",
|
|
2461
|
+
"space-separated-tokens": "^2.0.0"
|
|
2462
|
+
},
|
|
2463
|
+
"funding": {
|
|
2464
|
+
"type": "opencollective",
|
|
2465
|
+
"url": "https://opencollective.com/unified"
|
|
2466
|
+
},
|
|
2467
|
+
"integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==",
|
|
2468
|
+
"license": "MIT",
|
|
2469
|
+
"resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz",
|
|
2470
|
+
"version": "9.0.1"
|
|
2471
|
+
},
|
|
2472
|
+
"node_modules/highlight.js": {
|
|
2473
|
+
"engines": {
|
|
2474
|
+
"node": "*"
|
|
2475
|
+
},
|
|
2476
|
+
"integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==",
|
|
2477
|
+
"license": "BSD-3-Clause",
|
|
2478
|
+
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
|
|
2479
|
+
"version": "10.7.3"
|
|
2480
|
+
},
|
|
2481
|
+
"node_modules/highlightjs-vue": {
|
|
2482
|
+
"integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==",
|
|
2483
|
+
"license": "CC0-1.0",
|
|
2484
|
+
"resolved": "https://registry.npmjs.org/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz",
|
|
2485
|
+
"version": "1.0.0"
|
|
2486
|
+
},
|
|
2346
2487
|
"node_modules/internmap": {
|
|
2347
2488
|
"engines": {
|
|
2348
2489
|
"node": ">=12"
|
|
@@ -2352,6 +2493,30 @@
|
|
|
2352
2493
|
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
|
|
2353
2494
|
"version": "2.0.3"
|
|
2354
2495
|
},
|
|
2496
|
+
"node_modules/is-alphabetical": {
|
|
2497
|
+
"funding": {
|
|
2498
|
+
"type": "github",
|
|
2499
|
+
"url": "https://github.com/sponsors/wooorm"
|
|
2500
|
+
},
|
|
2501
|
+
"integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==",
|
|
2502
|
+
"license": "MIT",
|
|
2503
|
+
"resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
|
|
2504
|
+
"version": "2.0.1"
|
|
2505
|
+
},
|
|
2506
|
+
"node_modules/is-alphanumerical": {
|
|
2507
|
+
"dependencies": {
|
|
2508
|
+
"is-alphabetical": "^2.0.0",
|
|
2509
|
+
"is-decimal": "^2.0.0"
|
|
2510
|
+
},
|
|
2511
|
+
"funding": {
|
|
2512
|
+
"type": "github",
|
|
2513
|
+
"url": "https://github.com/sponsors/wooorm"
|
|
2514
|
+
},
|
|
2515
|
+
"integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==",
|
|
2516
|
+
"license": "MIT",
|
|
2517
|
+
"resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz",
|
|
2518
|
+
"version": "2.0.1"
|
|
2519
|
+
},
|
|
2355
2520
|
"node_modules/is-binary-path": {
|
|
2356
2521
|
"dependencies": {
|
|
2357
2522
|
"binary-extensions": "^2.0.0"
|
|
@@ -2381,6 +2546,16 @@
|
|
|
2381
2546
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
|
2382
2547
|
"version": "2.16.1"
|
|
2383
2548
|
},
|
|
2549
|
+
"node_modules/is-decimal": {
|
|
2550
|
+
"funding": {
|
|
2551
|
+
"type": "github",
|
|
2552
|
+
"url": "https://github.com/sponsors/wooorm"
|
|
2553
|
+
},
|
|
2554
|
+
"integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==",
|
|
2555
|
+
"license": "MIT",
|
|
2556
|
+
"resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz",
|
|
2557
|
+
"version": "2.0.1"
|
|
2558
|
+
},
|
|
2384
2559
|
"node_modules/is-extglob": {
|
|
2385
2560
|
"dev": true,
|
|
2386
2561
|
"engines": {
|
|
@@ -2404,6 +2579,16 @@
|
|
|
2404
2579
|
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
|
2405
2580
|
"version": "4.0.3"
|
|
2406
2581
|
},
|
|
2582
|
+
"node_modules/is-hexadecimal": {
|
|
2583
|
+
"funding": {
|
|
2584
|
+
"type": "github",
|
|
2585
|
+
"url": "https://github.com/sponsors/wooorm"
|
|
2586
|
+
},
|
|
2587
|
+
"integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==",
|
|
2588
|
+
"license": "MIT",
|
|
2589
|
+
"resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz",
|
|
2590
|
+
"version": "2.0.1"
|
|
2591
|
+
},
|
|
2407
2592
|
"node_modules/is-number": {
|
|
2408
2593
|
"dev": true,
|
|
2409
2594
|
"engines": {
|
|
@@ -2494,6 +2679,20 @@
|
|
|
2494
2679
|
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
|
2495
2680
|
"version": "1.4.0"
|
|
2496
2681
|
},
|
|
2682
|
+
"node_modules/lowlight": {
|
|
2683
|
+
"dependencies": {
|
|
2684
|
+
"fault": "^1.0.0",
|
|
2685
|
+
"highlight.js": "~10.7.0"
|
|
2686
|
+
},
|
|
2687
|
+
"funding": {
|
|
2688
|
+
"type": "github",
|
|
2689
|
+
"url": "https://github.com/sponsors/wooorm"
|
|
2690
|
+
},
|
|
2691
|
+
"integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==",
|
|
2692
|
+
"license": "MIT",
|
|
2693
|
+
"resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz",
|
|
2694
|
+
"version": "1.20.0"
|
|
2695
|
+
},
|
|
2497
2696
|
"node_modules/lru-cache": {
|
|
2498
2697
|
"dependencies": {
|
|
2499
2698
|
"yallist": "^3.0.2"
|
|
@@ -2636,6 +2835,31 @@
|
|
|
2636
2835
|
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
|
|
2637
2836
|
"version": "3.0.0"
|
|
2638
2837
|
},
|
|
2838
|
+
"node_modules/parse-entities": {
|
|
2839
|
+
"dependencies": {
|
|
2840
|
+
"@types/unist": "^2.0.0",
|
|
2841
|
+
"character-entities-legacy": "^3.0.0",
|
|
2842
|
+
"character-reference-invalid": "^2.0.0",
|
|
2843
|
+
"decode-named-character-reference": "^1.0.0",
|
|
2844
|
+
"is-alphanumerical": "^2.0.0",
|
|
2845
|
+
"is-decimal": "^2.0.0",
|
|
2846
|
+
"is-hexadecimal": "^2.0.0"
|
|
2847
|
+
},
|
|
2848
|
+
"funding": {
|
|
2849
|
+
"type": "github",
|
|
2850
|
+
"url": "https://github.com/sponsors/wooorm"
|
|
2851
|
+
},
|
|
2852
|
+
"integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==",
|
|
2853
|
+
"license": "MIT",
|
|
2854
|
+
"resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz",
|
|
2855
|
+
"version": "4.0.2"
|
|
2856
|
+
},
|
|
2857
|
+
"node_modules/parse-entities/node_modules/@types/unist": {
|
|
2858
|
+
"integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
|
|
2859
|
+
"license": "MIT",
|
|
2860
|
+
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
|
|
2861
|
+
"version": "2.0.11"
|
|
2862
|
+
},
|
|
2639
2863
|
"node_modules/path-parse": {
|
|
2640
2864
|
"dev": true,
|
|
2641
2865
|
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
|
@@ -2846,6 +3070,15 @@
|
|
|
2846
3070
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
|
2847
3071
|
"version": "4.2.0"
|
|
2848
3072
|
},
|
|
3073
|
+
"node_modules/prismjs": {
|
|
3074
|
+
"engines": {
|
|
3075
|
+
"node": ">=6"
|
|
3076
|
+
},
|
|
3077
|
+
"integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==",
|
|
3078
|
+
"license": "MIT",
|
|
3079
|
+
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
|
|
3080
|
+
"version": "1.30.0"
|
|
3081
|
+
},
|
|
2849
3082
|
"node_modules/prop-types": {
|
|
2850
3083
|
"dependencies": {
|
|
2851
3084
|
"loose-envify": "^1.4.0",
|
|
@@ -2863,6 +3096,16 @@
|
|
|
2863
3096
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
|
2864
3097
|
"version": "16.13.1"
|
|
2865
3098
|
},
|
|
3099
|
+
"node_modules/property-information": {
|
|
3100
|
+
"funding": {
|
|
3101
|
+
"type": "github",
|
|
3102
|
+
"url": "https://github.com/sponsors/wooorm"
|
|
3103
|
+
},
|
|
3104
|
+
"integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==",
|
|
3105
|
+
"license": "MIT",
|
|
3106
|
+
"resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz",
|
|
3107
|
+
"version": "7.1.0"
|
|
3108
|
+
},
|
|
2866
3109
|
"node_modules/queue-microtask": {
|
|
2867
3110
|
"dev": true,
|
|
2868
3111
|
"funding": [
|
|
@@ -2972,6 +3215,26 @@
|
|
|
2972
3215
|
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
|
|
2973
3216
|
"version": "4.0.4"
|
|
2974
3217
|
},
|
|
3218
|
+
"node_modules/react-syntax-highlighter": {
|
|
3219
|
+
"dependencies": {
|
|
3220
|
+
"@babel/runtime": "^7.28.4",
|
|
3221
|
+
"highlight.js": "^10.4.1",
|
|
3222
|
+
"highlightjs-vue": "^1.0.0",
|
|
3223
|
+
"lowlight": "^1.17.0",
|
|
3224
|
+
"prismjs": "^1.30.0",
|
|
3225
|
+
"refractor": "^5.0.0"
|
|
3226
|
+
},
|
|
3227
|
+
"engines": {
|
|
3228
|
+
"node": ">= 16.20.2"
|
|
3229
|
+
},
|
|
3230
|
+
"integrity": "sha512-E40/hBiP5rCNwkeBN1vRP+xow1X0pndinO+z3h7HLsHyjztbyjfzNWNKuAsJj+7DLam9iT4AaaOZnueCU+Nplg==",
|
|
3231
|
+
"license": "MIT",
|
|
3232
|
+
"peerDependencies": {
|
|
3233
|
+
"react": ">= 0.14.0"
|
|
3234
|
+
},
|
|
3235
|
+
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-16.1.0.tgz",
|
|
3236
|
+
"version": "16.1.0"
|
|
3237
|
+
},
|
|
2975
3238
|
"node_modules/react-transition-group": {
|
|
2976
3239
|
"dependencies": {
|
|
2977
3240
|
"@babel/runtime": "^7.5.5",
|
|
@@ -3061,6 +3324,22 @@
|
|
|
3061
3324
|
"resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz",
|
|
3062
3325
|
"version": "0.4.5"
|
|
3063
3326
|
},
|
|
3327
|
+
"node_modules/refractor": {
|
|
3328
|
+
"dependencies": {
|
|
3329
|
+
"@types/hast": "^3.0.0",
|
|
3330
|
+
"@types/prismjs": "^1.0.0",
|
|
3331
|
+
"hastscript": "^9.0.0",
|
|
3332
|
+
"parse-entities": "^4.0.0"
|
|
3333
|
+
},
|
|
3334
|
+
"funding": {
|
|
3335
|
+
"type": "github",
|
|
3336
|
+
"url": "https://github.com/sponsors/wooorm"
|
|
3337
|
+
},
|
|
3338
|
+
"integrity": "sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==",
|
|
3339
|
+
"license": "MIT",
|
|
3340
|
+
"resolved": "https://registry.npmjs.org/refractor/-/refractor-5.0.0.tgz",
|
|
3341
|
+
"version": "5.0.0"
|
|
3342
|
+
},
|
|
3064
3343
|
"node_modules/resolve": {
|
|
3065
3344
|
"bin": {
|
|
3066
3345
|
"resolve": "bin/resolve"
|
|
@@ -3188,6 +3467,16 @@
|
|
|
3188
3467
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
|
3189
3468
|
"version": "1.2.1"
|
|
3190
3469
|
},
|
|
3470
|
+
"node_modules/space-separated-tokens": {
|
|
3471
|
+
"funding": {
|
|
3472
|
+
"type": "github",
|
|
3473
|
+
"url": "https://github.com/sponsors/wooorm"
|
|
3474
|
+
},
|
|
3475
|
+
"integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
|
|
3476
|
+
"license": "MIT",
|
|
3477
|
+
"resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
|
|
3478
|
+
"version": "2.0.2"
|
|
3479
|
+
},
|
|
3191
3480
|
"node_modules/sucrase": {
|
|
3192
3481
|
"bin": {
|
|
3193
3482
|
"sucrase": "bin/sucrase",
|
flowyml/ui/frontend/package.json
CHANGED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { useSearchParams, Link } from 'react-router-dom';
|
|
3
|
+
import { fetchApi } from '../../utils/api';
|
|
4
|
+
import { ArrowLeft, PlayCircle, Clock, Calendar, CheckCircle, XCircle, AlertTriangle, Activity, ArrowRight, Layers } from 'lucide-react';
|
|
5
|
+
import { Card } from '../../components/ui/Card';
|
|
6
|
+
import { Badge } from '../../components/ui/Badge';
|
|
7
|
+
import { Button } from '../../components/ui/Button';
|
|
8
|
+
import { format } from 'date-fns';
|
|
9
|
+
import { StatusBadge } from '../../components/ui/ExecutionStatus';
|
|
10
|
+
|
|
11
|
+
export function RunComparisonPage() {
|
|
12
|
+
const [searchParams] = useSearchParams();
|
|
13
|
+
const [runs, setRuns] = useState([]);
|
|
14
|
+
const [loading, setLoading] = useState(true);
|
|
15
|
+
const [error, setError] = useState(null);
|
|
16
|
+
|
|
17
|
+
const runIdsParam = searchParams.get('runs');
|
|
18
|
+
const run1Id = searchParams.get('run1');
|
|
19
|
+
const run2Id = searchParams.get('run2');
|
|
20
|
+
|
|
21
|
+
// Normalize run IDs from different possible query params
|
|
22
|
+
const runIds = runIdsParam
|
|
23
|
+
? runIdsParam.split(',').filter(Boolean)
|
|
24
|
+
: [run1Id, run2Id].filter(Boolean);
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (runIds.length < 2) {
|
|
28
|
+
setError('Please select at least two runs to compare.');
|
|
29
|
+
setLoading(false);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
fetchData();
|
|
33
|
+
}, [runIdsParam, run1Id, run2Id]);
|
|
34
|
+
|
|
35
|
+
const fetchData = async () => {
|
|
36
|
+
setLoading(true);
|
|
37
|
+
try {
|
|
38
|
+
const promises = runIds.map(id => fetchApi(`/api/runs/${id}`));
|
|
39
|
+
const responses = await Promise.all(promises);
|
|
40
|
+
|
|
41
|
+
const data = [];
|
|
42
|
+
for (const res of responses) {
|
|
43
|
+
if (!res.ok) throw new Error('Failed to fetch run details');
|
|
44
|
+
data.push(await res.json());
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
setRuns(data);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
setError(err.message);
|
|
50
|
+
} finally {
|
|
51
|
+
setLoading(false);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
if (loading) {
|
|
56
|
+
return (
|
|
57
|
+
<div className="flex flex-col items-center justify-center h-screen bg-slate-50 dark:bg-slate-900">
|
|
58
|
+
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600 mb-4"></div>
|
|
59
|
+
<p className="text-slate-500">Loading comparison...</p>
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (error) {
|
|
65
|
+
return (
|
|
66
|
+
<div className="flex flex-col items-center justify-center h-screen bg-slate-50 dark:bg-slate-900">
|
|
67
|
+
<div className="bg-red-50 p-6 rounded-xl border border-red-100 max-w-md text-center">
|
|
68
|
+
<AlertTriangle className="mx-auto text-red-500 mb-2" size={32} />
|
|
69
|
+
<h2 className="text-xl font-bold text-slate-800 mb-2">Error</h2>
|
|
70
|
+
<p className="text-slate-600 mb-4">{error}</p>
|
|
71
|
+
<Link to="/runs">
|
|
72
|
+
<Button>Back to Runs</Button>
|
|
73
|
+
</Link>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Helper to find best values (min duration)
|
|
80
|
+
const minDuration = Math.min(...runs.map(r => r.duration || Infinity));
|
|
81
|
+
|
|
82
|
+
// Calculate step specific diffs
|
|
83
|
+
const allStepNames = Array.from(new Set(
|
|
84
|
+
runs.flatMap(r => Object.keys(r.steps || {}))
|
|
85
|
+
));
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<div className="min-h-screen bg-slate-50 dark:bg-slate-900 overflow-y-auto">
|
|
89
|
+
{/* Header */}
|
|
90
|
+
<div className="bg-white dark:bg-slate-800 border-b border-slate-200 dark:border-slate-700 sticky top-0 z-10 w-full overflow-x-auto">
|
|
91
|
+
<div className="w-full min-w-max px-6 py-4">
|
|
92
|
+
<div className="flex items-center gap-4 mb-4">
|
|
93
|
+
<Link to="/runs" className="p-2 hover:bg-slate-100 dark:hover:bg-slate-700 rounded-full transition-colors">
|
|
94
|
+
<ArrowLeft size={20} className="text-slate-500" />
|
|
95
|
+
</Link>
|
|
96
|
+
<h1 className="text-xl font-bold text-slate-900 dark:text-white flex items-center gap-2">
|
|
97
|
+
Run Comparison
|
|
98
|
+
<Badge variant="secondary" className="ml-2">({runs.length} runs)</Badge>
|
|
99
|
+
</h1>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<div className={`grid gap-4`} style={{ gridTemplateColumns: `repeat(${runs.length}, minmax(300px, 1fr))` }}>
|
|
103
|
+
{runs.map(run => (
|
|
104
|
+
<div key={run.run_id} className="flex items-center gap-3 p-3 rounded-lg bg-slate-50 dark:bg-slate-900 border border-slate-200 dark:border-slate-700">
|
|
105
|
+
<StatusBadge status={run.status} />
|
|
106
|
+
<div className="min-w-0">
|
|
107
|
+
<h3 className="font-bold text-slate-900 dark:text-white truncate" title={run.run_id}>{run.run_id}</h3>
|
|
108
|
+
<p className="text-xs text-slate-500">{format(new Date(run.start_time), 'MMM d, HH:mm:ss')}</p>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
))}
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<div className="w-full overflow-x-auto">
|
|
117
|
+
<div className="min-w-max px-6 py-8 space-y-8">
|
|
118
|
+
|
|
119
|
+
{/* Metrics Comparison */}
|
|
120
|
+
<section>
|
|
121
|
+
<h2 className="text-lg font-bold text-slate-900 dark:text-white mb-4 flex items-center gap-2">
|
|
122
|
+
<Activity size={18} /> Performance Metrics
|
|
123
|
+
</h2>
|
|
124
|
+
<Card className="overflow-hidden">
|
|
125
|
+
<table className="w-full text-sm text-left">
|
|
126
|
+
<thead className="bg-slate-50 dark:bg-slate-800 border-b border-slate-200 dark:border-slate-700 text-xs uppercase text-slate-500 font-medium">
|
|
127
|
+
<tr>
|
|
128
|
+
<th className="px-6 py-3 w-48 sticky left-0 bg-slate-50 dark:bg-slate-800 z-10 border-r border-slate-200 dark:border-slate-700">Metric</th>
|
|
129
|
+
{runs.map((run, i) => (
|
|
130
|
+
<th key={run.run_id} className={`px-6 py-3 min-w-[200px] ${i < runs.length - 1 ? 'border-r border-slate-200 dark:border-slate-700' : ''}`}>
|
|
131
|
+
{run.run_id.slice(0, 8)}
|
|
132
|
+
</th>
|
|
133
|
+
))}
|
|
134
|
+
</tr>
|
|
135
|
+
</thead>
|
|
136
|
+
<tbody className="divide-y divide-slate-100 dark:divide-slate-700">
|
|
137
|
+
<tr className="hover:bg-slate-50 dark:hover:bg-slate-800/50">
|
|
138
|
+
<td className="px-6 py-3 font-medium text-slate-700 dark:text-slate-300 sticky left-0 bg-white dark:bg-slate-900 border-r border-slate-100 dark:border-slate-700">Duration</td>
|
|
139
|
+
{runs.map((run, i) => (
|
|
140
|
+
<td key={run.run_id} className={`px-6 py-3 ${i < runs.length - 1 ? 'border-r border-slate-100 dark:border-slate-700' : ''} ${run.duration === minDuration ? 'text-emerald-600 font-bold' : 'text-slate-600'}`}>
|
|
141
|
+
{run.duration?.toFixed(2)}s
|
|
142
|
+
{run.duration === minDuration && runs.length > 1 && <span className="ml-2 text-xs bg-emerald-100 dark:bg-emerald-900/30 text-emerald-600 px-1.5 py-0.5 rounded-full">Fastest</span>}
|
|
143
|
+
</td>
|
|
144
|
+
))}
|
|
145
|
+
</tr>
|
|
146
|
+
<tr className="hover:bg-slate-50 dark:hover:bg-slate-800/50">
|
|
147
|
+
<td className="px-6 py-3 font-medium text-slate-700 dark:text-slate-300 sticky left-0 bg-white dark:bg-slate-900 border-r border-slate-100 dark:border-slate-700">Step Count</td>
|
|
148
|
+
{runs.map((run, i) => (
|
|
149
|
+
<td key={run.run_id} className={`px-6 py-3 text-slate-600 ${i < runs.length - 1 ? 'border-r border-slate-100 dark:border-slate-700' : ''}`}>
|
|
150
|
+
{Object.keys(run.steps || {}).length}
|
|
151
|
+
</td>
|
|
152
|
+
))}
|
|
153
|
+
</tr>
|
|
154
|
+
</tbody>
|
|
155
|
+
</table>
|
|
156
|
+
</Card>
|
|
157
|
+
</section>
|
|
158
|
+
|
|
159
|
+
{/* Steps Comparison */}
|
|
160
|
+
<section>
|
|
161
|
+
<h2 className="text-lg font-bold text-slate-900 dark:text-white mb-4 flex items-center gap-2">
|
|
162
|
+
<Layers size={18} /> Step Execution
|
|
163
|
+
</h2>
|
|
164
|
+
<Card className="overflow-hidden">
|
|
165
|
+
<table className="w-full text-sm text-left">
|
|
166
|
+
<thead className="bg-slate-50 dark:bg-slate-800 border-b border-slate-200 dark:border-slate-700 text-xs uppercase text-slate-500 font-medium">
|
|
167
|
+
<tr>
|
|
168
|
+
<th className="px-6 py-3 w-48 sticky left-0 bg-slate-50 dark:bg-slate-800 z-10 border-r border-slate-200 dark:border-slate-700">Step Name</th>
|
|
169
|
+
{runs.map((run, i) => (
|
|
170
|
+
<th key={run.run_id} className={`px-6 py-3 min-w-[200px] ${i < runs.length - 1 ? 'border-r border-slate-200 dark:border-slate-700' : ''}`}>
|
|
171
|
+
{run.run_id.slice(0, 8)}
|
|
172
|
+
</th>
|
|
173
|
+
))}
|
|
174
|
+
</tr>
|
|
175
|
+
</thead>
|
|
176
|
+
<tbody className="divide-y divide-slate-100 dark:divide-slate-700">
|
|
177
|
+
{allStepNames.map(stepName => {
|
|
178
|
+
// Calculate diffs for this step row
|
|
179
|
+
const stepDurations = runs.map(r => r.steps?.[stepName]?.duration || Infinity);
|
|
180
|
+
const minStepDuration = Math.min(...stepDurations);
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<tr key={stepName} className="hover:bg-slate-50 dark:hover:bg-slate-800/50">
|
|
184
|
+
<td className="px-6 py-3 font-medium text-slate-700 dark:text-slate-300 sticky left-0 bg-white dark:bg-slate-900 border-r border-slate-100 dark:border-slate-700">{stepName}</td>
|
|
185
|
+
{runs.map((run, i) => {
|
|
186
|
+
const step = run.steps?.[stepName];
|
|
187
|
+
return (
|
|
188
|
+
<td key={run.run_id} className={`px-6 py-3 ${i < runs.length - 1 ? 'border-r border-slate-100 dark:border-slate-700' : ''}`}>
|
|
189
|
+
{step ? (
|
|
190
|
+
<div className="flex items-center justify-between gap-4">
|
|
191
|
+
<StatusBadge status={step.success ? 'completed' : step.error ? 'failed' : 'pending'} size="sm" />
|
|
192
|
+
<span className={`font-mono text-xs ${step.duration === minStepDuration && runs.length > 1 ? 'text-emerald-600 font-bold' : 'text-slate-500'}`}>
|
|
193
|
+
{step.duration?.toFixed(2)}s
|
|
194
|
+
</span>
|
|
195
|
+
</div>
|
|
196
|
+
) : (
|
|
197
|
+
<span className="text-slate-400 italic text-xs">Not executed</span>
|
|
198
|
+
)}
|
|
199
|
+
</td>
|
|
200
|
+
);
|
|
201
|
+
})}
|
|
202
|
+
</tr>
|
|
203
|
+
);
|
|
204
|
+
})}
|
|
205
|
+
</tbody>
|
|
206
|
+
</table>
|
|
207
|
+
</Card>
|
|
208
|
+
</section>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
);
|
|
213
|
+
}
|