Skip to content

Conversation

@acfranzen
Copy link
Owner

@acfranzen acfranzen commented Feb 4, 2026

Summary

Adds complete dashboard export/import API for sharing dashboards between Glance instances.

Endpoints

  • POST /api/dashboard/export — Export widgets, layout, and theme to .glance.json
  • POST /api/dashboard/import/preview — Validate import, detect slug conflicts, report missing credentials
  • POST /api/dashboard/import — Import with conflict resolution strategies

Conflict Resolution

Three strategies supported:

  • overwrite — Replace existing widgets with same slug
  • rename — Import as {slug}-1, {slug}-2, etc.
  • skip — Don't import conflicting widgets

Features

  • Breakpoint-specific layouts (desktop/tablet/mobile)
  • Theme preservation (colors, custom CSS)
  • Credential detection (lists what's needed vs missing)
  • Dry-run preview before import

Testing

All endpoints tested via curl ✅


UI dialogs for export/import scheduled for Week 3


Note

Medium Risk
Adds new API routes that create/update/delete widget and theme records and introduces a large client-side import UI; mistakes could overwrite existing widgets/layout or allow unintended configuration changes, though access is gated by existing auth and upload size limits.

Overview
Adds full dashboard sharing via new POST /api/dashboard/export, POST /api/dashboard/import/preview, and POST /api/dashboard/import endpoints that serialize custom widgets, layout, theme, and required credential metadata to/from .glance.json, with validation, conflict handling (overwrite/rename/skip), and 5MB upload limits.

Introduces DashboardExportModal and a multi-step DashboardImportModal (previewing widget code, conflicts, theme, and missing credentials) and wires both into DashboardHeader via a new Import/Export menu.

Tightens typing in /api/widgets/[slug]/refresh by replacing any with https.RequestOptions, and updates the WidgetRow type to include custom_widget_id to support dashboard layout export/import.

Written by Cursor Bugbot for commit 0897b2f. This will update automatically on new commits. Configure here.

- POST /api/dashboard/export - Export widgets, layout, and theme to .glance.json
- POST /api/dashboard/import/preview - Validate import, detect conflicts, report missing credentials
- POST /api/dashboard/import - Import with conflict resolution (overwrite/rename/skip)

Supports breakpoint-specific layouts (desktop/tablet/mobile) and theme preservation.
@vercel
Copy link

vercel bot commented Feb 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
glance Ready Ready Preview, Comment Feb 5, 2026 3:47pm

Request Review

- DashboardExportModal: Export entire dashboard as .glance.json file
  - Configurable name, description, author
  - Downloads JSON file with all widgets, layout, and theme

- DashboardImportModal: Import dashboard from .glance.json file
  - File upload with drag-and-drop support
  - Preview step showing widgets, layout, theme, credentials
  - Conflict resolution: overwrite/rename/skip options
  - Shows missing credentials that need configuration
  - Import options: layout, theme, clear existing
  - Success summary with import results

- Updated DashboardHeader with dropdown menu for export/import
- Mobile-responsive: buttons also available in mobile menu

Tested:
- Export API creates valid .glance.json
- Import with preview shows conflicts and credentials
- Conflict resolution works (overwrite/rename/skip)
- No console errors
- Add custom_widget_id to WidgetRow interface
- Use CredentialRequirement type for credential access
- Use type guards for unknown body validation
- Use RequestOptions type for http/https requests
- Update CLAUDE.md: NEVER use any (strict rule)
Bug fixes:
- Fix import skipping multiple instances of same widget
- Fix renamed widgets causing false conflicts during import
- Extract shared DashboardExportFormat type and validateDashboardFormat
  function to src/lib/dashboard-format.ts

Improvements:
- Add typed ExportRequestBody interface for export endpoint
- Add 5MB file size limit on import to prevent DoS
- Add try-catch around JSON.parse for position data in export

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
const layout = dashboard.layout as Record<string, unknown>;
if (!Array.isArray(layout.desktop)) {
errors.push("Missing or invalid layout.desktop array");
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing layout item validation causes NaN grid calculations

Medium Severity

The validateDashboardFormat function checks that layout.desktop is an array but doesn't validate that each item has the required x, y, w, h properties. When a malformed layout item passes validation and is imported, the position object has undefined values. JSON.stringify({x: undefined, ...}) produces '{}'. When the dashboard loads, getMaxY calculates undefined + undefined = NaN, causing NaN to propagate through grid calculations and potentially breaking the dashboard display.

Additional Locations (1)

Fix in Cursor Fix in Web

} from "@/lib/dashboard-format";

// Maximum import file size (5MB) to prevent DoS
const MAX_IMPORT_SIZE = 5 * 1024 * 1024;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated constant across import route files

Low Severity

MAX_IMPORT_SIZE constant is defined identically in both preview/route.ts and route.ts. This should be defined once in the shared src/lib/dashboard-format.ts file (which already exists for shared dashboard import/export types and utilities) and imported where needed.

Fix in Cursor Fix in Web

Enhance the import preview to show detailed information before importing:
- Widget details section with expandable source/server code previews
- Full credential details with type badges and configured/missing status
- Theme CSS preview with line counts
- Per-widget credential badges and line count indicators

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Move WidgetPreviewDetail, CredentialPreviewDetail, ThemePreviewDetail,
WidgetConflict, ImportPreviewResponse, and ImportResponse interfaces
to shared dashboard-format.ts to eliminate duplication across API routes
and DashboardImportModal.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@acfranzen acfranzen merged commit 5ddd3eb into main Feb 5, 2026
8 checks passed
@acfranzen acfranzen deleted the zeus/dashboard-export-import branch February 5, 2026 15:49
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 4 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.


// Check content length to prevent oversized uploads
const contentLength = request.headers.get("content-length");
if (contentLength && parseInt(contentLength, 10) > MAX_IMPORT_SIZE) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File size limit can be bypassed via header manipulation

Medium Severity

The MAX_IMPORT_SIZE check relies on the client-provided Content-Length header, which can be omitted or spoofed. The condition contentLength && parseInt(...) short-circuits when the header is missing, allowing request.json() to process arbitrarily large payloads. This bypasses the intended DoS protection since malicious clients can send oversized requests without the header or with a falsified small value.

Additional Locations (1)

Fix in Cursor Fix in Web

}

const dashboard = dashboardData as DashboardExportFormat;
const errors: string[] = [];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused errors array is never populated

Low Severity

The errors array is declared but never populated with any values during the import process. It's always returned as an empty array in the success response at line 400, making it dead code.

Fix in Cursor Fix in Web


/**
* Validates the structure of a dashboard export file
*/
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Orphaned JSDoc comment without associated code

Low Severity

Lines 75-77 contain an orphaned JSDoc comment "Validates the structure of a dashboard export file" that doesn't document any code. The actual validateDashboardFormat function has its own JSDoc at lines 161-163. This is a copy-paste artifact.

Fix in Cursor Fix in Web

</p>
</div>
)}
</div>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drag and drop UI claim lacks implementation handlers

Medium Severity

The file drop zone UI states "or drag and drop here" (line 294) but the div only has an onClick handler with no drag/drop event handlers (onDrop, onDragOver, onDragEnter, onDragLeave). Without onDragOver calling preventDefault(), the browser's default behavior occurs when a file is dropped - navigating to the file URL, which causes the user to leave the application unexpectedly.

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant