bittensor-cli 9.4.3__py3-none-any.whl → 9.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.
@@ -1,33 +1,18 @@
1
1
  import asyncio
2
- import json
3
2
  import os
4
3
  import tempfile
5
4
  import webbrowser
6
5
  import netaddr
7
- from dataclasses import asdict, is_dataclass
8
6
  from typing import Any
9
- from pywry import PyWry
10
7
 
11
8
  from bittensor_cli.src.bittensor.balances import Balance
12
9
  from bittensor_cli.src.bittensor.subtensor_interface import SubtensorInterface
13
- from bittensor_cli.src.bittensor.utils import console, WalletLike
10
+ from bittensor_cli.src.bittensor.utils import console, WalletLike, jinja_env
14
11
  from bittensor_wallet import Wallet
15
12
  from bittensor_cli.src import defaults
16
13
 
17
- root_symbol_html = f"&#x{ord('τ'):X};"
18
14
 
19
-
20
- class Encoder(json.JSONEncoder):
21
- """JSON encoder for serializing dataclasses and balances"""
22
-
23
- def default(self, obj):
24
- if is_dataclass(obj):
25
- return asdict(obj)
26
-
27
- elif isinstance(obj, Balance):
28
- return obj.tao
29
-
30
- return super().default(obj)
15
+ ROOT_SYMBOL_HTML = f"&#x{ord('τ'):X};"
31
16
 
32
17
 
