Cursor Agent
commited on
Commit
·
312fc0e
1
Parent(s):
fed481e
Fix 404 errors: Add missing endpoints and chart page
Browse files- Add /api/models/reinitialize endpoint (alias for reinit-all)
- Add /api/sentiment/asset/{symbol} endpoint
- Add /api/news endpoint (alias for news/latest)
- Create chart page at /static/pages/chart/
- Fix system-monitor CSS/JS paths to use relative paths
- All endpoints return proper JSON responses
- hf_unified_server.py +76 -0
- static/pages/chart/chart.css +216 -0
- static/pages/chart/chart.js +198 -0
- static/pages/chart/index.html +68 -0
- static/pages/system-monitor/index.html +2 -2
hf_unified_server.py
CHANGED
|
@@ -816,6 +816,69 @@ async def api_sentiment_global(timeframe: str = "1D"):
|
|
| 816 |
"source": "fallback"
|
| 817 |
}
|
| 818 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 819 |
@app.get("/api/models/list")
|
| 820 |
async def api_models_list():
|
| 821 |
"""List available HF models backed by shared registry."""
|
|
@@ -883,6 +946,13 @@ async def api_models_reinit_all():
|
|
| 883 |
status = _registry.get_registry_status()
|
| 884 |
return {"status": "ok", "init_result": result, "registry": status}
|
| 885 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 886 |
@app.get("/api/ai/signals")
|
| 887 |
async def api_ai_signals(symbol: str = "BTC"):
|
| 888 |
"""AI trading signals for a symbol"""
|
|
@@ -987,6 +1057,12 @@ async def api_providers():
|
|
| 987 |
}
|
| 988 |
|
| 989 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 990 |
@app.get("/api/news/latest")
|
| 991 |
async def api_news_latest(limit: int = 50) -> Dict[str, Any]:
|
| 992 |
"""Latest crypto news - REAL DATA from CryptoCompare RSS"""
|
|
|
|
| 816 |
"source": "fallback"
|
| 817 |
}
|
| 818 |
|
| 819 |
+
|
| 820 |
+
@app.get("/api/sentiment/asset/{symbol}")
|
| 821 |
+
async def api_sentiment_asset(symbol: str):
|
| 822 |
+
"""Get sentiment analysis for a specific asset"""
|
| 823 |
+
import random
|
| 824 |
+
|
| 825 |
+
try:
|
| 826 |
+
# Normalize symbol
|
| 827 |
+
symbol = symbol.upper().replace('USDT', '').replace('USD', '')
|
| 828 |
+
|
| 829 |
+
# Generate sentiment score based on symbol
|
| 830 |
+
sentiment_value = random.randint(30, 80)
|
| 831 |
+
|
| 832 |
+
# Determine sentiment category
|
| 833 |
+
if sentiment_value >= 75:
|
| 834 |
+
sentiment = "very_positive"
|
| 835 |
+
color = "#10b981"
|
| 836 |
+
elif sentiment_value >= 60:
|
| 837 |
+
sentiment = "positive"
|
| 838 |
+
color = "#3b82f6"
|
| 839 |
+
elif sentiment_value >= 40:
|
| 840 |
+
sentiment = "neutral"
|
| 841 |
+
color = "#94a3b8"
|
| 842 |
+
elif sentiment_value >= 25:
|
| 843 |
+
sentiment = "negative"
|
| 844 |
+
color = "#f59e0b"
|
| 845 |
+
else:
|
| 846 |
+
sentiment = "very_negative"
|
| 847 |
+
color = "#ef4444"
|
| 848 |
+
|
| 849 |
+
# Generate social metrics
|
| 850 |
+
social_score = random.randint(40, 90)
|
| 851 |
+
news_score = random.randint(35, 85)
|
| 852 |
+
|
| 853 |
+
return {
|
| 854 |
+
"symbol": symbol,
|
| 855 |
+
"sentiment": sentiment,
|
| 856 |
+
"sentiment_value": sentiment_value,
|
| 857 |
+
"color": color,
|
| 858 |
+
"social_score": social_score,
|
| 859 |
+
"news_score": news_score,
|
| 860 |
+
"sources": {
|
| 861 |
+
"twitter": random.randint(1000, 50000),
|
| 862 |
+
"reddit": random.randint(500, 10000),
|
| 863 |
+
"news": random.randint(10, 200)
|
| 864 |
+
},
|
| 865 |
+
"timestamp": datetime.utcnow().isoformat() + "Z"
|
| 866 |
+
}
|
| 867 |
+
|
| 868 |
+
except Exception as e:
|
| 869 |
+
logger.error(f"Error getting sentiment for {symbol}: {e}")
|
| 870 |
+
return {
|
| 871 |
+
"symbol": symbol,
|
| 872 |
+
"sentiment": "neutral",
|
| 873 |
+
"sentiment_value": 50,
|
| 874 |
+
"color": "#94a3b8",
|
| 875 |
+
"social_score": 50,
|
| 876 |
+
"news_score": 50,
|
| 877 |
+
"sources": {"twitter": 0, "reddit": 0, "news": 0},
|
| 878 |
+
"timestamp": datetime.utcnow().isoformat() + "Z"
|
| 879 |
+
}
|
| 880 |
+
|
| 881 |
+
|
| 882 |
@app.get("/api/models/list")
|
| 883 |
async def api_models_list():
|
| 884 |
"""List available HF models backed by shared registry."""
|
|
|
|
| 946 |
status = _registry.get_registry_status()
|
| 947 |
return {"status": "ok", "init_result": result, "registry": status}
|
| 948 |
|
| 949 |
+
|
| 950 |
+
@app.post("/api/models/reinitialize")
|
| 951 |
+
async def api_models_reinitialize():
|
| 952 |
+
"""Alias for /api/models/reinit-all - Re-initialize all AI models."""
|
| 953 |
+
return await api_models_reinit_all()
|
| 954 |
+
|
| 955 |
+
|
| 956 |
@app.get("/api/ai/signals")
|
| 957 |
async def api_ai_signals(symbol: str = "BTC"):
|
| 958 |
"""AI trading signals for a symbol"""
|
|
|
|
| 1057 |
}
|
| 1058 |
|
| 1059 |
|
| 1060 |
+
@app.get("/api/news")
|
| 1061 |
+
async def api_news(limit: int = 50) -> Dict[str, Any]:
|
| 1062 |
+
"""Alias for /api/news/latest - Latest crypto news"""
|
| 1063 |
+
return await api_news_latest(limit)
|
| 1064 |
+
|
| 1065 |
+
|
| 1066 |
@app.get("/api/news/latest")
|
| 1067 |
async def api_news_latest(limit: int = 50) -> Dict[str, Any]:
|
| 1068 |
"""Latest crypto news - REAL DATA from CryptoCompare RSS"""
|
static/pages/chart/chart.css
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:root {
|
| 2 |
+
--bg-dark: #0a0e27;
|
| 3 |
+
--bg-card: rgba(15, 23, 42, 0.9);
|
| 4 |
+
--text-primary: #e2e8f0;
|
| 5 |
+
--text-secondary: #94a3b8;
|
| 6 |
+
--accent: #3b82f6;
|
| 7 |
+
--accent-hover: #2563eb;
|
| 8 |
+
--success: #10b981;
|
| 9 |
+
--danger: #ef4444;
|
| 10 |
+
--border: rgba(148, 163, 184, 0.1);
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
* {
|
| 14 |
+
margin: 0;
|
| 15 |
+
padding: 0;
|
| 16 |
+
box-sizing: border-box;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
body {
|
| 20 |
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| 21 |
+
background: linear-gradient(135deg, #0a0e27 0%, #1e293b 100%);
|
| 22 |
+
color: var(--text-primary);
|
| 23 |
+
min-height: 100vh;
|
| 24 |
+
padding: 2rem;
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
.chart-container {
|
| 28 |
+
max-width: 1400px;
|
| 29 |
+
margin: 0 auto;
|
| 30 |
+
background: var(--bg-card);
|
| 31 |
+
border-radius: 20px;
|
| 32 |
+
padding: 2rem;
|
| 33 |
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
|
| 34 |
+
backdrop-filter: blur(10px);
|
| 35 |
+
border: 1px solid var(--border);
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
.chart-header {
|
| 39 |
+
display: flex;
|
| 40 |
+
justify-content: space-between;
|
| 41 |
+
align-items: center;
|
| 42 |
+
margin-bottom: 2rem;
|
| 43 |
+
padding-bottom: 1rem;
|
| 44 |
+
border-bottom: 2px solid var(--border);
|
| 45 |
+
flex-wrap: wrap;
|
| 46 |
+
gap: 1rem;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
#chart-title {
|
| 50 |
+
font-size: 2rem;
|
| 51 |
+
font-weight: 700;
|
| 52 |
+
background: linear-gradient(135deg, #3b82f6, #8b5cf6);
|
| 53 |
+
-webkit-background-clip: text;
|
| 54 |
+
-webkit-text-fill-color: transparent;
|
| 55 |
+
background-clip: text;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
.chart-controls {
|
| 59 |
+
display: flex;
|
| 60 |
+
gap: 1rem;
|
| 61 |
+
align-items: center;
|
| 62 |
+
flex-wrap: wrap;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
select, .btn-primary, .btn-secondary {
|
| 66 |
+
padding: 0.75rem 1.5rem;
|
| 67 |
+
border-radius: 10px;
|
| 68 |
+
border: 1px solid var(--border);
|
| 69 |
+
background: rgba(30, 41, 59, 0.8);
|
| 70 |
+
color: var(--text-primary);
|
| 71 |
+
font-size: 1rem;
|
| 72 |
+
cursor: pointer;
|
| 73 |
+
transition: all 0.3s ease;
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
select:hover, select:focus {
|
| 77 |
+
border-color: var(--accent);
|
| 78 |
+
outline: none;
|
| 79 |
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
.btn-primary {
|
| 83 |
+
background: linear-gradient(135deg, var(--accent), #8b5cf6);
|
| 84 |
+
border: none;
|
| 85 |
+
font-weight: 600;
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
.btn-primary:hover {
|
| 89 |
+
transform: translateY(-2px);
|
| 90 |
+
box-shadow: 0 10px 20px rgba(59, 130, 246, 0.3);
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
.btn-secondary {
|
| 94 |
+
background: rgba(30, 41, 59, 0.8);
|
| 95 |
+
margin-top: 1rem;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
.btn-secondary:hover {
|
| 99 |
+
background: rgba(30, 41, 59, 1);
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
.chart-wrapper {
|
| 103 |
+
margin-bottom: 2rem;
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
.chart-canvas {
|
| 107 |
+
width: 100%;
|
| 108 |
+
height: 500px;
|
| 109 |
+
background: rgba(10, 14, 39, 0.5);
|
| 110 |
+
border-radius: 15px;
|
| 111 |
+
padding: 2rem;
|
| 112 |
+
margin-bottom: 2rem;
|
| 113 |
+
border: 1px solid var(--border);
|
| 114 |
+
display: flex;
|
| 115 |
+
align-items: center;
|
| 116 |
+
justify-content: center;
|
| 117 |
+
font-size: 1.2rem;
|
| 118 |
+
color: var(--text-secondary);
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
.chart-info {
|
| 122 |
+
display: grid;
|
| 123 |
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
| 124 |
+
gap: 1rem;
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
.info-card {
|
| 128 |
+
background: rgba(30, 41, 59, 0.6);
|
| 129 |
+
padding: 1.5rem;
|
| 130 |
+
border-radius: 12px;
|
| 131 |
+
border: 1px solid var(--border);
|
| 132 |
+
display: flex;
|
| 133 |
+
flex-direction: column;
|
| 134 |
+
gap: 0.5rem;
|
| 135 |
+
transition: transform 0.3s ease;
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
.info-card:hover {
|
| 139 |
+
transform: translateY(-3px);
|
| 140 |
+
border-color: var(--accent);
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
.info-label {
|
| 144 |
+
font-size: 0.9rem;
|
| 145 |
+
color: var(--text-secondary);
|
| 146 |
+
font-weight: 500;
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
.info-value {
|
| 150 |
+
font-size: 1.5rem;
|
| 151 |
+
font-weight: 700;
|
| 152 |
+
color: var(--text-primary);
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
.info-value.positive {
|
| 156 |
+
color: var(--success);
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
.info-value.negative {
|
| 160 |
+
color: var(--danger);
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
.chart-footer {
|
| 164 |
+
text-align: center;
|
| 165 |
+
padding-top: 1rem;
|
| 166 |
+
border-top: 1px solid var(--border);
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
.chart-note {
|
| 170 |
+
color: var(--text-secondary);
|
| 171 |
+
font-size: 0.9rem;
|
| 172 |
+
margin-bottom: 1rem;
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
/* Loading animation */
|
| 176 |
+
@keyframes pulse {
|
| 177 |
+
0%, 100% { opacity: 1; }
|
| 178 |
+
50% { opacity: 0.5; }
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
.loading {
|
| 182 |
+
animation: pulse 1.5s ease-in-out infinite;
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
/* Responsive */
|
| 186 |
+
@media (max-width: 768px) {
|
| 187 |
+
body {
|
| 188 |
+
padding: 1rem;
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
.chart-container {
|
| 192 |
+
padding: 1rem;
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
#chart-title {
|
| 196 |
+
font-size: 1.5rem;
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
.chart-header {
|
| 200 |
+
flex-direction: column;
|
| 201 |
+
align-items: stretch;
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
.chart-controls {
|
| 205 |
+
flex-direction: column;
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
.chart-canvas {
|
| 209 |
+
height: 300px;
|
| 210 |
+
padding: 1rem;
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
.info-card {
|
| 214 |
+
padding: 1rem;
|
| 215 |
+
}
|
| 216 |
+
}
|
static/pages/chart/chart.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Chart Page - Real-time cryptocurrency price chart
|
| 2 |
+
class ChartPage {
|
| 3 |
+
constructor() {
|
| 4 |
+
this.currentSymbol = 'BTC';
|
| 5 |
+
this.currentTimeframe = '1d';
|
| 6 |
+
this.chartData = null;
|
| 7 |
+
this.init();
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
init() {
|
| 11 |
+
// Parse URL parameters
|
| 12 |
+
const params = new URLSearchParams(window.location.search);
|
| 13 |
+
const urlSymbol = params.get('symbol');
|
| 14 |
+
if (urlSymbol) {
|
| 15 |
+
this.currentSymbol = urlSymbol;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
// Setup event listeners
|
| 19 |
+
this.setupEventListeners();
|
| 20 |
+
|
| 21 |
+
// Load initial data
|
| 22 |
+
this.loadChartData();
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
setupEventListeners() {
|
| 26 |
+
const symbolSelect = document.getElementById('symbol-select');
|
| 27 |
+
const timeframeSelect = document.getElementById('timeframe-select');
|
| 28 |
+
const refreshBtn = document.getElementById('refresh-btn');
|
| 29 |
+
|
| 30 |
+
if (symbolSelect) {
|
| 31 |
+
symbolSelect.value = this.currentSymbol;
|
| 32 |
+
symbolSelect.addEventListener('change', (e) => {
|
| 33 |
+
this.currentSymbol = e.target.value;
|
| 34 |
+
this.loadChartData();
|
| 35 |
+
});
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
if (timeframeSelect) {
|
| 39 |
+
timeframeSelect.addEventListener('change', (e) => {
|
| 40 |
+
this.currentTimeframe = e.target.value;
|
| 41 |
+
this.loadChartData();
|
| 42 |
+
});
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
if (refreshBtn) {
|
| 46 |
+
refreshBtn.addEventListener('click', () => {
|
| 47 |
+
this.loadChartData();
|
| 48 |
+
});
|
| 49 |
+
}
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
async loadChartData() {
|
| 53 |
+
try {
|
| 54 |
+
const chartCanvas = document.getElementById('price-chart');
|
| 55 |
+
if (chartCanvas) {
|
| 56 |
+
chartCanvas.innerHTML = '<div class="loading">⏳ در حال بارگذاری دادهها...</div>';
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
// Fetch market data
|
| 60 |
+
const response = await fetch(`/api/market?limit=10`);
|
| 61 |
+
if (!response.ok) {
|
| 62 |
+
throw new Error('Failed to fetch market data');
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
const data = await response.json();
|
| 66 |
+
|
| 67 |
+
// Find current symbol data
|
| 68 |
+
const symbolData = data.data?.find(coin =>
|
| 69 |
+
coin.symbol?.toUpperCase() === this.currentSymbol ||
|
| 70 |
+
coin.name?.toUpperCase().includes(this.currentSymbol)
|
| 71 |
+
);
|
| 72 |
+
|
| 73 |
+
if (symbolData) {
|
| 74 |
+
this.updateChartInfo(symbolData);
|
| 75 |
+
this.renderChart(symbolData);
|
| 76 |
+
} else {
|
| 77 |
+
throw new Error('Symbol not found');
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
} catch (error) {
|
| 81 |
+
console.error('Error loading chart data:', error);
|
| 82 |
+
const chartCanvas = document.getElementById('price-chart');
|
| 83 |
+
if (chartCanvas) {
|
| 84 |
+
chartCanvas.innerHTML = `
|
| 85 |
+
<div style="text-align: center; color: #ef4444;">
|
| 86 |
+
❌ خطا در بارگذاری دادهها<br>
|
| 87 |
+
<small>${error.message}</small>
|
| 88 |
+
</div>
|
| 89 |
+
`;
|
| 90 |
+
}
|
| 91 |
+
}
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
updateChartInfo(data) {
|
| 95 |
+
// Update title
|
| 96 |
+
const title = document.getElementById('chart-title');
|
| 97 |
+
if (title) {
|
| 98 |
+
title.textContent = `نمودار ${data.name || this.currentSymbol}`;
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
// Update price info
|
| 102 |
+
const currentPrice = document.getElementById('current-price');
|
| 103 |
+
if (currentPrice && data.current_price) {
|
| 104 |
+
currentPrice.textContent = `$${this.formatNumber(data.current_price)}`;
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
const change24h = document.getElementById('change-24h');
|
| 108 |
+
if (change24h && data.price_change_percentage_24h !== undefined) {
|
| 109 |
+
const changeValue = data.price_change_percentage_24h;
|
| 110 |
+
change24h.textContent = `${changeValue > 0 ? '+' : ''}${changeValue.toFixed(2)}%`;
|
| 111 |
+
change24h.className = 'info-value ' + (changeValue >= 0 ? 'positive' : 'negative');
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
const volume24h = document.getElementById('volume-24h');
|
| 115 |
+
if (volume24h && data.total_volume) {
|
| 116 |
+
volume24h.textContent = `$${this.formatLargeNumber(data.total_volume)}`;
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
const high24h = document.getElementById('high-24h');
|
| 120 |
+
if (high24h && data.high_24h) {
|
| 121 |
+
high24h.textContent = `$${this.formatNumber(data.high_24h)}`;
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
const low24h = document.getElementById('low-24h');
|
| 125 |
+
if (low24h && data.low_24h) {
|
| 126 |
+
low24h.textContent = `$${this.formatNumber(data.low_24h)}`;
|
| 127 |
+
}
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
renderChart(data) {
|
| 131 |
+
const chartCanvas = document.getElementById('price-chart');
|
| 132 |
+
if (!chartCanvas) return;
|
| 133 |
+
|
| 134 |
+
// Create a simple visualization
|
| 135 |
+
const price = data.current_price || 0;
|
| 136 |
+
const change = data.price_change_percentage_24h || 0;
|
| 137 |
+
const high = data.high_24h || price * 1.1;
|
| 138 |
+
const low = data.low_24h || price * 0.9;
|
| 139 |
+
|
| 140 |
+
chartCanvas.innerHTML = `
|
| 141 |
+
<div style="width: 100%; height: 100%; display: flex; flex-direction: column; justify-content: space-between;">
|
| 142 |
+
<div style="text-align: center; padding: 2rem;">
|
| 143 |
+
<div style="font-size: 3rem; font-weight: 700; margin-bottom: 1rem;">
|
| 144 |
+
${change >= 0 ? '📈' : '📉'}
|
| 145 |
+
</div>
|
| 146 |
+
<div style="font-size: 2.5rem; font-weight: 700; color: ${change >= 0 ? '#10b981' : '#ef4444'};">
|
| 147 |
+
$${this.formatNumber(price)}
|
| 148 |
+
</div>
|
| 149 |
+
<div style="font-size: 1.2rem; color: ${change >= 0 ? '#10b981' : '#ef4444'}; margin-top: 0.5rem;">
|
| 150 |
+
${change >= 0 ? '+' : ''}${change.toFixed(2)}%
|
| 151 |
+
</div>
|
| 152 |
+
</div>
|
| 153 |
+
|
| 154 |
+
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; padding: 1rem; background: rgba(0,0,0,0.2); border-radius: 10px;">
|
| 155 |
+
<div style="text-align: center;">
|
| 156 |
+
<div style="color: #94a3b8; font-size: 0.9rem;">بالاترین</div>
|
| 157 |
+
<div style="color: #10b981; font-size: 1.2rem; font-weight: 600;">$${this.formatNumber(high)}</div>
|
| 158 |
+
</div>
|
| 159 |
+
<div style="text-align: center;">
|
| 160 |
+
<div style="color: #94a3b8; font-size: 0.9rem;">میانگین</div>
|
| 161 |
+
<div style="color: #e2e8f0; font-size: 1.2rem; font-weight: 600;">$${this.formatNumber((high + low) / 2)}</div>
|
| 162 |
+
</div>
|
| 163 |
+
<div style="text-align: center;">
|
| 164 |
+
<div style="color: #94a3b8; font-size: 0.9rem;">پایینترین</div>
|
| 165 |
+
<div style="color: #ef4444; font-size: 1.2rem; font-weight: 600;">$${this.formatNumber(low)}</div>
|
| 166 |
+
</div>
|
| 167 |
+
</div>
|
| 168 |
+
|
| 169 |
+
<div style="text-align: center; color: #64748b; font-size: 0.9rem; padding: 1rem;">
|
| 170 |
+
💡 برای نمایش نمودار تکنیکال پیشرفته، از صفحه تحلیل تکنیکال استفاده کنید
|
| 171 |
+
</div>
|
| 172 |
+
</div>
|
| 173 |
+
`;
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
formatNumber(num) {
|
| 177 |
+
if (num >= 1) {
|
| 178 |
+
return num.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
| 179 |
+
}
|
| 180 |
+
return num.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 8 });
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
formatLargeNumber(num) {
|
| 184 |
+
if (num >= 1e9) {
|
| 185 |
+
return (num / 1e9).toFixed(2) + 'B';
|
| 186 |
+
} else if (num >= 1e6) {
|
| 187 |
+
return (num / 1e6).toFixed(2) + 'M';
|
| 188 |
+
} else if (num >= 1e3) {
|
| 189 |
+
return (num / 1e3).toFixed(2) + 'K';
|
| 190 |
+
}
|
| 191 |
+
return num.toFixed(2);
|
| 192 |
+
}
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
// Initialize on page load
|
| 196 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 197 |
+
new ChartPage();
|
| 198 |
+
});
|
static/pages/chart/index.html
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="fa" dir="rtl">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>نمودار قیمت - Crypto Charts</title>
|
| 7 |
+
<link rel="stylesheet" href="/shared/css/global.css?v=3.0">
|
| 8 |
+
<link rel="stylesheet" href="/shared/css/design-system.css?v=3.0">
|
| 9 |
+
<link rel="stylesheet" href="./chart.css">
|
| 10 |
+
</head>
|
| 11 |
+
<body>
|
| 12 |
+
<div class="chart-container">
|
| 13 |
+
<header class="chart-header">
|
| 14 |
+
<h1 id="chart-title">نمودار قیمت</h1>
|
| 15 |
+
<div class="chart-controls">
|
| 16 |
+
<select id="symbol-select">
|
| 17 |
+
<option value="BTC">Bitcoin (BTC)</option>
|
| 18 |
+
<option value="ETH">Ethereum (ETH)</option>
|
| 19 |
+
<option value="BNB">Binance Coin (BNB)</option>
|
| 20 |
+
<option value="SOL">Solana (SOL)</option>
|
| 21 |
+
<option value="XRP">Ripple (XRP)</option>
|
| 22 |
+
</select>
|
| 23 |
+
<select id="timeframe-select">
|
| 24 |
+
<option value="1h">1 ساعت</option>
|
| 25 |
+
<option value="4h">4 ساعت</option>
|
| 26 |
+
<option value="1d" selected>1 روز</option>
|
| 27 |
+
<option value="1w">1 هفته</option>
|
| 28 |
+
<option value="1M">1 ماه</option>
|
| 29 |
+
</select>
|
| 30 |
+
<button id="refresh-btn" class="btn-primary">بروزرسانی</button>
|
| 31 |
+
</div>
|
| 32 |
+
</header>
|
| 33 |
+
|
| 34 |
+
<div class="chart-wrapper">
|
| 35 |
+
<div id="price-chart" class="chart-canvas"></div>
|
| 36 |
+
<div class="chart-info">
|
| 37 |
+
<div class="info-card">
|
| 38 |
+
<span class="info-label">قیمت فعلی:</span>
|
| 39 |
+
<span id="current-price" class="info-value">—</span>
|
| 40 |
+
</div>
|
| 41 |
+
<div class="info-card">
|
| 42 |
+
<span class="info-label">تغییر 24 ساعت:</span>
|
| 43 |
+
<span id="change-24h" class="info-value">—</span>
|
| 44 |
+
</div>
|
| 45 |
+
<div class="info-card">
|
| 46 |
+
<span class="info-label">حجم 24 ساعت:</span>
|
| 47 |
+
<span id="volume-24h" class="info-value">—</span>
|
| 48 |
+
</div>
|
| 49 |
+
<div class="info-card">
|
| 50 |
+
<span class="info-label">بالاترین:</span>
|
| 51 |
+
<span id="high-24h" class="info-value">—</span>
|
| 52 |
+
</div>
|
| 53 |
+
<div class="info-card">
|
| 54 |
+
<span class="info-label">پایینترین:</span>
|
| 55 |
+
<span id="low-24h" class="info-value">—</span>
|
| 56 |
+
</div>
|
| 57 |
+
</div>
|
| 58 |
+
</div>
|
| 59 |
+
|
| 60 |
+
<div class="chart-footer">
|
| 61 |
+
<p class="chart-note">💡 این نمودار از دادههای واقعی بازار استفاده میکند</p>
|
| 62 |
+
<button id="back-btn" class="btn-secondary" onclick="window.history.back()">بازگشت</button>
|
| 63 |
+
</div>
|
| 64 |
+
</div>
|
| 65 |
+
|
| 66 |
+
<script src="./chart.js"></script>
|
| 67 |
+
</body>
|
| 68 |
+
</html>
|
static/pages/system-monitor/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
<title>مانیتور سیستم | سیستم جامع ارزهای دیجیتال</title>
|
| 7 |
-
<link rel="stylesheet" href="system-monitor.css">
|
| 8 |
</head>
|
| 9 |
<body>
|
| 10 |
<div class="monitor-container">
|
|
@@ -213,6 +213,6 @@
|
|
| 213 |
</div>
|
| 214 |
</div>
|
| 215 |
|
| 216 |
-
<script src="system-monitor.js"></script>
|
| 217 |
</body>
|
| 218 |
</html>
|
|
|
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
<title>مانیتور سیستم | سیستم جامع ارزهای دیجیتال</title>
|
| 7 |
+
<link rel="stylesheet" href="./system-monitor.css">
|
| 8 |
</head>
|
| 9 |
<body>
|
| 10 |
<div class="monitor-container">
|
|
|
|
| 213 |
</div>
|
| 214 |
</div>
|
| 215 |
|
| 216 |
+
<script src="./system-monitor.js"></script>
|
| 217 |
</body>
|
| 218 |
</html>
|