From c510f0888379c595b7cd3b27c0877b5888eb0edc Mon Sep 17 00:00:00 2001 From: 6-keem <6ukeem@gmail.com> Date: Thu, 21 Aug 2025 04:25:21 +0900 Subject: [PATCH] feat: bundle AI, Backend server with app --- electron-builder.yml | 13 +- package-lock.json | 116 ++++++++++++- package.json | 7 +- src/main/index.ts | 155 ++++++++++++++++-- src/main/windows/mainWindow.ts | 4 +- src/main/windows/subWindow.ts | 11 +- src/renderer/index.html | 2 +- src/renderer/src/App.tsx | 43 ++++- src/renderer/src/Loading.tsx | 22 +++ .../assets/database-image}/mariadb.png | Bin .../assets/database-image}/microsoft.png | Bin .../assets/database-image}/mysql.png | Bin .../assets/database-image}/oracle.png | Bin .../assets/database-image}/postgresql.png | Bin .../assets/database-image}/sqlite.png | Bin src/renderer/src/assets/icons/mac.png | Bin 23385 -> 0 bytes .../connection-wizard/confirm-settings.tsx | 4 +- .../connection-wizard/test-connection.tsx | 7 +- .../connection-wizard/wizard-modal.tsx | 1 + .../connection-wizard/wizard.type.ts | 19 ++- src/renderer/src/utils/api.ts | 2 +- 21 files changed, 362 insertions(+), 44 deletions(-) create mode 100644 src/renderer/src/Loading.tsx rename src/renderer/{public => src/assets/database-image}/mariadb.png (100%) rename src/renderer/{public => src/assets/database-image}/microsoft.png (100%) rename src/renderer/{public => src/assets/database-image}/mysql.png (100%) rename src/renderer/{public => src/assets/database-image}/oracle.png (100%) rename src/renderer/{public => src/assets/database-image}/postgresql.png (100%) rename src/renderer/{public => src/assets/database-image}/sqlite.png (100%) delete mode 100644 src/renderer/src/assets/icons/mac.png 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 8b8a2e0812a9c27b0e2328cdb3048fddd3f80723..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23385 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 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(