Conversation
- Fix Dashboard statistics display (stats API now returns proper data structure) - Add endpoint drag-and-drop reordering functionality - Add current endpoint display and manual switching - Add model fetching from API endpoints - Add test status persistence (localStorage) - Add copy buttons for API URLs - Add WebUI authentication (HTTP Basic Auth via env vars) - Update docker-compose with authentication configuration - Add CSS styles for new features (badges, copy buttons, drag-drop) 🤖 Generated with Claude Code Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This pull request adds significant WebUI enhancements to the ccNexus admin interface, focusing on improving endpoint management capabilities and fixing dashboard statistics display.
Key Changes:
- Implemented drag-and-drop functionality for endpoint reordering with visual feedback
- Added current endpoint tracking and manual switching capabilities
- Introduced model fetching from API endpoints with an interactive selection modal
- Implemented test status persistence using localStorage with visual indicators
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 15 comments.
| File | Description |
|---|---|
| cmd/server/webui/ui/js/components/endpoints.js | Core functionality additions including drag-and-drop reordering, endpoint switching, model fetching, copy-to-clipboard, and test status persistence |
| cmd/server/webui/ui/css/components.css | Added styling for new UI components including badge-primary class, copy button icons, and drag-and-drop visual states |
| cmd/server/webui/api/stats.go | Fixed dashboard statistics API to return proper data structure with aggregated totals (TotalRequests, TotalErrors, tokens) instead of raw stats object |
| cmd/server/docker-compose.yml | Updated build context path to reference parent directory structure correctly (../../ instead of .) and removed deprecated version field |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| getTestStatus(endpointName) { | ||
| try { | ||
| const statusMap = JSON.parse(localStorage.getItem('ccNexus_endpointTestStatus') || '{}'); |
There was a problem hiding this comment.
The variable name 'ccNexus_endpointTestStatus' uses inconsistent casing (camelCase after underscore). For better consistency with JavaScript naming conventions, consider using either 'ccnexus_endpoint_test_status' (snake_case) or 'ccNexusEndpointTestStatus' (camelCase) throughout.
| document.querySelectorAll('.model-item').forEach(item => { | ||
| item.addEventListener('click', () => { | ||
| const selectedModel = item.dataset.model; | ||
| modelInput.value = selectedModel; | ||
| notifications.success(`Model selected: ${selectedModel}`); | ||
| modelModal.remove(); | ||
| }); | ||
|
|
||
| item.addEventListener('mouseenter', () => { | ||
| item.style.backgroundColor = '#f3f4f6'; | ||
| }); | ||
|
|
||
| item.addEventListener('mouseleave', () => { | ||
| item.style.backgroundColor = ''; | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Memory leak potential: Event listeners are added to model items but the modal is never properly cleaned up. When the modal is removed with modelModal.remove(), the event listeners should be explicitly removed first to prevent potential memory leaks, especially if this operation is performed frequently.
| <button class="btn-icon copy-btn" data-copy="${this.escapeHtml(ep.apiUrl)}" title="Copy URL"> | ||
| 📋 | ||
| </button> |
There was a problem hiding this comment.
Accessibility issue: The copy button uses only an emoji (📋) without any text alternative. Screen readers will announce this as "clipboard emoji" which is not descriptive. Add an aria-label attribute with descriptive text like "Copy API URL to clipboard" for better accessibility.
| totalInputTokens += int64(stats.InputTokens) | ||
| totalOutputTokens += int64(stats.OutputTokens) |
There was a problem hiding this comment.
Potential type conversion issue: InputTokens and OutputTokens are being cast to int64, but if the source type is already int64 or larger, this is redundant. If they are smaller types (like int or int32), the conversion is correct, but without seeing the stats structure definition, this could indicate a type mismatch. Consider verifying that the source types match the target types to avoid unnecessary conversions or potential overflow issues.
| saveTestStatus(endpointName, success) { | ||
| try { | ||
| const statusMap = JSON.parse(localStorage.getItem('ccNexus_endpointTestStatus') || '{}'); | ||
| statusMap[endpointName] = success; |
There was a problem hiding this comment.
The variable name 'ccNexus_endpointTestStatus' uses inconsistent casing (camelCase after underscore). For better consistency with JavaScript naming conventions, consider using either 'ccnexus_endpoint_test_status' (snake_case) or 'ccNexusEndpointTestStatus' (camelCase) throughout.
| <tr data-endpoint="${this.escapeHtml(ep.name)}" data-index="${index}" draggable="true" style="cursor: move;"> | ||
| <td style="cursor: grab; text-align: center;">⋮⋮</td> | ||
| <td> | ||
| <strong>${this.escapeHtml(ep.name)}</strong> | ||
| <span title="${testStatusTitle}" style="margin-left: 5px;">${testStatusIcon}</span> | ||
| ${isCurrentEndpoint ? '<span class="badge badge-primary" style="margin-left: 5px;">Current</span>' : ''} | ||
| </td> | ||
| <td> | ||
| <code style="font-size: 12px;">${this.escapeHtml(ep.apiUrl)}</code> | ||
| <button class="btn-icon copy-btn" data-copy="${this.escapeHtml(ep.apiUrl)}" title="Copy URL"> | ||
| 📋 | ||
| </button> | ||
| </td> |
There was a problem hiding this comment.
Inline styles are used throughout the endpoint row rendering instead of CSS classes. This mixing of inline styles with class-based styling makes the code harder to maintain and violates separation of concerns. Consider defining proper CSS classes for these elements (e.g., .endpoint-row-drag-handle, .endpoint-api-url, .endpoint-name-cell) to improve maintainability and consistency.
| <td><strong>${this.escapeHtml(ep.name)}</strong></td> | ||
| <td><code>${this.escapeHtml(ep.apiUrl)}</code></td> | ||
| <tr data-endpoint="${this.escapeHtml(ep.name)}" data-index="${index}" draggable="true" style="cursor: move;"> | ||
| <td style="cursor: grab; text-align: center;">⋮⋮</td> |
There was a problem hiding this comment.
Accessibility issue: The drag handle uses only a visual indicator (⋮⋮) without proper ARIA attributes. Screen reader users won't understand that rows are draggable or how to reorder them. Consider adding aria-label="Drag to reorder" to the drag handle cell and role="button" with appropriate keyboard handlers (arrow keys) for accessibility compliance.
| ${models.map(model => ` | ||
| <div class="model-item" style="padding: 10px; border-bottom: 1px solid #e5e7eb; cursor: pointer;" data-model="${this.escapeHtml(model)}"> | ||
| <strong>${this.escapeHtml(model)}</strong> | ||
| </div> | ||
| `).join('')} |
There was a problem hiding this comment.
The model selection modal doesn't handle empty model names or special characters that could break the HTML structure. If the API returns models with names containing quotes or HTML special characters, the escapeHtml call will protect against XSS but the data-model attribute could still break. Consider using JSON encoding for the data-model attribute or accessing the model name from the text content instead.
| .badge-primary { | ||
| background-color: #dbeafe; | ||
| color: #1e40af; | ||
| } |
There was a problem hiding this comment.
The badge-primary class is duplicated with identical styling to the existing badge-info class (lines 114-117). This creates unnecessary code duplication. Consider reusing the existing badge-info class or creating a shared base class if different badge types are needed in the future.
|
|
||
| showModelSelectionModal(models, modelInput) { | ||
| const modalContainer = document.getElementById('modal-container'); | ||
| const currentModal = modalContainer.querySelector('.modal'); |
There was a problem hiding this comment.
Unused variable currentModal.
* refactor: split app.go * refactor: reorganize project to standard Go layout with cmd/desktop and cmd/server * fix: use full path for macOS terminals (Ghostty, Alacritty, etc.) macOS GUI apps have limited PATH, causing "executable not found" errors. Now uses detected .app bundle paths instead of relying on PATH lookup. * feat: Add WebUI enhancements and authentication (#70) - Fix Dashboard statistics display (stats API now returns proper data structure) - Add endpoint drag-and-drop reordering functionality - Add current endpoint display and manual switching - Add model fetching from API endpoints - Add test status persistence (localStorage) - Add copy buttons for API URLs - Add WebUI authentication (HTTP Basic Auth via env vars) - Update docker-compose with authentication configuration - Add CSS styles for new features (badges, copy buttons, drag-drop) 🤖 Generated with Claude Code Co-authored-by: root <root@ser960574800116.local> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com> * feat: add configurable proxy. close #37 close #69 * v4.6.0 * Feature:新增列表视图模式 (#71) * resolve merge conflict in main.go, keep remote version * 简单视图模式合并02 * 简单视图模式03 * 顺序调整 --------- Co-authored-by: hea7en <hea7enn@qq.com> * v4.7.0 * fix:修复已知问题 (#72) * resolve merge conflict in main.go, keep remote version * 已知问题修复 --------- Co-authored-by: hea7en <hea7enn@qq.com> * v4.7.1 * feat: use proxy for update if configured * v4.8.0 * chore: fix qr code path * fix:修复已知问题 (#74) * resolve merge conflict in main.go, keep remote version * 界面样式优化 * 样式优化 * 修复已知问题 * pic * message --------- Co-authored-by: hea7en <hea7enn@qq.com> * v4.8.1 * Feature:新增节日氛围效果 (#79) * resolve merge conflict in main.go, keep remote version * 新增节日氛围效果 --------- Co-authored-by: hea7en <hea7enn@qq.com> * v4.8.2 * feature:新增更多节日氛围效果 (#81) * resolve merge conflict in main.go, keep remote version * feature:新增更多节日氛围效果 * 优化 * 优化 * tip * top * ON * config --------- Co-authored-by: hea7en <hea7enn@qq.com> * v4.8.3 * fix: use login shell for third-party terminals to load PATH correctly --------- Co-authored-by: Changhua <lichanghua0821@gmail.com> Co-authored-by: flyingcoding <wbq1724593655@gmail.com> Co-authored-by: 杨鑫亮 <50096523+ANIIAN91@users.noreply.github.com> Co-authored-by: root <root@ser960574800116.local> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com> Co-authored-by: hea7en <46583161+hea7enn@users.noreply.github.com> Co-authored-by: hea7en <hea7enn@qq.com>
🤖 Generated with Claude Code