Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontend/src/assets/drivers/mysql.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions frontend/src/assets/drivers/postgres.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions frontend/src/components/DriverIcon.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!-- A database engine's own logo, or the app's cylinder for one without. -->
<script setup>
import { computed } from 'vue'
import AppLogo from './AppLogo.vue'
import { driverIcon, driverName } from '../utils/drivers'

const props = defineProps({
driver: { type: String, default: '' },
})

const src = computed(() => driverIcon(props.driver))
</script>

<template>
<img v-if="src" :src="src" :alt="driverName(driver)" data-test="driver-icon" draggable="false" />
<AppLogo v-else />
</template>
11 changes: 11 additions & 0 deletions frontend/src/utils/drivers.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
// The engines' original logos, from devicon (MIT): devicon.dev.
import mysqlIcon from '../assets/drivers/mysql.svg'
import postgresIcon from '../assets/drivers/postgres.svg'

const NAMES = { postgres: 'PostgreSQL', mysql: 'MySQL' }

/** A driver id as people say it. */
export function driverName(id) {
return NAMES[id] || id || ''
}

const ICONS = { postgres: postgresIcon, mysql: mysqlIcon }

/** The URL of a driver's logo, or '' when it has none. */
export function driverIcon(id) {
return ICONS[id] || ''
}