33
18
  async def display_network_dashboard(
@@ -51,30 +36,15 @@ async def display_network_dashboard(
51
36
 
52
37
  if use_wry:
53
38
  console.print(
54
- "[dark_sea_green3]Opening dashboard in a window. Press Ctrl+C to close.[/dark_sea_green3]"
39
+ "[dark_sea_green3]Opening dashboard in a window.[/dark_sea_green3]"
55
40
  )
56
- window = PyWry()
57
- window.send_html(
58
- html=html_content,
59
- title="Bittensor View",
60
- width=1200,
61
- height=800,
62
- )
63
- window.start()
64
- await asyncio.sleep(10)
65
- try:
66
- while True:
67
- if _has_exited(window):
68
- break
69
- await asyncio.sleep(1)
70
- except KeyboardInterrupt:
71
- console.print("\n[yellow]Closing Bittensor View...[/yellow]")
72
- finally:
73
- if not _has_exited(window):
74
- try:
75
- window.close()
76
- except Exception:
77
- pass
41
+ with tempfile.NamedTemporaryFile(
42
+ "w", delete=False, suffix=".html"
43
+ ) as dashboard_file:
44
+ url = f"file://{dashboard_file.name}"
45
+ dashboard_file.write(html_content)
46
+
47
+ webbrowser.open(url, new=1)
78
48
  else:
79
49
  if save_file:
80
50
  dir_path = os.path.expanduser(dashboard_path)
@@ -122,7 +92,7 @@ def get_identity(
122
92
  hotkey_ss58: str,
123
93
  identities: dict,
124
94
  old_identities: dict,
125
- trucate_length: int = 4,
95
+ truncate_length: int = 4,
126
96
  return_bool: bool = False,
127
97
  lookup_hk: bool = True,
128
98
  ) -> str:
@@ -144,7 +114,7 @@ def get_identity(
144
114
  if return_bool:
145
115
  return False
146
116
  else:
147
- return f"{hotkey_ss58[:trucate_length]}...{hotkey_ss58[-trucate_length:]}"
117
+ return f"{hotkey_ss58[:truncate_length]}...{hotkey_ss58[-truncate_length:]}"
148
118
 
149
119
 
150
120
  async def fetch_subnet_data(
@@ -283,7 +253,7 @@ def process_subnet_data(raw_data: dict[str, Any]) -> dict[str, Any]:
283
253
  # Add identities
284
254
  for hotkey in meta_info.hotkeys:
285
255
  identity = get_identity(
286
- hotkey, ck_hk_identities, old_identities, trucate_length=2
256
+ hotkey, ck_hk_identities, old_identities, truncate_length=2
287
257
  )
288
258
  metagraph_info["updated_identities"].append(identity)
289
259
 
@@ -358,324 +328,13 @@ def process_subnet_data(raw_data: dict[str, Any]) -> dict[str, Any]:
358
328
  }
359
329
 
360
330
 
361
- def _has_exited(handler) -> bool:
362
- """Check if PyWry process has cleanly exited with returncode 0."""
363
- return (
364
- hasattr(handler, "runner")
365
- and handler.runner is not None
366
- and handler.runner.returncode == 0
367
- )
368
-
369
-
370
331
  def generate_full_page(data: dict[str, Any]) -> str:
371
332
  """
372
333
  Generate full HTML content for the interface.
373
334
  """
374
- serializable_data = {
375
- "wallet_info": data["wallet_info"],
376
- "subnets": data["subnets"],
377
- }
378
- wallet_info_json = json.dumps(
379
- serializable_data["wallet_info"], cls=Encoder
380
- ).replace("'", "'")
381
- subnets_json = json.dumps(serializable_data["subnets"], cls=Encoder).replace(
382
- "'", "'"
383
- )
384
-
385
- return f"""
386
- <!DOCTYPE html>
387
- <html>
388
- <head>
389
- <meta charset="UTF-8">
390
- <title>Bittensor CLI Interface</title>
391
- <style>
392
- {get_css_styles()}
393
- </style>
394
- </head>
395
- <body>
396
- <!-- Embedded JSON data used by JS -->
397
- <div id="initial-data"
398
- data-wallet-info='{wallet_info_json}'
399
- data-subnets='{subnets_json}'>
400
- </div>
401
- <div id="splash-screen">
402
- <div class="splash-content">
403
- <div class="title-row">
404
- <h1 class="splash-title">Btcli View</h1>
405
- <span class="beta-text">Beta</span>
406
- </div>
407
- </div>
408
- </div>
409
-
410
- <!-- Main content area -->
411
- <div id="main-content">
412
- {generate_main_header(data["wallet_info"], data["block_number"])}
413
- {generate_main_filters()}
414
- {generate_subnets_table(data["subnets"])}
415
- </div>
416
-
417
- <!-- Subnet details page (hidden by default) -->
418
- <div id="subnet-page" style="display: none;">
419
- {generate_subnet_details_header()}
420
- {generate_subnet_metrics()}
421
- {generate_neuron_details()}
422
- </div>
423
-
424
- <script>
425
- {get_javascript()}
426
- </script>
427
- </body>
428
- </html>
429
- """
430
-
431
-
432
- def generate_subnet_details_header() -> str:
433
- """
434
- Generates the header section for the subnet details page,
435
- including the back button, toggle controls, title, and network visualization.
436
- """
437
- return """
438
- <div class="subnet-header">
439
- <div class="header-row">
440
- <button class="back-button">&larr; Back</button>
441
- <div class="toggle-group">
442
- <label class="toggle-label">
443
- <input type="checkbox" id="stake-toggle" onchange="toggleStakeView()">
444
- Show Stakes
445
- </label>
446
- <label class="toggle-label">
447
- <input type="checkbox" id="verbose-toggle" onchange="toggleVerboseNumbers()">
448
- Precise Numbers
449
- </label>
450
- </div>
451
- </div>
452
-
453
- <div class="subnet-title-row">
454
- <div class="title-price">
455
- <h2 id="subnet-title"></h2>
456
- <div class="subnet-price" id="subnet-price"></div>
457
- </div>
458
- <div class="network-visualization-container">
459
- <div class="network-visualization">
460
- <canvas id="network-canvas" width="700" height="80"></canvas>
461
- </div>
462
- </div>
463
- </div>
464
- <div class="network-metrics">
465
- <div class="metric-card network-card">
466
- <div class="metric-label">Moving Price</div>
467
- <div id="network-moving-price" class="metric-value"></div>
468
- </div>
469
- <div class="metric-card network-card">
470
- <div class="metric-label">Registration</div>
471
- <div id="network-registration" class="metric-value registration-status"></div>
472
- </div>
473
- <div class="metric-card network-card">
474
- <div class="metric-label">CR Weights</div>
475
- <div id="network-cr" class="metric-value cr-status"></div>
476
- </div>
477
- <div class="metric-card network-card">
478
- <div class="metric-label">Neurons</div>
479
- <div id="network-neurons" class="metric-value"></div>
480
- </div>
481
- <div class="metric-card network-card">
482
- <div class="metric-label">Blocks Since Step</div>
483
- <div id="network-blocks-since-step" class="metric-value"></div>
484
- </div>
485
- </div>
486
- </div>
487
- """
488
-
489
-
490
- def generate_subnet_metrics() -> str:
491
- """
492
- Generates the metrics section for the subnet details page,
493
- including market metrics and the stakes table.
494
- """
495
- return """
496
- <div class="metrics-section">
497
- <div class="metrics-group market-metrics">
498
- <div class="metric-card">
499
- <div class="metric-label">Market Cap</div>
500
- <div id="subnet-market-cap" class="metric-value"></div>
501
- </div>
502
- <div class="metric-card">
503
- <div class="metric-label">Total Stake</div>
504
- <div id="subnet-total-stake" class="metric-value"></div>
505
- </div>
506
- <div class="metric-card">
507
- <div class="metric-label">Alpha Reserves</div>
508
- <div id="network-alpha-in" class="metric-value"></div>
509
- </div>
510
- <div class="metric-card">
511
- <div class="metric-label">Tao Reserves</div>
512
- <div id="network-tau-in" class="metric-value"></div>
513
- </div>
514
- <div class="metric-card">
515
- <div class="metric-label">Emission</div>
516
- <div id="subnet-emission" class="metric-value"></div>
517
- </div>
518
- </div>
519
-
520
- <div class="stakes-container">
521
- <div class="stakes-header">
522
- <h3 class="view-header">Metagraph</h3>
523
- <div class="button-group">
524
- <button class="manage-button add-stake-button" disabled title="Coming soon">
525
- Add Stake (Coming soon)
526
- </button>
527
- <button class="manage-button export-csv-button" disabled title="Coming soon">
528
- Export CSV (Coming soon)
529
- </button>
530
- </div>
531
- </div>
532
-
533
- <div class="stakes-table-container">
534
- <table class="stakes-table">
535
- <thead>
536
- <tr>
537
- <th>Hotkey</th>
538
- <th>Amount</th>
539
- <th>Value</th>
540
- <th>Value (w/ slippage)</th>
541
- <th>Alpha emission</th>
542
- <th>Tao emission</th>
543
- <th>Registered</th>
544
- <th>Actions</th>
545
- </tr>
546
- </thead>
547
- <tbody id="stakes-table-body">
548
- </tbody>
549
- </table>
550
- </div>
551
- </div>
552
- </div>
553
- """
554
-
555
-
556
- def generate_neuron_details() -> str:
557
- """
558
- Generates the neuron detail container, which is hidden by default.
559
- This section shows detailed information for a selected neuron.
560
- """
561
- return """
562
- <div id="neuron-detail-container" style="display: none;">
563
- <div class="neuron-detail-header">
564
- <button class="back-button neuron-detail-back" onclick="closeNeuronDetails()">&larr; Back</button>
565
- </div>
566
- <div class="neuron-detail-content">
567
- <div class="neuron-info-top">
568
- <h2 class="neuron-name" id="neuron-name"></h2>
569
- <div class="neuron-keys">
570
- <div class="hotkey-label">
571
- <span style="color: #FF9900;">Hotkey:</span>
572
- <span id="neuron-hotkey" class="truncated-address"></span>
573
- </div>
574
- <div class="coldkey-label">
575
- <span style="color: #FF9900;">Coldkey:</span>
576
- <span id="neuron-coldkey" class="truncated-address"></span>
577
- </div>
578
- </div>
579
- </div>
580
- <div class="neuron-cards-container">
581
- <!-- First row: Stakes, Dividends, Incentive, Emissions -->
582
- <div class="neuron-metrics-row">
583
- <div class="metric-card">
584
- <div class="metric-label">Stake Weight</div>
585
- <div id="neuron-stake-total" class="metric-value formatted-number"
586
- data-value="0" data-symbol=""></div>
587
- </div>
588
-
589
- <div class="metric-card">
590
- <div class="metric-label">Stake (Alpha)</div>
591
- <div id="neuron-stake-token" class="metric-value formatted-number"
592
- data-value="0" data-symbol=""></div>
593
- </div>
594
-
595
- <div class="metric-card">
596
- <div class="metric-label">Stake (Root)</div>
597
- <div id="neuron-stake-root" class="metric-value formatted-number"
598
- data-value="0" data-symbol="&#x03C4;"></div>
599
- </div>
600
-
601
- <div class="metric-card">
602
- <div class="metric-label">Dividends</div>
603
- <div id="neuron-dividends" class="metric-value formatted-number"
604
- data-value="0" data-symbol=""></div>
605
- </div>
606
-
607
- <div class="metric-card">
608
- <div class="metric-label">Incentive</div>
609
- <div id="neuron-incentive" class="metric-value formatted-number"
610
- data-value="0" data-symbol=""></div>
611
- </div>
612
-
613
- <div class="metric-card">
614
- <div class="metric-label">Emissions</div>
615
- <div id="neuron-emissions" class="metric-value formatted-number"
616
- data-value="0" data-symbol=""></div>
617
- </div>
618
- </div>
619
-
620
- <!-- Second row: Rank, Trust, Pruning Score, Validator Permit, Consensus, Last Update -->
621
- <div class="neuron-metrics-row">
622
- <div class="metric-card">
623
- <div class="metric-label">Rank</div>
624
- <div id="neuron-rank" class="metric-value"></div>
625
- </div>
626
-
627
- <div class="metric-card">
628
- <div class="metric-label">Trust</div>
629
- <div id="neuron-trust" class="metric-value"></div>
630
- </div>
631
-
632
- <div class="metric-card">
633
- <div class="metric-label">Pruning Score</div>
634
- <div id="neuron-pruning-score" class="metric-value"></div>
635
- </div>
636
-
637
- <div class="metric-card">
638
- <div class="metric-label">Validator Permit</div>
639
- <div id="neuron-validator-permit" class="metric-value"></div>
640
- </div>
641
-
642
- <div class="metric-card">
643
- <div class="metric-label">Consensus</div>
644
- <div id="neuron-consensus" class="metric-value"></div>
645
- </div>
646
-
647
- <div class="metric-card">
648
- <div class="metric-label">Last Update</div>
649
- <div id="neuron-last-update" class="metric-value"></div>
650
- </div>
651
- </div>
652
-
653
- <!-- Third row: Reg Block, IP Info, Active -->
654
- <div class="neuron-metrics-row last-row">
655
- <div class="metric-card">
656
- <div class="metric-label">Reg Block</div>
657
- <div id="neuron-reg-block" class="metric-value"></div>
658
- </div>
659
-
660
- <div class="metric-card">
661
- <div class="metric-label">IP Info</div>
662
- <div id="neuron-ipinfo" class="metric-value"></div>
663
- </div>
664
-
665
- <div class="metric-card">
666
- <div class="metric-label">Active</div>
667
- <div id="neuron-active" class="metric-value"></div>
668
- </div>
669
- </div>
670
- </div>
671
- </div>
672
- </div>
673
- """
674
-
675
-
676
- def generate_main_header(wallet_info: dict[str, Any], block_number: int) -> str:
335
+ wallet_info = data["wallet_info"]
677
336
  truncated_coldkey = f"{wallet_info['coldkey'][:6]}...{wallet_info['coldkey'][-6:]}"
678
-
337
+ block_number = data["block_number"]
679
338
  # Calculate slippage percentage
680
339
  ideal_value = wallet_info["total_ideal_stake_value"]
681
340
  slippage_value = wallet_info["total_slippage_value"]
@@ -683,2263 +342,13 @@ def generate_main_header(wallet_info: dict[str, Any], block_number: int) -> str:
683
342
  ((ideal_value - slippage_value) / ideal_value * 100) if ideal_value > 0 else 0
684
343
  )
685
344
 
686
- return f"""
687
- <div class="header">
688
- <meta charset="UTF-8">
689
- <div class="wallet-info">
690
- <span class="wallet-name">{wallet_info["name"]}</span>
691
- <div class="wallet-address-container" onclick="copyToClipboard('{wallet_info["coldkey"]}', this)">
692
- <span class="wallet-address" title="Click to copy">{truncated_coldkey}</span>
693
- <span class="copy-indicator">Copy</span>
694
- </div>
695
- </div>
696
- <div class="stake-metrics">
697
- <div class="stake-metric">
698
- <span class="metric-label">Block</span>
699
- <span class="metric-value" style="color: #FF9900;">{block_number}</span>
700
- </div>
701
- <div class="stake-metric">
702
- <span class="metric-label">Balance</span>
703
- <span class="metric-value">{wallet_info["balance"]:.4f} {root_symbol_html}</span>
704
- </div>
705
- <div class="stake-metric">
706
- <span class="metric-label">Total Stake Value</span>
707
- <span class="metric-value">{wallet_info["total_ideal_stake_value"]:.4f} {root_symbol_html}</span>
708
- </div>
709
- <div class="stake-metric">
710
- <span class="metric-label">Slippage Impact</span>
711
- <span class="metric-value slippage-value">
712
- {slippage_percentage:.2f}% <span class="slippage-detail">({wallet_info["total_slippage_value"]:.4f} {root_symbol_html})</span>
713
- </span>
714
- </div>
715
- </div>
716
- </div>
717
- """
718
-
719
-
720
- def generate_main_filters() -> str:
721
- return """
722
- <div class="filters-section">
723
- <div class="search-box">
724
- <input type="text" id="subnet-search" placeholder="search for name, or netuid..." onkeyup="filterSubnets()">
725
- </div>
726
- <div class="filter-toggles">
727
- <label>
728
- <input type="checkbox" id="show-verbose" onchange="toggleVerboseNumbers()">
729
- Precise Numbers
730
- </label>
731
- <label>
732
- <input type="checkbox" id="show-staked" onchange="filterSubnets()">
733
- Show Only Staked
734
- </label>
735
- <label>
736
- <input type="checkbox" id="show-tiles" onchange="toggleTileView()" checked>
737
- Tile View
738
- </label>
739
- <label class="disabled-label" title="Coming soon">
740
- <input type="checkbox" id="live-mode" disabled>
741
- Live Mode (coming soon)
742
- </label>
743
- </div>
744
- </div>
745
- <div id="subnet-tiles-container" class="subnet-tiles-container"></div>
746
- """
747
-
748
-
749
- def generate_subnets_table(subnets: list[dict[str, Any]]) -> str:
750
- rows = []
751
- for subnet in subnets:
752
- total_your_stake = sum(stake["amount"] for stake in subnet["your_stakes"])
753
- stake_status = (
754
- '<span class="stake-status staked">Staked</span>'
755
- if total_your_stake > 0
756
- else '<span class="stake-status unstaked">Not Staked</span>'
757
- )
758
- rows.append(f"""
759
- <tr class="subnet-row" onclick="showSubnetPage({subnet["netuid"]})">
760
- <td class="subnet-name" data-value="{subnet["netuid"]}"><span style="color: #FF9900">{subnet["netuid"]}</span> - {subnet["name"]}</td>
761
- <td class="price" data-value="{subnet["price"]}"><span class="formatted-number" data-value="{subnet["price"]}" data-symbol="{subnet["symbol"]}"></span></td>
762
- <td class="market-cap" data-value="{subnet["market_cap"]}"><span class="formatted-number" data-value="{subnet["market_cap"]}" data-symbol="{root_symbol_html}"></span></td>
763
- <td class="your-stake" data-value="{total_your_stake}"><span class="formatted-number" data-value="{total_your_stake}" data-symbol="{subnet["symbol"]}"></span></td>
764
- <td class="emission" data-value="{subnet["emission"]}"><span class="formatted-number" data-value="{subnet["emission"]}" data-symbol="{root_symbol_html}"></span></td>
765
- <td class="stake-status-cell">{stake_status}</td>
766
- </tr>
767
- """)
768
- return f"""
769
- <div class="subnets-table-container">
770
- <table class="subnets-table">
771
- <thead>
772
- <tr>
773
- <th class="sortable" onclick="sortMainTable(0)">Subnet</th>
774
- <th class="sortable" onclick="sortMainTable(1)">Price</th>
775
- <th class="sortable" onclick="sortMainTable(2)" data-sort="desc">Market Cap</th>
776
- <th class="sortable" onclick="sortMainTable(3)">Your Stake</th>
777
- <th class="sortable" onclick="sortMainTable(4)">Emission</th>
778
- <th>Status</th>
779
- </tr>
780
- </thead>
781
- <tbody>
782
- {"".join(rows)}
783
- </tbody>
784
- </table>
785
- </div>
786
- """
787
-
788
-
789
- def generate_subnet_details_html() -> str:
790
- return """
791
- <div id="subnet-modal" class="modal hidden">
792
- <div class="modal-content">
793
- <div class="modal-header">
794
- <h2 class="subnet-title"></h2>
795
- <button class="close-button" onclick="closeSubnetModal()">&times;</button>
796
- </div>
797
- <div class="subnet-overview">
798
- <div class="overview-item">
799
- <span class="label">Price</span>
800
- <span class="value price"></span>
801
- </div>
802
- <div class="overview-item">
803
- <span class="label">Market Cap</span>
804
- <span class="value market-cap"></span>
805
- </div>
806
- <div class="overview-item">
807
- <span class="label">Emission Rate</span>
808
- <span class="value emission"></span>
809
- </div>
810
- <div class="overview-item">
811
- <span class="label">Your Total Stake</span>
812
- <span class="value total-stake"></span>
813
- </div>
814
- </div>
815
- <div class="stakes-section">
816
- <h3>Your Stakes</h3>
817
- <div class="stakes-list"></div>
818
- </div>
819
- </div>
820
- </div>
821
- """
822
-
823
-
824
- def get_css_styles() -> str:
825
- """Get CSS styles for the interface."""
826
- return """
827
- /* ===================== Base Styles & Typography ===================== */
828
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Noto+Sans:wght@400;500;600&display=swap');
829
-
830
- body {
831
- font-family: 'Inter', 'Noto Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Arial Unicode MS', sans-serif;
832
- margin: 0;
833
- padding: 24px;
834
- background: #000000;
835
- color: #ffffff;
836
- }
837
-
838
- input, button, select {
839
- font-family: inherit;
840
- font-feature-settings: normal;
841
- }
842
-
843
- /* ===================== Main Page Header ===================== */
844
- .header {
845
- display: flex;
846
- justify-content: space-between;
847
- align-items: center;
848
- padding: 16px 24px;
849
- background: rgba(255, 255, 255, 0.05);
850
- border-radius: 12px;
851
- margin-bottom: 24px;
852
- backdrop-filter: blur(10px);
853
- }
854
-
855
- .wallet-info {
856
- display: flex;
857
- flex-direction: column;
858
- gap: 4px;
859
- }
860
-
861
- .wallet-name {
862
- font-size: 1.1em;
863
- font-weight: 500;
864
- color: #FF9900;
865
- }
866
-
867
- .wallet-address-container {
868
- position: relative;
869
- cursor: pointer;
870
- display: inline-flex;
871
- align-items: center;
872
- gap: 8px;
873
- }
874
-
875
- .wallet-address {
876
- font-size: 0.9em;
877
- color: rgba(255, 255, 255, 0.5);
878
- font-family: monospace;
879
- transition: color 0.2s ease;
880
- }
881
-
882
- .wallet-address-container:hover .wallet-address {
883
- color: rgba(255, 255, 255, 0.8);
884
- }
885
-
886
- .copy-indicator {
887
- background: rgba(255, 153, 0, 0.1);
888
- color: rgba(255, 153, 0, 0.8);
889
- padding: 2px 6px;
890
- border-radius: 4px;
891
- font-size: 0.7em;
892
- transition: all 0.2s ease;
893
- opacity: 0;
894
- }
895
-
896
- .wallet-address-container:hover .copy-indicator {
897
- opacity: 1;
898
- background: rgba(255, 153, 0, 0.2);
899
- }
900
-
901
- .wallet-address-container.copied .copy-indicator {
902
- opacity: 1;
903
- background: rgba(255, 153, 0, 0.3);
904
- color: #FF9900;
905
- }
906
-
907
- .stake-metrics {
908
- display: flex;
909
- gap: 24px;
910
- align-items: center;
911
- }
912
-
913
- .stake-metric {
914
- display: flex;
915
- flex-direction: column;
916
- align-items: flex-end;
917
- gap: 2px;
918
- position: relative;
919
- padding: 8px 16px;
920
- border-radius: 8px;
921
- transition: all 0.2s ease;
922
- }
923
-
924
- .stake-metric:hover {
925
- background: rgba(255, 153, 0, 0.05);
926
- }
927
-
928
- .stake-metric .metric-label {
929
- font-size: 0.8em;
930
- color: rgba(255, 255, 255, 0.6);
931
- text-transform: uppercase;
932
- letter-spacing: 0.5px;
933
- }
934
-
935
- .stake-metric .metric-value {
936
- font-size: 1.1em;
937
- font-weight: 500;
938
- color: #FF9900;
939
- font-feature-settings: "tnum";
940
- font-variant-numeric: tabular-nums;
941
- }
942
-
943
- .slippage-value {
944
- display: flex;
945
- align-items: center;
946
- gap: 6px;
947
- }
948
-
949
- .slippage-detail {
950
- font-size: 0.8em;
951
- color: rgba(255, 255, 255, 0.5);
952
- }
953
-
954
- /* ===================== Main Page Filters ===================== */
955
- .filters-section {
956
- display: flex;
957
- justify-content: space-between;
958
- align-items: center;
959
- margin: 24px 0;
960
- gap: 16px;
961
- }
962
-
963
- .search-box input {
964
- padding: 10px 16px;
965
- border-radius: 8px;
966
- border: 1px solid rgba(255, 255, 255, 0.1);
967
- background: rgba(255, 255, 255, 0.03);
968
- color: rgba(255, 255, 255, 0.7);
969
- width: 240px;
970
- font-size: 0.9em;
971
- transition: all 0.2s ease;
972
- }
973
- .search-box input::placeholder {
974
- color: rgba(255, 255, 255, 0.4);
975
- }
976
-
977
- .search-box input:focus {
978
- outline: none;
979
- border-color: rgba(255, 153, 0, 0.5);
980
- background: rgba(255, 255, 255, 0.06);
981
- color: rgba(255, 255, 255, 0.9);
982
- }
983
-
984
- .filter-toggles {
985
- display: flex;
986
- gap: 16px;
987
- }
988
-
989
- .filter-toggles label {
990
- display: flex;
991
- align-items: center;
992
- gap: 8px;
993
- color: rgba(255, 255, 255, 0.7);
994
- font-size: 0.9em;
995
- cursor: pointer;
996
- user-select: none;
997
- }
998
-
999
- /* Checkbox styling for both main page and subnet page */
1000
- .filter-toggles input[type="checkbox"],
1001
- .toggle-label input[type="checkbox"] {
1002
- -webkit-appearance: none;
1003
- -moz-appearance: none;
1004
- appearance: none;
1005
- width: 18px;
1006
- height: 18px;
1007
- border: 2px solid rgba(255, 153, 0, 0.3);
1008
- border-radius: 4px;
1009
- background: rgba(0, 0, 0, 0.2);
1010
- cursor: pointer;
1011
- position: relative;
1012
- transition: all 0.2s ease;
1013
- }
1014
-
1015
- .filter-toggles input[type="checkbox"]:hover,
1016
- .toggle-label input[type="checkbox"]:hover {
1017
- border-color: #FF9900;
1018
- }
1019
-
1020
- .filter-toggles input[type="checkbox"]:checked,
1021
- .toggle-label input[type="checkbox"]:checked {
1022
- background: #FF9900;
1023
- border-color: #FF9900;
1024
- }
1025
-
1026
- .filter-toggles input[type="checkbox"]:checked::after,
1027
- .toggle-label input[type="checkbox"]:checked::after {
1028
- content: '';
1029
- position: absolute;
1030
- left: 5px;
1031
- top: 2px;
1032
- width: 4px;
1033
- height: 8px;
1034
- border: solid #000;
1035
- border-width: 0 2px 2px 0;
1036
- transform: rotate(45deg);
1037
- }
1038
-
1039
- .filter-toggles label:hover,
1040
- .toggle-label:hover {
1041
- color: rgba(255, 255, 255, 0.9);
1042
- }
1043
- .disabled-label {
1044
- opacity: 0.5;
1045
- cursor: not-allowed;
1046
- }
1047
- .add-stake-button {
1048
- padding: 10px 20px;
1049
- font-size: 0.8rem;
1050
- }
1051
- .export-csv-button {
1052
- padding: 10px 20px;
1053
- font-size: 0.8rem;
1054
- }
1055
- .button-group {
1056
- display: flex;
1057
- gap: 8px;
1058
- }
1059
-
1060
- /* ===================== Main Page Subnet Table ===================== */
1061
- .subnets-table-container {
1062
- background: rgba(255, 255, 255, 0.02);
1063
- border-radius: 12px;
1064
- overflow: hidden;
1065
- }
1066
-
1067
- .subnets-table {
1068
- width: 100%;
1069
- border-collapse: collapse;
1070
- font-size: 0.95em;
1071
- }
1072
-
1073
- .subnets-table th {
1074
- background: rgba(255, 255, 255, 0.05);
1075
- font-weight: 500;
1076
- text-align: left;
1077
- padding: 16px;
1078
- color: rgba(255, 255, 255, 0.7);
1079
- }
1080
-
1081
- .subnets-table td {
1082
- padding: 14px 16px;
1083
- border-top: 1px solid rgba(255, 255, 255, 0.05);
1084
- }
1085
-
1086
- .subnet-row {
1087
- cursor: pointer;
1088
- transition: background-color 0.2s ease;
1089
- }
1090
-
1091
- .subnet-row:hover {
1092
- background: rgba(255, 255, 255, 0.05);
1093
- }
1094
-
1095
- .subnet-name {
1096
- color: #ffffff;
1097
- font-weight: 500;
1098
- font-size: 0.95em;
1099
- }
1100
-
1101
- .price, .market-cap, .your-stake, .emission {
1102
- font-family: 'Inter', monospace;
1103
- font-size: 1.0em;
1104
- font-feature-settings: "tnum";
1105
- font-variant-numeric: tabular-nums;
1106
- letter-spacing: 0.01em;
1107
- white-space: nowrap;
1108
- }
1109
-
1110
- .stake-status {
1111
- font-size: 0.85em;
1112
- padding: 4px 8px;
1113
- border-radius: 4px;
1114
- background: rgba(255, 255, 255, 0.05);
1115
- }
1116
-
1117
- .stake-status.staked {
1118
- background: rgba(255, 153, 0, 0.1);
1119
- color: #FF9900;
1120
- }
1121
-
1122
- .subnets-table th.sortable {
1123
- cursor: pointer;
1124
- position: relative;
1125
- padding-right: 20px;
1126
- }
1127
-
1128
- .subnets-table th.sortable:hover {
1129
- color: #FF9900;
1130
- }
1131
-
1132
- .subnets-table th[data-sort] {
1133
- color: #FF9900;
1134
- }
1135
-
1136
- /* ===================== Subnet Tiles View ===================== */
1137
- .subnet-tiles-container {
1138
- display: flex;
1139
- flex-wrap: wrap;
1140
- justify-content: center;
1141
- gap: 1rem;
1142
- padding: 1rem;
1143
- }
1144
-
1145
- .subnet-tile {
1146
- width: clamp(75px, 6vw, 600px);
1147
- height: clamp(75px, 6vw, 600px);
1148
- display: flex;
1149
- flex-direction: column;
1150
- align-items: center;
1151
- justify-content: center;
1152
- background: rgba(255, 255, 255, 0.05);
1153
- border-radius: 8px;
1154
- position: relative;
1155
- cursor: pointer;
1156
- transition: all 0.2s ease;
1157
- overflow: hidden;
1158
- font-size: clamp(0.6rem, 1vw, 1.4rem);
1159
- }
1160
-
1161
- .tile-netuid {
1162
- position: absolute;
1163
- top: 0.4em;
1164
- left: 0.4em;
1165
- font-size: 0.7em;
1166
- color: rgba(255, 255, 255, 0.6);
1167
- }
1168
-
1169
- .tile-symbol {
1170
- font-size: 1.6em;
1171
- margin-bottom: 0.4em;
1172
- color: #FF9900;
1173
- }
1174
-
1175
- .tile-name {
1176
- display: block;
1177
- width: 100%;
1178
- white-space: nowrap;
1179
- overflow: hidden;
1180
- text-overflow: ellipsis;
1181
- font-size: 1em;
1182
- text-align: center;
1183
- color: rgba(255, 255, 255, 0.9);
1184
- margin: 0 0.4em;
1185
- }
1186
-
1187
- .tile-market-cap {
1188
- font-size: 0.9em;
1189
- color: rgba(255, 255, 255, 0.5);
1190
- margin-top: 2px;
1191
- }
1192
-
1193
- .subnet-tile:hover {
1194
- transform: translateY(-2px);
1195
- box-shadow:
1196
- 0 0 12px rgba(255, 153, 0, 0.6),
1197
- 0 0 24px rgba(255, 153, 0, 0.3);
1198
- background: rgba(255, 255, 255, 0.08);
1199
- }
1200
-
1201
- .subnet-tile.staked {
1202
- border: 1px solid rgba(255, 153, 0, 0.3);
1203
- }
1204
-
1205
- .subnet-tile.staked::before {
1206
- content: '';
1207
- position: absolute;
1208
- top: 0.4em;
1209
- right: 0.4em;
1210
- width: 0.5em;
1211
- height: 0.5em;
1212
- border-radius: 50%;
1213
- background: #FF9900;
1214
- }
1215
-
1216
- /* ===================== Subnet Detail Page Header ===================== */
1217
- .subnet-header {
1218
- padding: 16px;
1219
- border-radius: 12px;
1220
- margin-bottom: 0px;
1221
- }
1222
-
1223
- .subnet-header h2 {
1224
- margin: 0;
1225
- font-size: 1.3em;
1226
- }
1227
-
1228
- .subnet-price {
1229
- font-size: 1.3em;
1230
- color: #FF9900;
1231
- }
1232
-
1233
- .subnet-title-row {
1234
- display: grid;
1235
- grid-template-columns: 300px 1fr 300px;
1236
- align-items: start;
1237
- margin: 0;
1238
- position: relative;
1239
- min-height: 60px;
1240
- }
1241
-
1242
- .title-price {
1243
- grid-column: 1;
1244
- padding-top: 0;
1245
- margin-top: -10px;
1246
- }
1247
-
1248
- .header-row {
1249
- display: flex;
1250
- justify-content: space-between;
1251
- align-items: center;
1252
- width: 100%;
1253
- margin-bottom: 16px;
1254
- }
1255
-
1256
- .toggle-group {
1257
- display: flex;
1258
- flex-direction: column;
1259
- gap: 8px;
1260
- align-items: flex-end;
1261
- }
1262
-
1263
- .toggle-label {
1264
- display: flex;
1265
- align-items: center;
1266
- gap: 8px;
1267
- color: rgba(255, 255, 255, 0.7);
1268
- font-size: 0.9em;
1269
- cursor: pointer;
1270
- user-select: none;
1271
- }
1272
-
1273
- .back-button {
1274
- background: rgba(255, 255, 255, 0.05);
1275
- border: 1px solid rgba(255, 255, 255, 0.1);
1276
- color: rgba(255, 255, 255, 0.8);
1277
- padding: 8px 16px;
1278
- border-radius: 8px;
1279
- cursor: pointer;
1280
- font-size: 0.9em;
1281
- transition: all 0.2s ease;
1282
- margin-bottom: 16px;
1283
- }
1284
-
1285
- .back-button:hover {
1286
- background: rgba(255, 255, 255, 0.1);
1287
- border-color: rgba(255, 255, 255, 0.2);
1288
- }
1289
-
1290
- /* ===================== Network Visualization ===================== */
1291
- .network-visualization-container {
1292
- position: absolute;
1293
- left: 50%;
1294
- transform: translateX(-50%);
1295
- top: -50px;
1296
- width: 700px;
1297
- height: 80px;
1298
- z-index: 1;
1299
- }
1300
-
1301
- .network-visualization {
1302
- width: 700px;
1303
- height: 80px;
1304
- position: relative;
1305
- }
1306
-
1307
- #network-canvas {
1308
- background: transparent;
1309
- position: relative;
1310
- z-index: 1;
1311
- }
1312
-
1313
- /* Gradient behind visualization */
1314
- .network-visualization::after {
1315
- content: '';
1316
- position: absolute;
1317
- top: 0;
1318
- left: 0;
1319
- right: 0;
1320
- height: 100%;
1321
- background: linear-gradient(to bottom, rgba(0, 0, 0, 0.95) 0%, rgba(0, 0, 0, 0.8) 100%);
1322
- z-index: 0;
1323
- pointer-events: none;
1324
- }
1325
-
1326
- /* ===================== Subnet Detail Metrics ===================== */
1327
- .network-metrics {
1328
- display: grid;
1329
- grid-template-columns: repeat(5, 1fr);
1330
- gap: 12px;
1331
- margin: 0;
1332
- margin-top: 16px;
1333
- }
1334
-
1335
- /* Base card styles - applied to both network and metric cards */
1336
- .network-card, .metric-card {
1337
- background: rgba(255, 255, 255, 0.05);
1338
- border-radius: 8px;
1339
- padding: 12px 16px;
1340
- min-height: 50px;
1341
- display: flex;
1342
- flex-direction: column;
1343
- justify-content: center;
1344
- gap: 4px;
1345
- }
1346
-
1347
- /* Separate styling for moving price value */
1348
- #network-moving-price {
1349
- color: #FF9900;
1350
- }
1351
-
1352
- .metrics-section {
1353
- margin-top: 0px;
1354
- margin-bottom: 16px;
1355
- }
1356
-
1357
- .metrics-group {
1358
- display: grid;
1359
- grid-template-columns: repeat(5, 1fr);
1360
- gap: 12px;
1361
- margin: 0;
1362
- margin-top: 2px;
1363
- }
1364
-
1365
- .market-metrics .metric-card {
1366
- background: rgba(255, 255, 255, 0.05);
1367
- min-height: 70px;
1368
- }
1369
-
1370
- .metric-label {
1371
- font-size: 0.85em;
1372
- color: rgba(255, 255, 255, 0.7);
1373
- margin: 0;
1374
- }
1375
-
1376
- .metric-value {
1377
- font-size: 1.2em;
1378
- line-height: 1.3;
1379
- margin: 0;
1380
- }
1381
-
1382
- /* Add status colors */
1383
- .registration-status {
1384
- color: #2ECC71;
1385
- }
1386
-
1387
- .registration-status.closed {
1388
- color: #ff4444; /* Red color for closed status */
1389
- }
1390
-
1391
- .cr-status {
1392
- color: #2ECC71;
1393
- }
1394
-
1395
- .cr-status.disabled {
1396
- color: #ff4444; /* Red color for disabled status */
1397
- }
1398
-
1399
- /* ===================== Stakes Table ===================== */
1400
- .stakes-container {
1401
- margin-top: 24px;
1402
- padding: 0 24px;
1403
- }
1404
-
1405
- .stakes-header {
1406
- display: flex;
1407
- justify-content: space-between;
1408
- align-items: center;
1409
- margin-bottom: 16px;
1410
- }
1411
-
1412
- .stakes-header h3 {
1413
- font-size: 1.2em;
1414
- color: #ffffff;
1415
- margin: 0;
1416
- }
1417
-
1418
- .stakes-table-container {
1419
- background: rgba(255, 255, 255, 0.02);
1420
- border-radius: 12px;
1421
- overflow: hidden;
1422
- margin-bottom: 24px;
1423
- width: 100%;
1424
- }
1425
-
1426
- .stakes-table {
1427
- width: 100%;
1428
- border-collapse: collapse;
1429
- }
1430
-
1431
- .stakes-table th {
1432
- background: rgba(255, 255, 255, 0.05);
1433
- padding: 16px;
1434
- text-align: left;
1435
- font-weight: 500;
1436
- color: rgba(255, 255, 255, 0.7);
1437
- }
1438
-
1439
- .stakes-table td {
1440
- padding: 16px;
1441
- border-top: 1px solid rgba(255, 255, 255, 0.05);
1442
- }
1443
-
1444
- .stakes-table tr {
1445
- transition: background-color 0.2s ease;
1446
- }
1447
-
1448
- .stakes-table tr:nth-child(even) {
1449
- background: rgba(255, 255, 255, 0.02);
1450
- }
1451
-
1452
- .stakes-table tr:hover {
1453
- background: transparent;
1454
- }
1455
-
1456
- .no-stakes-row td {
1457
- text-align: center;
1458
- padding: 32px;
1459
- color: rgba(255, 255, 255, 0.5);
1460
- }
1461
-
1462
- /* Table styles consistency */
1463
- .stakes-table th, .network-table th {
1464
- background: rgba(255, 255, 255, 0.05);
1465
- padding: 16px;
1466
- text-align: left;
1467
- font-weight: 500;
1468
- color: rgba(255, 255, 255, 0.7);
1469
- transition: color 0.2s ease;
1470
- }
1471
-
1472
- /* Sortable columns */
1473
- .stakes-table th.sortable, .network-table th.sortable {
1474
- cursor: pointer;
1475
- }
1476
-
1477
- /* Active sort column - only change color */
1478
- .stakes-table th.sortable[data-sort], .network-table th.sortable[data-sort] {
1479
- color: #FF9900;
1480
- }
1481
-
1482
- /* Hover effects - only change color */
1483
- .stakes-table th.sortable:hover, .network-table th.sortable:hover {
1484
- color: #FF9900;
1485
- }
1486
-
1487
- /* Remove hover background from table rows */
1488
- .stakes-table tr:hover {
1489
- background: transparent;
1490
- }
1491
-
1492
- /* ===================== Network Table ===================== */
1493
- .network-table-container {
1494
- margin-top: 60px;
1495
- position: relative;
1496
- z-index: 2;
1497
- background: rgba(0, 0, 0, 0.8);
1498
- }
1499
-
1500
- .network-table {
1501
- width: 100%;
1502
- border-collapse: collapse;
1503
- table-layout: fixed;
1504
- }
1505
-
1506
- .network-table th {
1507
- background: rgba(255, 255, 255, 0.05);
1508
- padding: 16px;
1509
- text-align: left;
1510
- font-weight: 500;
1511
- color: rgba(255, 255, 255, 0.7);
1512
- }
1513
-
1514
- .network-table td {
1515
- padding: 16px;
1516
- border-top: 1px solid rgba(255, 255, 255, 0.05);
1517
- }
1518
-
1519
- .network-table tr {
1520
- cursor: pointer;
1521
- transition: background-color 0.2s ease;
1522
- }
1523
-
1524
- .network-table tr:hover {
1525
- background-color: rgba(255, 255, 255, 0.05);
1526
- }
1527
-
1528
- .network-table tr:nth-child(even) {
1529
- background-color: rgba(255, 255, 255, 0.02);
1530
- }
1531
-
1532
- .network-table tr:nth-child(even):hover {
1533
- background-color: rgba(255, 255, 255, 0.05);
1534
- }
1535
-
1536
- .network-search-container {
1537
- display: flex;
1538
- align-items: center;
1539
- margin-bottom: 16px;
1540
- padding: 0 16px;
1541
- }
1542
-
1543
- .network-search {
1544
- width: 100%;
1545
- padding: 12px 16px;
1546
- border: 1px solid rgba(255, 153, 0, 0.2);
1547
- border-radius: 8px;
1548
- background: rgba(0, 0, 0, 0.2);
1549
- color: #ffffff;
1550
- font-size: 0.95em;
1551
- transition: all 0.2s ease;
1552
- }
1553
-
1554
- .network-search:focus {
1555
- outline: none;
1556
- border-color: rgba(255, 153, 0, 0.5);
1557
- background: rgba(0, 0, 0, 0.3);
1558
- caret-color: #FF9900;
1559
- }
1560
-
1561
- .network-search::placeholder {
1562
- color: rgba(255, 255, 255, 0.3);
1563
- }
1564
-
1565
- /* ===================== Cell Styles & Formatting ===================== */
1566
- .hotkey-cell {
1567
- max-width: 200px;
1568
- position: relative;
1569
- }
1570
-
1571
- .hotkey-container {
1572
- position: relative;
1573
- display: inline-block;
1574
- max-width: 100%;
1575
- }
1576
-
1577
- .hotkey-identity, .truncated-address {
1578
- color: rgba(255, 255, 255, 0.8);
1579
- display: inline-block;
1580
- max-width: 100%;
1581
- overflow: hidden;
1582
- text-overflow: ellipsis;
1583
- }
1584
-
1585
- .copy-button {
1586
- position: absolute;
1587
- top: -20px; /* Position above the text */
1588
- right: 0;
1589
- background: rgba(255, 153, 0, 0.1);
1590
- color: rgba(255, 255, 255, 0.6);
1591
- padding: 2px 6px;
1592
- border-radius: 4px;
1593
- font-size: 0.7em;
1594
- cursor: pointer;
1595
- opacity: 0;
1596
- transition: all 0.2s ease;
1597
- transform: translateY(5px);
1598
- }
1599
-
1600
- .hotkey-container:hover .copy-button {
1601
- opacity: 1;
1602
- transform: translateY(0);
1603
- }
1604
-
1605
- .copy-button:hover {
1606
- background: rgba(255, 153, 0, 0.2);
1607
- color: #FF9900;
1608
- }
1609
-
1610
- .address-cell {
1611
- max-width: 150px;
1612
- position: relative;
1613
- white-space: nowrap;
1614
- overflow: hidden;
1615
- text-overflow: ellipsis;
1616
- }
1617
-
1618
- .address-container {
1619
- display: flex;
1620
- align-items: center;
1621
- cursor: pointer;
1622
- position: relative;
1623
- }
1624
-
1625
- .address-container:hover::after {
1626
- content: 'Click to copy';
1627
- position: absolute;
1628
- right: 0;
1629
- top: 50%;
1630
- transform: translateY(-50%);
1631
- background: rgba(255, 153, 0, 0.1);
1632
- color: #FF9900;
1633
- padding: 2px 6px;
1634
- border-radius: 4px;
1635
- font-size: 0.8em;
1636
- opacity: 0.8;
1637
- }
1638
-
1639
- .truncated-address {
1640
- font-family: monospace;
1641
- color: rgba(255, 255, 255, 0.8);
1642
- overflow: hidden;
1643
- text-overflow: ellipsis;
1644
- }
1645
-
1646
- .truncated-address:hover {
1647
- color: #FF9900;
1648
- }
1649
-
1650
- .registered-yes {
1651
- color: #FF9900;
1652
- font-weight: 500;
1653
- display: flex;
1654
- align-items: center;
1655
- gap: 4px;
1656
- }
1657
-
1658
- .registered-no {
1659
- color: #ff4444;
1660
- font-weight: 500;
1661
- display: flex;
1662
- align-items: center;
1663
- gap: 4px;
1664
- }
1665
-
1666
- .manage-button {
1667
- background: rgba(255, 153, 0, 0.1);
1668
- border: 1px solid rgba(255, 153, 0, 0.2);
1669
- color: #FF9900;
1670
- padding: 6px 12px;
1671
- border-radius: 6px;
1672
- cursor: pointer;
1673
- transition: all 0.2s ease;
1674
- }
1675
-
1676
- .manage-button:hover {
1677
- background: rgba(255, 153, 0, 0.2);
1678
- transform: translateY(-1px);
1679
- }
1680
-
1681
- .hotkey-identity {
1682
- display: inline-block;
1683
- max-width: 100%;
1684
- overflow: hidden;
1685
- text-overflow: ellipsis;
1686
- color: #FF9900;
1687
- }
1688
-
1689
- .identity-cell {
1690
- max-width: 700px;
1691
- font-size: 0.90em;
1692
- letter-spacing: -0.2px;
1693
- color: #FF9900;
1694
- }
1695
-
1696
- .per-day {
1697
- font-size: 0.75em;
1698
- opacity: 0.7;
1699
- margin-left: 4px;
1700
- }
1701
-
1702
- /* ===================== Neuron Detail Panel ===================== */
1703
- #neuron-detail-container {
1704
- background: rgba(255, 255, 255, 0.02);
1705
- border-radius: 12px;
1706
- padding: 16px;
1707
- margin-top: 16px;
1708
- }
1709
-
1710
- .neuron-detail-header {
1711
- display: flex;
1712
- align-items: center;
1713
- gap: 16px;
1714
- margin-bottom: 16px;
1715
- }
1716
-
1717
- .neuron-detail-content {
1718
- display: flex;
1719
- flex-direction: column;
1720
- gap: 16px;
1721
- }
1722
-
1723
- .neuron-info-top {
1724
- display: flex;
1725
- flex-direction: column;
1726
- gap: 8px;
1727
- }
1728
-
1729
- .neuron-keys {
1730
- display: flex;
1731
- flex-direction: column;
1732
- gap: 4px;
1733
- font-size: 0.9em;
1734
- color: rgba(255, 255, 255, 0.6);
1735
- font-size: 1em;
1736
- color: rgba(255, 255, 255, 0.7);
1737
- }
1738
-
1739
- .neuron-cards-container {
1740
- display: flex;
1741
- flex-direction: column;
1742
- gap: 12px;
1743
- }
1744
-
1745
- .neuron-metrics-row {
1746
- display: grid;
1747
- grid-template-columns: repeat(6, 1fr);
1748
- gap: 12px;
1749
- margin: 0;
1750
- }
1751
-
1752
- .neuron-metrics-row.last-row {
1753
- grid-template-columns: repeat(3, 1fr);
1754
- }
1755
-
1756
- /* IP Info styling */
1757
- #neuron-ipinfo {
1758
- font-size: 0.85em;
1759
- line-height: 1.4;
1760
- white-space: nowrap;
1761
- }
1762
-
1763
- #neuron-ipinfo .no-connection {
1764
- color: #ff4444;
1765
- font-weight: 500;
1766
- }
1767
-
1768
- /* Adjust metric card for IP info to accommodate multiple lines */
1769
- .neuron-cards-container .metric-card:has(#neuron-ipinfo) {
1770
- min-height: 85px;
1771
- }
1772
-
1773
- /* ===================== Subnet Page Color Overrides ===================== */
1774
- /* Subnet page specific style */
1775
- .subnet-page .metric-card-title,
1776
- .subnet-page .network-card-title {
1777
- color: rgba(255, 255, 255, 0.7);
1778
- }
1779
-
1780
- .subnet-page .metric-card .metric-value,
1781
- .subnet-page .metric-value {
1782
- color: white;
1783
- }
1784
-
1785
- /* Green values */
1786
- .subnet-page .validator-true,
1787
- .subnet-page .active-yes,
1788
- .subnet-page .registration-open,
1789
- .subnet-page .cr-enabled,
1790
- .subnet-page .ip-info {
1791
- color: #FF9900;
1792
- }
1793
-
1794
- /* Red values */
1795
- .subnet-page .validator-false,
1796
- .subnet-page .active-no,
1797
- .subnet-page .registration-closed,
1798
- .subnet-page .cr-disabled,
1799
- .subnet-page .ip-na {
1800
- color: #ff4444;
1801
- }
1802
-
1803
- /* Keep symbols green in subnet page */
1804
- .subnet-page .symbol {
1805
- color: #FF9900;
1806
- }
1807
-
1808
- /* ===================== Responsive Styles ===================== */
1809
- @media (max-width: 1200px) {
1810
- .stakes-table {
1811
- display: block;
1812
- overflow-x: auto;
1813
- }
1814
-
1815
- .network-metrics {
1816
- grid-template-columns: repeat(3, 1fr);
1817
- }
1818
- }
1819
-
1820
- @media (min-width: 1201px) {
1821
- .network-metrics {
1822
- grid-template-columns: repeat(5, 1fr);
1823
- }
1824
- }
1825
- /* ===== Splash Screen ===== */
1826
- #splash-screen {
1827
- position: fixed;
1828
- top: 0;
1829
- left: 0;
1830
- width: 100vw;
1831
- height: 100vh;
1832
- background: #000000;
1833
- display: flex;
1834
- align-items: center;
1835
- justify-content: center;
1836
- z-index: 999999;
1837
- opacity: 1;
1838
- transition: opacity 1s ease;
1839
- }
1840
-
1841
- #splash-screen.fade-out {
1842
- opacity: 0;
1843
- }
1844
-
1845
- .splash-content {
1846
- text-align: center;
1847
- color: #FF9900;
1848
- opacity: 0;
1849
- animation: fadeIn 1.2s ease forwards;
1850
- }
1851
- @keyframes fadeIn {
1852
- 0% {
1853
- opacity: 0;
1854
- transform: scale(0.97);
1855
- }
1856
- 100% {
1857
- opacity: 1;
1858
- transform: scale(1);
1859
- }
1860
- }
1861
-
1862
- /* Title & text styling */
1863
- .title-row {
1864
- display: flex;
1865
- align-items: baseline;
1866
- gap: 1rem;
1867
- }
1868
-
1869
- .splash-title {
1870
- font-size: 2.4rem;
1871
- margin: 0;
1872
- padding: 0;
1873
- font-weight: 600;
1874
- color: #FF9900;
1875
- }
1876
-
1877
- .beta-text {
1878
- font-size: 0.9rem;
1879
- color: #FF9900;
1880
- background: rgba(255, 153, 0, 0.1);
1881
- padding: 2px 6px;
1882
- border-radius: 4px;
1883
- font-weight: 500;
1884
- }
1885
-
1886
-
1887
- """
1888
-
1889
-
1890
- def get_javascript() -> str:
1891
- return """
1892
- /* ===================== Global Variables ===================== */
1893
- const root_symbol_html = '&#x03C4;';
1894
- let verboseNumbers = false;
1895
-
1896
- /* ===================== Clipboard Functions ===================== */
1897
- /**
1898
- * Copies text to clipboard and shows visual feedback
1899
- * @param {string} text The text to copy
1900
- * @param {HTMLElement} element Optional element to show feedback on
1901
- */
1902
- function copyToClipboard(text, element) {
1903
- navigator.clipboard.writeText(text)
1904
- .then(() => {
1905
- const targetElement = element || (event && event.target);
1906
-
1907
- if (targetElement) {
1908
- const copyIndicator = targetElement.querySelector('.copy-indicator');
1909
-
1910
- if (copyIndicator) {
1911
- const originalText = copyIndicator.textContent;
1912
- copyIndicator.textContent = 'Copied!';
1913
- copyIndicator.style.color = '#FF9900';
1914
-
1915
- setTimeout(() => {
1916
- copyIndicator.textContent = originalText;
1917
- copyIndicator.style.color = '';
1918
- }, 1000);
1919
- } else {
1920
- const originalText = targetElement.textContent;
1921
- targetElement.textContent = 'Copied!';
1922
- targetElement.style.color = '#FF9900';
1923
-
1924
- setTimeout(() => {
1925
- targetElement.textContent = originalText;
1926
- targetElement.style.color = '';
1927
- }, 1000);
1928
- }
1929
- }
1930
- })
1931
- .catch(err => {
1932
- console.error('Failed to copy:', err);
1933
- });
1934
- }
1935
-
1936
-
1937
- /* ===================== Initialization and DOMContentLoaded Handler ===================== */
1938
- document.addEventListener('DOMContentLoaded', function() {
1939
- try {
1940
- const initialDataElement = document.getElementById('initial-data');
1941
- if (!initialDataElement) {
1942
- throw new Error('Initial data element (#initial-data) not found.');
1943
- }
1944
- window.initialData = {
1945
- wallet_info: JSON.parse(initialDataElement.getAttribute('data-wallet-info')),
1946
- subnets: JSON.parse(initialDataElement.getAttribute('data-subnets'))
1947
- };
1948
- } catch (error) {
1949
- console.error('Error loading initial data:', error);
1950
- }
1951
-
1952
- // Return to the main list of subnets.
1953
- const backButton = document.querySelector('.back-button');
1954
- if (backButton) {
1955
- backButton.addEventListener('click', function() {
1956
- // First check if neuron details are visible and close them if needed
1957
- const neuronDetails = document.getElementById('neuron-detail-container');
1958
- if (neuronDetails && neuronDetails.style.display !== 'none') {
1959
- closeNeuronDetails();
1960
- return; // Stop here, don't go back to main page yet
1961
- }
1962
-
1963
- // Otherwise go back to main subnet list
1964
- document.getElementById('main-content').style.display = 'block';
1965
- document.getElementById('subnet-page').style.display = 'none';
1966
- });
1967
- }
1968
-
1969
-
1970
- // Splash screen logic
1971
- const splash = document.getElementById('splash-screen');
1972
- const mainContent = document.getElementById('main-content');
1973
- mainContent.style.display = 'none';
1974
-
1975
- setTimeout(() => {
1976
- splash.classList.add('fade-out');
1977
- splash.addEventListener('transitionend', () => {
1978
- splash.style.display = 'none';
1979
- mainContent.style.display = 'block';
1980
- }, { once: true });
1981
- }, 2000);
1982
-
1983
- initializeFormattedNumbers();
1984
-
1985
- // Keep main page's "verbose" checkbox and the Subnet page's "verbose" checkbox in sync
1986
- const mainVerboseCheckbox = document.getElementById('show-verbose');
1987
- const subnetVerboseCheckbox = document.getElementById('verbose-toggle');
1988
- if (mainVerboseCheckbox && subnetVerboseCheckbox) {
1989
- mainVerboseCheckbox.addEventListener('change', function() {
1990
- subnetVerboseCheckbox.checked = this.checked;
1991
- toggleVerboseNumbers();
1992
- });
1993
- subnetVerboseCheckbox.addEventListener('change', function() {
1994
- mainVerboseCheckbox.checked = this.checked;
1995
- toggleVerboseNumbers();
1996
- });
1997
- }
1998
-
1999
- // Initialize tile view as default
2000
- const tilesContainer = document.getElementById('subnet-tiles-container');
2001
- const tableContainer = document.querySelector('.subnets-table-container');
2002
-
2003
- // Generate and show tiles
2004
- generateSubnetTiles();
2005
- tilesContainer.style.display = 'flex';
2006
- tableContainer.style.display = 'none';
2007
- });
2008
-
2009
- /* ===================== Main Page Functions ===================== */
2010
- /**
2011
- * Sort the main Subnets table by the specified column index.
2012
- * Toggles ascending/descending on each click.
2013
- * @param {number} columnIndex Index of the column to sort.
2014
- */
2015
- function sortMainTable(columnIndex) {
2016
- const table = document.querySelector('.subnets-table');
2017
- const headers = table.querySelectorAll('th');
2018
- const header = headers[columnIndex];
2019
-
2020
- // Determine new sort direction
2021
- let isDescending = header.getAttribute('data-sort') !== 'desc';
2022
-
2023
- // Clear sort markers on all columns, then set the new one
2024
- headers.forEach(th => { th.removeAttribute('data-sort'); });
2025
- header.setAttribute('data-sort', isDescending ? 'desc' : 'asc');
2026
-
2027
- // Sort rows based on numeric value (or netuid in col 0)
2028
- const tbody = table.querySelector('tbody');
2029
- const rows = Array.from(tbody.querySelectorAll('tr'));
2030
- rows.sort((rowA, rowB) => {
2031
- const cellA = rowA.cells[columnIndex];
2032
- const cellB = rowB.cells[columnIndex];
2033
-
2034
- // Special handling for the first column with netuid in data-value
2035
- if (columnIndex === 0) {
2036
- const netuidA = parseInt(cellA.getAttribute('data-value'), 10);
2037
- const netuidB = parseInt(cellB.getAttribute('data-value'), 10);
2038
- return isDescending ? (netuidB - netuidA) : (netuidA - netuidB);
2039
- }
2040
-
2041
- // Otherwise parse float from data-value
2042
- const valueA = parseFloat(cellA.getAttribute('data-value')) || 0;
2043
- const valueB = parseFloat(cellB.getAttribute('data-value')) || 0;
2044
- return isDescending ? (valueB - valueA) : (valueA - valueB);
2045
- });
2046
-
2047
- // Re-inject rows in sorted order
2048
- tbody.innerHTML = '';
2049
- rows.forEach(row => tbody.appendChild(row));
2050
- }
2051
-
2052
- /**
2053
- * Filters the main Subnets table rows based on user search and "Show Only Staked" checkbox.
2054
- */
2055
- function filterSubnets() {
2056
- const searchText = document.getElementById('subnet-search').value.toLowerCase();
2057
- const showStaked = document.getElementById('show-staked').checked;
2058
- const showTiles = document.getElementById('show-tiles').checked;
2059
-
2060
- // Filter table rows
2061
- const rows = document.querySelectorAll('.subnet-row');
2062
- rows.forEach(row => {
2063
- const name = row.querySelector('.subnet-name').textContent.toLowerCase();
2064
- const stakeStatus = row.querySelector('.stake-status').textContent; // "Staked" or "Not Staked"
2065
-
2066
- let isVisible = name.includes(searchText);
2067
- if (showStaked) {
2068
- // If "Show only Staked" is checked, the row must have "Staked" to be visible
2069
- isVisible = isVisible && (stakeStatus === 'Staked');
2070
- }
2071
- row.style.display = isVisible ? '' : 'none';
2072
- });
2073
-
2074
- // Filter tiles if they're being shown
2075
- if (showTiles) {
2076
- const tiles = document.querySelectorAll('.subnet-tile');
2077
- tiles.forEach(tile => {
2078
- const name = tile.querySelector('.tile-name').textContent.toLowerCase();
2079
- const netuid = tile.querySelector('.tile-netuid').textContent;
2080
- const isStaked = tile.classList.contains('staked');
2081
-
2082
- let isVisible = name.includes(searchText) || netuid.includes(searchText);
2083
- if (showStaked) {
2084
- isVisible = isVisible && isStaked;
2085
- }
2086
- tile.style.display = isVisible ? '' : 'none';
2087
- });
2088
- }
2089
- }
2090
-
2091
-
2092
- /* ===================== Subnet Detail Page Functions ===================== */
2093
- /**
2094
- * Displays the Subnet page (detailed view) for the selected netuid.
2095
- * Hides the main content and populates all the metrics / stakes / network table.
2096
- * @param {number} netuid The netuid of the subnet to show in detail.
2097
- */
2098
- function showSubnetPage(netuid) {
2099
- try {
2100
- window.currentSubnet = netuid;
2101
- window.scrollTo(0, 0);
2102
-
2103
- const subnet = window.initialData.subnets.find(s => s.netuid === parseInt(netuid, 10));
2104
- if (!subnet) {
2105
- throw new Error(`Subnet not found for netuid: ${netuid}`);
2106
- }
2107
- window.currentSubnetSymbol = subnet.symbol;
2108
-
2109
- // Insert the "metagraph" table beneath the "stakes" table in the hidden container
2110
- const networkTableHTML = `
2111
- <div class="network-table-container" style="display: none;">
2112
- <div class="network-search-container">
2113
- <input type="text" class="network-search" placeholder="Search for name, hotkey, or coldkey ss58..."
2114
- oninput="filterNetworkTable(this.value)" id="network-search">
2115
- </div>
2116
- <table class="network-table">
2117
- <thead>
2118
- <tr>
2119
- <th>Name</th>
2120
- <th>Stake Weight</th>
2121
- <th>Stake <span style="color: #FF9900">${subnet.symbol}</span></th>
2122
- <th>Stake <span style="color: #FF9900">${root_symbol_html}</span></th>
2123
- <th>Dividends</th>
2124
- <th>Incentive</th>
2125
- <th>Emissions <span class="per-day">/day</span></th>
2126
- <th>Hotkey</th>
2127
- <th>Coldkey</th>
2128
- </tr>
2129
- </thead>
2130
- <tbody>
2131
- ${generateNetworkTableRows(subnet.metagraph_info)}
2132
- </tbody>
2133
- </table>
2134
- </div>
2135
- `;
2136
-
2137
- // Show/hide main content vs. subnet detail
2138
- document.getElementById('main-content').style.display = 'none';
2139
- document.getElementById('subnet-page').style.display = 'block';
2140
-
2141
- document.querySelector('#subnet-title').textContent = `${subnet.netuid} - ${subnet.name}`;
2142
- document.querySelector('#subnet-price').innerHTML = formatNumber(subnet.price, subnet.symbol);
2143
- document.querySelector('#subnet-market-cap').innerHTML = formatNumber(subnet.market_cap, root_symbol_html);
2144
- document.querySelector('#subnet-total-stake').innerHTML= formatNumber(subnet.total_stake, subnet.symbol);
2145
- document.querySelector('#subnet-emission').innerHTML = formatNumber(subnet.emission, root_symbol_html);
2146
-
2147
-
2148
- const metagraphInfo = subnet.metagraph_info;
2149
- document.querySelector('#network-alpha-in').innerHTML = formatNumber(metagraphInfo.alpha_in, subnet.symbol);
2150
- document.querySelector('#network-tau-in').innerHTML = formatNumber(metagraphInfo.tao_in, root_symbol_html);
2151
- document.querySelector('#network-moving-price').innerHTML = formatNumber(metagraphInfo.moving_price, subnet.symbol);
2152
-
2153
- // Registration status
2154
- const registrationElement = document.querySelector('#network-registration');
2155
- registrationElement.textContent = metagraphInfo.registration_allowed ? 'Open' : 'Closed';
2156
- registrationElement.classList.toggle('closed', !metagraphInfo.registration_allowed);
2157
-
2158
- // Commit-Reveal Weight status
2159
- const crElement = document.querySelector('#network-cr');
2160
- crElement.textContent = metagraphInfo.commit_reveal_weights_enabled ? 'Enabled' : 'Disabled';
2161
- crElement.classList.toggle('disabled', !metagraphInfo.commit_reveal_weights_enabled);
2162
-
2163
- // Blocks since last step, out of tempo
2164
- document.querySelector('#network-blocks-since-step').innerHTML =
2165
- `${metagraphInfo.blocks_since_last_step}/${metagraphInfo.tempo}`;
2166
-
2167
- // Number of neurons vs. max
2168
- document.querySelector('#network-neurons').innerHTML =
2169
- `${metagraphInfo.num_uids}/${metagraphInfo.max_uids}`;
2170
-
2171
- // Update "Your Stakes" table
2172
- const stakesTableBody = document.querySelector('#stakes-table-body');
2173
- stakesTableBody.innerHTML = '';
2174
- if (subnet.your_stakes && subnet.your_stakes.length > 0) {
2175
- subnet.your_stakes.forEach(stake => {
2176
- const row = document.createElement('tr');
2177
- row.innerHTML = `
2178
- <td class="hotkey-cell">
2179
- <div class="hotkey-container">
2180
- <span class="hotkey-identity" style="color: #FF9900">${stake.hotkey_identity}</span>
2181
- <!-- Remove the unused event param -->
2182
- <span class="copy-button" onclick="copyToClipboard('${stake.hotkey}')">copy</span>
2183
- </div>
2184
- </td>
2185
- <td>${formatNumber(stake.amount, subnet.symbol)}</td>
2186
- <td>${formatNumber(stake.ideal_value, root_symbol_html)}</td>
2187
- <td>${formatNumber(stake.slippage_value, root_symbol_html)} (${stake.slippage_percentage.toFixed(2)}%)</td>
2188
- <td>${formatNumber(stake.emission, subnet.symbol + '/day')}</td>
2189
- <td>${formatNumber(stake.tao_emission, root_symbol_html + '/day')}</td>
2190
- <td class="registered-cell">
2191
- <span class="${stake.is_registered ? 'registered-yes' : 'registered-no'}">
2192
- ${stake.is_registered ? 'Yes' : 'No'}
2193
- </span>
2194
- </td>
2195
- <td class="actions-cell">
2196
- <button class="manage-button">Coming soon</button>
2197
- </td>
2198
- `;
2199
- stakesTableBody.appendChild(row);
2200
- });
2201
- } else {
2202
- // If no user stake in this subnet
2203
- stakesTableBody.innerHTML = `
2204
- <tr class="no-stakes-row">
2205
- <td colspan="8">No stakes found for this subnet</td>
2206
- </tr>
2207
- `;
2208
- }
2209
-
2210
- // Remove any previously injected network table then add the new one
2211
- const existingNetworkTable = document.querySelector('.network-table-container');
2212
- if (existingNetworkTable) {
2213
- existingNetworkTable.remove();
2214
- }
2215
- document.querySelector('.stakes-table-container').insertAdjacentHTML('afterend', networkTableHTML);
2216
-
2217
- // Format the new numbers
2218
- initializeFormattedNumbers();
345
+ template = jinja_env.get_template("view.j2")
2219
346
 
2220
- // Initialize connectivity visualization (the dots / lines "animation")
2221
- setTimeout(() => { initNetworkVisualization(); }, 100);
2222
-
2223
- // Toggle whether we are showing the "Your Stakes" or "Metagraph" table
2224
- toggleStakeView();
2225
-
2226
- // Initialize sorting on newly injected table columns
2227
- initializeSorting();
2228
-
2229
- // Auto-sort by Stake descending on the network table for convenience
2230
- setTimeout(() => {
2231
- const networkTable = document.querySelector('.network-table');
2232
- if (networkTable) {
2233
- const stakeColumn = networkTable.querySelector('th:nth-child(2)');
2234
- if (stakeColumn) {
2235
- sortTable(networkTable, 1, stakeColumn, true);
2236
- stakeColumn.setAttribute('data-sort', 'desc');
2237
- }
2238
- }
2239
- }, 100);
2240
-
2241
- console.log('Subnet page updated successfully');
2242
- } catch (error) {
2243
- console.error('Error updating subnet page:', error);
2244
- }
2245
- }
2246
-
2247
- /**
2248
- * Generates the rows for the "Neurons" table (shown when the user unchecks "Show Stakes").
2249
- * Each row, when clicked, calls showNeuronDetails(i).
2250
- * @param {Object} metagraphInfo The "metagraph_info" of the subnet that holds hotkeys, etc.
2251
- */
2252
- function generateNetworkTableRows(metagraphInfo) {
2253
- const rows = [];
2254
- console.log('Generating network table rows with data:', metagraphInfo);
2255
-
2256
- for (let i = 0; i < metagraphInfo.hotkeys.length; i++) {
2257
- // Subnet symbol is used to show token vs. root stake
2258
- const subnet = window.initialData.subnets.find(s => s.netuid === window.currentSubnet);
2259
- const subnetSymbol = subnet ? subnet.symbol : '';
2260
-
2261
- // Possibly show hotkey/coldkey truncated for readability
2262
- const truncatedHotkey = truncateAddress(metagraphInfo.hotkeys[i]);
2263
- const truncatedColdkey = truncateAddress(metagraphInfo.coldkeys[i]);
2264
- const identityName = metagraphInfo.updated_identities[i] || '~';
2265
-
2266
- // Root stake is being scaled by 0.18 arbitrarily here
2267
- const adjustedRootStake = metagraphInfo.tao_stake[i] * 0.18;
2268
-
2269
- rows.push(`
2270
- <tr onclick="showNeuronDetails(${i})">
2271
- <td class="identity-cell">${identityName}</td>
2272
- <td data-value="${metagraphInfo.total_stake[i]}">
2273
- <span class="formatted-number" data-value="${metagraphInfo.total_stake[i]}" data-symbol="${subnetSymbol}"></span>
2274
- </td>
2275
- <td data-value="${metagraphInfo.alpha_stake[i]}">
2276
- <span class="formatted-number" data-value="${metagraphInfo.alpha_stake[i]}" data-symbol="${subnetSymbol}"></span>
2277
- </td>
2278
- <td data-value="${adjustedRootStake}">
2279
- <span class="formatted-number" data-value="${adjustedRootStake}" data-symbol="${root_symbol_html}"></span>
2280
- </td>
2281
- <td data-value="${metagraphInfo.dividends[i]}">
2282
- <span class="formatted-number" data-value="${metagraphInfo.dividends[i]}" data-symbol=""></span>
2283
- </td>
2284
- <td data-value="${metagraphInfo.incentives[i]}">
2285
- <span class="formatted-number" data-value="${metagraphInfo.incentives[i]}" data-symbol=""></span>
2286
- </td>
2287
- <td data-value="${metagraphInfo.emission[i]}">
2288
- <span class="formatted-number" data-value="${metagraphInfo.emission[i]}" data-symbol="${subnetSymbol}"></span>
2289
- </td>
2290
- <td class="address-cell">
2291
- <div class="hotkey-container" data-full-address="${metagraphInfo.hotkeys[i]}">
2292
- <span class="truncated-address">${truncatedHotkey}</span>
2293
- <span class="copy-button" onclick="event.stopPropagation(); copyToClipboard('${metagraphInfo.hotkeys[i]}')">copy</span>
2294
- </div>
2295
- </td>
2296
- <td class="address-cell">
2297
- <div class="hotkey-container" data-full-address="${metagraphInfo.coldkeys[i]}">
2298
- <span class="truncated-address">${truncatedColdkey}</span>
2299
- <span class="copy-button" onclick="event.stopPropagation(); copyToClipboard('${metagraphInfo.coldkeys[i]}')">copy</span>
2300
- </div>
2301
- </td>
2302
- </tr>
2303
- `);
2304
- }
2305
- return rows.join('');
2306
- }
2307
-
2308
- /**
2309
- * Handles toggling between the "Your Stakes" view and the "Neurons" view on the Subnet page.
2310
- * The "Show Stakes" checkbox (#stake-toggle) controls which table is visible.
2311
- */
2312
- function toggleStakeView() {
2313
- const showStakes = document.getElementById('stake-toggle').checked;
2314
- const stakesTable = document.querySelector('.stakes-table-container');
2315
- const networkTable = document.querySelector('.network-table-container');
2316
- const sectionHeader = document.querySelector('.view-header');
2317
- const neuronDetails = document.getElementById('neuron-detail-container');
2318
- const addStakeButton = document.querySelector('.add-stake-button');
2319
- const exportCsvButton = document.querySelector('.export-csv-button');
2320
- const stakesHeader = document.querySelector('.stakes-header');
2321
-
2322
- // First, close neuron details if they're open
2323
- if (neuronDetails && neuronDetails.style.display !== 'none') {
2324
- neuronDetails.style.display = 'none';
2325
- }
2326
-
2327
- // Always show the section header and stakes header when toggling views
2328
- if (sectionHeader) sectionHeader.style.display = 'block';
2329
- if (stakesHeader) stakesHeader.style.display = 'flex';
2330
-
2331
- if (showStakes) {
2332
- // Show the Stakes table, hide the Neurons table
2333
- stakesTable.style.display = 'block';
2334
- networkTable.style.display = 'none';
2335
- sectionHeader.textContent = 'Your Stakes';
2336
- if (addStakeButton) {
2337
- addStakeButton.style.display = 'none';
2338
- }
2339
- if (exportCsvButton) {
2340
- exportCsvButton.style.display = 'none';
2341
- }
2342
- } else {
2343
- // Show the Neurons table, hide the Stakes table
2344
- stakesTable.style.display = 'none';
2345
- networkTable.style.display = 'block';
2346
- sectionHeader.textContent = 'Metagraph';
2347
- if (addStakeButton) {
2348
- addStakeButton.style.display = 'block';
2349
- }
2350
- if (exportCsvButton) {
2351
- exportCsvButton.style.display = 'block';
2352
- }
2353
- }
2354
- }
2355
-
2356
- /**
2357
- * Called when you click a row in the "Neurons" table, to display more detail about that neuron.
2358
- * This hides the "Neurons" table and shows the #neuron-detail-container.
2359
- * @param {number} rowIndex The index of the neuron in the arrays (hotkeys, coldkeys, etc.)
2360
- */
2361
- function showNeuronDetails(rowIndex) {
2362
- try {
2363
- // Hide the network table & stakes table
2364
- const networkTable = document.querySelector('.network-table-container');
2365
- if (networkTable) networkTable.style.display = 'none';
2366
- const stakesTable = document.querySelector('.stakes-table-container');
2367
- if (stakesTable) stakesTable.style.display = 'none';
2368
-
2369
- // Hide the stakes header with the action buttons
2370
- const stakesHeader = document.querySelector('.stakes-header');
2371
- if (stakesHeader) stakesHeader.style.display = 'none';
2372
-
2373
- // Hide the view header that says "Neurons"
2374
- const viewHeader = document.querySelector('.view-header');
2375
- if (viewHeader) viewHeader.style.display = 'none';
2376
-
2377
- // Show the neuron detail panel
2378
- const detailContainer = document.getElementById('neuron-detail-container');
2379
- if (detailContainer) detailContainer.style.display = 'block';
2380
-
2381
- // Pull out the current subnet
2382
- const subnet = window.initialData.subnets.find(s => s.netuid === window.currentSubnet);
2383
- if (!subnet) {
2384
- console.error('No subnet data for netuid:', window.currentSubnet);
2385
- return;
2386
- }
2387
-
2388
- const metagraphInfo = subnet.metagraph_info;
2389
- const subnetSymbol = subnet.symbol || '';
2390
-
2391
- // Pull axon data, for IP info
2392
- const axonData = metagraphInfo.processed_axons ? metagraphInfo.processed_axons[rowIndex] : null;
2393
- let ipInfoString;
2394
-
2395
- // Update IP info card - hide header if IP info is present
2396
- const ipInfoCard = document.getElementById('neuron-ipinfo').closest('.metric-card');
2397
- if (axonData && axonData.ip !== 'N/A') {
2398
- // If we have valid IP info, hide the "IP Info" label
2399
- if (ipInfoCard && ipInfoCard.querySelector('.metric-label')) {
2400
- ipInfoCard.querySelector('.metric-label').style.display = 'none';
2401
- }
2402
- // Format IP info with green labels
2403
- ipInfoString = `<span style="color: #FF9900">IP:</span> ${axonData.ip}<br>` +
2404
- `<span style="color: #FF9900">Port:</span> ${axonData.port}<br>` +
2405
- `<span style="color: #FF9900">Type:</span> ${axonData.ip_type}`;
2406
- } else {
2407
- // If no IP info, show the label
2408
- if (ipInfoCard && ipInfoCard.querySelector('.metric-label')) {
2409
- ipInfoCard.querySelector('.metric-label').style.display = 'block';
2410
- }
2411
- ipInfoString = '<span style="color: #ff4444; font-size: 1.2em;">N/A</span>';
2412
- }
2413
-
2414
- // Basic identity and hotkey/coldkey info
2415
- const name = metagraphInfo.updated_identities[rowIndex] || '~';
2416
- const hotkey = metagraphInfo.hotkeys[rowIndex];
2417
- const coldkey = metagraphInfo.coldkeys[rowIndex];
2418
- const rank = metagraphInfo.rank ? metagraphInfo.rank[rowIndex] : 0;
2419
- const trust = metagraphInfo.trust ? metagraphInfo.trust[rowIndex] : 0;
2420
- const pruning = metagraphInfo.pruning_score ? metagraphInfo.pruning_score[rowIndex] : 0;
2421
- const vPermit = metagraphInfo.validator_permit ? metagraphInfo.validator_permit[rowIndex] : false;
2422
- const lastUpd = metagraphInfo.last_update ? metagraphInfo.last_update[rowIndex] : 0;
2423
- const consensus = metagraphInfo.consensus ? metagraphInfo.consensus[rowIndex] : 0;
2424
- const regBlock = metagraphInfo.block_at_registration ? metagraphInfo.block_at_registration[rowIndex] : 0;
2425
- const active = metagraphInfo.active ? metagraphInfo.active[rowIndex] : false;
2426
-
2427
- // Update UI fields
2428
- document.getElementById('neuron-name').textContent = name;
2429
- document.getElementById('neuron-name').style.color = '#FF9900';
2430
-
2431
- document.getElementById('neuron-hotkey').textContent = hotkey;
2432
- document.getElementById('neuron-coldkey').textContent = coldkey;
2433
- document.getElementById('neuron-trust').textContent = trust.toFixed(4);
2434
- document.getElementById('neuron-pruning-score').textContent = pruning.toFixed(4);
2435
-
2436
- // Validator
2437
- const validatorElem = document.getElementById('neuron-validator-permit');
2438
- if (vPermit) {
2439
- validatorElem.style.color = '#2ECC71';
2440
- validatorElem.textContent = 'True';
2441
- } else {
2442
- validatorElem.style.color = '#ff4444';
2443
- validatorElem.textContent = 'False';
2444
- }
2445
-
2446
- document.getElementById('neuron-last-update').textContent = lastUpd;
2447
- document.getElementById('neuron-consensus').textContent = consensus.toFixed(4);
2448
- document.getElementById('neuron-reg-block').textContent = regBlock;
2449
- document.getElementById('neuron-ipinfo').innerHTML = ipInfoString;
2450
-
2451
- const activeElem = document.getElementById('neuron-active');
2452
- if (active) {
2453
- activeElem.style.color = '#2ECC71';
2454
- activeElem.textContent = 'Yes';
2455
- } else {
2456
- activeElem.style.color = '#ff4444';
2457
- activeElem.textContent = 'No';
2458
- }
2459
-
2460
- // Add stake data ("total_stake", "alpha_stake", "tao_stake")
2461
- document.getElementById('neuron-stake-total').setAttribute(
2462
- 'data-value', metagraphInfo.total_stake[rowIndex]
2463
- );
2464
- document.getElementById('neuron-stake-total').setAttribute(
2465
- 'data-symbol', subnetSymbol
2466
- );
2467
-
2468
- document.getElementById('neuron-stake-token').setAttribute(
2469
- 'data-value', metagraphInfo.alpha_stake[rowIndex]
2470
- );
2471
- document.getElementById('neuron-stake-token').setAttribute(
2472
- 'data-symbol', subnetSymbol
2473
- );
2474
-
2475
- // Multiply tao_stake by 0.18
2476
- const originalStakeRoot = metagraphInfo.tao_stake[rowIndex];
2477
- const calculatedStakeRoot = originalStakeRoot * 0.18;
2478
-
2479
- document.getElementById('neuron-stake-root').setAttribute(
2480
- 'data-value', calculatedStakeRoot
2481
- );
2482
- document.getElementById('neuron-stake-root').setAttribute(
2483
- 'data-symbol', root_symbol_html
2484
- );
2485
- // Also set the inner text right away, so we show a correct format on load
2486
- document.getElementById('neuron-stake-root').innerHTML =
2487
- formatNumber(calculatedStakeRoot, root_symbol_html);
2488
-
2489
- // Dividends, Incentive
2490
- document.getElementById('neuron-dividends').setAttribute(
2491
- 'data-value', metagraphInfo.dividends[rowIndex]
2492
- );
2493
- document.getElementById('neuron-dividends').setAttribute('data-symbol', '');
2494
-
2495
- document.getElementById('neuron-incentive').setAttribute(
2496
- 'data-value', metagraphInfo.incentives[rowIndex]
2497
- );
2498
- document.getElementById('neuron-incentive').setAttribute('data-symbol', '');
2499
-
2500
- // Emissions
2501
- document.getElementById('neuron-emissions').setAttribute(
2502
- 'data-value', metagraphInfo.emission[rowIndex]
2503
- );
2504
- document.getElementById('neuron-emissions').setAttribute('data-symbol', subnetSymbol);
2505
-
2506
- // Rank
2507
- document.getElementById('neuron-rank').textContent = rank.toFixed(4);
2508
-
2509
- // Re-run formatting so the newly updated data-values appear in numeric form
2510
- initializeFormattedNumbers();
2511
- } catch (err) {
2512
- console.error('Error showing neuron details:', err);
2513
- }
2514
- }
2515
-
2516
- /**
2517
- * Closes the neuron detail panel and goes back to whichever table was selected ("Stakes" or "Metagraph").
2518
- */
2519
- function closeNeuronDetails() {
2520
- // Hide neuron details
2521
- const detailContainer = document.getElementById('neuron-detail-container');
2522
- if (detailContainer) detailContainer.style.display = 'none';
2523
-
2524
- // Show the stakes header with action buttons
2525
- const stakesHeader = document.querySelector('.stakes-header');
2526
- if (stakesHeader) stakesHeader.style.display = 'flex';
2527
-
2528
- // Show the view header again
2529
- const viewHeader = document.querySelector('.view-header');
2530
- if (viewHeader) viewHeader.style.display = 'block';
2531
-
2532
- // Show the appropriate table based on toggle state
2533
- const showStakes = document.getElementById('stake-toggle').checked;
2534
- const stakesTable = document.querySelector('.stakes-table-container');
2535
- const networkTable = document.querySelector('.network-table-container');
2536
-
2537
- if (showStakes) {
2538
- stakesTable.style.display = 'block';
2539
- networkTable.style.display = 'none';
2540
-
2541
- // Hide action buttons when showing stakes
2542
- const addStakeButton = document.querySelector('.add-stake-button');
2543
- const exportCsvButton = document.querySelector('.export-csv-button');
2544
- if (addStakeButton) addStakeButton.style.display = 'none';
2545
- if (exportCsvButton) exportCsvButton.style.display = 'none';
2546
- } else {
2547
- stakesTable.style.display = 'none';
2548
- networkTable.style.display = 'block';
2549
-
2550
- // Show action buttons when showing metagraph
2551
- const addStakeButton = document.querySelector('.add-stake-button');
2552
- const exportCsvButton = document.querySelector('.export-csv-button');
2553
- if (addStakeButton) addStakeButton.style.display = 'block';
2554
- if (exportCsvButton) exportCsvButton.style.display = 'block';
2555
- }
2556
- }
2557
-
2558
-
2559
- /* ===================== Number Formatting Functions ===================== */
2560
- /**
2561
- * Toggles the numeric display between "verbose" and "short" notations
2562
- * across all .formatted-number elements on the page.
2563
- */
2564
- function toggleVerboseNumbers() {
2565
- // We read from the main or subnet checkboxes
2566
- verboseNumbers =
2567
- document.getElementById('verbose-toggle')?.checked ||
2568
- document.getElementById('show-verbose')?.checked ||
2569
- false;
2570
-
2571
- // Reformat all visible .formatted-number elements
2572
- document.querySelectorAll('.formatted-number').forEach(element => {
2573
- const value = parseFloat(element.dataset.value);
2574
- const symbol = element.dataset.symbol;
2575
- element.innerHTML = formatNumber(value, symbol);
2576
- });
2577
-
2578
- // If we're currently on the Subnet detail page, update those numbers too
2579
- if (document.getElementById('subnet-page').style.display !== 'none') {
2580
- updateAllNumbers();
2581
- }
2582
- }
2583
-
2584
- /**
2585
- * Scans all .formatted-number elements and replaces their text with
2586
- * the properly formatted version (short or verbose).
2587
- */
2588
- function initializeFormattedNumbers() {
2589
- document.querySelectorAll('.formatted-number').forEach(element => {
2590
- const value = parseFloat(element.dataset.value);
2591
- const symbol = element.dataset.symbol;
2592
- element.innerHTML = formatNumber(value, symbol);
2593
- });
2594
- }
2595
-
2596
- /**
2597
- * Called by toggleVerboseNumbers() to reformat key metrics on the Subnet page
2598
- * that might not be directly wrapped in .formatted-number but need to be updated anyway.
2599
- */
2600
- function updateAllNumbers() {
2601
- try {
2602
- const subnet = window.initialData.subnets.find(s => s.netuid === window.currentSubnet);
2603
- if (!subnet) {
2604
- console.error('Could not find subnet data for netuid:', window.currentSubnet);
2605
- return;
2606
- }
2607
- // Reformat a few items in the Subnet detail header
2608
- document.querySelector('#subnet-market-cap').innerHTML =
2609
- formatNumber(subnet.market_cap, root_symbol_html);
2610
- document.querySelector('#subnet-total-stake').innerHTML =
2611
- formatNumber(subnet.total_stake, subnet.symbol);
2612
- document.querySelector('#subnet-emission').innerHTML =
2613
- formatNumber(subnet.emission, root_symbol_html);
2614
-
2615
- // Reformat the Metagraph table data
2616
- const netinfo = subnet.metagraph_info;
2617
- document.querySelector('#network-alpha-in').innerHTML =
2618
- formatNumber(netinfo.alpha_in, subnet.symbol);
2619
- document.querySelector('#network-tau-in').innerHTML =
2620
- formatNumber(netinfo.tao_in, root_symbol_html);
2621
-
2622
- // Reformat items in "Your Stakes" table
2623
- document.querySelectorAll('#stakes-table-body .formatted-number').forEach(element => {
2624
- const value = parseFloat(element.dataset.value);
2625
- const symbol = element.dataset.symbol;
2626
- element.innerHTML = formatNumber(value, symbol);
2627
- });
2628
- } catch (error) {
2629
- console.error('Error updating numbers:', error);
2630
- }
2631
- }
2632
-
2633
- /**
2634
- * Format a numeric value into either:
2635
- * - a short format (e.g. 1.23k, 3.45m) if verboseNumbers==false
2636
- * - a more precise format (1,234.5678) if verboseNumbers==true
2637
- * @param {number} num The numeric value to format.
2638
- * @param {string} symbol A short suffix or currency symbol (e.g. 'τ') that we append.
2639
- */
2640
- function formatNumber(num, symbol = '') {
2641
- if (num === undefined || num === null || isNaN(num)) {
2642
- return '0.00 ' + `<span style="color: #FF9900">${symbol}</span>`;
2643
- }
2644
- num = parseFloat(num);
2645
- if (num === 0) {
2646
- return '0.00 ' + `<span style="color: #FF9900">${symbol}</span>`;
2647
- }
2648
-
2649
- // If user requested verbose
2650
- if (verboseNumbers) {
2651
- return num.toLocaleString('en-US', {
2652
- minimumFractionDigits: 4,
2653
- maximumFractionDigits: 4
2654
- }) + ' ' + `<span style="color: #FF9900">${symbol}</span>`;
2655
- }
2656
-
2657
- // Otherwise show short scale for large numbers
2658
- const absNum = Math.abs(num);
2659
- if (absNum >= 1000) {
2660
- const suffixes = ['', 'k', 'm', 'b', 't'];
2661
- const magnitude = Math.min(4, Math.floor(Math.log10(absNum) / 3));
2662
- const scaledNum = num / Math.pow(10, magnitude * 3);
2663
- return scaledNum.toFixed(2) + suffixes[magnitude] + ' ' +
2664
- `<span style="color: #FF9900">${symbol}</span>`;
2665
- } else {
2666
- // For small numbers <1000, just show 4 decimals
2667
- return num.toFixed(4) + ' ' + `<span style="color: #FF9900">${symbol}</span>`;
2668
- }
2669
- }
2670
-
2671
- /**
2672
- * Truncates a string address into the format "ABC..XYZ" for a bit more readability
2673
- * @param {string} address
2674
- * @returns {string} truncated address form
2675
- */
2676
- function truncateAddress(address) {
2677
- if (!address || address.length <= 7) {
2678
- return address; // no need to truncate if very short
2679
- }
2680
- return `${address.substring(0, 3)}..${address.substring(address.length - 3)}`;
2681
- }
2682
-
2683
- /**
2684
- * Format a number in compact notation (K, M, B) for tile display
2685
- */
2686
- function formatTileNumbers(num) {
2687
- if (num >= 1000000000) {
2688
- return (num / 1000000000).toFixed(1) + 'B';
2689
- } else if (num >= 1000000) {
2690
- return (num / 1000000).toFixed(1) + 'M';
2691
- } else if (num >= 1000) {
2692
- return (num / 1000).toFixed(1) + 'K';
2693
- } else {
2694
- return num.toFixed(1);
2695
- }
2696
- }
2697
-
2698
-
2699
- /* ===================== Table Sorting and Filtering Functions ===================== */
2700
- /**
2701
- * Switches the Metagraph or Stakes table from sorting ascending to descending on a column, and vice versa.
2702
- * @param {HTMLTableElement} table The table element itself
2703
- * @param {number} columnIndex The column index to sort by
2704
- * @param {HTMLTableHeaderCellElement} header The <th> element clicked
2705
- * @param {boolean} forceDescending If true and no existing sort marker, will do a descending sort by default
2706
- */
2707
- function sortTable(table, columnIndex, header, forceDescending = false) {
2708
- const tbody = table.querySelector('tbody');
2709
- const rows = Array.from(tbody.querySelectorAll('tr'));
2710
-
2711
- // If forcing descending and the header has no 'data-sort', default to 'desc'
2712
- let isDescending;
2713
- if (forceDescending && !header.hasAttribute('data-sort')) {
2714
- isDescending = true;
2715
- } else {
2716
- isDescending = header.getAttribute('data-sort') !== 'desc';
2717
- }
2718
-
2719
- // Clear data-sort from all headers in the table
2720
- table.querySelectorAll('th').forEach(th => {
2721
- th.removeAttribute('data-sort');
2722
- });
2723
- // Mark the clicked header with new direction
2724
- header.setAttribute('data-sort', isDescending ? 'desc' : 'asc');
2725
-
2726
- // Sort numerically
2727
- rows.sort((rowA, rowB) => {
2728
- const cellA = rowA.cells[columnIndex];
2729
- const cellB = rowB.cells[columnIndex];
2730
-
2731
- // Attempt to parse float from data-value or fallback to textContent
2732
- let valueA = parseFloat(cellA.getAttribute('data-value')) ||
2733
- parseFloat(cellA.textContent.replace(/[^\\d.-]/g, '')) ||
2734
- 0;
2735
- let valueB = parseFloat(cellB.getAttribute('data-value')) ||
2736
- parseFloat(cellB.textContent.replace(/[^\\d.-]/g, '')) ||
2737
- 0;
2738
-
2739
- return isDescending ? (valueB - valueA) : (valueA - valueB);
2740
- });
2741
-
2742
- // Reinsert sorted rows
2743
- tbody.innerHTML = '';
2744
- rows.forEach(row => tbody.appendChild(row));
2745
- }
2746
-
2747
- /**
2748
- * Adds sortable behavior to certain columns in the "stakes-table" or "network-table".
2749
- * Called after these tables are created in showSubnetPage().
2750
- */
2751
- function initializeSorting() {
2752
- const networkTable = document.querySelector('.network-table');
2753
- if (networkTable) {
2754
- initializeTableSorting(networkTable);
2755
- }
2756
- const stakesTable = document.querySelector('.stakes-table');
2757
- if (stakesTable) {
2758
- initializeTableSorting(stakesTable);
2759
- }
2760
- }
2761
-
2762
- /**
2763
- * Helper function that attaches sort handlers to appropriate columns in a table.
2764
- * @param {HTMLTableElement} table The table element to set up sorting for.
2765
- */
2766
- function initializeTableSorting(table) {
2767
- const headers = table.querySelectorAll('th');
2768
- headers.forEach((header, index) => {
2769
- // We only want some columns to be sortable, as in original code
2770
- if (table.classList.contains('stakes-table') && index >= 1 && index <= 5) {
2771
- header.classList.add('sortable');
2772
- header.addEventListener('click', () => {
2773
- sortTable(table, index, header, true);
2774
- });
2775
- } else if (table.classList.contains('network-table') && index < 6) {
2776
- header.classList.add('sortable');
2777
- header.addEventListener('click', () => {
2778
- sortTable(table, index, header, true);
2779
- });
2780
- }
2781
- });
2782
- }
2783
-
2784
- /**
2785
- * Filters rows in the Metagraph table by name, hotkey, or coldkey.
2786
- * Invoked by the oninput event of the #network-search field.
2787
- * @param {string} searchValue The substring typed by the user.
2788
- */
2789
- function filterNetworkTable(searchValue) {
2790
- const searchTerm = searchValue.toLowerCase().trim();
2791
- const rows = document.querySelectorAll('.network-table tbody tr');
2792
-
2793
- rows.forEach(row => {
2794
- const nameCell = row.querySelector('.identity-cell');
2795
- const hotkeyContainer = row.querySelector('.hotkey-container[data-full-address]');
2796
- const coldkeyContainer = row.querySelectorAll('.hotkey-container[data-full-address]')[1];
2797
-
2798
- const name = nameCell ? nameCell.textContent.toLowerCase() : '';
2799
- const hotkey = hotkeyContainer ? hotkeyContainer.getAttribute('data-full-address').toLowerCase() : '';
2800
- const coldkey= coldkeyContainer ? coldkeyContainer.getAttribute('data-full-address').toLowerCase() : '';
2801
-
2802
- const matches = (name.includes(searchTerm) || hotkey.includes(searchTerm) || coldkey.includes(searchTerm));
2803
- row.style.display = matches ? '' : 'none';
2804
- });
2805
- }
2806
-
2807
-
2808
- /* ===================== Network Visualization Functions ===================== */
2809
- /**
2810
- * Initializes the network visualization on the canvas element.
2811
- */
2812
- function initNetworkVisualization() {
2813
- try {
2814
- const canvas = document.getElementById('network-canvas');
2815
- if (!canvas) {
2816
- console.error('Canvas element (#network-canvas) not found');
2817
- return;
2818
- }
2819
- const ctx = canvas.getContext('2d');
2820
-
2821
- const subnet = window.initialData.subnets.find(s => s.netuid === window.currentSubnet);
2822
- if (!subnet) {
2823
- console.error('Could not find subnet data for netuid:', window.currentSubnet);
2824
- return;
2825
- }
2826
- const numNeurons = subnet.metagraph_info.num_uids;
2827
- const nodes = [];
2828
-
2829
- // Randomly place nodes, each with a small velocity
2830
- for (let i = 0; i < numNeurons; i++) {
2831
- nodes.push({
2832
- x: Math.random() * canvas.width,
2833
- y: Math.random() * canvas.height,
2834
- radius: 2,
2835
- vx: (Math.random() - 0.5) * 0.5,
2836
- vy: (Math.random() - 0.5) * 0.5
2837
- });
2838
- }
2839
-
2840
- // Animation loop
2841
- function animate() {
2842
- ctx.clearRect(0, 0, canvas.width, canvas.height);
2843
-
2844
- ctx.beginPath();
2845
- ctx.strokeStyle = 'rgba(255, 153, 0, 0.2)';
2846
- for (let i = 0; i < nodes.length; i++) {
2847
- for (let j = i + 1; j < nodes.length; j++) {
2848
- const dx = nodes[i].x - nodes[j].x;
2849
- const dy = nodes[i].y - nodes[j].y;
2850
- const distance = Math.sqrt(dx * dx + dy * dy);
2851
- if (distance < 30) {
2852
- ctx.moveTo(nodes[i].x, nodes[i].y);
2853
- ctx.lineTo(nodes[j].x, nodes[j].y);
2854
- }
2855
- }
2856
- }
2857
- ctx.stroke();
2858
-
2859
- nodes.forEach(node => {
2860
- node.x += node.vx;
2861
- node.y += node.vy;
2862
-
2863
- // Bounce them off the edges
2864
- if (node.x <= 0 || node.x >= canvas.width) node.vx *= -1;
2865
- if (node.y <= 0 || node.y >= canvas.height) node.vy *= -1;
2866
-
2867
- ctx.beginPath();
2868
- ctx.fillStyle = '#FF9900';
2869
- ctx.arc(node.x, node.y, node.radius, 0, Math.PI * 2);
2870
- ctx.fill();
2871
- });
2872
-
2873
- requestAnimationFrame(animate);
2874
- }
2875
- animate();
2876
- } catch (error) {
2877
- console.error('Error in network visualization:', error);
2878
- }
2879
- }
2880
-
2881
-
2882
- /* ===================== Tile View Functions ===================== */
2883
- /**
2884
- * Toggles between the tile view and table view of subnets.
2885
- */
2886
- function toggleTileView() {
2887
- const showTiles = document.getElementById('show-tiles').checked;
2888
- const tilesContainer = document.getElementById('subnet-tiles-container');
2889
- const tableContainer = document.querySelector('.subnets-table-container');
2890
-
2891
- if (showTiles) {
2892
- // Show tiles, hide table
2893
- tilesContainer.style.display = 'flex';
2894
- tableContainer.style.display = 'none';
2895
-
2896
- // Generate tiles if they don't exist yet
2897
- if (tilesContainer.children.length === 0) {
2898
- generateSubnetTiles();
2899
- }
2900
-
2901
- // Apply current filters to the tiles
2902
- filterSubnets();
2903
- } else {
2904
- // Show table, hide tiles
2905
- tilesContainer.style.display = 'none';
2906
- tableContainer.style.display = 'block';
2907
- }
2908
- }
2909
-
2910
- /**
2911
- * Generates the subnet tiles based on the initialData.
2912
- */
2913
- function generateSubnetTiles() {
2914
- const tilesContainer = document.getElementById('subnet-tiles-container');
2915
- tilesContainer.innerHTML = ''; // Clear existing tiles
2916
-
2917
- // Sort subnets by market cap (descending)
2918
- const sortedSubnets = [...window.initialData.subnets].sort((a, b) => b.market_cap - a.market_cap);
2919
-
2920
- sortedSubnets.forEach(subnet => {
2921
- const isStaked = subnet.your_stakes && subnet.your_stakes.length > 0;
2922
- const marketCapFormatted = formatTileNumbers(subnet.market_cap);
2923
-
2924
- const tile = document.createElement('div');
2925
- tile.className = `subnet-tile ${isStaked ? 'staked' : ''}`;
2926
- tile.onclick = () => showSubnetPage(subnet.netuid);
2927
-
2928
- // Calculate background intensity based on market cap relative to max
2929
- const maxMarketCap = sortedSubnets[0].market_cap;
2930
- const intensity = Math.max(5, Math.min(15, 5 + (subnet.market_cap / maxMarketCap) * 10));
2931
-
2932
- tile.innerHTML = `
2933
- <span class="tile-netuid">${subnet.netuid}</span>
2934
- <span class="tile-symbol">${subnet.symbol}</span>
2935
- <span class="tile-name">${subnet.name}</span>
2936
- <span class="tile-market-cap">${marketCapFormatted} ${root_symbol_html}</span>
2937
- `;
2938
-
2939
- // Set background intensity
2940
- tile.style.background = `rgba(255, 255, 255, 0.0${intensity.toFixed(0)})`;
2941
-
2942
- tilesContainer.appendChild(tile);
2943
- });
2944
- }
2945
- """
347
+ return template.render(
348
+ root_symbol_html=ROOT_SYMBOL_HTML,
349
+ block_number=block_number,
350
+ truncated_coldkey=truncated_coldkey,
351
+ slippage_percentage=slippage_percentage,
352
+ wallet_info=wallet_info,
353
+ subnets=data["subnets"],
354
+ )