openclaw-agent-dashboard 1.0.21 → 1.0.23

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.
Files changed (135) hide show
  1. package/README.md +55 -321
  2. package/frontend-dist/assets/index-B7XqKAxm.css +1 -0
  3. package/frontend-dist/assets/index-CxJaSYyo.js +24 -0
  4. package/{frontend → frontend-dist}/index.html +2 -1
  5. package/{plugin/openclaw.plugin.json → openclaw.plugin.json} +2 -2
  6. package/package.json +21 -13
  7. package/.github/workflows/release.yml +0 -56
  8. package/VERSION_DISPLAY_delivery.md +0 -242
  9. package/VERSION_DISPLAY_implementation_summary.md +0 -315
  10. package/design_manifest.md +0 -100
  11. package/docs/CHANGELOG_AGENT_MODIFICATIONS.md +0 -132
  12. package/docs/MAINTAINER_RELEASE_WORKFLOW.md +0 -211
  13. package/docs/Openclaw-Agent-Dashboard/345/217/221/345/270/203/344/270/216/346/233/264/346/226/260.md +0 -147
  14. package/docs/RELEASE-LATEST.md +0 -189
  15. package/docs/RELEASE-MODEL-CONFIG.md +0 -95
  16. package/docs/WINDOWS_INSTALL_TROUBLESHOOTING.md +0 -171
  17. package/docs/design/.gitkeep +0 -0
  18. package/docs/design/VERSION_DISPLAY_design.md +0 -1236
  19. package/docs/release-guide.md +0 -259
  20. package/docs/release-operations-manual.md +0 -167
  21. package/docs/reviews/.gitkeep +0 -0
  22. package/docs/reviews/approval_history.json +0 -14
  23. package/docs/reviews/cr_VERSION_DISPLAY.md +0 -397
  24. package/docs/reviews/traceability_manifest.json +0 -279
  25. package/docs/specs/VERSION_DISPLAY_spec.md +0 -371
  26. package/docs/specs/tr3-install-system.md +0 -580
  27. package/docs/windows-collaboration-model-paths-troubleshooting.md +0 -0
  28. package/frontend/package-lock.json +0 -1240
  29. package/frontend/package.json +0 -19
  30. package/frontend/src/App.vue +0 -355
  31. package/frontend/src/components/AgentCard.vue +0 -796
  32. package/frontend/src/components/AgentConfigPanel.vue +0 -539
  33. package/frontend/src/components/AgentDetailPanel.vue +0 -738
  34. package/frontend/src/components/ErrorAnalysisView.vue +0 -546
  35. package/frontend/src/components/ErrorCenterPanel.vue +0 -844
  36. package/frontend/src/components/PerformanceMonitor.vue +0 -515
  37. package/frontend/src/components/SettingsPanel.vue +0 -236
  38. package/frontend/src/components/TokenAnalysisPanel.vue +0 -683
  39. package/frontend/src/components/chain/ChainEdge.vue +0 -85
  40. package/frontend/src/components/chain/ChainNode.vue +0 -166
  41. package/frontend/src/components/chain/TaskChainView.vue +0 -425
  42. package/frontend/src/components/chain/index.ts +0 -3
  43. package/frontend/src/components/chain/types.ts +0 -70
  44. package/frontend/src/components/collaboration/CollaborationFlowSection.vue +0 -1032
  45. package/frontend/src/components/collaboration/CollaborationFlowWrapper.vue +0 -113
  46. package/frontend/src/components/common/VersionDisplay.vue +0 -187
  47. package/frontend/src/components/performance/PerformancePanel.vue +0 -119
  48. package/frontend/src/components/performance/PerformanceSection.vue +0 -1137
  49. package/frontend/src/components/tasks/TaskStatusSection.vue +0 -973
  50. package/frontend/src/components/timeline/TimelineConnector.vue +0 -31
  51. package/frontend/src/components/timeline/TimelineRound.vue +0 -135
  52. package/frontend/src/components/timeline/TimelineStep.vue +0 -691
  53. package/frontend/src/components/timeline/TimelineToolLink.vue +0 -109
  54. package/frontend/src/components/timeline/TimelineView.vue +0 -540
  55. package/frontend/src/components/timeline/index.ts +0 -5
  56. package/frontend/src/components/timeline/types.ts +0 -120
  57. package/frontend/src/composables/index.ts +0 -7
  58. package/frontend/src/composables/useDebounce.ts +0 -48
  59. package/frontend/src/composables/useRealtime.ts +0 -52
  60. package/frontend/src/composables/useState.ts +0 -52
  61. package/frontend/src/composables/useThrottle.ts +0 -46
  62. package/frontend/src/composables/useVirtualScroll.ts +0 -106
  63. package/frontend/src/main.ts +0 -4
  64. package/frontend/src/managers/EventDispatcher.ts +0 -127
  65. package/frontend/src/managers/RealtimeDataManager.ts +0 -302
  66. package/frontend/src/managers/StateManager.ts +0 -128
  67. package/frontend/src/managers/index.ts +0 -5
  68. package/frontend/src/types/collaboration.ts +0 -135
  69. package/frontend/src/types/index.ts +0 -20
  70. package/frontend/src/types/performance.ts +0 -105
  71. package/frontend/src/types/task.ts +0 -38
  72. package/frontend/vite.config.ts +0 -18
  73. package/legacy_code_anatomy.md +0 -518
  74. package/plugin/README.md +0 -99
  75. package/plugin/config.json.example +0 -1
  76. package/plugin/package.json +0 -26
  77. package/scripts/build-plugin.js +0 -81
  78. package/scripts/bundle.sh +0 -62
  79. package/scripts/install-plugin.sh +0 -162
  80. package/scripts/install-python-deps.sh +0 -226
  81. package/scripts/install.js +0 -684
  82. package/scripts/install.sh +0 -367
  83. package/scripts/lib/common.sh +0 -137
  84. package/scripts/release-pack.sh +0 -110
  85. package/scripts/start.js +0 -50
  86. package/scripts/test_available_models.py +0 -284
  87. package/scripts/test_version_display.sh +0 -128
  88. package/scripts/test_websocket_ping.py +0 -44
  89. package/session_registry.json +0 -58
  90. package/tests/.gitkeep +0 -0
  91. package/tests/qa_regression_report.md +0 -359
  92. package/tests/qa_version_display_report.md +0 -598
  93. /package/{src/backend → dashboard}/agents.py +0 -0
  94. /package/{src/backend → dashboard}/api/__init__.py +0 -0
  95. /package/{src/backend → dashboard}/api/agent_config_api.py +0 -0
  96. /package/{src/backend → dashboard}/api/agents.py +0 -0
  97. /package/{src/backend → dashboard}/api/agents_config.py +0 -0
  98. /package/{src/backend → dashboard}/api/chains.py +0 -0
  99. /package/{src/backend → dashboard}/api/collaboration.py +0 -0
  100. /package/{src/backend → dashboard}/api/debug_paths.py +0 -0
  101. /package/{src/backend → dashboard}/api/error_analysis.py +0 -0
  102. /package/{src/backend → dashboard}/api/errors.py +0 -0
  103. /package/{src/backend → dashboard}/api/performance.py +0 -0
  104. /package/{src/backend → dashboard}/api/subagents.py +0 -0
  105. /package/{src/backend → dashboard}/api/timeline.py +0 -0
  106. /package/{src/backend → dashboard}/api/version.py +0 -0
  107. /package/{src/backend → dashboard}/api/websocket.py +0 -0
  108. /package/{src/backend → dashboard}/collaboration.py +0 -0
  109. /package/{src/backend → dashboard}/data/__init__.py +0 -0
  110. /package/{src/backend → dashboard}/data/agent_config_manager.py +0 -0
  111. /package/{src/backend → dashboard}/data/chain_reader.py +0 -0
  112. /package/{src/backend → dashboard}/data/config_reader.py +0 -0
  113. /package/{src/backend → dashboard}/data/error_analyzer.py +0 -0
  114. /package/{src/backend → dashboard}/data/session_reader.py +0 -0
  115. /package/{src/backend → dashboard}/data/subagent_reader.py +0 -0
  116. /package/{src/backend → dashboard}/data/task_history.py +0 -0
  117. /package/{src/backend → dashboard}/data/timeline_reader.py +0 -0
  118. /package/{src/backend → dashboard}/data/version_info_reader.py +0 -0
  119. /package/{src/backend → dashboard}/errors.py +0 -0
  120. /package/{src/backend → dashboard}/main.py +0 -0
  121. /package/{src/backend → dashboard}/mechanism_reader.py +0 -0
  122. /package/{src/backend → dashboard}/mechanisms.py +0 -0
  123. /package/{src/backend → dashboard}/performance.py +0 -0
  124. /package/{src/backend → dashboard}/requirements.txt +0 -0
  125. /package/{src/backend → dashboard}/session_reader.py +0 -0
  126. /package/{src/backend → dashboard}/status/__init__.py +0 -0
  127. /package/{src/backend → dashboard}/status/change_tracker.py +0 -0
  128. /package/{src/backend → dashboard}/status/error_detector.py +0 -0
  129. /package/{src/backend → dashboard}/status/status_cache.py +0 -0
  130. /package/{src/backend → dashboard}/status/status_calculator.py +0 -0
  131. /package/{src/backend → dashboard}/status_calculator.py +0 -0
  132. /package/{src/backend → dashboard}/subagent_reader.py +0 -0
  133. /package/{src/backend → dashboard}/watchers/__init__.py +0 -0
  134. /package/{src/backend → dashboard}/watchers/file_watcher.py +0 -0
  135. /package/{plugin/index.js → index.js} +0 -0