/** Used when GET /drivers fails, so the form still renders. */
export const FALLBACK_DRIVERS = [
{ id: 'postgres', name: 'PostgreSQL', available: true },
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/views/DatabaseDetailView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
<!-- Page header -->
<div class="py-2 mb-6 flex flex-col lg:flex-row lg:items-start justify-between gap-4 px-4">
<div class="min-w-0 flex items-start gap-3">
<div class="w-10 h-10 rounded-xl bg-black dark:bg-white text-white dark:text-black flex items-center justify-center shrink-0 rotate-3">
<AppLogo class="w-5 h-5" />
<div class="w-10 h-10 rounded-xl bg-white border border-gray-200 dark:border-zinc-700 text-gray-700 flex items-center justify-center shrink-0 rotate-3">
<DriverIcon :driver="database.driver" class="w-6 h-6" />
</div>
<div class="min-w-0">
<div class="flex items-center gap-2 flex-wrap">
Expand Down Expand Up @@ -55,7 +55,7 @@
import { computed, onMounted, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import ActivityPanel from '../components/ActivityPanel.vue'
import AppLogo from '../components/AppLogo.vue'
import DriverIcon from '../components/DriverIcon.vue'
import ConnectPanel from '../components/ConnectPanel.vue'
import LoadingState from '../components/LoadingState.vue'
import PiiEditor from '../components/PiiEditor.vue'
Expand Down
7 changes: 3 additions & 4 deletions frontend/src/views/DatabaseFormView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,8 @@
form.driver === d.id ? 'border-gray-900 dark:border-white bg-gray-50 dark:bg-zinc-800/60' : 'border-gray-200 dark:border-zinc-800 hover:border-gray-300 dark:hover:border-zinc-700',
!d.available ? 'opacity-50' : ''
]">
<div class="w-9 h-9 rounded-lg flex items-center justify-center shrink-0"
:class="form.driver === d.id ? 'bg-black dark:bg-white text-white dark:text-black' : 'bg-gray-100 dark:bg-zinc-800 text-gray-600 dark:text-zinc-300'">
<AppLogo class="w-5 h-5" />
<div class="w-9 h-9 rounded-lg flex items-center justify-center shrink-0 bg-white border border-gray-200 dark:border-zinc-700 text-gray-600">
<DriverIcon :driver="d.id" class="w-6 h-6" />
</div>
<div class="min-w-0 flex-1">
<div class="text-sm font-bold text-gray-900 dark:text-zinc-100">{{ d.name }}</div>
Expand Down Expand Up @@ -96,8 +95,8 @@
<script setup>
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import AppLogo from '../components/AppLogo.vue'
import DeleteModal from '../components/DeleteModal.vue'
import DriverIcon from '../components/DriverIcon.vue'
import LoadingState from '../components/LoadingState.vue'
import { createDatabase, deleteDatabase, fetchDatabase, fetchDrivers, testConnection, updateDatabase } from '../api'
import { useToasts } from '../composables/useToasts'
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/views/DatabasesView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
:class="[CARD, 'block p-5 hover:border-gray-300 dark:hover:border-zinc-700 transition-colors min-w-0']">
<div class="flex items-start justify-between gap-3 mb-4">
<div class="flex items-center gap-3 min-w-0">
<div class="w-9 h-9 rounded-lg bg-gray-100 dark:bg-zinc-800 flex items-center justify-center shrink-0">
<AppLogo class="w-5 h-5 text-gray-700 dark:text-zinc-300" />
<div class="w-9 h-9 rounded-lg bg-white border border-gray-200 dark:border-zinc-700 flex items-center justify-center shrink-0 text-gray-700">
<DriverIcon :driver="db.driver" class="w-6 h-6" />
</div>
<div class="min-w-0">
<h3 class="text-sm font-bold text-gray-900 dark:text-zinc-100 truncate">{{ db.name }}</h3>
Expand All @@ -62,6 +62,7 @@
import { onMounted } from 'vue'
import AppLogo from '../components/AppLogo.vue'
import CopyField from '../components/CopyField.vue'
import DriverIcon from '../components/DriverIcon.vue'
import LoadingState from '../components/LoadingState.vue'
import { useDatabaseStore } from '../stores/databaseStore'
import { driverName } from '../utils/drivers'
Expand Down
5 changes: 5 additions & 0 deletions frontend/test/DatabaseFormView.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ describe('DatabaseFormView', () => {
expect(pg.attributes('aria-checked')).toBe('true')
expect(ora.attributes('disabled')).toBeDefined()
expect(ora.find('[data-test="coming-soon"]').text()).toBe('Coming soon')
// Each engine shows its own logo; one without falls back to the cylinder.
expect(pg.find('[data-test="driver-icon"]').attributes('alt')).toBe('PostgreSQL')
expect(wrapper.find('[data-test="driver-mysql"] [data-test="driver-icon"]').attributes('alt')).toBe('MySQL')
expect(ora.find('[data-test="driver-icon"]').exists()).toBe(false)
expect(ora.find('svg').exists()).toBe(true)
await ora.trigger('click')
expect(pg.attributes('aria-checked')).toBe('true')
expect(wrapper.find('[data-test="dsn-format"]').text()).toContain('postgres://')
Expand Down
10 changes: 9 additions & 1 deletion frontend/test/utils.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest'
import { loginRedirect } from '../src/utils/loginRedirect'
import { claudeCodeCommand, mcpJsonConfig, mcpServerName } from '../src/utils/mcpSnippets'
import { driverName, DSN_FORMATS, DSN_PLACEHOLDERS, FALLBACK_DRIVERS } from '../src/utils/drivers'
import { driverIcon, driverName, DSN_FORMATS, DSN_PLACEHOLDERS, FALLBACK_DRIVERS } from '../src/utils/drivers'

const origin = 'https://sql.example.com'

Expand Down Expand Up @@ -38,6 +38,14 @@ describe('drivers', () => {
expect(driverName('postgres')).toBe('PostgreSQL')
expect(driverName('oracle')).toBe('oracle')
expect(driverName(undefined)).toBe('')
})

it('has the original logo of each engine', () => {
// Vite inlines the small one as a data: URL.
expect(driverIcon('postgres')).toBeTruthy()
expect(driverIcon('mysql')).toBeTruthy()
expect(driverIcon('mysql')).not.toBe(driverIcon('postgres'))
expect(driverIcon('oracle')).toBe('')
expect(FALLBACK_DRIVERS.find((d) => d.id === 'mysql').available).toBe(true)
expect(DSN_PLACEHOLDERS.postgres).toContain('sslmode=require')
expect(DSN_PLACEHOLDERS.mysql).toMatch(/^mysql:\/\//)
Expand Down
Loading