diff --git a/electron-builder.yml b/electron-builder.yml index db45e57..ffc77e9 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -1,11 +1,16 @@ appId: com.Queryus.QGenie productName: QGenie +extraResources: + - from: 'resources/' + to: 'resources' + filter: + - '**/*' directories: output: dist buildResources: build files: - - "out/**" - - "package.json" + - 'out/**' + - 'package.json' asarUnpack: - resources/** win: @@ -21,6 +26,9 @@ mac: icon: build/icon.icns target: dmg entitlementsInherit: build/entitlements.mac.plist + binaries: + - resources/mac/qgenie-api + - resources/mac/qgenie-ai extendInfo: - NSCameraUsageDescription: Application requests access to the device's camera. - NSMicrophoneUsageDescription: Application requests access to the device's microphone. @@ -42,4 +50,3 @@ publish: provider: github owner: Queryus repo: QGenie_app - diff --git a/package-lock.json b/package-lock.json index eb1e345..9dc7170 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,8 @@ "tailwind-merge": "^3.3.1", "tailwindcss-animate": "^1.0.7", "tw-animate-css": "^1.3.5", - "uuid": "^11.1.0" + "uuid": "^11.1.0", + "wait-on": "^8.0.4" }, "devDependencies": { "@electron-toolkit/eslint-config-prettier": "^3.0.0", @@ -44,6 +45,7 @@ "@types/react-dom": "^19.1.2", "@vitejs/plugin-react": "^4.6.0", "autoprefixer": "^10.4.21", + "cross-env": "^10.0.0", "electron": "^37.1.0", "electron-builder": "^26.0.12", "electron-vite": "^3.1.0", @@ -1162,6 +1164,13 @@ "node": ">= 10.0.0" } }, + "node_modules/@epic-web/invariant": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz", + "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==", + "dev": true, + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.6", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.6.tgz", @@ -1791,6 +1800,21 @@ "dev": true, "license": "MIT" }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -2691,6 +2715,27 @@ "win32" ] }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "license": "BSD-3-Clause" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "license": "BSD-3-Clause" + }, "node_modules/@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", @@ -4345,13 +4390,13 @@ } }, "node_modules/axios": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", - "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, @@ -5204,6 +5249,24 @@ "optional": true, "peer": true }, + "node_modules/cross-env": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.0.0.tgz", + "integrity": "sha512-aU8qlEK/nHYtVuN4p7UQgAwVljzMg8hB4YK5ThRqD2l/ziSnryncPNn7bMLt5cFYsKVKBh8HqLqyCoTupEUu7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@epic-web/invariant": "^1.0.0", + "cross-spawn": "^7.0.6" + }, + "bin": { + "cross-env": "dist/bin/cross-env.js", + "cross-env-shell": "dist/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -8180,6 +8243,19 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/joi": { + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -8744,7 +8820,6 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, "license": "MIT" }, "node_modules/lodash.escaperegexp": { @@ -9112,7 +9187,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -10540,6 +10614,15 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safe-array-concat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", @@ -12106,6 +12189,25 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/wait-on": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-8.0.4.tgz", + "integrity": "sha512-8f9LugAGo4PSc0aLbpKVCVtzayd36sSCp4WLpVngkYq6PK87H79zt77/tlCU6eKCLqR46iFvcl0PU5f+DmtkwA==", + "license": "MIT", + "dependencies": { + "axios": "^1.11.0", + "joi": "^17.13.3", + "lodash": "^4.17.21", + "minimist": "^1.2.8", + "rxjs": "^7.8.2" + }, + "bin": { + "wait-on": "bin/wait-on" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", diff --git a/package.json b/package.json index 384c63b..75a7372 100644 --- a/package.json +++ b/package.json @@ -15,13 +15,14 @@ "url": "https://github.com/Queryus/QGenie_app/issues" }, "scripts": { + "prebuild": "chmod +x resources/mac/qgenie-*", "format": "prettier --write .", "lint": "eslint --cache .", "typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false", "typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false", "typecheck": "npm run typecheck:node && npm run typecheck:web", "start": "electron .", - "dev": "electron-vite dev -w", + "dev": "cross-env NODE_ENV=development electron-vite dev -w", "build:electron-app": "npm run typecheck && electron-vite build", "build": "npm run build:electron-app && electron-builder --publish never", "postinstall": "electron-builder install-app-deps", @@ -59,7 +60,8 @@ "tailwind-merge": "^3.3.1", "tailwindcss-animate": "^1.0.7", "tw-animate-css": "^1.3.5", - "uuid": "^11.1.0" + "uuid": "^11.1.0", + "wait-on": "^8.0.4" }, "devDependencies": { "@electron-toolkit/eslint-config-prettier": "^3.0.0", @@ -72,6 +74,7 @@ "@types/react-dom": "^19.1.2", "@vitejs/plugin-react": "^4.6.0", "autoprefixer": "^10.4.21", + "cross-env": "^10.0.0", "electron": "^37.1.0", "electron-builder": "^26.0.12", "electron-vite": "^3.1.0", diff --git a/src/main/index.ts b/src/main/index.ts index d5af63c..4d48e1d 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,27 +1,162 @@ import { app, BrowserWindow } from 'electron' import { electronApp, optimizer } from '@electron-toolkit/utils' -import { createMainWindow } from './windows/mainWindow' +import { createMainWindow, getMainWindow } from './windows/mainWindow' import { registerIpcHandlers } from './ipc/handlers' +import { spawn, ChildProcess } from 'child_process' +import path from 'path' +import os from 'os' +import waitOn from 'wait-on' +import fs from 'fs' +import net from 'net' + +let apiProcess: ChildProcess | null = null +let aiProcess: ChildProcess | null = null app.disableHardwareAcceleration() -app.whenReady().then(() => { +app.whenReady().then(async () => { electronApp.setAppUserModelId('com.Queryus.QGenie') - - app.on('browser-window-created', (_, window) => { - optimizer.watchWindowShortcuts(window) - }) + app.on('browser-window-created', (_, window) => optimizer.watchWindowShortcuts(window)) createMainWindow() registerIpcHandlers() - app.on('activate', function () { + console.log('NODE_ENV in main process:', process.env.NODE_ENV) + + getMainWindow()?.webContents.openDevTools() + if (process.env.NODE_ENV === 'development') { + setTimeout(() => { + getMainWindow()?.webContents.send('backend-ready', { apiPort: 39722 }) + }, 1000) + } else { + try { + console.log('Starting backend services...') + await startBackendServices() + getMainWindow()?.webContents.send('backend-ready', { apiPort: 39722 }) + } catch (err) { + console.error('Backend startup failed:', err) + getMainWindow()?.webContents.send('backend-error', err || 'Unknown error') + } + } + + app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createMainWindow() }) }) app.on('window-all-closed', () => { - if (process.platform !== 'darwin') { - app.quit() - } + if (process.platform !== 'darwin') app.quit() }) + +app.on('before-quit', async () => { + console.log('Shutting down backend services...') + const shutdownPromises: Promise[] = [] + + const killProcess = (proc: ChildProcess | null, name: string): Promise => + new Promise((resolve) => { + if (!proc) return resolve() + proc.kill('SIGTERM') + const timeout = setTimeout(() => { + if (!proc.killed) proc.kill('SIGKILL') + resolve() + }, 5000) + proc.on('exit', () => { + clearTimeout(timeout) + console.log(`${name} exited`) + resolve() + }) + }) + + shutdownPromises.push(killProcess(apiProcess, 'API')) + shutdownPromises.push(killProcess(aiProcess, 'AI')) + + await Promise.all(shutdownPromises) + console.log('All backend services stopped') +}) + +// 포트 사용 여부 확인 +function isPortInUse(port: number): Promise { + return new Promise((resolve) => { + const server = net.createServer() + server.listen(port, () => server.close(() => resolve(false))) + server.on('error', () => resolve(true)) + }) +} + +// API 헬스 체크 +async function checkApiHealth(port: number, maxRetries = 10): Promise { + for (let i = 0; i < maxRetries; i++) { + try { + const res = await fetch(`http://127.0.0.1:${port}/health`) + if (res.ok) return true + } catch { + continue + } + await new Promise((r) => setTimeout(r, 1000)) + } + return false +} + +// 리소스 경로 +function getResourcePath(): string { + if (process.env.NODE_ENV === 'development') return path.join(__dirname, '../../resources') + if (process.resourcesPath) return path.join(process.resourcesPath, 'resources') + return path.join(process.cwd(), 'resources') +} + +// 백엔드 시작 +async function startBackendServices(): Promise { + const platform = os.platform() + const basePath = getResourcePath() + const apiPort = 39722 + + let apiPath: string + let aiPath: string + + if (platform === 'win32') { + apiPath = path.join(basePath, 'win', 'qgenie-api.exe') + aiPath = path.join(basePath, 'win', 'qgenie-ai.exe') + } else if (platform === 'darwin') { + apiPath = path.join(basePath, 'mac', 'qgenie-api') + aiPath = path.join(basePath, 'mac', 'qgenie-ai') + } else { + throw new Error(`Unsupported platform: ${platform}`) + } + + if (!fs.existsSync(apiPath)) throw new Error(`API executable not found: ${apiPath}`) + if (!fs.existsSync(aiPath)) throw new Error(`AI executable not found: ${aiPath}`) + + if (platform === 'darwin') { + fs.chmodSync(apiPath, 0o755) + fs.chmodSync(aiPath, 0o755) + } + + const portInUse = await isPortInUse(apiPort) + if (portInUse && (await checkApiHealth(apiPort))) { + console.log('Existing API server healthy, skipping startup') + } else { + await new Promise((resolve, reject) => { + console.log('Spawning API process:', apiPath) + apiProcess = spawn(apiPath, [`--port=${apiPort}`], { + cwd: path.dirname(apiPath), + stdio: 'inherit' + }) + apiProcess.on('error', (err) => reject(err)) + apiProcess.on('exit', (code) => + code !== 0 ? reject(new Error(`API exited with ${code}`)) : resolve() + ) + + // 포트 열림 감지 + const waitOpts = { resources: [`tcp:127.0.0.1:${apiPort}`], timeout: 30000 } + waitOn(waitOpts) + .then(() => checkApiHealth(apiPort)) + .then((healthy) => (healthy ? resolve() : reject(new Error('API failed health check')))) + .catch(reject) + }) + } + + // AI 시작 + console.log('Spawning AI process:', aiPath) + aiProcess = spawn(aiPath, [], { cwd: path.dirname(aiPath), stdio: 'inherit' }) + aiProcess.on('error', (err) => console.error('AI spawn error:', err)) +} diff --git a/src/main/windows/mainWindow.ts b/src/main/windows/mainWindow.ts index 89f7963..2303eeb 100644 --- a/src/main/windows/mainWindow.ts +++ b/src/main/windows/mainWindow.ts @@ -15,7 +15,7 @@ export function createMainWindow(): void { autoHideMenuBar: true, ...(process.platform === 'linux' ? { icon } : {}), webPreferences: { - preload: join(__dirname, '../../out/preload/index.cjs'), + preload: join(__dirname, '../preload/index.cjs'), sandbox: true, contextIsolation: true, nodeIntegration: false @@ -34,7 +34,7 @@ export function createMainWindow(): void { if (is.dev && process.env['ELECTRON_RENDERER_URL']) { mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL']) } else { - mainWindow.loadFile(join(__dirname, '../../renderer/index.html')) + mainWindow.loadFile(join(__dirname, '../renderer/index.html')) } } diff --git a/src/main/windows/subWindow.ts b/src/main/windows/subWindow.ts index c741d91..ec6e74a 100644 --- a/src/main/windows/subWindow.ts +++ b/src/main/windows/subWindow.ts @@ -50,8 +50,10 @@ export function createSubWindow({ autoHideMenuBar: true, ...(process.platform === 'linux' ? { icon } : {}), webPreferences: { - preload: join(__dirname, '../../out/preload/index.cjs'), - sandbox: true + preload: join(__dirname, '../preload/index.cjs'), + sandbox: true, + contextIsolation: true, + nodeIntegration: false } }) @@ -63,13 +65,10 @@ export function createSubWindow({ subWindow = null }) - /** - * NOTE: 해시 라우팅 - */ if (is.dev && process.env['ELECTRON_RENDERER_URL']) { subWindow.loadURL(`${process.env['ELECTRON_RENDERER_URL']}#${route}`) } else { - subWindow.loadFile(join(__dirname, '../../renderer/index.html'), { + subWindow.loadFile(join(__dirname, '../renderer/index.html'), { hash: route }) } diff --git a/src/renderer/index.html b/src/renderer/index.html index aaefb85..78dee6f 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -11,7 +11,7 @@ style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data: https://fastly.jsdelivr.net; - connect-src 'self' http://localhost:39722; + connect-src 'self' http://127.0.0.1:39722; " /> diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index a671811..6105c4b 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -1,16 +1,55 @@ +import { useEffect, useState } from 'react' import { HashRouter, Route, Routes } from 'react-router-dom' import { MainPage } from '@/components/workspace/main-page' import { ConnectionWizard } from '@/components/connection-wizard/wizard-modal' import SettingModal from './components/setting/setting-modal' import ErdPage from './erd/erd-page' +import { Loading } from './Loading' function App(): React.JSX.Element { + const [backendReady, setBackendReady] = useState(false) + const [backendError, setBackendError] = useState(null) + + useEffect(() => { + if (window.electron?.ipcRenderer) { + const handleReady = (): void => setBackendReady(true) + const handleError = (_: unknown, err: unknown): void => { + setBackendError( + err instanceof Error ? err.message : typeof err === 'string' ? err : JSON.stringify(err) + ) + } + + window.electron.ipcRenderer.once('backend-ready', handleReady) + window.electron.ipcRenderer.once('backend-error', handleError) + + return () => { + window.electron.ipcRenderer.removeAllListeners('backend-ready') + window.electron.ipcRenderer.removeAllListeners('backend-error') + } + } else if (process.env.NODE_ENV === 'development') { + setTimeout(() => setBackendReady(true), 1000) + } + + return undefined + }, []) + return ( - }> + + ) : backendReady ? ( + + ) : ( + + ) + } + /> } /> - }> + } /> } /> diff --git a/src/renderer/src/Loading.tsx b/src/renderer/src/Loading.tsx new file mode 100644 index 0000000..3c98add --- /dev/null +++ b/src/renderer/src/Loading.tsx @@ -0,0 +1,22 @@ +import { JSX } from 'react' + +interface LoadingProps { + backendError: string | null +} + +export function Loading({ backendError }: LoadingProps): JSX.Element { + return ( +
+
+
+

QGenie 시작 중...

+

백엔드 서비스를 초기화하고 있습니다.

+ {backendError && ( +
+

오류가 발생했습니다: {backendError}

+
+ )} +
+
+ ) +} diff --git a/src/renderer/public/mariadb.png b/src/renderer/src/assets/database-image/mariadb.png similarity index 100% rename from src/renderer/public/mariadb.png rename to src/renderer/src/assets/database-image/mariadb.png diff --git a/src/renderer/public/microsoft.png b/src/renderer/src/assets/database-image/microsoft.png similarity index 100% rename from src/renderer/public/microsoft.png rename to src/renderer/src/assets/database-image/microsoft.png diff --git a/src/renderer/public/mysql.png b/src/renderer/src/assets/database-image/mysql.png similarity index 100% rename from src/renderer/public/mysql.png rename to src/renderer/src/assets/database-image/mysql.png diff --git a/src/renderer/public/oracle.png b/src/renderer/src/assets/database-image/oracle.png similarity index 100% rename from src/renderer/public/oracle.png rename to src/renderer/src/assets/database-image/oracle.png diff --git a/src/renderer/public/postgresql.png b/src/renderer/src/assets/database-image/postgresql.png similarity index 100% rename from src/renderer/public/postgresql.png rename to src/renderer/src/assets/database-image/postgresql.png diff --git a/src/renderer/public/sqlite.png b/src/renderer/src/assets/database-image/sqlite.png similarity index 100% rename from src/renderer/public/sqlite.png rename to src/renderer/src/assets/database-image/sqlite.png diff --git a/src/renderer/src/assets/icons/mac.png b/src/renderer/src/assets/icons/mac.png deleted file mode 100644 index 8b8a2e0..0000000 Binary files a/src/renderer/src/assets/icons/mac.png and /dev/null differ diff --git a/src/renderer/src/components/connection-wizard/confirm-settings.tsx b/src/renderer/src/components/connection-wizard/confirm-settings.tsx index f290eed..5fcab74 100644 --- a/src/renderer/src/components/connection-wizard/confirm-settings.tsx +++ b/src/renderer/src/components/connection-wizard/confirm-settings.tsx @@ -1,5 +1,5 @@ import { DATABASES, ConnectionDetail } from './wizard.type' - +import AppIcon from '../../assets/icon.svg' interface ConfirmSettingsProp { connectionDetail: ConnectionDetail } @@ -50,7 +50,7 @@ export default function ConfirmSettings({ return (
-
+
연결 설정 완료!
diff --git a/src/renderer/src/components/connection-wizard/test-connection.tsx b/src/renderer/src/components/connection-wizard/test-connection.tsx index bd33b14..9db54b2 100644 --- a/src/renderer/src/components/connection-wizard/test-connection.tsx +++ b/src/renderer/src/components/connection-wizard/test-connection.tsx @@ -6,12 +6,14 @@ import { api } from '@renderer/utils/api' interface TestConnectionProp { selectedDatabase: DatabaseInfo connectionDetail: ConnectionDetail + isTested: boolean setIsTested: (isTested: boolean) => void } export default function TestConnection({ selectedDatabase, connectionDetail, + isTested, setIsTested }: TestConnectionProp): React.JSX.Element { const handleTest = async (): Promise => { @@ -30,6 +32,7 @@ export default function TestConnection({ .post('/api/user/db/connect/test', filteredPayload) .then((response) => { const flag = response.data as boolean + toast.success('데이터베이스 연결 테스트 완료 🎉') setIsTested(flag) }) .catch(() => { @@ -56,9 +59,9 @@ export default function TestConnection({ data-status="Point" className="bg-gradient-genie-primary rounded-lg outline-1 outline-offset-[-1px] outline-white/20 flex justify-center items-center gap-2" > -
diff --git a/src/renderer/src/components/connection-wizard/wizard-modal.tsx b/src/renderer/src/components/connection-wizard/wizard-modal.tsx index cd2f9ce..cccfec0 100644 --- a/src/renderer/src/components/connection-wizard/wizard-modal.tsx +++ b/src/renderer/src/components/connection-wizard/wizard-modal.tsx @@ -200,6 +200,7 @@ export function ConnectionWizard(): JSX.Element { )} diff --git a/src/renderer/src/components/connection-wizard/wizard.type.ts b/src/renderer/src/components/connection-wizard/wizard.type.ts index b60bc7f..d1e0df6 100644 --- a/src/renderer/src/components/connection-wizard/wizard.type.ts +++ b/src/renderer/src/components/connection-wizard/wizard.type.ts @@ -1,3 +1,10 @@ +import mysqlIcon from '../../assets/database-image/mysql.png' +import mariadbIcon from '../../assets/database-image/mariadb.png' +import postgresqlIcon from '../../assets/database-image/postgresql.png' +import sqliteIcon from '../../assets/database-image/sqlite.png' +import microsoftIcon from '../../assets/database-image/microsoft.png' +import oracleIcon from '../../assets/database-image/oracle.png' + export enum DBSetupStep { SelectDatabase = 'selectDatabase', InstallDriver = 'installDriver', @@ -36,17 +43,17 @@ export interface DatabaseInfo { } export const DATABASES: DatabaseInfo[] = [ - { key: Database.MySQL, label: 'MySQL', icon: '/mysql.png', id: 'mysql' }, - { key: Database.MariaDB, label: 'MariaDB', icon: '/mariadb.png', id: 'mariadb' }, - { key: Database.PostgreSQL, label: 'PostgreSQL', icon: '/postgresql.png', id: 'postgresql' }, - { key: Database.SQLite, label: 'SQLite', icon: '/sqlite.png', id: 'sqlite' }, + { key: Database.MySQL, label: 'MySQL', icon: mysqlIcon, id: 'mysql' }, + { key: Database.MariaDB, label: 'MariaDB', icon: mariadbIcon, id: 'mariadb' }, + { key: Database.PostgreSQL, label: 'PostgreSQL', icon: postgresqlIcon, id: 'postgresql' }, + { key: Database.SQLite, label: 'SQLite', icon: sqliteIcon, id: 'sqlite' }, { key: Database.MicrosoftSQLServer, label: 'Microsoft SQL Server', - icon: '/microsoft.png', + icon: microsoftIcon, id: 'sqlserver' }, - { key: Database.Oracle, label: 'Oracle', icon: '/oracle.png', id: 'oracle' } + { key: Database.Oracle, label: 'Oracle', icon: oracleIcon, id: 'oracle' } ] export interface ConnectionDetail { diff --git a/src/renderer/src/utils/api.ts b/src/renderer/src/utils/api.ts index f57a052..a4d4fe3 100644 --- a/src/renderer/src/utils/api.ts +++ b/src/renderer/src/utils/api.ts @@ -1,6 +1,6 @@ import axios from 'axios' -export const api = axios.create({ baseURL: `http://localhost:39722` }) +export const api = axios.create({ baseURL: `http://127.0.0.1:39722` }) // 요청 전 configuration api.interceptors.request.use(