coding-proxy 0.2.3a2__py3-none-any.whl → 0.2.3a3__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.
- coding/proxy/server/dashboard.py +354 -154
- {coding_proxy-0.2.3a2.dist-info → coding_proxy-0.2.3a3.dist-info}/METADATA +1 -1
- {coding_proxy-0.2.3a2.dist-info → coding_proxy-0.2.3a3.dist-info}/RECORD +6 -6
- {coding_proxy-0.2.3a2.dist-info → coding_proxy-0.2.3a3.dist-info}/WHEEL +0 -0
- {coding_proxy-0.2.3a2.dist-info → coding_proxy-0.2.3a3.dist-info}/entry_points.txt +0 -0
- {coding_proxy-0.2.3a2.dist-info → coding_proxy-0.2.3a3.dist-info}/licenses/LICENSE +0 -0
coding/proxy/server/dashboard.py
CHANGED
|
@@ -56,6 +56,9 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
56
56
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
57
57
|
<title>Coding Proxy Dashboard</title>
|
|
58
58
|
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
|
59
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
60
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
61
|
+
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet" />
|
|
59
62
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"></script>
|
|
60
63
|
<style>
|
|
61
64
|
:root {
|
|
@@ -63,16 +66,26 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
63
66
|
--bg-card: #161b22;
|
|
64
67
|
--bg-card-hover: #1c2128;
|
|
65
68
|
--border: #30363d;
|
|
69
|
+
--border-subtle: rgba(48,54,61,.6);
|
|
66
70
|
--text-primary: #e6edf3;
|
|
67
71
|
--text-secondary: #8b949e;
|
|
72
|
+
--text-tertiary: #6e7681;
|
|
68
73
|
--accent-blue: #58a6ff;
|
|
69
74
|
--accent-green: #3fb950;
|
|
70
75
|
--accent-yellow: #d29922;
|
|
71
76
|
--accent-red: #f85149;
|
|
72
77
|
--accent-purple: #bc8cff;
|
|
73
78
|
--accent-orange: #ffa657;
|
|
74
|
-
--
|
|
75
|
-
--
|
|
79
|
+
--accent-teal: #39d353;
|
|
80
|
+
--radius: 10px;
|
|
81
|
+
--radius-sm: 6px;
|
|
82
|
+
--shadow: 0 1px 3px rgba(0,0,0,.4), 0 1px 2px rgba(0,0,0,.3);
|
|
83
|
+
--shadow-md: 0 4px 12px rgba(0,0,0,.4), 0 2px 4px rgba(0,0,0,.3);
|
|
84
|
+
--glow-blue: 0 0 0 1px rgba(88,166,255,.15), 0 4px 16px rgba(88,166,255,.06);
|
|
85
|
+
}
|
|
86
|
+
@keyframes fadeInUp {
|
|
87
|
+
from { opacity: 0; transform: translateY(10px); }
|
|
88
|
+
to { opacity: 1; transform: translateY(0); }
|
|
76
89
|
}
|
|
77
90
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
78
91
|
body {
|
|
@@ -85,9 +98,11 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
85
98
|
}
|
|
86
99
|
/* ── 头部 ── */
|
|
87
100
|
header {
|
|
88
|
-
background:
|
|
101
|
+
background: rgba(22,27,34,.85);
|
|
102
|
+
backdrop-filter: blur(12px);
|
|
103
|
+
-webkit-backdrop-filter: blur(12px);
|
|
89
104
|
border-bottom: 1px solid var(--border);
|
|
90
|
-
padding:
|
|
105
|
+
padding: 13px 24px;
|
|
91
106
|
display: flex;
|
|
92
107
|
align-items: center;
|
|
93
108
|
justify-content: space-between;
|
|
@@ -97,75 +112,105 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
97
112
|
}
|
|
98
113
|
.header-left { display: flex; align-items: center; gap: 12px; }
|
|
99
114
|
.logo {
|
|
100
|
-
width:
|
|
115
|
+
width: 30px; height: 30px;
|
|
101
116
|
background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple));
|
|
102
|
-
border-radius:
|
|
117
|
+
border-radius: 8px;
|
|
103
118
|
display: flex; align-items: center; justify-content: center;
|
|
104
|
-
font-size:
|
|
119
|
+
font-size: 15px; font-weight: 700; color: #fff;
|
|
120
|
+
box-shadow: 0 2px 8px rgba(88,166,255,.3);
|
|
105
121
|
}
|
|
106
|
-
h1 { font-size:
|
|
122
|
+
h1 { font-size: 15px; font-weight: 600; color: var(--text-primary); letter-spacing: -.2px; }
|
|
107
123
|
.header-right { display: flex; align-items: center; gap: 12px; }
|
|
108
124
|
.badge {
|
|
109
125
|
font-size: 11px; padding: 2px 8px;
|
|
110
126
|
border-radius: 12px;
|
|
111
|
-
background: rgba(88,166,255,.
|
|
127
|
+
background: rgba(88,166,255,.1);
|
|
112
128
|
color: var(--accent-blue);
|
|
113
|
-
border: 1px solid rgba(88,166,255,.
|
|
129
|
+
border: 1px solid rgba(88,166,255,.2);
|
|
130
|
+
font-family: 'JetBrains Mono', monospace;
|
|
114
131
|
}
|
|
115
|
-
.refresh-time { font-size:
|
|
132
|
+
.refresh-time { font-size: 11px; color: var(--text-tertiary); }
|
|
116
133
|
.btn-refresh {
|
|
117
|
-
padding: 5px 12px; border-radius: var(--radius);
|
|
118
|
-
background: rgba(48,54,61,.
|
|
134
|
+
padding: 5px 12px; border-radius: var(--radius-sm);
|
|
135
|
+
background: rgba(48,54,61,.5);
|
|
119
136
|
border: 1px solid var(--border);
|
|
120
|
-
color: var(--text-
|
|
137
|
+
color: var(--text-secondary);
|
|
121
138
|
font-size: 12px; cursor: pointer;
|
|
122
|
-
transition:
|
|
139
|
+
transition: all .2s ease;
|
|
140
|
+
}
|
|
141
|
+
.btn-refresh:hover {
|
|
142
|
+
background: var(--bg-card-hover);
|
|
143
|
+
color: var(--text-primary);
|
|
144
|
+
border-color: rgba(88,166,255,.4);
|
|
123
145
|
}
|
|
124
|
-
.btn-refresh:hover { background: var(--bg-card-hover); }
|
|
125
146
|
/* ── 主内容 ── */
|
|
126
|
-
main { padding: 20px 24px; max-width:
|
|
147
|
+
main { padding: 20px 24px; max-width: 1440px; margin: 0 auto; }
|
|
127
148
|
/* ── KPI 卡片 ── */
|
|
128
149
|
.kpi-grid {
|
|
129
150
|
display: grid;
|
|
130
|
-
grid-template-columns: repeat(auto-fit, minmax(
|
|
131
|
-
gap:
|
|
132
|
-
margin-bottom:
|
|
151
|
+
grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
|
|
152
|
+
gap: 12px;
|
|
153
|
+
margin-bottom: 18px;
|
|
133
154
|
}
|
|
134
155
|
.kpi-card {
|
|
135
156
|
background: var(--bg-card);
|
|
136
157
|
border: 1px solid var(--border);
|
|
137
158
|
border-radius: var(--radius);
|
|
138
|
-
padding: 16px
|
|
159
|
+
padding: 16px 18px 14px;
|
|
139
160
|
box-shadow: var(--shadow);
|
|
140
|
-
transition:
|
|
161
|
+
transition: all .2s ease;
|
|
162
|
+
animation: fadeInUp .4s ease both;
|
|
163
|
+
position: relative;
|
|
164
|
+
overflow: hidden;
|
|
141
165
|
}
|
|
142
|
-
.kpi-card
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
166
|
+
.kpi-card::before {
|
|
167
|
+
content: '';
|
|
168
|
+
position: absolute;
|
|
169
|
+
top: 0; left: 0; right: 0;
|
|
170
|
+
height: 2px;
|
|
171
|
+
border-radius: var(--radius) var(--radius) 0 0;
|
|
172
|
+
}
|
|
173
|
+
.kpi-card:nth-child(1)::before { background: var(--accent-blue); }
|
|
174
|
+
.kpi-card:nth-child(2)::before { background: var(--accent-purple); }
|
|
175
|
+
.kpi-card:nth-child(3)::before { background: var(--accent-green); }
|
|
176
|
+
.kpi-card:nth-child(4)::before { background: var(--accent-yellow); }
|
|
177
|
+
.kpi-card:nth-child(5)::before { background: var(--accent-red); }
|
|
178
|
+
.kpi-card:nth-child(6)::before { background: var(--accent-orange); }
|
|
179
|
+
.kpi-card:hover {
|
|
180
|
+
background: var(--bg-card-hover);
|
|
181
|
+
box-shadow: var(--glow-blue);
|
|
182
|
+
transform: translateY(-1px);
|
|
183
|
+
}
|
|
184
|
+
.kpi-header { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; }
|
|
185
|
+
.kpi-icon { font-size: 13px; opacity: .8; }
|
|
186
|
+
.kpi-label { font-size: 11px; color: var(--text-secondary); font-weight: 500; letter-spacing: .2px; }
|
|
187
|
+
.kpi-value {
|
|
188
|
+
font-size: 24px; font-weight: 700; line-height: 1.2;
|
|
189
|
+
font-family: 'JetBrains Mono', monospace;
|
|
190
|
+
letter-spacing: -0.5px;
|
|
191
|
+
}
|
|
192
|
+
.kpi-sub { font-size: 11px; color: var(--text-tertiary); margin-top: 5px; }
|
|
149
193
|
.color-blue { color: var(--accent-blue); }
|
|
150
194
|
.color-green { color: var(--accent-green); }
|
|
151
195
|
.color-yellow { color: var(--accent-yellow); }
|
|
152
196
|
.color-red { color: var(--accent-red); }
|
|
153
197
|
.color-purple { color: var(--accent-purple); }
|
|
198
|
+
.color-orange { color: var(--accent-orange); }
|
|
154
199
|
/* ── 图表网格 ── */
|
|
155
200
|
.charts-grid {
|
|
156
201
|
display: grid;
|
|
157
202
|
grid-template-columns: 1fr 2fr;
|
|
158
|
-
gap:
|
|
159
|
-
margin-bottom:
|
|
203
|
+
gap: 12px;
|
|
204
|
+
margin-bottom: 12px;
|
|
160
205
|
}
|
|
161
|
-
.charts-grid-
|
|
206
|
+
.charts-grid-2 {
|
|
162
207
|
display: grid;
|
|
163
208
|
grid-template-columns: 1fr 2fr;
|
|
164
|
-
gap:
|
|
165
|
-
margin-bottom:
|
|
209
|
+
gap: 12px;
|
|
210
|
+
margin-bottom: 12px;
|
|
166
211
|
}
|
|
167
|
-
@media (max-width:
|
|
168
|
-
.charts-grid, .charts-grid-
|
|
212
|
+
@media (max-width: 960px) {
|
|
213
|
+
.charts-grid, .charts-grid-2 { grid-template-columns: 1fr; }
|
|
169
214
|
}
|
|
170
215
|
.card {
|
|
171
216
|
background: var(--bg-card);
|
|
@@ -173,101 +218,131 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
173
218
|
border-radius: var(--radius);
|
|
174
219
|
padding: 16px 20px;
|
|
175
220
|
box-shadow: var(--shadow);
|
|
221
|
+
transition: box-shadow .2s ease;
|
|
222
|
+
animation: fadeInUp .4s ease both;
|
|
176
223
|
}
|
|
224
|
+
.card:hover { box-shadow: var(--shadow-md); }
|
|
177
225
|
.card-title {
|
|
178
|
-
font-size:
|
|
179
|
-
color: var(--text-
|
|
226
|
+
font-size: 11px; font-weight: 600;
|
|
227
|
+
color: var(--text-tertiary);
|
|
180
228
|
text-transform: uppercase;
|
|
181
|
-
letter-spacing: .
|
|
229
|
+
letter-spacing: .8px;
|
|
182
230
|
margin-bottom: 14px;
|
|
183
231
|
display: flex; align-items: center; justify-content: space-between;
|
|
184
232
|
}
|
|
185
233
|
.chart-wrap { position: relative; height: 220px; }
|
|
186
234
|
.chart-wrap-lg { position: relative; height: 240px; }
|
|
235
|
+
.chart-wrap-xl { position: relative; height: 260px; }
|
|
187
236
|
/* ── 供应商状态 ── */
|
|
188
|
-
.vendor-list { display: flex; flex-direction: column; gap:
|
|
237
|
+
.vendor-list { display: flex; flex-direction: column; gap: 8px; }
|
|
189
238
|
.vendor-item {
|
|
190
239
|
display: flex; align-items: center; justify-content: space-between;
|
|
191
240
|
padding: 10px 12px;
|
|
192
|
-
background: rgba(255,255,255,.
|
|
193
|
-
border: 1px solid var(--border);
|
|
194
|
-
border-radius:
|
|
241
|
+
background: rgba(255,255,255,.02);
|
|
242
|
+
border: 1px solid var(--border-subtle);
|
|
243
|
+
border-radius: var(--radius-sm);
|
|
244
|
+
transition: background .15s;
|
|
245
|
+
}
|
|
246
|
+
.vendor-item:hover { background: rgba(255,255,255,.04); }
|
|
247
|
+
.vendor-info { display: flex; align-items: center; gap: 10px; }
|
|
248
|
+
.vendor-avatar {
|
|
249
|
+
width: 28px; height: 28px; border-radius: 50%;
|
|
250
|
+
background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple));
|
|
251
|
+
display: flex; align-items: center; justify-content: center;
|
|
252
|
+
font-size: 11px; font-weight: 700; color: #fff;
|
|
253
|
+
flex-shrink: 0;
|
|
195
254
|
}
|
|
196
|
-
.vendor-name { font-weight: 600; font-size:
|
|
197
|
-
.vendor-badges { display: flex; gap:
|
|
255
|
+
.vendor-name { font-weight: 600; font-size: 12px; }
|
|
256
|
+
.vendor-badges { display: flex; gap: 5px; flex-wrap: wrap; align-items: center; }
|
|
198
257
|
.status-badge {
|
|
199
|
-
font-size:
|
|
258
|
+
font-size: 10px; padding: 2px 7px;
|
|
200
259
|
border-radius: 10px;
|
|
201
260
|
font-weight: 500;
|
|
202
261
|
}
|
|
203
|
-
.sb-ok { background: rgba(63,185,80,.
|
|
204
|
-
.sb-warn { background: rgba(210,153,34,.
|
|
205
|
-
.sb-err { background: rgba(248,81,73,.
|
|
206
|
-
.sb-info { background: rgba(88,166,255,.
|
|
207
|
-
.quota-bar-wrap { flex: 1; margin: 0
|
|
262
|
+
.sb-ok { background: rgba(63,185,80,.12); color: var(--accent-green); border: 1px solid rgba(63,185,80,.2); }
|
|
263
|
+
.sb-warn { background: rgba(210,153,34,.12); color: var(--accent-yellow); border: 1px solid rgba(210,153,34,.2); }
|
|
264
|
+
.sb-err { background: rgba(248,81,73,.12); color: var(--accent-red); border: 1px solid rgba(248,81,73,.2); }
|
|
265
|
+
.sb-info { background: rgba(88,166,255,.12); color: var(--accent-blue); border: 1px solid rgba(88,166,255,.2); }
|
|
266
|
+
.quota-bar-wrap { flex: 1; margin: 0 10px; max-width: 100px; }
|
|
208
267
|
.quota-bar-bg {
|
|
209
|
-
height:
|
|
210
|
-
background: rgba(255,255,255,.
|
|
268
|
+
height: 4px; border-radius: 2px;
|
|
269
|
+
background: rgba(255,255,255,.06);
|
|
211
270
|
overflow: hidden;
|
|
212
271
|
}
|
|
213
|
-
.quota-bar-fill {
|
|
214
|
-
|
|
272
|
+
.quota-bar-fill {
|
|
273
|
+
height: 100%; border-radius: 2px;
|
|
274
|
+
transition: width .6s cubic-bezier(.4,0,.2,1);
|
|
275
|
+
}
|
|
215
276
|
/* ── 故障转移表 ── */
|
|
216
277
|
.ft-table-wrap { overflow-x: auto; }
|
|
217
278
|
table { width: 100%; border-collapse: collapse; }
|
|
279
|
+
thead tr { position: sticky; top: 0; background: var(--bg-card); z-index: 1; }
|
|
218
280
|
th {
|
|
219
|
-
text-align: left; font-size:
|
|
220
|
-
font-weight:
|
|
281
|
+
text-align: left; font-size: 11px; color: var(--text-tertiary);
|
|
282
|
+
font-weight: 600; padding: 6px 10px;
|
|
221
283
|
border-bottom: 1px solid var(--border);
|
|
284
|
+
letter-spacing: .4px; text-transform: uppercase;
|
|
222
285
|
}
|
|
223
|
-
td { padding: 8px 10px; font-size: 13px; border-bottom: 1px solid
|
|
286
|
+
td { padding: 8px 10px; font-size: 13px; border-bottom: 1px solid var(--border-subtle); }
|
|
224
287
|
tr:last-child td { border-bottom: none; }
|
|
225
288
|
tr:hover td { background: rgba(255,255,255,.02); }
|
|
226
289
|
.tag-vendor {
|
|
227
290
|
display: inline-block;
|
|
228
|
-
font-size: 11px; padding:
|
|
291
|
+
font-size: 11px; padding: 2px 8px;
|
|
229
292
|
border-radius: 10px;
|
|
230
|
-
background: rgba(188,140,255,.
|
|
293
|
+
background: rgba(188,140,255,.1);
|
|
231
294
|
color: var(--accent-purple);
|
|
232
|
-
border: 1px solid rgba(188,140,255,.
|
|
295
|
+
border: 1px solid rgba(188,140,255,.2);
|
|
296
|
+
font-weight: 500;
|
|
233
297
|
}
|
|
234
|
-
.arrow { color: var(--text-
|
|
298
|
+
.arrow { color: var(--text-tertiary); margin: 0 4px; }
|
|
235
299
|
/* ── 时间区间选择栏 ── */
|
|
236
300
|
.time-range-bar {
|
|
237
301
|
display: flex; align-items: center; gap: 8px;
|
|
238
|
-
margin-bottom:
|
|
302
|
+
margin-bottom: 18px; flex-wrap: wrap;
|
|
303
|
+
padding: 10px 14px;
|
|
304
|
+
background: rgba(22,27,34,.6);
|
|
305
|
+
border: 1px solid var(--border-subtle);
|
|
306
|
+
border-radius: var(--radius);
|
|
307
|
+
backdrop-filter: blur(8px);
|
|
239
308
|
}
|
|
240
|
-
.time-range-label { font-size:
|
|
309
|
+
.time-range-label { font-size: 12px; color: var(--text-tertiary); font-weight: 500; }
|
|
241
310
|
.range-btn {
|
|
242
311
|
padding: 4px 14px; border-radius: 14px;
|
|
243
|
-
background:
|
|
244
|
-
border: 1px solid
|
|
312
|
+
background: transparent;
|
|
313
|
+
border: 1px solid transparent;
|
|
245
314
|
color: var(--text-secondary);
|
|
246
315
|
font-size: 12px; cursor: pointer;
|
|
247
|
-
transition:
|
|
316
|
+
transition: all .2s ease;
|
|
248
317
|
}
|
|
249
|
-
.range-btn:hover { background:
|
|
318
|
+
.range-btn:hover { background: rgba(255,255,255,.05); color: var(--text-primary); }
|
|
250
319
|
.range-btn.active {
|
|
251
|
-
background: rgba(88,166,255,.
|
|
252
|
-
border-color: rgba(88,166,255,.
|
|
320
|
+
background: rgba(88,166,255,.12);
|
|
321
|
+
border-color: rgba(88,166,255,.35);
|
|
253
322
|
color: var(--accent-blue);
|
|
323
|
+
font-weight: 500;
|
|
254
324
|
}
|
|
255
325
|
.range-custom { display: none; align-items: center; gap: 6px; }
|
|
256
326
|
.range-custom.visible { display: flex; }
|
|
257
327
|
.range-date {
|
|
258
|
-
padding: 3px
|
|
259
|
-
background:
|
|
328
|
+
padding: 3px 10px; border-radius: var(--radius-sm);
|
|
329
|
+
background: rgba(48,54,61,.4); border: 1px solid var(--border);
|
|
260
330
|
color: var(--text-primary); font-size: 12px;
|
|
261
331
|
color-scheme: dark;
|
|
332
|
+
transition: border-color .2s;
|
|
262
333
|
}
|
|
263
|
-
.range-
|
|
334
|
+
.range-date:focus { outline: none; border-color: rgba(88,166,255,.5); }
|
|
335
|
+
.range-sep { font-size: 12px; color: var(--text-tertiary); }
|
|
264
336
|
/* ── 空态 ── */
|
|
265
337
|
.empty {
|
|
266
338
|
text-align: center; padding: 32px;
|
|
267
|
-
color: var(--text-
|
|
339
|
+
color: var(--text-tertiary); font-size: 13px;
|
|
268
340
|
}
|
|
341
|
+
.empty-icon { font-size: 28px; margin-bottom: 8px; opacity: .5; }
|
|
269
342
|
/* ── 加载态 ── */
|
|
270
343
|
.loading { opacity: .4; pointer-events: none; }
|
|
344
|
+
/* ── 图表标签截断 ── */
|
|
345
|
+
.chart-legend-note { font-size: 11px; color: var(--text-tertiary); margin-top: 4px; text-align: center; }
|
|
271
346
|
</style>
|
|
272
347
|
</head>
|
|
273
348
|
<body>
|
|
@@ -286,28 +361,52 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
286
361
|
<main>
|
|
287
362
|
<!-- 时间区间选择器 -->
|
|
288
363
|
<div class="time-range-bar">
|
|
289
|
-
<span class="time-range-label"
|
|
290
|
-
<button class="range-btn active" onclick="setTimeRange(7, this)"
|
|
291
|
-
<button class="range-btn" onclick="setTimeRange(30, this)"
|
|
364
|
+
<span class="time-range-label">时间区间</span>
|
|
365
|
+
<button class="range-btn active" onclick="setTimeRange(7, this)">近 7 天</button>
|
|
366
|
+
<button class="range-btn" onclick="setTimeRange(30, this)">近 30 天</button>
|
|
292
367
|
<button class="range-btn" onclick="setTimeRange(0, this)">自选区间</button>
|
|
293
368
|
<div class="range-custom" id="range-custom">
|
|
294
369
|
<input type="date" id="range-start" class="range-date" onchange="applyCustomRange()" />
|
|
295
|
-
<span class="range-sep"
|
|
370
|
+
<span class="range-sep">→</span>
|
|
296
371
|
<input type="date" id="range-end" class="range-date" onchange="applyCustomRange()" />
|
|
297
372
|
</div>
|
|
298
373
|
</div>
|
|
299
374
|
|
|
300
375
|
<!-- KPI 卡片 -->
|
|
301
376
|
<div class="kpi-grid" id="kpi-grid">
|
|
302
|
-
<div class="kpi-card"
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
<div class="kpi-card"
|
|
377
|
+
<div class="kpi-card">
|
|
378
|
+
<div class="kpi-header"><span class="kpi-icon">📊</span><span class="kpi-label">今日请求数</span></div>
|
|
379
|
+
<div class="kpi-value color-blue" id="kpi-req-today">–</div>
|
|
380
|
+
<div class="kpi-sub" id="kpi-req-week">本周 –</div>
|
|
381
|
+
</div>
|
|
382
|
+
<div class="kpi-card">
|
|
383
|
+
<div class="kpi-header"><span class="kpi-icon">🔢</span><span class="kpi-label">今日 Token 总量</span></div>
|
|
384
|
+
<div class="kpi-value color-purple" id="kpi-tok-today">–</div>
|
|
385
|
+
<div class="kpi-sub" id="kpi-tok-week">本周 –</div>
|
|
386
|
+
</div>
|
|
387
|
+
<div class="kpi-card">
|
|
388
|
+
<div class="kpi-header"><span class="kpi-icon">💬</span><span class="kpi-label">今日输出 Token</span></div>
|
|
389
|
+
<div class="kpi-value color-green" id="kpi-out-today">–</div>
|
|
390
|
+
<div class="kpi-sub" id="kpi-out-week">本周 –</div>
|
|
391
|
+
</div>
|
|
392
|
+
<div class="kpi-card">
|
|
393
|
+
<div class="kpi-header"><span class="kpi-icon">💰</span><span class="kpi-label">今日费用估算</span></div>
|
|
394
|
+
<div class="kpi-value color-yellow" id="kpi-cost-today">–</div>
|
|
395
|
+
<div class="kpi-sub" id="kpi-cost-week">本周 –</div>
|
|
396
|
+
</div>
|
|
397
|
+
<div class="kpi-card">
|
|
398
|
+
<div class="kpi-header"><span class="kpi-icon">🔄</span><span class="kpi-label">故障转移(今日)</span></div>
|
|
399
|
+
<div class="kpi-value color-red" id="kpi-fo-today">–</div>
|
|
400
|
+
<div class="kpi-sub" id="kpi-fo-week">本周 –</div>
|
|
401
|
+
</div>
|
|
402
|
+
<div class="kpi-card">
|
|
403
|
+
<div class="kpi-header"><span class="kpi-icon">⚡</span><span class="kpi-label">平均延迟(今日)</span></div>
|
|
404
|
+
<div class="kpi-value color-orange" id="kpi-lat-today">–</div>
|
|
405
|
+
<div class="kpi-sub" id="kpi-lat-week">本周 –</div>
|
|
406
|
+
</div>
|
|
308
407
|
</div>
|
|
309
408
|
|
|
310
|
-
<!-- 供应商状态 +
|
|
409
|
+
<!-- 供应商状态 + 请求量趋势折线图 -->
|
|
311
410
|
<div class="charts-grid">
|
|
312
411
|
<div class="card">
|
|
313
412
|
<div class="card-title">供应商状态</div>
|
|
@@ -323,8 +422,8 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
323
422
|
</div>
|
|
324
423
|
</div>
|
|
325
424
|
|
|
326
|
-
<!-- 供应商分布 + Token
|
|
327
|
-
<div class="charts-grid-
|
|
425
|
+
<!-- 供应商分布 + Token 量趋势(按 vendor) -->
|
|
426
|
+
<div class="charts-grid-2">
|
|
328
427
|
<div class="card">
|
|
329
428
|
<div class="card-title" id="title-vendor-dist">供应商请求分布(近 7 天)</div>
|
|
330
429
|
<div class="chart-wrap">
|
|
@@ -332,13 +431,21 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
332
431
|
</div>
|
|
333
432
|
</div>
|
|
334
433
|
<div class="card">
|
|
335
|
-
<div class="card-title" id="title-token-timeline">近 7 天 Token
|
|
434
|
+
<div class="card-title" id="title-token-timeline">近 7 天 Token 量趋势(按供应商)</div>
|
|
336
435
|
<div class="chart-wrap-lg">
|
|
337
436
|
<canvas id="chart-token-timeline"></canvas>
|
|
338
437
|
</div>
|
|
339
438
|
</div>
|
|
340
439
|
</div>
|
|
341
440
|
|
|
441
|
+
<!-- Token 用量(按 Vendor / 模型)堆叠图 -->
|
|
442
|
+
<div class="card" style="margin-bottom:12px">
|
|
443
|
+
<div class="card-title" id="title-model-token-timeline">近 7 天 Token 用量(按 Vendor / 模型)</div>
|
|
444
|
+
<div class="chart-wrap-xl">
|
|
445
|
+
<canvas id="chart-model-token-timeline"></canvas>
|
|
446
|
+
</div>
|
|
447
|
+
</div>
|
|
448
|
+
|
|
342
449
|
<!-- 故障转移明细表 -->
|
|
343
450
|
<div class="card">
|
|
344
451
|
<div class="card-title">故障转移明细</div>
|
|
@@ -364,13 +471,9 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
364
471
|
const VENDOR_COLORS = [
|
|
365
472
|
'#58a6ff','#bc8cff','#3fb950','#ffa657','#f85149',
|
|
366
473
|
'#79c0ff','#d2a8ff','#56d364','#ffb77c','#ff7b72',
|
|
474
|
+
'#39d353','#e3b341','#a5d6ff','#f0a9eb','#7ee787',
|
|
475
|
+
'#ffa198','#cae8ff','#dbedff','#b6e3ff','#54aeff',
|
|
367
476
|
];
|
|
368
|
-
const TOKEN_COLORS = {
|
|
369
|
-
input: '#58a6ff',
|
|
370
|
-
output: '#3fb950',
|
|
371
|
-
cache_creation: '#d29922',
|
|
372
|
-
cache_read: '#bc8cff',
|
|
373
|
-
};
|
|
374
477
|
|
|
375
478
|
// ── 工具函数 ──────────────────────────────────────────────
|
|
376
479
|
function fmtTokens(n) {
|
|
@@ -385,20 +488,36 @@ function now() {
|
|
|
385
488
|
return new Date().toLocaleTimeString('zh-CN', {hour:'2-digit',minute:'2-digit',second:'2-digit'});
|
|
386
489
|
}
|
|
387
490
|
|
|
491
|
+
// ── 渐变填充工具 ──────────────────────────────────────────
|
|
492
|
+
function makeGradient(ctx, color) {
|
|
493
|
+
const h = ctx.canvas.height;
|
|
494
|
+
const grad = ctx.createLinearGradient(0, 0, 0, h);
|
|
495
|
+
grad.addColorStop(0, color + '44');
|
|
496
|
+
grad.addColorStop(1, color + '04');
|
|
497
|
+
return grad;
|
|
498
|
+
}
|
|
499
|
+
|
|
388
500
|
// ── Chart.js 全局默认 ─────────────────────────────────────
|
|
389
501
|
Chart.defaults.color = '#8b949e';
|
|
390
|
-
Chart.defaults.borderColor = '
|
|
502
|
+
Chart.defaults.borderColor = 'rgba(255,255,255,.04)';
|
|
391
503
|
Chart.defaults.font.family = '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif';
|
|
392
|
-
Chart.defaults.font.size =
|
|
504
|
+
Chart.defaults.font.size = 11;
|
|
505
|
+
|
|
506
|
+
const COMMON_SCALE_X = { grid: { display: false }, ticks: { maxTicksLimit: 10 } };
|
|
507
|
+
const COMMON_SCALE_Y = { grid: { color: 'rgba(255,255,255,.04)' }, beginAtZero: true };
|
|
508
|
+
const COMMON_LEGEND = { position: 'bottom', labels: { boxWidth: 8, padding: 14, usePointStyle: true, pointStyleWidth: 8, font: { size: 11 } } };
|
|
509
|
+
const COMMON_LINE_DATASET = { tension: .35, pointRadius: 0, pointHoverRadius: 5, borderWidth: 2 };
|
|
393
510
|
|
|
394
511
|
// ── 图表实例 ──────────────────────────────────────────────
|
|
395
512
|
let chartTimeline = null;
|
|
396
513
|
let chartVendorDist = null;
|
|
397
514
|
let chartTokenTimeline = null;
|
|
515
|
+
let chartModelTokenTimeline = null;
|
|
398
516
|
|
|
399
517
|
function destroyCharts() {
|
|
400
|
-
[chartTimeline, chartVendorDist, chartTokenTimeline]
|
|
401
|
-
|
|
518
|
+
[chartTimeline, chartVendorDist, chartTokenTimeline, chartModelTokenTimeline]
|
|
519
|
+
.forEach(c => c && c.destroy());
|
|
520
|
+
chartTimeline = chartVendorDist = chartTokenTimeline = chartModelTokenTimeline = null;
|
|
402
521
|
}
|
|
403
522
|
|
|
404
523
|
// ── 数据拉取 ──────────────────────────────────────────────
|
|
@@ -410,7 +529,7 @@ async function fetchJSON(url) {
|
|
|
410
529
|
|
|
411
530
|
// ── KPI 更新 ──────────────────────────────────────────────
|
|
412
531
|
function updateKPI(summary) {
|
|
413
|
-
const t = summary.today, w = summary.week
|
|
532
|
+
const t = summary.today, w = summary.week;
|
|
414
533
|
|
|
415
534
|
document.getElementById('kpi-req-today').textContent = fmtNum(t.requests);
|
|
416
535
|
document.getElementById('kpi-req-week').textContent = '本周 ' + fmtNum(w.requests);
|
|
@@ -464,7 +583,7 @@ function updateVendorStatus(status) {
|
|
|
464
583
|
const tiers = status.tiers || [];
|
|
465
584
|
const list = document.getElementById('vendor-list');
|
|
466
585
|
if (!tiers.length) {
|
|
467
|
-
list.innerHTML = '<div class="empty">无供应商数据</div>';
|
|
586
|
+
list.innerHTML = '<div class="empty"><div class="empty-icon">🔌</div>无供应商数据</div>';
|
|
468
587
|
return;
|
|
469
588
|
}
|
|
470
589
|
list.innerHTML = tiers.map(tier => {
|
|
@@ -475,25 +594,28 @@ function updateVendorStatus(status) {
|
|
|
475
594
|
const cbLabel = cbStateLabel(cb.state);
|
|
476
595
|
const pct = qg.usage_percent != null ? Math.round(qg.usage_percent) : null;
|
|
477
596
|
const wpct = wqg.usage_percent != null ? Math.round(wqg.usage_percent) : null;
|
|
597
|
+
const initial = (tier.name || '?').charAt(0).toUpperCase();
|
|
478
598
|
|
|
479
599
|
let quotaHTML = '';
|
|
480
600
|
if (pct != null) {
|
|
481
601
|
quotaHTML += `
|
|
482
|
-
<span class="status-badge ${quotaClass(pct)}"
|
|
602
|
+
<span class="status-badge ${quotaClass(pct)}">日配 ${pct}%</span>
|
|
483
603
|
<div class="quota-bar-wrap">
|
|
484
604
|
<div class="quota-bar-bg"><div class="quota-bar-fill" style="width:${Math.min(pct,100)}%;background:${quotaBarColor(pct)}"></div></div>
|
|
485
605
|
</div>`;
|
|
486
606
|
}
|
|
487
607
|
if (wpct != null) {
|
|
488
|
-
quotaHTML += `<span class="status-badge ${quotaClass(wpct)}"
|
|
608
|
+
quotaHTML += `<span class="status-badge ${quotaClass(wpct)}">周配 ${wpct}%</span>`;
|
|
489
609
|
}
|
|
490
610
|
|
|
491
611
|
const rlInfo = tier.rate_limit || {};
|
|
492
|
-
const rlHtml = rlInfo.limited
|
|
493
|
-
? `<span class="status-badge sb-warn">限速中</span>` : '';
|
|
612
|
+
const rlHtml = rlInfo.limited ? `<span class="status-badge sb-warn">限速中</span>` : '';
|
|
494
613
|
|
|
495
614
|
return `<div class="vendor-item">
|
|
496
|
-
<
|
|
615
|
+
<div class="vendor-info">
|
|
616
|
+
<div class="vendor-avatar">${initial}</div>
|
|
617
|
+
<span class="vendor-name">${tier.name}</span>
|
|
618
|
+
</div>
|
|
497
619
|
<div class="vendor-badges">
|
|
498
620
|
<span class="status-badge ${cbClass}">${cbLabel}${cb.failure_count ? ' ×'+cb.failure_count : ''}</span>
|
|
499
621
|
${quotaHTML}
|
|
@@ -503,10 +625,9 @@ function updateVendorStatus(status) {
|
|
|
503
625
|
}).join('');
|
|
504
626
|
}
|
|
505
627
|
|
|
506
|
-
// ──
|
|
628
|
+
// ── 时序折线图(请求量,按 vendor)────────────────────────
|
|
507
629
|
function buildTimeline(rows) {
|
|
508
|
-
|
|
509
|
-
const vendorDateMap = {}; // vendor → {date → count}
|
|
630
|
+
const vendorDateMap = {};
|
|
510
631
|
const allDates = new Set();
|
|
511
632
|
for (const r of rows) {
|
|
512
633
|
const v = r.vendor, d = r.date;
|
|
@@ -518,29 +639,30 @@ function buildTimeline(rows) {
|
|
|
518
639
|
const dates = [...allDates].sort();
|
|
519
640
|
const vendors = Object.keys(vendorDateMap).sort();
|
|
520
641
|
|
|
521
|
-
const datasets = vendors.map((v, i) => ({
|
|
522
|
-
label: v,
|
|
523
|
-
data: dates.map(d => vendorDateMap[v][d] || 0),
|
|
524
|
-
borderColor: VENDOR_COLORS[i % VENDOR_COLORS.length],
|
|
525
|
-
backgroundColor: VENDOR_COLORS[i % VENDOR_COLORS.length] + '22',
|
|
526
|
-
fill: true,
|
|
527
|
-
tension: .3,
|
|
528
|
-
pointRadius: 3,
|
|
529
|
-
pointHoverRadius: 5,
|
|
530
|
-
}));
|
|
531
|
-
|
|
532
642
|
if (chartTimeline) chartTimeline.destroy();
|
|
533
643
|
const ctx = document.getElementById('chart-timeline').getContext('2d');
|
|
644
|
+
const datasets = vendors.map((v, i) => {
|
|
645
|
+
const color = VENDOR_COLORS[i % VENDOR_COLORS.length];
|
|
646
|
+
return {
|
|
647
|
+
...COMMON_LINE_DATASET,
|
|
648
|
+
label: v,
|
|
649
|
+
data: dates.map(d => vendorDateMap[v][d] || 0),
|
|
650
|
+
borderColor: color,
|
|
651
|
+
backgroundColor: makeGradient(ctx, color),
|
|
652
|
+
fill: true,
|
|
653
|
+
};
|
|
654
|
+
});
|
|
655
|
+
|
|
534
656
|
chartTimeline = new Chart(ctx, {
|
|
535
657
|
type: 'line',
|
|
536
658
|
data: { labels: dates, datasets },
|
|
537
659
|
options: {
|
|
538
660
|
responsive: true, maintainAspectRatio: false,
|
|
539
661
|
interaction: { mode: 'index', intersect: false },
|
|
540
|
-
plugins: { legend:
|
|
662
|
+
plugins: { legend: COMMON_LEGEND },
|
|
541
663
|
scales: {
|
|
542
|
-
x:
|
|
543
|
-
y: {
|
|
664
|
+
x: COMMON_SCALE_X,
|
|
665
|
+
y: { ...COMMON_SCALE_Y, ticks: { precision: 0 } },
|
|
544
666
|
},
|
|
545
667
|
},
|
|
546
668
|
});
|
|
@@ -560,7 +682,7 @@ function buildVendorDist(rows) {
|
|
|
560
682
|
if (chartVendorDist) chartVendorDist.destroy();
|
|
561
683
|
const ctx = document.getElementById('chart-vendor-dist').getContext('2d');
|
|
562
684
|
if (!labels.length) {
|
|
563
|
-
ctx.canvas.parentElement.innerHTML = '<div class="empty">暂无数据</div>';
|
|
685
|
+
ctx.canvas.parentElement.innerHTML = '<div class="empty"><div class="empty-icon">📭</div>暂无数据</div>';
|
|
564
686
|
return;
|
|
565
687
|
}
|
|
566
688
|
chartVendorDist = new Chart(ctx, {
|
|
@@ -571,23 +693,22 @@ function buildVendorDist(rows) {
|
|
|
571
693
|
data,
|
|
572
694
|
backgroundColor: labels.map((_,i) => VENDOR_COLORS[i % VENDOR_COLORS.length]),
|
|
573
695
|
borderWidth: 0,
|
|
574
|
-
hoverOffset:
|
|
696
|
+
hoverOffset: 8,
|
|
575
697
|
}],
|
|
576
698
|
},
|
|
577
699
|
options: {
|
|
578
700
|
responsive: true, maintainAspectRatio: false,
|
|
579
701
|
plugins: {
|
|
580
|
-
legend:
|
|
581
|
-
tooltip: { callbacks: { label:
|
|
702
|
+
legend: COMMON_LEGEND,
|
|
703
|
+
tooltip: { callbacks: { label: c => ` ${c.label}: ${c.raw.toLocaleString()} 次` } },
|
|
582
704
|
},
|
|
583
705
|
},
|
|
584
706
|
});
|
|
585
707
|
}
|
|
586
708
|
|
|
587
|
-
// ── Token
|
|
709
|
+
// ── Token 量趋势折线图(按 vendor)───────────────────────
|
|
588
710
|
function buildTokenTimeline(rows) {
|
|
589
|
-
|
|
590
|
-
const vendorDateMap = {}; // vendor → {date → total_tokens}
|
|
711
|
+
const vendorDateMap = {};
|
|
591
712
|
const allDates = new Set();
|
|
592
713
|
for (const r of rows) {
|
|
593
714
|
const v = r.vendor, d = r.date;
|
|
@@ -604,20 +725,21 @@ function buildTokenTimeline(rows) {
|
|
|
604
725
|
if (chartTokenTimeline) chartTokenTimeline.destroy();
|
|
605
726
|
const ctx = document.getElementById('chart-token-timeline').getContext('2d');
|
|
606
727
|
if (!dates.length) {
|
|
607
|
-
ctx.canvas.parentElement.innerHTML = '<div class="empty">暂无数据</div>';
|
|
728
|
+
ctx.canvas.parentElement.innerHTML = '<div class="empty"><div class="empty-icon">📭</div>暂无数据</div>';
|
|
608
729
|
return;
|
|
609
730
|
}
|
|
610
731
|
|
|
611
|
-
const datasets = vendors.map((v, i) =>
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
732
|
+
const datasets = vendors.map((v, i) => {
|
|
733
|
+
const color = VENDOR_COLORS[i % VENDOR_COLORS.length];
|
|
734
|
+
return {
|
|
735
|
+
...COMMON_LINE_DATASET,
|
|
736
|
+
label: v,
|
|
737
|
+
data: dates.map(d => vendorDateMap[v][d] || 0),
|
|
738
|
+
borderColor: color,
|
|
739
|
+
backgroundColor: makeGradient(ctx, color),
|
|
740
|
+
fill: true,
|
|
741
|
+
};
|
|
742
|
+
});
|
|
621
743
|
|
|
622
744
|
chartTokenTimeline = new Chart(ctx, {
|
|
623
745
|
type: 'line',
|
|
@@ -625,11 +747,94 @@ function buildTokenTimeline(rows) {
|
|
|
625
747
|
options: {
|
|
626
748
|
responsive: true, maintainAspectRatio: false,
|
|
627
749
|
interaction: { mode: 'index', intersect: false },
|
|
628
|
-
plugins: {
|
|
750
|
+
plugins: {
|
|
751
|
+
legend: COMMON_LEGEND,
|
|
752
|
+
tooltip: { callbacks: { label: c => ` ${c.dataset.label}: ${fmtTokens(c.raw)}` } },
|
|
753
|
+
},
|
|
754
|
+
scales: {
|
|
755
|
+
x: COMMON_SCALE_X,
|
|
756
|
+
y: { ...COMMON_SCALE_Y, ticks: { callback: v => fmtTokens(v) } },
|
|
757
|
+
},
|
|
758
|
+
},
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// ── Token 用量趋势(按 Vendor / 模型,堆叠面积图)────────
|
|
763
|
+
function buildModelTokenTimeline(rows) {
|
|
764
|
+
const modelDateMap = {};
|
|
765
|
+
const allDates = new Set();
|
|
766
|
+
for (const r of rows) {
|
|
767
|
+
const key = (r.vendor || '?') + ' / ' + (r.model_served || '?');
|
|
768
|
+
const d = r.date;
|
|
769
|
+
if (!d) continue;
|
|
770
|
+
if (!modelDateMap[key]) modelDateMap[key] = {};
|
|
771
|
+
const total = (r.total_input || 0) + (r.total_output || 0)
|
|
772
|
+
+ (r.total_cache_creation || 0) + (r.total_cache_read || 0);
|
|
773
|
+
modelDateMap[key][d] = (modelDateMap[key][d] || 0) + total;
|
|
774
|
+
allDates.add(d);
|
|
775
|
+
}
|
|
776
|
+
const dates = [...allDates].sort();
|
|
777
|
+
// 按总量降序排列 key
|
|
778
|
+
const keys = Object.keys(modelDateMap).sort((a, b) => {
|
|
779
|
+
const sumA = Object.values(modelDateMap[a]).reduce((s, v) => s + v, 0);
|
|
780
|
+
const sumB = Object.values(modelDateMap[b]).reduce((s, v) => s + v, 0);
|
|
781
|
+
return sumB - sumA;
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
if (chartModelTokenTimeline) chartModelTokenTimeline.destroy();
|
|
785
|
+
const canvasEl = document.getElementById('chart-model-token-timeline');
|
|
786
|
+
if (!canvasEl) return;
|
|
787
|
+
const ctx = canvasEl.getContext('2d');
|
|
788
|
+
if (!dates.length || !keys.length) {
|
|
789
|
+
ctx.canvas.parentElement.innerHTML = '<div class="empty"><div class="empty-icon">📭</div>暂无数据</div>';
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
const datasets = keys.map((key, i) => {
|
|
794
|
+
const color = VENDOR_COLORS[i % VENDOR_COLORS.length];
|
|
795
|
+
return {
|
|
796
|
+
...COMMON_LINE_DATASET,
|
|
797
|
+
label: key,
|
|
798
|
+
data: dates.map(d => modelDateMap[key][d] || 0),
|
|
799
|
+
borderColor: color,
|
|
800
|
+
backgroundColor: color + '30',
|
|
801
|
+
fill: true,
|
|
802
|
+
};
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
chartModelTokenTimeline = new Chart(ctx, {
|
|
806
|
+
type: 'line',
|
|
807
|
+
data: { labels: dates, datasets },
|
|
808
|
+
options: {
|
|
809
|
+
responsive: true, maintainAspectRatio: false,
|
|
810
|
+
interaction: { mode: 'index', intersect: false },
|
|
811
|
+
plugins: {
|
|
812
|
+
legend: {
|
|
813
|
+
position: keys.length > 8 ? 'right' : 'bottom',
|
|
814
|
+
labels: {
|
|
815
|
+
...COMMON_LEGEND.labels,
|
|
816
|
+
generateLabels: chart => Chart.defaults.plugins.legend.labels.generateLabels(chart).map(item => {
|
|
817
|
+
const maxLen = 32;
|
|
818
|
+
if (item.text.length > maxLen) item.text = item.text.slice(0, maxLen) + '…';
|
|
819
|
+
return item;
|
|
820
|
+
}),
|
|
821
|
+
},
|
|
822
|
+
},
|
|
823
|
+
tooltip: {
|
|
824
|
+
callbacks: {
|
|
825
|
+
label: c => ` ${c.dataset.label}: ${fmtTokens(c.raw)}`,
|
|
826
|
+
footer: items => {
|
|
827
|
+
const total = items.reduce((s, i) => s + (i.raw || 0), 0);
|
|
828
|
+
return total > 0 ? '合计: ' + fmtTokens(total) : '';
|
|
829
|
+
},
|
|
830
|
+
},
|
|
831
|
+
},
|
|
832
|
+
},
|
|
629
833
|
scales: {
|
|
630
|
-
x:
|
|
834
|
+
x: COMMON_SCALE_X,
|
|
631
835
|
y: {
|
|
632
|
-
|
|
836
|
+
...COMMON_SCALE_Y,
|
|
837
|
+
stacked: true,
|
|
633
838
|
ticks: { callback: v => fmtTokens(v) },
|
|
634
839
|
},
|
|
635
840
|
},
|
|
@@ -641,14 +846,14 @@ function buildTokenTimeline(rows) {
|
|
|
641
846
|
function updateFtTable(failoverStats) {
|
|
642
847
|
const tbody = document.getElementById('ft-tbody');
|
|
643
848
|
if (!failoverStats || !failoverStats.length) {
|
|
644
|
-
tbody.innerHTML = '<tr><td colspan="3" class="empty">暂无故障转移记录</td></tr>';
|
|
849
|
+
tbody.innerHTML = '<tr><td colspan="3" class="empty"><div class="empty-icon">✅</div>暂无故障转移记录</td></tr>';
|
|
645
850
|
return;
|
|
646
851
|
}
|
|
647
852
|
tbody.innerHTML = failoverStats.map(r => `
|
|
648
853
|
<tr>
|
|
649
854
|
<td><span class="tag-vendor">${r.failover_from || 'unknown'}</span></td>
|
|
650
855
|
<td><span class="tag-vendor">${r.vendor || ''}</span></td>
|
|
651
|
-
<td>${fmtNum(r.count)}</td>
|
|
856
|
+
<td><span style="font-family:'JetBrains Mono',monospace">${fmtNum(r.count)}</span></td>
|
|
652
857
|
</tr>`).join('');
|
|
653
858
|
}
|
|
654
859
|
|
|
@@ -662,7 +867,6 @@ function setTimeRange(days, btn) {
|
|
|
662
867
|
const customEl = document.getElementById('range-custom');
|
|
663
868
|
if (days === 0) {
|
|
664
869
|
customEl.classList.add('visible');
|
|
665
|
-
// 初始化日期:默认今天往前 7 天
|
|
666
870
|
const today = new Date();
|
|
667
871
|
const weekAgo = new Date(today);
|
|
668
872
|
weekAgo.setDate(weekAgo.getDate() - 6);
|
|
@@ -686,20 +890,16 @@ function applyCustomRange() {
|
|
|
686
890
|
refresh();
|
|
687
891
|
}
|
|
688
892
|
|
|
689
|
-
function rangeLabel() {
|
|
690
|
-
if (currentDays <= 7) return '近 7 天';
|
|
691
|
-
if (currentDays <= 30) return '近 30 天';
|
|
692
|
-
return '近 ' + currentDays + ' 天';
|
|
693
|
-
}
|
|
694
|
-
|
|
695
893
|
function updateChartTitles(days) {
|
|
696
894
|
const label = days <= 7 ? '近 7 天' : (days <= 30 ? '近 30 天' : '近 ' + days + ' 天');
|
|
697
895
|
const tl = document.getElementById('title-timeline');
|
|
698
896
|
const tt = document.getElementById('title-token-timeline');
|
|
699
897
|
const vd = document.getElementById('title-vendor-dist');
|
|
898
|
+
const mt = document.getElementById('title-model-token-timeline');
|
|
700
899
|
if (tl) tl.textContent = label + ' 请求量趋势';
|
|
701
|
-
if (tt) tt.textContent = label + ' Token
|
|
900
|
+
if (tt) tt.textContent = label + ' Token 量趋势(按供应商)';
|
|
702
901
|
if (vd) vd.textContent = '供应商请求分布(' + label + ')';
|
|
902
|
+
if (mt) mt.textContent = label + ' Token 用量(按 Vendor / 模型)';
|
|
703
903
|
}
|
|
704
904
|
|
|
705
905
|
// ── 主刷新逻辑 ────────────────────────────────────────────
|
|
@@ -716,7 +916,6 @@ async function refresh() {
|
|
|
716
916
|
fetchJSON('/api/status'),
|
|
717
917
|
]);
|
|
718
918
|
|
|
719
|
-
// 版本号
|
|
720
919
|
if (summary.version) {
|
|
721
920
|
document.getElementById('version-badge').textContent = 'v' + summary.version;
|
|
722
921
|
}
|
|
@@ -729,6 +928,7 @@ async function refresh() {
|
|
|
729
928
|
buildTimeline(rows);
|
|
730
929
|
buildVendorDist(rows);
|
|
731
930
|
buildTokenTimeline(rows);
|
|
931
|
+
buildModelTokenTimeline(rows);
|
|
732
932
|
|
|
733
933
|
updateFtTable(summary.failover_stats || []);
|
|
734
934
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: coding-proxy
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3a3
|
|
4
4
|
Summary: A High-Availability, Transparent, and Smart Multi-Vendor Proxy for Claude Code. Support Claude Plans, GitHub Copilot, Google Antigravity, ZAI/GLM, MiniMax, Qwen, Xiaomi, Kimi, Doubao...
|
|
5
5
|
Project-URL: Source Code, https://github.com/ThreeFish-AI/coding-proxy
|
|
6
6
|
Project-URL: User Guide, https://github.com/ThreeFish-AI/coding-proxy/blob/master/docs/user-guide.md
|
|
@@ -56,7 +56,7 @@ coding/proxy/routing/usage_parser.py,sha256=j4G0ArFduQ2D4Yeuad94DVlwdc-JvSh7SJLV
|
|
|
56
56
|
coding/proxy/routing/usage_recorder.py,sha256=pObOrX2yIITTiyojl1fJcqO0yWWpbP4KqsJvFdmlt04,6273
|
|
57
57
|
coding/proxy/server/__init__.py,sha256=KeH7mEu36v9v27m3VgeSxSeFz9sLTJrxS_EVATJ7Vks,20
|
|
58
58
|
coding/proxy/server/app.py,sha256=kRGgb772dZu8200LPnn7Nt0IU5oagcbBf_Vc5ynxxzE,5599
|
|
59
|
-
coding/proxy/server/dashboard.py,sha256=
|
|
59
|
+
coding/proxy/server/dashboard.py,sha256=GD1s_k7B-fYaKv5CuDJ73o5XfKQ-eoCB3XXqruph49I,42627
|
|
60
60
|
coding/proxy/server/factory.py,sha256=w8VFvxoogw9K9sO8MlT6bIP7xM7mR6sCohrZle9y_Gg,9985
|
|
61
61
|
coding/proxy/server/request_normalizer.py,sha256=XUqpZP42_DJmsoX9aMFVk7oLWDeG3UgXdmCv0A76FN4,12334
|
|
62
62
|
coding/proxy/server/responses.py,sha256=i0ugnLRNOdRYGHEWxkwsxR35ChmdMQsSaD8AjRluTn4,2167
|
|
@@ -80,8 +80,8 @@ coding/proxy/vendors/native_anthropic.py,sha256=SxtM71PDci0gqLqiwCrFnT410SnSoD7F
|
|
|
80
80
|
coding/proxy/vendors/token_manager.py,sha256=s10t4Com0jNnKGkPyJ_HpG5SjHrCEJvfArEOAaPKA_k,4189
|
|
81
81
|
coding/proxy/vendors/xiaomi.py,sha256=E-GcmJBZh7GOtDFonxZmlf0hKRhrlrXzL0IxHFRYcRo,860
|
|
82
82
|
coding/proxy/vendors/zhipu.py,sha256=3j_rqNFu1CX-B5ugtrL6Y1OeWSy9yiqsVa9Bi1ssaAA,1062
|
|
83
|
-
coding_proxy-0.2.
|
|
84
|
-
coding_proxy-0.2.
|
|
85
|
-
coding_proxy-0.2.
|
|
86
|
-
coding_proxy-0.2.
|
|
87
|
-
coding_proxy-0.2.
|
|
83
|
+
coding_proxy-0.2.3a3.dist-info/METADATA,sha256=qEW8vHGSB9gQZhF4OMgK68ZRq2h1wDw0MtUvt8YmvYY,10819
|
|
84
|
+
coding_proxy-0.2.3a3.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
85
|
+
coding_proxy-0.2.3a3.dist-info/entry_points.txt,sha256=moIVzt5ho0Wk9B47LOo2SEAbhzuDDHWi-EfM30U0XBg,54
|
|
86
|
+
coding_proxy-0.2.3a3.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
87
|
+
coding_proxy-0.2.3a3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|