@@ -1,546 +0,0 @@
1
- <template>
2
- <div class="error-analysis-view">
3
- <div class="header">
4
- <h3>🔍 错误分析</h3>
5
- <button class="refresh-btn" @click="loadAnalysis" :disabled="loading">
6
- {{ loading ? '分析中...' : '刷新' }}
7
- </button>
8
- </div>
9
-
10
- <!-- 当前 Agent 统计 -->
11
- <div v-if="summary" class="agent-summary">
12
- <div class="summary-card total">
13
- <span class="count">{{ summary.total || 0 }}</span>
14
- <span class="label">错误数</span>
15
- </div>
16
- <div class="summary-card critical" v-if="summary.bySeverity?.critical">
17
- <span class="count">{{ summary.bySeverity.critical }}</span>
18
- <span class="label">严重</span>
19
- </div>
20
- <div class="summary-card high" v-if="summary.bySeverity?.high">
21
- <span class="count">{{ summary.bySeverity.high }}</span>
22
- <span class="label">高</span>
23
- </div>
24
- <div class="summary-card medium" v-if="summary.bySeverity?.medium">
25
- <span class="count">{{ summary.bySeverity.medium }}</span>
26
- <span class="label">中</span>
27
- </div>
28
- </div>
29
-
30
- <div v-if="loading && !errors.length" class="loading-state">
31
- 正在分析错误...
32
- </div>
33
-
34
- <div v-else-if="!errors.length" class="empty-state">
35
- ✅ 暂无错误记录
36
- </div>
37
-
38
- <!-- 错误列表 -->
39
- <div v-else class="errors-list">
40
- <div
41
- v-for="(error, idx) in errors"
42
- :key="idx"
43
- class="error-item"
44
- :class="`severity-${error.severity}`"
45
- @click="toggleError(idx)"
46
- >
47
- <div class="error-header">
48
- <span class="error-type" :style="{ color: error.severityColor }">
49
- {{ error.severityLabel }} - {{ error.errorTypeLabel }}
50
- </span>
51
- <div class="error-badges">
52
- <span v-if="error.isArchived" class="badge archived" title="已归档的子任务">📦 归档</span>
53
- <span v-if="error.provider" class="badge provider">{{ error.provider }}</span>
54
- <span v-if="error.model" class="badge model">{{ error.model }}</span>
55
- </div>
56
- <span class="error-time">{{ formatTime(error.timestamp) }}</span>
57
- </div>
58
- <div class="error-message">{{ truncate(error.rawMessage, 150) }}</div>
59
-
60
- <!-- 错误详情 -->
61
- <div v-if="expandedErrors.has(idx)" class="error-detail">
62
- <div class="detail-section">
63
- <h4>错误信息</h4>
64
- <pre class="error-full">{{ error.rawMessage || '无详细信息' }}</pre>
65
- </div>
66
-
67
- <div v-if="error.toolChain?.length" class="detail-section">
68
- <h4>工具调用链(错误前)</h4>
69
- <div class="tool-chain">
70
- <div v-for="(tool, tIdx) in error.toolChain" :key="tIdx" class="tool-item">
71
- <span class="tool-index">{{ tIdx + 1 }}</span>
72
- <span class="tool-name">{{ tool.toolName }}</span>
73
- <span class="tool-time">{{ formatTime(tool.timestamp) }}</span>
74
- </div>
75
- </div>
76
- </div>
77
-
78
- <div v-if="error.suggestions?.length" class="detail-section">
79
- <h4>修复建议</h4>
80
- <ul class="suggestions">
81
- <li v-for="(s, sIdx) in error.suggestions" :key="sIdx">{{ s }}</li>
82
- </ul>
83
- </div>
84
-
85
- <div class="detail-section meta">
86
- <span>Session: {{ error.sessionFile }}</span>
87
- <span>Turn: {{ error.turnIndex }}</span>
88
- </div>
89
- </div>
90
- </div>
91
- </div>
92
-
93
- <!-- 按类型统计 -->
94
- <div v-if="summary?.byType && Object.keys(summary.byType).length > 0" class="type-summary">
95
- <h4>错误类型分布</h4>
96
- <div class="type-bars">
97
- <div v-for="(count, type) in summary.byType" :key="type" class="type-bar">
98
- <span class="type-label">{{ getTypeLabel(type as string) }}</span>
99
- <div class="bar-container">
100
- <div class="bar-fill" :style="{ width: getBarWidth(count, summary.total) }"></div>
101
- </div>
102
- <span class="type-count">{{ count }}</span>
103
- </div>
104
- </div>
105
- </div>
106
- </div>
107
- </template>
108
-
109
- <script setup lang="ts">
110
- import { ref, onMounted, watch } from 'vue'
111
-
112
- interface ErrorSummary {
113
- total: number
114
- byType: Record<string, number>
115
- bySeverity: Record<string, number>
116
- }
117
-
118
- interface ErrorItem {
119
- turnIndex: number
120
- timestamp: number
121
- rawMessage: string
122
- errorType: string
123
- errorTypeLabel: string
124
- severity: string
125
- severityLabel: string
126
- severityColor: string
127
- suggestions: string[]
128
- toolChain?: any[]
129
- sessionFile: string
130
- isArchived?: boolean
131
- provider?: string
132
- model?: string
133
- }
134
-
135
- const props = defineProps<{
136
- agentId: string
137
- }>()
138
-
139
- const errors = ref<ErrorItem[]>([])
140
- const summary = ref<ErrorSummary | null>(null)
141
- const loading = ref(false)
142
- const expandedErrors = ref(new Set<number>())
143
-
144
- const typeLabels: Record<string, string> = {
145
- api_auth: 'API 认证',
146
- api_rate_limit: 'API 限流',
147
- api_model: '模型错误',
148
- timeout: '超时',
149
- permission: '权限错误',
150
- tool_error: '工具错误',
151
- subagent: '子任务错误',
152
- network: '网络错误',
153
- unknown: '未知',
154
- }
155
-
156
- function getTypeLabel(type: string): string {
157
- return typeLabels[type] || type
158
- }
159
-
160
- function formatTime(ts: number | undefined): string {
161
- if (!ts) return ''
162
- const d = new Date(ts)
163
- return d.toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
164
- }
165
-
166
- function truncate(s: string, max: number): string {
167
- if (!s || s.length <= max) return s || ''
168
- return s.slice(0, max) + '...'
169
- }
170
-
171
- function getBarWidth(count: number, total: number): string {
172
- if (!total) return '0%'
173
- return Math.round((count / total) * 100) + '%'
174
- }
175
-
176
- function toggleError(idx: number) {
177
- if (expandedErrors.value.has(idx)) {
178
- expandedErrors.value.delete(idx)
179
- } else {
180
- expandedErrors.value.add(idx)
181
- }
182
- expandedErrors.value = new Set(expandedErrors.value)
183
- }
184
-
185
- async function loadAnalysis() {
186
- if (!props.agentId) return
187
-
188
- loading.value = true
189
- try {
190
- const res = await fetch(`/api/error-analysis/${props.agentId}?session_limit=5`)
191
- if (res.ok) {
192
- const data = await res.json()
193
- errors.value = data.errors || []
194
- summary.value = data.summary || null
195
- }
196
- } catch (e) {
197
- console.error('Failed to load error analysis:', e)
198
- } finally {
199
- loading.value = false
200
- }
201
- }
202
-
203
- watch(() => props.agentId, () => {
204
- loadAnalysis()
205
- }, { immediate: true })
206
-
207
- onMounted(() => {
208
- loadAnalysis()
209
- })
210
- </script>
211
-
212
- <style scoped>
213
- .error-analysis-view {
214
- padding: 12px;
215
- max-height: 600px;
216
- overflow-y: auto;
217
- }
218
-
219
- .header {
220
- display: flex;
221
- justify-content: space-between;
222
- align-items: center;
223
- margin-bottom: 16px;
224
- }
225
-
226
- .header h3 {
227
- margin: 0;
228
- font-size: 14px;
229
- color: #374151;
230
- }
231
-
232
- .refresh-btn {
233
- padding: 4px 12px;
234
- font-size: 12px;
235
- border: 1px solid #d1d5db;
236
- border-radius: 4px;
237
- background: #fff;
238
- cursor: pointer;
239
- }
240
-
241
- .refresh-btn:hover:not(:disabled) {
242
- background: #f3f4f6;
243
- }
244
-
245
- .refresh-btn:disabled {
246
- opacity: 0.5;
247
- }
248
-
249
- .agent-summary {
250
- display: flex;
251
- gap: 12px;
252
- margin-bottom: 16px;
253
- flex-wrap: wrap;
254
- }
255
-
256
- .summary-card {
257
- display: flex;
258
- flex-direction: column;
259
- align-items: center;
260
- padding: 10px 16px;
261
- background: #f9fafb;
262
- border-radius: 8px;
263
- min-width: 70px;
264
- }
265
-
266
- .summary-card.total {
267
- background: #eff6ff;
268
- }
269
-
270
- .summary-card.total .count {
271
- color: #3b82f6;
272
- }
273
-
274
- .summary-card.critical {
275
- background: #fef2f2;
276
- }
277
-
278
- .summary-card.critical .count {
279
- color: #dc2626;
280
- }
281
-
282
- .summary-card.high {
283
- background: #fff7ed;
284
- }
285
-
286
- .summary-card.high .count {
287
- color: #f97316;
288
- }
289
-
290
- .summary-card.medium {
291
- background: #fffbeb;
292
- }
293
-
294
- .summary-card.medium .count {
295
- color: #f59e0b;
296
- }
297
-
298
- .summary-card .count {
299
- font-size: 20px;
300
- font-weight: 700;
301
- }
302
-
303
- .summary-card .label {
304
- font-size: 10px;
305
- color: #6b7280;
306
- margin-top: 2px;
307
- }
308
-
309
- .loading-state,
310
- .empty-state {
311
- text-align: center;
312
- padding: 40px 20px;
313
- color: #6b7280;
314
- }
315
-
316
- .errors-list {
317
- display: flex;
318
- flex-direction: column;
319
- gap: 8px;
320
- margin-bottom: 16px;
321
- }
322
-
323
- .error-item {
324
- padding: 10px;
325
- background: #fff;
326
- border-radius: 6px;
327
- border-left: 3px solid #e5e7eb;
328
- cursor: pointer;
329
- }
330
-
331
- .error-item.severity-critical {
332
- border-left-color: #dc2626;
333
- background: #fef2f2;
334
- }
335
-
336
- .error-item.severity-high {
337
- border-left-color: #f97316;
338
- background: #fff7ed;
339
- }
340
-
341
- .error-item.severity-medium {
342
- border-left-color: #f59e0b;
343
- background: #fffbeb;
344
- }
345
-
346
- .error-item.severity-low {
347
- border-left-color: #6b7280;
348
- }
349
-
350
- .error-header {
351
- display: flex;
352
- justify-content: space-between;
353
- align-items: center;
354
- margin-bottom: 6px;
355
- flex-wrap: wrap;
356
- gap: 4px;
357
- }
358
-
359
- .error-type {
360
- font-weight: 600;
361
- font-size: 13px;
362
- }
363
-
364
- .error-badges {
365
- display: flex;
366
- gap: 4px;
367
- }
368
-
369
- .badge {
370
- font-size: 10px;
371
- padding: 2px 6px;
372
- border-radius: 3px;
373
- background: #e5e7eb;
374
- color: #6b7280;
375
- }
376
-
377
- .badge.archived {
378
- background: #fef3c7;
379
- color: #92400e;
380
- }
381
-
382
- .badge.provider {
383
- background: #dbeafe;
384
- color: #1d4ed8;
385
- }
386
-
387
- .badge.model {
388
- background: #f3e8ff;
389
- color: #7c3aed;
390
- font-family: monospace;
391
- }
392
-
393
- .error-time {
394
- font-size: 11px;
395
- color: #9ca3af;
396
- }
397
-
398
- .error-message {
399
- font-size: 12px;
400
- color: #4b5563;
401
- line-height: 1.4;
402
- }
403
-
404
- .error-detail {
405
- margin-top: 12px;
406
- padding-top: 12px;
407
- border-top: 1px solid #e5e7eb;
408
- }
409
-
410
- .detail-section {
411
- margin-bottom: 12px;
412
- }
413
-
414
- .detail-section:last-child {
415
- margin-bottom: 0;
416
- }
417
-
418
- .detail-section h4 {
419
- margin: 0 0 8px 0;
420
- font-size: 12px;
421
- color: #374151;
422
- }
423
-
424
- .error-full {
425
- margin: 0;
426
- padding: 10px;
427
- background: #1f2937;
428
- color: #e5e7eb;
429
- border-radius: 6px;
430
- font-size: 11px;
431
- overflow-x: auto;
432
- white-space: pre-wrap;
433
- word-break: break-all;
434
- max-height: 150px;
435
- }
436
-
437
- .tool-chain {
438
- display: flex;
439
- flex-direction: column;
440
- gap: 4px;
441
- }
442
-
443
- .tool-item {
444
- display: flex;
445
- align-items: center;
446
- gap: 8px;
447
- padding: 6px 10px;
448
- background: #f3f4f6;
449
- border-radius: 4px;
450
- font-size: 12px;
451
- }
452
-
453
- .tool-index {
454
- width: 20px;
455
- height: 20px;
456
- background: #3b82f6;
457
- color: #fff;
458
- border-radius: 50%;
459
- display: flex;
460
- align-items: center;
461
- justify-content: center;
462
- font-size: 10px;
463
- font-weight: 600;
464
- }
465
-
466
- .tool-name {
467
- flex: 1;
468
- font-family: monospace;
469
- color: #1f2937;
470
- }
471
-
472
- .tool-time {
473
- font-size: 10px;
474
- color: #9ca3af;
475
- }
476
-
477
- .suggestions {
478
- margin: 0;
479
- padding-left: 18px;
480
- }
481
-
482
- .suggestions li {
483
- margin: 6px 0;
484
- font-size: 12px;
485
- color: #4b5563;
486
- }
487
-
488
- .detail-section.meta {
489
- display: flex;
490
- gap: 16px;
491
- font-size: 10px;
492
- color: #9ca3af;
493
- font-family: monospace;
494
- }
495
-
496
- .type-summary {
497
- padding-top: 16px;
498
- border-top: 1px solid #e5e7eb;
499
- }
500
-
501
- .type-summary h4 {
502
- margin: 0 0 12px 0;
503
- font-size: 13px;
504
- color: #374151;
505
- }
506
-
507
- .type-bars {
508
- display: flex;
509
- flex-direction: column;
510
- gap: 8px;
511
- }
512
-
513
- .type-bar {
514
- display: flex;
515
- align-items: center;
516
- gap: 8px;
517
- }
518
-
519
- .type-label {
520
- width: 80px;
521
- font-size: 11px;
522
- color: #6b7280;
523
- }
524
-
525
- .bar-container {
526
- flex: 1;
527
- height: 8px;
528
- background: #e5e7eb;
529
- border-radius: 4px;
530
- overflow: hidden;
531
- }
532
-
533
- .bar-fill {
534
- height: 100%;
535
- background: #3b82f6;
536
- border-radius: 4px;
537
- transition: width 0.3s;
538
- }
539
-
540
- .type-count {
541
- width: 30px;
542
- font-size: 11px;
543
- color: #374151;
544
- text-align: right;
545
- }
546
- </style>