tr]:last:border-b-0', className)}
36 | {...props}
37 | />
38 | )
39 | }
40 |
41 | function TableRow({ className, ...props }: React.ComponentProps<'tr'>) {
42 | return (
43 |
51 | )
52 | }
53 |
54 | function TableHead({ className, ...props }: React.ComponentProps<'th'>) {
55 | return (
56 | [role=checkbox]]:translate-y-[2px]',
60 | className
61 | )}
62 | {...props}
63 | />
64 | )
65 | }
66 |
67 | function TableCell({ className, ...props }: React.ComponentProps<'td'>) {
68 | return (
69 | | [role=checkbox]]:translate-y-[2px]',
73 | className
74 | )}
75 | {...props}
76 | />
77 | )
78 | }
79 |
80 | function TableCaption({ className, ...props }: React.ComponentProps<'caption'>) {
81 | return (
82 |
87 | )
88 | }
89 |
90 | export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption }
91 |
--------------------------------------------------------------------------------
/apps/docs/content/docs/getting-started/first-connection.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: First Connection
3 | description: Connect to your first database in data-peek
4 | ---
5 |
6 | # First Connection
7 |
8 | Let's connect data-peek to your database. This guide uses PostgreSQL, but the process is similar for MySQL.
9 |
10 | ## Adding a Connection
11 |
12 | 1. Click the **+** button in the sidebar under "Connections"
13 | 2. Fill in your connection details:
14 |
15 | | Field | Description | Example |
16 | |-------|-------------|---------|
17 | | **Name** | A friendly name for this connection | `Local Dev` |
18 | | **Host** | Database server hostname or IP | `localhost` |
19 | | **Port** | Database port | `5432` (PostgreSQL) or `3306` (MySQL) |
20 | | **Database** | Database name | `myapp_development` |
21 | | **User** | Database username | `postgres` |
22 | | **Password** | Database password | `••••••••` |
23 |
24 | 3. Click **Test Connection** to verify your settings
25 | 4. Click **Save** to add the connection
26 |
27 |
28 | You can also paste a connection string directly. data-peek will auto-fill all fields from URLs like:
29 | ```
30 | postgresql://user:password@localhost:5432/database
31 | ```
32 |
33 |
34 | ## Connection String Format
35 |
36 | ### PostgreSQL
37 |
38 | ```
39 | postgresql://[user]:[password]@[host]:[port]/[database]?sslmode=[mode]
40 | ```
41 |
42 | ### MySQL
43 |
44 | ```
45 | mysql://[user]:[password]@[host]:[port]/[database]
46 | ```
47 |
48 | ## SSL/TLS Connections
49 |
50 | For secure connections to cloud databases:
51 |
52 | 1. Check the **SSL** option when adding your connection
53 | 2. For services like Supabase, Railway, or Neon, SSL is usually required
54 |
55 |
56 | Always use SSL when connecting to production databases over the internet.
57 |
58 |
59 | ## Managing Connections
60 |
61 | ### Editing a Connection
62 |
63 | 1. Right-click the connection in the sidebar
64 | 2. Select **Edit**
65 | 3. Modify the details and save
66 |
67 | ### Deleting a Connection
68 |
69 | 1. Right-click the connection in the sidebar
70 | 2. Select **Delete**
71 | 3. Confirm the deletion
72 |
73 | ### Switching Connections
74 |
75 | - Click on any connection in the sidebar to switch to it
76 | - Use `Cmd+P` (macOS) or `Ctrl+P` (Windows/Linux) to open the connection picker
77 | - Use `Cmd+Shift+1-9` to quickly switch between connections 1-9
78 |
79 | ## Connection Status
80 |
81 | The connection indicator shows:
82 |
83 | | Status | Meaning |
84 | |--------|---------|
85 | | 🟢 Green | Connected and ready |
86 | | 🟡 Yellow | Connecting... |
87 | | 🔴 Red | Connection error |
88 |
89 | ## Next Steps
90 |
91 | Now that you're connected, let's [run your first query](/docs/getting-started/first-query)!
92 |
--------------------------------------------------------------------------------
/apps/desktop/src/main/ipc/index.ts:
--------------------------------------------------------------------------------
1 | import type { ConnectionConfig, SavedQuery } from '@shared/index'
2 | import type { DpStorage } from '../storage'
3 | import { registerConnectionHandlers } from './connection-handlers'
4 | import { registerQueryHandlers } from './query-handlers'
5 | import { registerDDLHandlers } from './ddl-handlers'
6 | import { registerLicenseHandlers } from './license-handlers'
7 | import { registerSavedQueriesHandlers } from './saved-queries-handlers'
8 | import { registerScheduledQueriesHandlers } from './scheduled-queries-handlers'
9 | import { registerDashboardHandlers } from './dashboard-handlers'
10 | import { registerAIHandlers } from './ai-handlers'
11 | import { createLogger } from '../lib/logger'
12 | import { registerFileHandlers } from './file-handlers'
13 | import { registerWindowHandlers } from './window-handler'
14 |
15 | const log = createLogger('ipc')
16 |
17 | export interface IpcStores {
18 | connections: DpStorage<{ connections: ConnectionConfig[] }>
19 | savedQueries: DpStorage<{ savedQueries: SavedQuery[] }>
20 | }
21 |
22 | /**
23 | * Register every IPC handler used by the application's main process.
24 | *
25 | * @param stores - Persistent stores required by handler categories; includes `connections` (connection configs) and `savedQueries` (saved query entries)
26 | */
27 | export function registerAllHandlers(stores: IpcStores): void {
28 | // Connection CRUD operations
29 | registerConnectionHandlers(stores.connections)
30 |
31 | // Database query and schema operations
32 | registerQueryHandlers()
33 |
34 | // DDL (table designer) operations
35 | registerDDLHandlers()
36 |
37 | // License management
38 | registerLicenseHandlers()
39 |
40 | // Saved queries management
41 | registerSavedQueriesHandlers(stores.savedQueries)
42 |
43 | // Scheduled queries management
44 | registerScheduledQueriesHandlers()
45 |
46 | // Dashboard management
47 | registerDashboardHandlers()
48 |
49 | // AI features
50 | registerAIHandlers()
51 |
52 | // File handler
53 | registerFileHandlers()
54 |
55 | // Window controls
56 | registerWindowHandlers()
57 |
58 | log.debug('All handlers registered')
59 | }
60 |
61 | // Re-export handler registration functions for testing or selective registration
62 | export { registerConnectionHandlers } from './connection-handlers'
63 | export { registerQueryHandlers } from './query-handlers'
64 | export { registerDDLHandlers } from './ddl-handlers'
65 | export { registerLicenseHandlers } from './license-handlers'
66 | export { registerSavedQueriesHandlers } from './saved-queries-handlers'
67 | export { registerScheduledQueriesHandlers } from './scheduled-queries-handlers'
68 | export { registerDashboardHandlers } from './dashboard-handlers'
69 | export { registerAIHandlers } from './ai-handlers'
70 |
--------------------------------------------------------------------------------
/apps/desktop/src/main/window-state.ts:
--------------------------------------------------------------------------------
1 | import { BrowserWindow, screen } from 'electron'
2 | import { DpStorage } from './storage'
3 |
4 | interface WindowState {
5 | x?: number
6 | y?: number
7 | width: number
8 | height: number
9 | isMaximized?: boolean
10 | }
11 |
12 | const DEFAULT_STATE: WindowState = {
13 | width: 1400,
14 | height: 900
15 | }
16 |
17 | let store: DpStorage | null = null
18 |
19 | async function getStore(): Promise> {
20 | if (!store) {
21 | store = await DpStorage.create({
22 | name: 'data-peek-window-state',
23 | defaults: DEFAULT_STATE
24 | })
25 | }
26 | return store
27 | }
28 |
29 | export async function getWindowState(): Promise {
30 | const storeInstance = await getStore()
31 | const state = {
32 | x: storeInstance.get('x'),
33 | y: storeInstance.get('y'),
34 | width: storeInstance.get('width', DEFAULT_STATE.width),
35 | height: storeInstance.get('height', DEFAULT_STATE.height),
36 | isMaximized: storeInstance.get('isMaximized', false)
37 | }
38 |
39 | // Validate that the window is within screen bounds
40 | const displays = screen.getAllDisplays()
41 | const isVisible = displays.some((display) => {
42 | const { x, y, width, height } = display.bounds
43 | const stateX = state.x ?? 0
44 | const stateY = state.y ?? 0
45 | return (
46 | stateX >= x &&
47 | stateY >= y &&
48 | stateX + state.width <= x + width &&
49 | stateY + state.height <= y + height
50 | )
51 | })
52 |
53 | if (!isVisible) {
54 | // Reset to default if window would be off-screen
55 | return DEFAULT_STATE
56 | }
57 |
58 | return state
59 | }
60 |
61 | export async function saveWindowState(window: BrowserWindow): Promise {
62 | if (window.isDestroyed()) return
63 |
64 | const storeInstance = await getStore()
65 | const isMaximized = window.isMaximized()
66 |
67 | if (!isMaximized) {
68 | const bounds = window.getBounds()
69 | storeInstance.set('x', bounds.x)
70 | storeInstance.set('y', bounds.y)
71 | storeInstance.set('width', bounds.width)
72 | storeInstance.set('height', bounds.height)
73 | }
74 | storeInstance.set('isMaximized', isMaximized)
75 | }
76 |
77 | export function trackWindowState(window: BrowserWindow): void {
78 | // Save state on various events
79 | const saveState = (): void => {
80 | saveWindowState(window)
81 | }
82 |
83 | let debounceTimer: NodeJS.Timeout | undefined
84 | const debouncedSaveState = () => {
85 | if (debounceTimer) clearTimeout(debounceTimer)
86 |
87 | debounceTimer = setTimeout(() => {
88 | saveState()
89 | }, 200)
90 | }
91 |
92 | window.on('resize', debouncedSaveState)
93 | window.on('move', debouncedSaveState)
94 | window.on('close', saveState)
95 | }
96 |
--------------------------------------------------------------------------------
/apps/docs/content/docs/features/query-plans.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Query Plans
3 | description: Analyze query performance with EXPLAIN ANALYZE
4 | ---
5 |
6 | # Query Plans
7 |
8 | Use EXPLAIN ANALYZE to understand how PostgreSQL executes your queries and optimize performance.
9 |
10 | ## Running EXPLAIN
11 |
12 | 1. Write your SELECT query
13 | 2. Execute the query to see results
14 | 3. Click **Explain** or **Explain Analyze** in the toolbar
15 |
16 | ### Modes
17 |
18 | | Mode | Description |
19 | |------|-------------|
20 | | **EXPLAIN** | Shows estimated plan without executing |
21 | | **EXPLAIN ANALYZE** | Runs query and shows actual statistics |
22 |
23 |
24 | EXPLAIN ANALYZE actually executes the query, so use with caution on slow queries or data-modifying statements.
25 |
26 |
27 | ## Plan Viewer
28 |
29 | ### Node Tree
30 |
31 | The plan displays as a collapsible tree:
32 |
33 | ```
34 | Aggregate (cost=1000..1001 rows=1)
35 | └── Index Scan on users_pkey (cost=0..900 rows=10000)
36 | Index Cond: (active = true)
37 | ```
38 |
39 | ### Node Information
40 |
41 | Each node shows:
42 |
43 | | Metric | Description |
44 | |--------|-------------|
45 | | **Node Type** | Type of operation (Seq Scan, Index Scan, etc.) |
46 | | **Cost** | Estimated startup..total cost |
47 | | **Rows** | Estimated rows (vs actual with ANALYZE) |
48 | | **Width** | Average row width in bytes |
49 | | **Time** | Actual execution time (ANALYZE only) |
50 | | **Loops** | Number of times node was executed |
51 |
52 | ### Color Coding
53 |
54 | Nodes are color-coded by type:
55 | - 🔵 **Scans**: Seq Scan, Index Scan, Bitmap Scan
56 | - 🟢 **Joins**: Nested Loop, Hash Join, Merge Join
57 | - 🟡 **Sorts**: Sort, Incremental Sort
58 | - 🟣 **Aggregates**: Aggregate, GroupAggregate, HashAggregate
59 |
60 | ## Understanding Plans
61 |
62 | ### Common Node Types
63 |
64 | | Node | Description | Performance |
65 | |------|-------------|-------------|
66 | | **Seq Scan** | Full table scan | Slow on large tables |
67 | | **Index Scan** | Uses index | Fast for selective queries |
68 | | **Index Only Scan** | Uses covering index | Very fast |
69 | | **Bitmap Scan** | Two-phase index scan | Good for medium selectivity |
70 | | **Hash Join** | Hash-based join | Fast for large joins |
71 | | **Nested Loop** | Row-by-row join | Fast for small sets |
72 |
73 | ### Performance Warnings
74 |
75 | Watch for:
76 | - **High row estimates** with low actual rows (bad statistics)
77 | - **Sequential scans** on large tables
78 | - **Sort operations** without indexes
79 | - **High loop counts** in nested loops
80 |
81 | ## Optimization Tips
82 |
83 | 1. **Add indexes** for columns in WHERE clauses
84 | 2. **Update statistics** with `ANALYZE table_name`
85 | 3. **Check for missing indexes** on join columns
86 | 4. **Consider partial indexes** for filtered queries
87 | 5. **Use LIMIT** when possible
88 |
--------------------------------------------------------------------------------
/apps/desktop/src/renderer/src/components/dashboard/dashboard-grid.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback } from 'react'
2 | import { ReactGridLayout } from 'react-grid-layout/legacy'
3 |
4 | import { useDashboardStore } from '@/stores'
5 | import { WidgetCard } from './widget-card'
6 | import type { Dashboard, WidgetLayout } from '@shared/index'
7 |
8 | import 'react-grid-layout/css/styles.css'
9 |
10 | interface GridLayoutItem {
11 | i: string
12 | x: number
13 | y: number
14 | w: number
15 | h: number
16 | minW?: number
17 | minH?: number
18 | }
19 |
20 | interface DashboardGridProps {
21 | dashboard: Dashboard
22 | editMode: boolean
23 | }
24 |
25 | /**
26 | * Render a dashboard's widget grid and synchronize layout changes with the store.
27 | *
28 | * Renders widgets inside a responsive grid and updates the dashboard's widget layouts when the user repositions or resizes items.
29 | *
30 | * @param dashboard - Dashboard object containing widgets and layout configuration used to build the grid
31 | * @param editMode - When `true`, enables dragging and resizing of widgets in the grid
32 | * @returns A React element that renders the dashboard grid and its WidgetCard children
33 | */
34 | export function DashboardGrid({ dashboard, editMode }: DashboardGridProps) {
35 | const updateWidgetLayouts = useDashboardStore((s) => s.updateWidgetLayouts)
36 |
37 | const layout: GridLayoutItem[] = dashboard.widgets.map((widget) => ({
38 | i: widget.id,
39 | x: widget.layout.x,
40 | y: widget.layout.y,
41 | w: widget.layout.w,
42 | h: widget.layout.h,
43 | minW: widget.layout.minW || 2,
44 | minH: widget.layout.minH || 2
45 | }))
46 |
47 | const handleLayoutChange = useCallback(
48 | (newLayout: readonly GridLayoutItem[]) => {
49 | const layouts: Record = {}
50 | for (const item of newLayout) {
51 | layouts[item.i] = {
52 | x: item.x,
53 | y: item.y,
54 | w: item.w,
55 | h: item.h,
56 | minW: item.minW,
57 | minH: item.minH
58 | }
59 | }
60 | updateWidgetLayouts(dashboard.id, layouts)
61 | },
62 | [dashboard.id, updateWidgetLayouts]
63 | )
64 |
65 | return (
66 |
67 |
78 | {dashboard.widgets.map((widget) => (
79 |
80 |
81 |
82 | ))}
83 |
84 |
85 | )
86 | }
87 |
--------------------------------------------------------------------------------
/apps/desktop/src/renderer/src/components/nav-workspaces.tsx:
--------------------------------------------------------------------------------
1 | import { ChevronRight, MoreHorizontal, Plus } from 'lucide-react'
2 |
3 | import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'
4 | import {
5 | SidebarGroup,
6 | SidebarGroupContent,
7 | SidebarGroupLabel,
8 | SidebarMenu,
9 | SidebarMenuAction,
10 | SidebarMenuButton,
11 | SidebarMenuItem,
12 | SidebarMenuSub,
13 | SidebarMenuSubButton,
14 | SidebarMenuSubItem
15 | } from '@/components/ui/sidebar'
16 |
17 | export function NavWorkspaces({
18 | workspaces
19 | }: {
20 | workspaces: {
21 | name: string
22 | emoji: React.ReactNode
23 | pages: {
24 | name: string
25 | emoji: React.ReactNode
26 | }[]
27 | }[]
28 | }) {
29 | return (
30 |
31 | Workspaces
32 |
33 |
34 | {workspaces.map((workspace) => (
35 |
36 |
37 |
38 |
39 | {workspace.emoji}
40 | {workspace.name}
41 |
42 |
43 |
44 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | {workspace.pages.map((page) => (
57 |
58 |
59 |
60 | {page.emoji}
61 | {page.name}
62 |
63 |
64 |
65 | ))}
66 |
67 |
68 |
69 |
70 | ))}
71 |
72 |
73 |
74 | More
75 |
76 |
77 |
78 |
79 |
80 | )
81 | }
82 |
--------------------------------------------------------------------------------
/apps/desktop/src/renderer/src/components/ui/breadcrumb.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { Slot } from '@radix-ui/react-slot'
3 | import { ChevronRight, MoreHorizontal } from 'lucide-react'
4 |
5 | import { cn } from '@/lib/utils'
6 |
7 | function Breadcrumb({ ...props }: React.ComponentProps<'nav'>) {
8 | return
9 | }
10 |
11 | function BreadcrumbList({ className, ...props }: React.ComponentProps<'ol'>) {
12 | return (
13 |
21 | )
22 | }
23 |
24 | function BreadcrumbItem({ className, ...props }: React.ComponentProps<'li'>) {
25 | return (
26 |
31 | )
32 | }
33 |
34 | function BreadcrumbLink({
35 | asChild,
36 | className,
37 | ...props
38 | }: React.ComponentProps<'a'> & {
39 | asChild?: boolean
40 | }) {
41 | const Comp = asChild ? Slot : 'a'
42 |
43 | return (
44 |
49 | )
50 | }
51 |
52 | function BreadcrumbPage({ className, ...props }: React.ComponentProps<'span'>) {
53 | return (
54 |
62 | )
63 | }
64 |
65 | function BreadcrumbSeparator({ children, className, ...props }: React.ComponentProps<'li'>) {
66 | return (
67 | svg]:size-3.5', className)}
72 | {...props}
73 | >
74 | {children ?? }
75 |
76 | )
77 | }
78 |
79 | function BreadcrumbEllipsis({ className, ...props }: React.ComponentProps<'span'>) {
80 | return (
81 |
88 |
89 | More
90 |
91 | )
92 | }
93 |
94 | export {
95 | Breadcrumb,
96 | BreadcrumbList,
97 | BreadcrumbItem,
98 | BreadcrumbLink,
99 | BreadcrumbPage,
100 | BreadcrumbSeparator,
101 | BreadcrumbEllipsis
102 | }
103 |
--------------------------------------------------------------------------------
/apps/desktop/src/renderer/src/components/team-switcher.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { ChevronDown, Plus } from 'lucide-react'
3 |
4 | import {
5 | DropdownMenu,
6 | DropdownMenuContent,
7 | DropdownMenuItem,
8 | DropdownMenuLabel,
9 | DropdownMenuSeparator,
10 | DropdownMenuShortcut,
11 | DropdownMenuTrigger
12 | } from '@/components/ui/dropdown-menu'
13 | import { SidebarMenu, SidebarMenuButton, SidebarMenuItem } from '@/components/ui/sidebar'
14 | import { keys } from '@/lib/utils'
15 |
16 | export function TeamSwitcher({
17 | teams
18 | }: {
19 | teams: {
20 | name: string
21 | logo: React.ElementType
22 | plan: string
23 | }[]
24 | }) {
25 | const [activeTeam, setActiveTeam] = React.useState(teams[0])
26 |
27 | if (!activeTeam) {
28 | return null
29 | }
30 |
31 | return (
32 |
33 |
34 |
35 |
36 |
37 |
40 | {activeTeam.name}
41 |
42 |
43 |
44 |
50 | Teams
51 | {teams.map((team, index) => (
52 | setActiveTeam(team)}
55 | className="gap-2 p-2"
56 | >
57 |
58 |
59 |
60 | {team.name}
61 |
62 | {keys.mod}
63 | {index + 1}
64 |
65 |
66 | ))}
67 |
68 |
69 |
72 | Add team
73 |
74 |
75 |
76 |
77 |
78 | )
79 | }
80 |
--------------------------------------------------------------------------------
/apps/desktop/src/main/ipc/connection-handlers.ts:
--------------------------------------------------------------------------------
1 | import { ipcMain } from 'electron'
2 | import type { ConnectionConfig } from '@shared/index'
3 | import type { DpStorage } from '../storage'
4 | import { windowManager } from '../window-manager'
5 |
6 | /**
7 | * Register connection CRUD handlers
8 | */
9 | export function registerConnectionHandlers(
10 | store: DpStorage<{ connections: ConnectionConfig[] }>
11 | ): void {
12 | // List all connections
13 | ipcMain.handle('connections:list', () => {
14 | try {
15 | const connections = store.get('connections', [])
16 | return { success: true, data: connections }
17 | } catch (error: unknown) {
18 | const errorMessage = error instanceof Error ? error.message : String(error)
19 | return { success: false, error: errorMessage }
20 | }
21 | })
22 |
23 | // Add a new connection
24 | ipcMain.handle('connections:add', (_, connection: ConnectionConfig) => {
25 | try {
26 | const connections = store.get('connections', [])
27 | connections.push(connection)
28 | store.set('connections', connections)
29 | // Broadcast to all windows that connections have changed
30 | windowManager.broadcastToAll('connections:updated')
31 | return { success: true, data: connection }
32 | } catch (error: unknown) {
33 | const errorMessage = error instanceof Error ? error.message : String(error)
34 | return { success: false, error: errorMessage }
35 | }
36 | })
37 |
38 | // Update an existing connection
39 | ipcMain.handle('connections:update', (_, connection: ConnectionConfig) => {
40 | try {
41 | const connections = store.get('connections', [])
42 | const index = connections.findIndex((c) => c.id === connection.id)
43 | if (index === -1) {
44 | return { success: false, error: 'Connection not found' }
45 | }
46 | connections[index] = connection
47 | store.set('connections', connections)
48 | // Broadcast to all windows that connections have changed
49 | windowManager.broadcastToAll('connections:updated')
50 | return { success: true, data: connection }
51 | } catch (error: unknown) {
52 | const errorMessage = error instanceof Error ? error.message : String(error)
53 | return { success: false, error: errorMessage }
54 | }
55 | })
56 |
57 | // Delete a connection
58 | ipcMain.handle('connections:delete', (_, id: string) => {
59 | try {
60 | const connections = store.get('connections', [])
61 | const filtered = connections.filter((c) => c.id !== id)
62 | store.set('connections', filtered)
63 | // Broadcast to all windows that connections have changed
64 | windowManager.broadcastToAll('connections:updated')
65 | return { success: true }
66 | } catch (error: unknown) {
67 | const errorMessage = error instanceof Error ? error.message : String(error)
68 | return { success: false, error: errorMessage }
69 | }
70 | })
71 | }
72 |
--------------------------------------------------------------------------------
/apps/docs/content/docs/database-support/mssql.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Microsoft SQL Server
3 | description: Using data-peek with Microsoft SQL Server
4 | ---
5 |
6 | # Microsoft SQL Server
7 |
8 | data-peek provides full support for Microsoft SQL Server databases.
9 |
10 | ## Connection Setup
11 |
12 | 1. Click **Add Connection** in the sidebar
13 | 2. Select **SQL Server** as the database type
14 | 3. Enter your connection details:
15 | - **Host**: Your SQL Server hostname or IP
16 | - **Port**: Default is `1433`
17 | - **Database**: The database name
18 | - **Username**: Your SQL Server username
19 | - **Password**: Your password
20 |
21 | ## Authentication Methods
22 |
23 | ### SQL Server Authentication
24 |
25 | Standard username/password authentication against SQL Server.
26 |
27 | ### Windows Authentication
28 |
29 | Windows authentication is not currently supported. Use SQL Server authentication instead.
30 |
31 | ## Supported Features
32 |
33 | | Feature | Support |
34 | |---------|:-------:|
35 | | Query execution | ✓ |
36 | | Multiple result sets | ✓ |
37 | | Schema explorer | ✓ |
38 | | Table preview | ✓ |
39 | | Inline editing | ✓ |
40 | | Table designer | ✓ |
41 | | ER diagrams | ✓ |
42 | | Query plans | ✓ |
43 | | Views | ✓ |
44 | | Stored procedures | ✓ |
45 | | Functions | ✓ |
46 | | Sequences | ✓ |
47 | | SSH tunnels | ✓ |
48 |
49 | ## Multiple Result Sets
50 |
51 | SQL Server queries can return multiple result sets. data-peek displays each result set in separate tabs within the results panel.
52 |
53 | ```sql
54 | -- Returns two result sets
55 | SELECT * FROM users;
56 | SELECT * FROM orders;
57 | ```
58 |
59 | ## Stored Procedures
60 |
61 | Browse and execute stored procedures from the schema explorer. Stored procedures appear under each schema in the sidebar.
62 |
63 | ## SSH Tunnel Support
64 |
65 | Connect to SQL Server through an SSH tunnel for secure remote access. See [SSH Tunnels](/docs/features/ssh-tunnels) for configuration details.
66 |
67 | ## Connection String Options
68 |
69 | data-peek uses the `mssql` Node.js driver. Advanced connection options can be configured in the connection dialog.
70 |
71 | ### Encryption
72 |
73 | SSL/TLS encryption is enabled by default. For servers with self-signed certificates, you may need to configure trust settings.
74 |
75 | ## Troubleshooting
76 |
77 | ### Connection Timeout
78 |
79 | If connections are timing out, try:
80 | - Verify the server is accessible from your network
81 | - Check firewall rules allow connections on port 1433
82 | - Ensure SQL Server is configured to allow remote connections
83 |
84 | ### Authentication Failures
85 |
86 | - Verify your username and password
87 | - Ensure the user has access to the specified database
88 | - Check that SQL Server authentication is enabled on the server
89 |
90 | ### Certificate Errors
91 |
92 | For self-signed certificates or internal servers, you may see certificate validation errors. Contact your database administrator for the correct SSL configuration.
93 |
--------------------------------------------------------------------------------
/apps/desktop/src/renderer/src/lib/export.ts:
--------------------------------------------------------------------------------
1 | // Export utilities for CSV, JSON, and Excel formats
2 |
3 | export interface ExportOptions {
4 | filename: string
5 | format: 'csv' | 'json' | 'xlsx'
6 | }
7 |
8 | export interface ExportData {
9 | columns: { name: string; dataType: string }[]
10 | rows: Record[]
11 | }
12 |
13 | // Convert value to CSV-safe string
14 | function escapeCSVValue(value: unknown): string {
15 | if (value === null || value === undefined) {
16 | return ''
17 | }
18 |
19 | const stringValue = typeof value === 'object' ? JSON.stringify(value) : String(value)
20 |
21 | // Escape if contains comma, newline, or double quotes
22 | if (stringValue.includes(',') || stringValue.includes('\n') || stringValue.includes('"')) {
23 | return `"${stringValue.replace(/"/g, '""')}"`
24 | }
25 |
26 | return stringValue
27 | }
28 |
29 | // Export data to CSV format
30 | export function exportToCSV(data: ExportData): string {
31 | const headers = data.columns.map((col) => escapeCSVValue(col.name)).join(',')
32 | const rows = data.rows.map((row) =>
33 | data.columns.map((col) => escapeCSVValue(row[col.name])).join(',')
34 | )
35 | return [headers, ...rows].join('\n')
36 | }
37 |
38 | // Export data to JSON format
39 | export function exportToJSON(data: ExportData, pretty: boolean = true): string {
40 | const jsonData = data.rows.map((row) => {
41 | const obj: Record = {}
42 | data.columns.forEach((col) => {
43 | obj[col.name] = row[col.name]
44 | })
45 | return obj
46 | })
47 | return pretty ? JSON.stringify(jsonData, null, 2) : JSON.stringify(jsonData)
48 | }
49 |
50 | // Trigger download in browser
51 | export function downloadFile(content: string | Blob, filename: string, mimeType: string): void {
52 | const blob = content instanceof Blob ? content : new Blob([content], { type: mimeType })
53 | const url = URL.createObjectURL(blob)
54 | const link = document.createElement('a')
55 | link.href = url
56 | link.download = filename
57 | document.body.appendChild(link)
58 | link.click()
59 | document.body.removeChild(link)
60 | URL.revokeObjectURL(url)
61 | }
62 |
63 | // Export and download CSV
64 | export function downloadCSV(data: ExportData, filename: string): void {
65 | const csv = exportToCSV(data)
66 | downloadFile(csv, filename.endsWith('.csv') ? filename : `${filename}.csv`, 'text/csv')
67 | }
68 |
69 | // Export and download JSON
70 | export function downloadJSON(data: ExportData, filename: string): void {
71 | const json = exportToJSON(data)
72 | downloadFile(json, filename.endsWith('.json') ? filename : `${filename}.json`, 'application/json')
73 | }
74 |
75 | // Generate default filename based on timestamp and optional table name
76 | export function generateExportFilename(tableName?: string): string {
77 | const timestamp = new Date().toISOString().slice(0, 19).replace(/[:.]/g, '-')
78 | return tableName ? `${tableName}_${timestamp}` : `query_result_${timestamp}`
79 | }
80 |
--------------------------------------------------------------------------------
/apps/desktop/src/main/lib/logger.ts:
--------------------------------------------------------------------------------
1 | import log from 'electron-log/main'
2 | import { app } from 'electron'
3 |
4 | // Initialize electron-log
5 | // Logs are stored in:
6 | // - macOS: ~/Library/Logs/data-peek/
7 | // - Windows: %USERPROFILE%\AppData\Roaming\data-peek\logs\
8 | // - Linux: ~/.config/data-peek/logs/
9 | log.initialize()
10 |
11 | const level = app.isPackaged ? 'info' : 'debug'
12 | log.transports.console.level = level
13 | log.transports.file.level = level
14 |
15 | log.transports.file.maxSize = 5 * 1024 * 1024 // 5MB
16 | log.transports.file.format = '[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}] {text}'
17 |
18 | const SENSITIVE_KEYS = [
19 | 'password',
20 | 'license_key',
21 | 'licenseKey',
22 | 'api_key',
23 | 'apiKey',
24 | 'secret',
25 | 'token',
26 | 'authorization'
27 | ]
28 |
29 | function redactSensitive(obj: unknown): unknown {
30 | if (obj === null || obj === undefined) return obj
31 | if (typeof obj !== 'object') return obj
32 |
33 | if (Array.isArray(obj)) {
34 | return obj.map(redactSensitive)
35 | }
36 |
37 | const result: Record = {}
38 | for (const [key, value] of Object.entries(obj as Record)) {
39 | const lowerKey = key.toLowerCase()
40 | if (SENSITIVE_KEYS.some((sk) => lowerKey.includes(sk))) {
41 | result[key] = '***REDACTED***'
42 | } else if (typeof value === 'object' && value !== null) {
43 | result[key] = redactSensitive(value)
44 | } else {
45 | result[key] = value
46 | }
47 | }
48 | return result
49 | }
50 |
51 | function formatArgs(args: unknown[]): string {
52 | return args
53 | .map((arg) => {
54 | if (typeof arg === 'object' && arg !== null) {
55 | return JSON.stringify(redactSensitive(arg), null, 2)
56 | }
57 | return String(arg)
58 | })
59 | .join(' ')
60 | }
61 |
62 | export function createLogger(module: string) {
63 | const scope = log.scope(module)
64 |
65 | return {
66 | debug: (message: string, ...args: unknown[]) => {
67 | if (args.length > 0) {
68 | scope.debug(message, formatArgs(args))
69 | } else {
70 | scope.debug(message)
71 | }
72 | },
73 |
74 | info: (message: string, ...args: unknown[]) => {
75 | if (args.length > 0) {
76 | scope.info(message, formatArgs(args))
77 | } else {
78 | scope.info(message)
79 | }
80 | },
81 |
82 | warn: (message: string, ...args: unknown[]) => {
83 | if (args.length > 0) {
84 | scope.warn(message, formatArgs(args))
85 | } else {
86 | scope.warn(message)
87 | }
88 | },
89 |
90 | error: (message: string, ...args: unknown[]) => {
91 | if (args.length > 0) {
92 | scope.error(message, formatArgs(args))
93 | } else {
94 | scope.error(message)
95 | }
96 | }
97 | }
98 | }
99 |
100 | export type Logger = ReturnType
101 |
102 | export { log }
103 |
--------------------------------------------------------------------------------
/apps/docs/content/docs/configuration/licensing.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Licensing
3 | description: Understand data-peek's licensing model
4 | ---
5 |
6 | # Licensing
7 |
8 | data-peek uses an honor-system licensing model inspired by sustainable indie software.
9 |
10 | ## License Types
11 |
12 | ### Personal (Free)
13 |
14 | Use data-peek for free if you're:
15 | - Working on personal projects
16 | - Learning or studying
17 | - Contributing to open source
18 | - A solo founder (company of one)
19 | - A student or educator
20 |
21 | **All features are included.**
22 |
23 | ### Pro ($29/year)
24 |
25 | A Pro license is required for:
26 | - Commercial use at for-profit companies (2+ people)
27 | - Freelancers billing clients
28 | - Agency work
29 |
30 | **Benefits:**
31 | - Commercial use rights
32 | - 3 device activations
33 | - 1 year of updates
34 | - Perpetual fallback license
35 |
36 | ## Activating a License
37 |
38 | ### Online Activation
39 |
40 | 1. Click the **license icon** in the sidebar
41 | 2. Enter your license key and email
42 | 3. Click **Activate**
43 |
44 | ### Offline Activation
45 |
46 | If your machine doesn't have internet access:
47 |
48 | 1. Click **Activate Offline**
49 | 2. Copy your device ID
50 | 3. Visit the activation portal
51 | 4. Enter device ID and license key
52 | 5. Copy the activation code
53 | 6. Paste and activate
54 |
55 | ## License Details
56 |
57 | ### Device Activations
58 |
59 | Each Pro license includes 3 device activations:
60 | - Work laptop
61 | - Home computer
62 | - Extra device
63 |
64 | Need more? Contact support.
65 |
66 | ### Updates
67 |
68 | Your license includes 1 year of updates from purchase date. After expiration:
69 | - Your current version continues working forever
70 | - New updates require renewal
71 | - Renew anytime to get latest features
72 |
73 | ### Perpetual Fallback
74 |
75 | If you don't renew:
76 | - Keep using the version you have
77 | - It doesn't stop working
78 | - No time limits
79 |
80 | ## Checking License Status
81 |
82 | Click the license icon to see:
83 | - License type
84 | - Expiry date
85 | - Days remaining
86 | - Activation status
87 |
88 | ## Deactivating
89 |
90 | To move your license to a new device:
91 |
92 | 1. Open license settings
93 | 2. Click **Deactivate**
94 | 3. Activate on the new device
95 |
96 | ## FAQ
97 |
98 | ### What counts as commercial use?
99 |
100 | Using data-peek for work-related activities in a for-profit organization of 2+ people.
101 |
102 | ### Can students use it for free?
103 |
104 | Yes! Students and educators can use data-peek for free. Contact us for a free license key.
105 |
106 | ### Is there DRM?
107 |
108 | No. We use an honor system. There's no aggressive license checking or "you've been logged out" surprises.
109 |
110 | ### What if my company has 1 person?
111 |
112 | Solo founders (company of one) can use the free version.
113 |
114 | ### Can I get a refund?
115 |
116 | Yes, 30-day money-back guarantee. No questions asked.
117 |
--------------------------------------------------------------------------------
/apps/docs/content/docs/reference/keyboard-shortcuts.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Keyboard Shortcuts
3 | description: All keyboard shortcuts for data-peek
4 | ---
5 |
6 | # Keyboard Shortcuts
7 |
8 | data-peek is designed for keyboard-first navigation. Here are all available shortcuts.
9 |
10 | ## Command Palette & AI
11 |
12 | | Action | macOS | Windows/Linux |
13 | |--------|-------|---------------|
14 | | Open command palette | `Cmd+K` | `Ctrl+K` |
15 | | Open AI assistant | `Cmd+I` | `Ctrl+I` |
16 |
17 | ## Query Execution
18 |
19 | | Action | macOS | Windows/Linux |
20 | |--------|-------|---------------|
21 | | Execute query | `Cmd+Enter` | `Ctrl+Enter` |
22 | | Format SQL | `Shift+Alt+F` | `Shift+Alt+F` |
23 | | Open saved queries | `Cmd+Shift+S` | `Ctrl+Shift+S` |
24 | | Clear results | `Cmd+K` | `Ctrl+K` |
25 |
26 | ## Tab Management
27 |
28 | | Action | macOS | Windows/Linux |
29 | |--------|-------|---------------|
30 | | New tab | `Cmd+T` | `Ctrl+T` |
31 | | Close tab | `Cmd+W` | `Ctrl+W` |
32 | | Switch to tab 1-9 | `Cmd+1` to `Cmd+9` | `Ctrl+1` to `Ctrl+9` |
33 | | Next tab | `Cmd+Option+→` | `Ctrl+Alt+→` |
34 | | Previous tab | `Cmd+Option+←` | `Ctrl+Alt+←` |
35 |
36 | ## Window Management
37 |
38 | | Action | macOS | Windows/Linux |
39 | |--------|-------|---------------|
40 | | New window | `Cmd+Shift+N` | `Ctrl+Shift+N` |
41 | | Close window | `Cmd+W` | `Alt+F4` |
42 |
43 | ## Connection Management
44 |
45 | | Action | macOS | Windows/Linux |
46 | |--------|-------|---------------|
47 | | Switch to connection 1-9 | `Cmd+Shift+1` to `Cmd+Shift+9` | `Ctrl+Shift+1` to `Ctrl+Shift+9` |
48 |
49 | ## View
50 |
51 | | Action | macOS | Windows/Linux |
52 | |--------|-------|---------------|
53 | | Toggle sidebar | `Cmd+B` | `Ctrl+B` |
54 | | Open settings | `Cmd+,` | `Ctrl+,` |
55 | | Toggle dev tools | `F12` | `F12` |
56 | | Zoom in | `Cmd++` | `Ctrl++` |
57 | | Zoom out | `Cmd+-` | `Ctrl+-` |
58 | | Reset zoom | `Cmd+0` | `Ctrl+0` |
59 | | Full screen | `Cmd+Ctrl+F` | `F11` |
60 |
61 | ## Editor
62 |
63 | | Action | macOS | Windows/Linux |
64 | |--------|-------|---------------|
65 | | Undo | `Cmd+Z` | `Ctrl+Z` |
66 | | Redo | `Cmd+Shift+Z` | `Ctrl+Shift+Z` |
67 | | Cut | `Cmd+X` | `Ctrl+X` |
68 | | Copy | `Cmd+C` | `Ctrl+C` |
69 | | Paste | `Cmd+V` | `Ctrl+V` |
70 | | Select all | `Cmd+A` | `Ctrl+A` |
71 | | Find | `Cmd+F` | `Ctrl+F` |
72 | | Trigger autocomplete | `Ctrl+Space` | `Ctrl+Space` |
73 |
74 | ## Application
75 |
76 | | Action | macOS | Windows/Linux |
77 | |--------|-------|---------------|
78 | | Quit | `Cmd+Q` | `Alt+F4` |
79 | | Hide | `Cmd+H` | - |
80 | | Minimize | `Cmd+M` | - |
81 |
82 | ## Results Navigation
83 |
84 | | Action | macOS | Windows/Linux |
85 | |--------|-------|---------------|
86 | | Navigate FK | `Cmd+Click` | `Ctrl+Click` |
87 | | Copy cell | Click cell | Click cell |
88 |
89 | ## Tips
90 |
91 | - Press `Cmd+K` / `Ctrl+K` to access the command palette for quick access to all features
92 | - Most shortcuts work when the editor is focused
93 | - Tab and window shortcuts work globally
94 | - Connection shortcuts work globally
95 |
--------------------------------------------------------------------------------
/apps/desktop/src/renderer/src/components/nav-favorites.tsx:
--------------------------------------------------------------------------------
1 | import { ArrowUpRight, Link, MoreHorizontal, StarOff, Trash2 } from 'lucide-react'
2 |
3 | import {
4 | DropdownMenu,
5 | DropdownMenuContent,
6 | DropdownMenuItem,
7 | DropdownMenuSeparator,
8 | DropdownMenuTrigger
9 | } from '@/components/ui/dropdown-menu'
10 | import {
11 | SidebarGroup,
12 | SidebarGroupLabel,
13 | SidebarMenu,
14 | SidebarMenuAction,
15 | SidebarMenuButton,
16 | SidebarMenuItem,
17 | useSidebar
18 | } from '@/components/ui/sidebar'
19 |
20 | export function NavFavorites({
21 | favorites
22 | }: {
23 | favorites: {
24 | name: string
25 | url: string
26 | emoji: string
27 | }[]
28 | }) {
29 | const { isMobile } = useSidebar()
30 |
31 | return (
32 |
33 | Favorites
34 |
35 | {favorites.map((item) => (
36 |
37 |
38 |
39 | {item.emoji}
40 | {item.name}
41 |
42 |
43 |
44 |
45 |
46 |
47 | More
48 |
49 |
50 |
55 |
56 |
57 | Remove from Favorites
58 |
59 |
60 |
61 |
62 | Copy Link
63 |
64 |
65 |
66 | Open in New Tab
67 |
68 |
69 |
70 |
71 | Delete
72 |
73 |
74 |
75 |
76 | ))}
77 |
78 |
79 |
80 | More
81 |
82 |
83 |
84 |
85 | )
86 | }
87 |
--------------------------------------------------------------------------------
/apps/web/src/components/marketing/cta.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link'
2 | import { Button } from '@/components/ui/button'
3 | import { ArrowRight, Download } from 'lucide-react'
4 |
5 | export function CTA() {
6 | return (
7 |
8 | {/* Background Effects */}
9 |
10 |
17 |
18 |
19 | {/* Headline */}
20 |
24 | Ready to peek?
25 |
26 |
30 | Download for free and start querying in seconds.
31 |
32 |
33 | No sign-up required.
34 |
35 |
36 | {/* CTA Buttons */}
37 |
38 |
44 |
50 |
51 |
52 | {/* Trust Signals */}
53 |
54 |
55 |
56 | No credit card required
57 |
58 |
59 |
60 | 30-day money-back guarantee
61 |
62 |
63 |
64 | Works offline
65 |
66 |
67 |
68 |
69 | )
70 | }
71 |
--------------------------------------------------------------------------------
/apps/docs/content/docs/database-support/sqlite.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: SQLite
3 | description: Using data-peek with SQLite databases
4 | ---
5 |
6 | # SQLite
7 |
8 | data-peek supports SQLite databases for working with local database files.
9 |
10 | ## Opening a SQLite Database
11 |
12 | 1. Click **Add Connection** in the sidebar
13 | 2. Select **SQLite** as the database type
14 | 3. Click **Browse** to select your `.db`, `.sqlite`, or `.sqlite3` file
15 | 4. Click **Connect**
16 |
17 | ## Supported Features
18 |
19 | | Feature | Support |
20 | |---------|:-------:|
21 | | Query execution | ✓ |
22 | | Multiple statements | ✓ |
23 | | Schema explorer | ✓ |
24 | | Table preview | ✓ |
25 | | Inline editing | ✓ |
26 | | Table designer | ✓ |
27 | | ER diagrams | ✓ |
28 | | Query plans | ✓ |
29 | | Views | ✓ |
30 | | Foreign keys | ✓ |
31 | | JSON support | ✓ |
32 |
33 | ## File-Based Access
34 |
35 | SQLite is a serverless database that stores everything in a single file. Unlike PostgreSQL or MySQL, there's no network connection - data-peek reads and writes directly to the file on your computer.
36 |
37 | ### Supported File Extensions
38 |
39 | - `.db`
40 | - `.sqlite`
41 | - `.sqlite3`
42 | - `.s3db`
43 |
44 | ## Query Execution
45 |
46 | ### Single Statement
47 |
48 | ```sql
49 | SELECT * FROM users WHERE active = 1;
50 | ```
51 |
52 | ### Multiple Statements
53 |
54 | Execute multiple statements in sequence:
55 |
56 | ```sql
57 | INSERT INTO users (name, email) VALUES ('John', 'john@example.com');
58 | SELECT * FROM users ORDER BY id DESC LIMIT 1;
59 | ```
60 |
61 | Each statement result is displayed in a separate result tab.
62 |
63 | ## Type Affinity
64 |
65 | SQLite uses dynamic typing with type affinity. data-peek displays the following types:
66 |
67 | | Affinity | Description |
68 | |----------|-------------|
69 | | INTEGER | Whole numbers |
70 | | REAL | Floating-point numbers |
71 | | TEXT | Strings |
72 | | BLOB | Binary data |
73 | | NUMERIC | Numbers, dates, booleans |
74 |
75 | ## Query Plans
76 |
77 | View query execution plans using SQLite's `EXPLAIN QUERY PLAN`:
78 |
79 | ```sql
80 | EXPLAIN QUERY PLAN SELECT * FROM users WHERE email = 'test@example.com';
81 | ```
82 |
83 | Or use the **Explain** button in the query editor.
84 |
85 | ## Limitations
86 |
87 | Since SQLite is file-based:
88 |
89 | - **No SSH tunnels** - Direct file access only
90 | - **No stored procedures** - SQLite doesn't support stored procedures
91 | - **No sequences** - Use `AUTOINCREMENT` on INTEGER PRIMARY KEY instead
92 | - **No custom types** - Use type affinity instead
93 |
94 | ## Use Cases
95 |
96 | SQLite is ideal for:
97 |
98 | - **Development databases** - Quick prototyping
99 | - **Application data** - Inspecting app databases (mobile apps, Electron apps)
100 | - **Data analysis** - Working with exported data
101 | - **Testing** - Lightweight test databases
102 |
103 | ## Performance
104 |
105 | SQLite handles databases up to several gigabytes efficiently. For databases exceeding 10+ GB or high-concurrency needs, consider PostgreSQL or MySQL.
106 |
--------------------------------------------------------------------------------
/apps/docs/content/docs/getting-started/installation.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Installation
3 | description: Download and install data-peek on your system
4 | ---
5 |
6 | # Installation
7 |
8 | data-peek is available for macOS, Windows, and Linux. Choose your platform below.
9 |
10 | ## macOS
11 |
12 | ### Apple Silicon (M1/M2/M3/M4)
13 |
14 | 1. Download `data-peek-mac-arm64.dmg` from the [releases page](https://github.com/Rohithgilla12/data-peek/releases)
15 | 2. Open the DMG file
16 | 3. Drag data-peek to your Applications folder
17 | 4. Launch data-peek from Applications
18 |
19 |
20 | On first launch, macOS may show a security warning. Right-click the app and select "Open" to bypass this.
21 |
22 |
23 | ### Intel Macs
24 |
25 | 1. Download `data-peek-mac-x64.dmg` from the [releases page](https://github.com/Rohithgilla12/data-peek/releases)
26 | 2. Open the DMG file
27 | 3. Drag data-peek to your Applications folder
28 | 4. Launch data-peek from Applications
29 |
30 | ## Windows
31 |
32 | ### Installer (Recommended)
33 |
34 | 1. Download `data-peek-win-setup.exe` from the [releases page](https://github.com/Rohithgilla12/data-peek/releases)
35 | 2. Run the installer
36 | 3. Follow the installation wizard
37 | 4. Launch data-peek from the Start menu
38 |
39 | ### Portable Version
40 |
41 | 1. Download `data-peek-win-portable.zip` from the [releases page](https://github.com/Rohithgilla12/data-peek/releases)
42 | 2. Extract to your preferred location
43 | 3. Run `data-peek.exe`
44 |
45 |
46 | The portable version doesn't require installation and can run from a USB drive.
47 |
48 |
49 | ## Linux
50 |
51 | ### AppImage (Recommended)
52 |
53 | 1. Download `data-peek-linux.AppImage` from the [releases page](https://github.com/Rohithgilla12/data-peek/releases)
54 | 2. Make it executable: `chmod +x data-peek-linux.AppImage`
55 | 3. Run: `./data-peek-linux.AppImage`
56 |
57 | ### Debian/Ubuntu (.deb)
58 |
59 | 1. Download `data-peek-linux.deb` from the [releases page](https://github.com/Rohithgilla12/data-peek/releases)
60 | 2. Install: `sudo dpkg -i data-peek-linux.deb`
61 | 3. Launch from your applications menu
62 |
63 | ## Verifying Your Installation
64 |
65 | After installation, launch data-peek. You should see the main window with:
66 |
67 | - A sidebar on the left showing "No connections"
68 | - An empty query editor in the main area
69 | - A dark theme by default
70 |
71 | If you see this, you're ready to [create your first connection](/docs/getting-started/first-connection)!
72 |
73 | ## Troubleshooting
74 |
75 | ### macOS: "App is damaged and can't be opened"
76 |
77 | Run this command in Terminal:
78 | ```bash
79 | xattr -cr /Applications/data-peek.app
80 | ```
81 |
82 | ### Linux: AppImage won't run
83 |
84 | Make sure you have FUSE installed:
85 | ```bash
86 | sudo apt install fuse libfuse2 # Debian/Ubuntu
87 | sudo dnf install fuse # Fedora
88 | ```
89 |
90 | ### Windows: Antivirus blocks installation
91 |
92 | Some antivirus software may flag the installer. Add an exception for data-peek or temporarily disable real-time protection during installation.
93 |
--------------------------------------------------------------------------------
/apps/web/src/app/api/updates/check/route.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest, NextResponse } from 'next/server'
2 | import { db, releases } from '@/db'
3 | import { eq, desc } from 'drizzle-orm'
4 |
5 | interface UpdateCheckResponse {
6 | hasUpdate: boolean
7 | latestVersion?: string
8 | currentVersion?: string
9 | downloadUrl?: string
10 | releaseNotes?: string
11 | forceUpdate?: boolean
12 | }
13 |
14 | function compareVersions(v1: string, v2: string): number {
15 | const parts1 = v1.split('.').map(Number)
16 | const parts2 = v2.split('.').map(Number)
17 |
18 | for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
19 | const p1 = parts1[i] || 0
20 | const p2 = parts2[i] || 0
21 | if (p1 > p2) return 1
22 | if (p1 < p2) return -1
23 | }
24 | return 0
25 | }
26 |
27 | export async function GET(request: NextRequest) {
28 | try {
29 | const { searchParams } = new URL(request.url)
30 | const currentVersion = searchParams.get('version')
31 | const platform = searchParams.get('platform') // macos, macos-arm, windows, linux
32 |
33 | if (!currentVersion) {
34 | return NextResponse.json(
35 | { hasUpdate: false },
36 | { status: 400 }
37 | )
38 | }
39 |
40 | // Get the latest release
41 | const latestRelease = await db.query.releases.findFirst({
42 | where: eq(releases.isLatest, true),
43 | orderBy: [desc(releases.releasedAt)],
44 | })
45 |
46 | if (!latestRelease) {
47 | return NextResponse.json({
48 | hasUpdate: false,
49 | currentVersion,
50 | })
51 | }
52 |
53 | // Compare versions
54 | const hasUpdate = compareVersions(latestRelease.version, currentVersion) > 0
55 |
56 | // Check if forced update is required
57 | const forceUpdate = latestRelease.minSupportedVersion
58 | ? compareVersions(latestRelease.minSupportedVersion, currentVersion) > 0
59 | : false
60 |
61 | // Get the appropriate download URL based on platform
62 | let downloadUrl: string | undefined
63 | switch (platform) {
64 | case 'macos':
65 | downloadUrl = latestRelease.downloadUrlMac ?? undefined
66 | break
67 | case 'macos-arm':
68 | downloadUrl = latestRelease.downloadUrlMacArm ?? undefined
69 | break
70 | case 'windows':
71 | downloadUrl = latestRelease.downloadUrlWindows ?? undefined
72 | break
73 | case 'linux':
74 | downloadUrl = latestRelease.downloadUrlLinux ?? undefined
75 | break
76 | default:
77 | // Return all URLs if platform not specified
78 | break
79 | }
80 |
81 | return NextResponse.json({
82 | hasUpdate,
83 | latestVersion: latestRelease.version,
84 | currentVersion,
85 | downloadUrl,
86 | releaseNotes: latestRelease.releaseNotes ?? undefined,
87 | forceUpdate,
88 | })
89 | } catch (error) {
90 | console.error('Update check error:', error)
91 | return NextResponse.json(
92 | { hasUpdate: false },
93 | { status: 500 }
94 | )
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
|