Upload 40 files
Browse files- admin.html +39 -6
- static/js/apiClient.js +9 -19
- static/js/wsClient.js +29 -30
admin.html
CHANGED
|
@@ -9,14 +9,25 @@
|
|
| 9 |
<link rel="stylesheet" href="static/css/dashboard.css" />
|
| 10 |
<link rel="stylesheet" href="static/css/pro-dashboard.css" />
|
| 11 |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js" defer></script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
</head>
|
| 13 |
<body data-theme="dark">
|
| 14 |
<!-- ===== تنظیم بکاند (اسکریپت) ===== -->
|
| 15 |
<script>
|
| 16 |
// اگر بکاندت روی Space دیگهای هست اینجا عوض کن
|
| 17 |
// مثال: window.BACKEND_URL = 'https://aminmckee-crypto-intelligence.hf.space';
|
| 18 |
-
//
|
| 19 |
-
window.BACKEND_URL = 'https://
|
| 20 |
</script>
|
| 21 |
|
| 22 |
<div class="app-shell">
|
|
@@ -93,11 +104,33 @@
|
|
| 93 |
<p class="text-muted">Live market data, AI-powered sentiment analysis, and comprehensive crypto intelligence</p>
|
| 94 |
</div>
|
| 95 |
<div class="status-group">
|
|
|
|
| 96 |
<div class="status-pill" data-api-health data-state="warn">
|
| 97 |
-
<span class="status-dot"></span
|
| 98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
<div class="status-pill" data-ws-status data-state="warn">
|
| 100 |
-
<span class="status-dot"></span
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
</div>
|
| 102 |
</div>
|
| 103 |
</header>
|
|
@@ -521,4 +554,4 @@
|
|
| 521 |
<!-- Load App JS as ES6 Module -->
|
| 522 |
<script type="module" src="static/js/app.js"></script>
|
| 523 |
</body>
|
| 524 |
-
</html>
|
|
|
|
| 9 |
<link rel="stylesheet" href="static/css/dashboard.css" />
|
| 10 |
<link rel="stylesheet" href="static/css/pro-dashboard.css" />
|
| 11 |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js" defer></script>
|
| 12 |
+
|
| 13 |
+
<!-- SVG status icon tweaks -->
|
| 14 |
+
<style>
|
| 15 |
+
.status-pill .status-icon {
|
| 16 |
+
margin-inline: 0.35rem;
|
| 17 |
+
flex-shrink: 0;
|
| 18 |
+
}
|
| 19 |
+
.status-pill .status-label {
|
| 20 |
+
white-space: nowrap;
|
| 21 |
+
}
|
| 22 |
+
</style>
|
| 23 |
</head>
|
| 24 |
<body data-theme="dark">
|
| 25 |
<!-- ===== تنظیم بکاند (اسکریپت) ===== -->
|
| 26 |
<script>
|
| 27 |
// اگر بکاندت روی Space دیگهای هست اینجا عوض کن
|
| 28 |
// مثال: window.BACKEND_URL = 'https://aminmckee-crypto-intelligence.hf.space';
|
| 29 |
+
// الان تنظیم شده روی Space فعلی دادهها:
|
| 30 |
+
window.BACKEND_URL = 'https://really-amin-datasourceforcryptocurrency.hf.space';
|
| 31 |
</script>
|
| 32 |
|
| 33 |
<div class="app-shell">
|
|
|
|
| 104 |
<p class="text-muted">Live market data, AI-powered sentiment analysis, and comprehensive crypto intelligence</p>
|
| 105 |
</div>
|
| 106 |
<div class="status-group">
|
| 107 |
+
<!-- API Health with SVG icon -->
|
| 108 |
<div class="status-pill" data-api-health data-state="warn">
|
| 109 |
+
<span class="status-dot"></span>
|
| 110 |
+
<svg class="status-icon" width="14" height="14" viewBox="0 0 24 24" aria-hidden="true">
|
| 111 |
+
<path d="M4 7h16M4 12h10M4 17h7"
|
| 112 |
+
stroke="currentColor"
|
| 113 |
+
stroke-width="2"
|
| 114 |
+
stroke-linecap="round"
|
| 115 |
+
stroke-linejoin="round" />
|
| 116 |
+
</svg>
|
| 117 |
+
<span class="status-label">checking</span>
|
| 118 |
+
</div>
|
| 119 |
+
<!-- WebSocket Status with SVG icon -->
|
| 120 |
<div class="status-pill" data-ws-status data-state="warn">
|
| 121 |
+
<span class="status-dot"></span>
|
| 122 |
+
<svg class="status-icon" width="14" height="14" viewBox="0 0 24 24" aria-hidden="true">
|
| 123 |
+
<path d="M4 5h16v6H4z"
|
| 124 |
+
stroke="currentColor"
|
| 125 |
+
stroke-width="2"
|
| 126 |
+
fill="none"
|
| 127 |
+
stroke-linejoin="round" />
|
| 128 |
+
<path d="M8 15h8M10 19h4"
|
| 129 |
+
stroke="currentColor"
|
| 130 |
+
stroke-width="2"
|
| 131 |
+
stroke-linecap="round" />
|
| 132 |
+
</svg>
|
| 133 |
+
<span class="status-label">connecting</span>
|
| 134 |
</div>
|
| 135 |
</div>
|
| 136 |
</header>
|
|
|
|
| 554 |
<!-- Load App JS as ES6 Module -->
|
| 555 |
<script type="module" src="static/js/app.js"></script>
|
| 556 |
</body>
|
| 557 |
+
</html>
|
static/js/apiClient.js
CHANGED
|
@@ -2,26 +2,15 @@ const DEFAULT_TTL = 60 * 1000; // 1 minute cache
|
|
| 2 |
|
| 3 |
class ApiClient {
|
| 4 |
constructor() {
|
| 5 |
-
// ====
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
//
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
? window.BACKEND_URL.trim().replace(/\/$/, '')
|
| 12 |
-
: null) ||
|
| 13 |
-
(window.location.origin.includes('hf.space')
|
| 14 |
-
? window.location.origin.replace(/\/$/, '')
|
| 15 |
-
: null) ||
|
| 16 |
-
'https://aminmckee-crypto-intelligence.hf.space';
|
| 17 |
-
|
| 18 |
-
// تضمین نهایی: اگر به هر دلیلی خالی بود
|
| 19 |
-
if (!this.baseURL || this.baseURL === 'null' || this.baseURL === '') {
|
| 20 |
-
this.baseURL = 'https://aminmckee-crypto-intelligence.hf.space';
|
| 21 |
}
|
| 22 |
|
| 23 |
-
console.log('[ApiClient] Backend
|
| 24 |
-
// ==== پایان اصلاح ====
|
| 25 |
|
| 26 |
this.cache = new Map();
|
| 27 |
this.requestLogs = [];
|
|
@@ -79,7 +68,8 @@ class ApiClient {
|
|
| 79 |
}
|
| 80 |
|
| 81 |
const started = performance.now();
|
| 82 |
-
const randomId = (window.crypto && window.crypto.randomUUID && window.crypto.randomUUID())
|
|
|
|
| 83 |
const entry = {
|
| 84 |
id: randomId,
|
| 85 |
method,
|
|
|
|
| 2 |
|
| 3 |
class ApiClient {
|
| 4 |
constructor() {
|
| 5 |
+
// ==== آدرس صحیح بکاند شما (Really-amin) ====
|
| 6 |
+
this.baseURL = 'https://really-amin-datasourceforcryptocurrency.hf.space';
|
| 7 |
+
|
| 8 |
+
// اگر بعداً بخوای دستی عوض کنی، از HTML استفاده کن
|
| 9 |
+
if (typeof window.BACKEND_URL === 'string' && window.BACKEND_URL.trim()) {
|
| 10 |
+
this.baseURL = window.BACKEND_URL.trim().replace(/\/$/, '');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
}
|
| 12 |
|
| 13 |
+
console.log('[ApiClient] Using Backend:', this.baseURL);
|
|
|
|
| 14 |
|
| 15 |
this.cache = new Map();
|
| 16 |
this.requestLogs = [];
|
|
|
|
| 68 |
}
|
| 69 |
|
| 70 |
const started = performance.now();
|
| 71 |
+
const randomId = (window.crypto && window.crypto.randomUUID && window.crypto.randomUUID())
|
| 72 |
+
|| `${Date.now()}-${Math.random()}`;
|
| 73 |
const entry = {
|
| 74 |
id: randomId,
|
| 75 |
method,
|
static/js/wsClient.js
CHANGED
|
@@ -11,11 +11,12 @@ class WSClient {
|
|
| 11 |
this.shouldReconnect = true;
|
| 12 |
}
|
| 13 |
|
|
|
|
| 14 |
get url() {
|
| 15 |
-
|
| 16 |
-
const wsProtocol = protocol === 'https:' ? 'wss:' : 'ws:';
|
| 17 |
-
return `${wsProtocol}//${host}/ws`;
|
| 18 |
}
|
|
|
|
|
|
|
| 19 |
|
| 20 |
logEvent(event) {
|
| 21 |
const entry = { ...event, time: new Date().toISOString() };
|
|
@@ -35,21 +36,21 @@ class WSClient {
|
|
| 35 |
}
|
| 36 |
|
| 37 |
subscribe(type, callback) {
|
| 38 |
-
if (!this.typeSubscribers.has(type))
|
| 39 |
-
this.typeSubscribers.set(type, new Set());
|
| 40 |
-
}
|
| 41 |
const set = this.typeSubscribers.get(type);
|
| 42 |
set.add(callback);
|
| 43 |
return () => set.delete(callback);
|
| 44 |
}
|
| 45 |
|
| 46 |
updateStatus(newStatus) {
|
| 47 |
-
this.status
|
| 48 |
-
|
|
|
|
|
|
|
| 49 |
}
|
| 50 |
|
| 51 |
connect() {
|
| 52 |
-
if (this.socket && (this.
|
| 53 |
return;
|
| 54 |
}
|
| 55 |
|
|
@@ -57,26 +58,27 @@ class WSClient {
|
|
| 57 |
this.socket = new WebSocket(this.url);
|
| 58 |
this.logEvent({ type: 'status', status: 'connecting' });
|
| 59 |
|
| 60 |
-
this.socket.
|
| 61 |
this.backoff = 1000;
|
| 62 |
this.updateStatus('connected');
|
| 63 |
this.logEvent({ type: 'status', status: 'connected' });
|
| 64 |
-
|
|
|
|
| 65 |
|
| 66 |
-
this.socket.
|
| 67 |
try {
|
| 68 |
const data = JSON.parse(event.data);
|
| 69 |
this.logEvent({ type: 'message', messageType: data.type || 'unknown' });
|
| 70 |
-
this.globalSubscribers.forEach(
|
| 71 |
if (data.type && this.typeSubscribers.has(data.type)) {
|
| 72 |
-
this.typeSubscribers.get(data.type).forEach(
|
| 73 |
}
|
| 74 |
-
} catch (
|
| 75 |
-
console.error('WS
|
| 76 |
}
|
| 77 |
-
}
|
| 78 |
|
| 79 |
-
this.socket.
|
| 80 |
this.updateStatus('disconnected');
|
| 81 |
this.logEvent({ type: 'status', status: 'disconnected' });
|
| 82 |
if (this.shouldReconnect) {
|
|
@@ -84,22 +86,17 @@ class WSClient {
|
|
| 84 |
this.backoff = Math.min(this.backoff * 2, this.maxBackoff);
|
| 85 |
setTimeout(() => this.connect(), delay);
|
| 86 |
}
|
| 87 |
-
}
|
| 88 |
|
| 89 |
-
this.socket.
|
| 90 |
-
console.error('WebSocket error', error);
|
| 91 |
-
this.logEvent({ type: 'error', details:
|
| 92 |
-
|
| 93 |
-
this.socket.close();
|
| 94 |
-
}
|
| 95 |
-
});
|
| 96 |
}
|
| 97 |
|
| 98 |
disconnect() {
|
| 99 |
this.shouldReconnect = false;
|
| 100 |
-
if (this.socket)
|
| 101 |
-
this.socket.close();
|
| 102 |
-
}
|
| 103 |
}
|
| 104 |
|
| 105 |
getEvents() {
|
|
@@ -108,4 +105,6 @@ class WSClient {
|
|
| 108 |
}
|
| 109 |
|
| 110 |
const wsClient = new WSClient();
|
| 111 |
-
|
|
|
|
|
|
|
|
|
| 11 |
this.shouldReconnect = true;
|
| 12 |
}
|
| 13 |
|
| 14 |
+
// ==== اصلاح اصلی: همیشه به Space شما وصل بشه ====
|
| 15 |
get url() {
|
| 16 |
+
return 'wss://really-amin-datasourceforcryptocurrency.hf.space/ws';
|
|
|
|
|
|
|
| 17 |
}
|
| 18 |
+
// اگر بعداً بخوای از HTML کنترل کنی:
|
| 19 |
+
// return window.WS_URL || 'wss://really-amin-datasourceforcryptocurrency.hf.space/ws';
|
| 20 |
|
| 21 |
logEvent(event) {
|
| 22 |
const entry = { ...event, time: new Date().toISOString() };
|
|
|
|
| 36 |
}
|
| 37 |
|
| 38 |
subscribe(type, callback) {
|
| 39 |
+
if (!this.typeSubscribers.has(type)) this.typeSubscribers.set(type, new Set());
|
|
|
|
|
|
|
| 40 |
const set = this.typeSubscribers.get(type);
|
| 41 |
set.add(callback);
|
| 42 |
return () => set.delete(callback);
|
| 43 |
}
|
| 44 |
|
| 45 |
updateStatus(newStatus) {
|
| 46 |
+
if (this.status !== newStatus) {
|
| 47 |
+
this.status = newStatus;
|
| 48 |
+
this.statusSubscribers.forEach(cb => cb(newStatus));
|
| 49 |
+
}
|
| 50 |
}
|
| 51 |
|
| 52 |
connect() {
|
| 53 |
+
if (this.socket && (this.socket.readyState === WebSocket.CONNECTING || this.socket.readyState === WebSocket.OPEN)) {
|
| 54 |
return;
|
| 55 |
}
|
| 56 |
|
|
|
|
| 58 |
this.socket = new WebSocket(this.url);
|
| 59 |
this.logEvent({ type: 'status', status: 'connecting' });
|
| 60 |
|
| 61 |
+
this.socket.onopen = () => {
|
| 62 |
this.backoff = 1000;
|
| 63 |
this.updateStatus('connected');
|
| 64 |
this.logEvent({ type: 'status', status: 'connected' });
|
| 65 |
+
console.log('[WS] Connected to Really-amin Space');
|
| 66 |
+
};
|
| 67 |
|
| 68 |
+
this.socket.onmessage = (event) => {
|
| 69 |
try {
|
| 70 |
const data = JSON.parse(event.data);
|
| 71 |
this.logEvent({ type: 'message', messageType: data.type || 'unknown' });
|
| 72 |
+
this.globalSubscribers.forEach(cb => cb(data));
|
| 73 |
if (data.type && this.typeSubscribers.has(data.type)) {
|
| 74 |
+
this.typeSubscribers.get(data.type).forEach(cb => cb(data));
|
| 75 |
}
|
| 76 |
+
} catch (e) {
|
| 77 |
+
console.error('WS parse error:', e);
|
| 78 |
}
|
| 79 |
+
};
|
| 80 |
|
| 81 |
+
this.socket.onclose = () => {
|
| 82 |
this.updateStatus('disconnected');
|
| 83 |
this.logEvent({ type: 'status', status: 'disconnected' });
|
| 84 |
if (this.shouldReconnect) {
|
|
|
|
| 86 |
this.backoff = Math.min(this.backoff * 2, this.maxBackoff);
|
| 87 |
setTimeout(() => this.connect(), delay);
|
| 88 |
}
|
| 89 |
+
};
|
| 90 |
|
| 91 |
+
this.socket.onerror = (error) => {
|
| 92 |
+
console.error('WebSocket error:', error);
|
| 93 |
+
this.logEvent({ type: 'error', details: 'connection failed' });
|
| 94 |
+
};
|
|
|
|
|
|
|
|
|
|
| 95 |
}
|
| 96 |
|
| 97 |
disconnect() {
|
| 98 |
this.shouldReconnect = false;
|
| 99 |
+
if (this.socket) this.socket.close();
|
|
|
|
|
|
|
| 100 |
}
|
| 101 |
|
| 102 |
getEvents() {
|
|
|
|
| 105 |
}
|
| 106 |
|
| 107 |
const wsClient = new WSClient();
|
| 108 |
+
wsClient.connect(); // خودکار وصل بشه
|
| 109 |
+
|
| 110 |
+
export default wsClient;
|