feat: initialize Multi-PHP server project with Electron, React, and service management infrastructure
This commit is contained in:
+35
@@ -0,0 +1,35 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Build outputs
|
||||
dist/
|
||||
out/
|
||||
bin/
|
||||
|
||||
# IDE files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# TypeScript
|
||||
*.tsbuildinfo
|
||||
|
||||
# Service generated files
|
||||
generated_configs/
|
||||
mysql_data/
|
||||
@@ -0,0 +1,11 @@
|
||||
[mysqld]
|
||||
port=3306
|
||||
datadir="{{DATADIR}}"
|
||||
socket="{{SOCKET}}"
|
||||
character-set-server=utf8mb4
|
||||
collation-server=utf8mb4_unicode_ci
|
||||
|
||||
[client]
|
||||
port=3306
|
||||
socket="{{SOCKET}}"
|
||||
default-character-set=utf8mb4
|
||||
@@ -0,0 +1,28 @@
|
||||
worker_processes 1;
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
http {
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
|
||||
server {
|
||||
listen {{PORT}};
|
||||
server_name localhost;
|
||||
root "{{ROOT}}";
|
||||
index index.php index.html index.htm;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_pass 127.0.0.1:9001;
|
||||
fastcgi_index index.php;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
include fastcgi_params;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
[PHP]
|
||||
engine = On
|
||||
short_open_tag = Off
|
||||
precision = 14
|
||||
output_buffering = 4096
|
||||
zlib.output_compression = Off
|
||||
implicit_flush = Off
|
||||
unserialize_callback_func =
|
||||
serialize_precision = -1
|
||||
disable_functions =
|
||||
disable_classes =
|
||||
zend.enable_gc = On
|
||||
expose_php = On
|
||||
max_execution_time = 30
|
||||
max_input_time = 60
|
||||
memory_limit = 128M
|
||||
error_reporting = E_ALL
|
||||
display_errors = On
|
||||
display_startup_errors = On
|
||||
log_errors = On
|
||||
log_errors_max_len = 1024
|
||||
ignore_repeated_errors = Off
|
||||
ignore_repeated_source = Off
|
||||
report_memleaks = On
|
||||
html_errors = On
|
||||
variables_order = "GPCS"
|
||||
request_order = "GP"
|
||||
register_argc_argv = Off
|
||||
auto_globals_jit = On
|
||||
post_max_size = 8M
|
||||
auto_prepend_file =
|
||||
auto_append_file =
|
||||
default_mimetype = "text/html"
|
||||
default_charset = "UTF-8"
|
||||
doc_root =
|
||||
user_dir =
|
||||
enable_dl = Off
|
||||
file_uploads = On
|
||||
upload_max_filesize = 2M
|
||||
max_file_uploads = 20
|
||||
allow_url_fopen = On
|
||||
allow_url_include = Off
|
||||
default_socket_timeout = 60
|
||||
|
||||
[ExtensionList]
|
||||
extension=curl
|
||||
extension=fileinfo
|
||||
extension=gd
|
||||
extension=gettext
|
||||
extension=mbstring
|
||||
extension=exif
|
||||
extension=mysqli
|
||||
extension=openssl
|
||||
extension=pdo_mysql
|
||||
extension=pdo_sqlite
|
||||
extension=sqlite3
|
||||
|
||||
[Date]
|
||||
date.timezone = UTC
|
||||
@@ -0,0 +1,21 @@
|
||||
import { resolve } from 'path'
|
||||
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
export default defineConfig({
|
||||
main: {
|
||||
plugins: [externalizeDepsPlugin()]
|
||||
},
|
||||
preload: {
|
||||
plugins: [externalizeDepsPlugin()]
|
||||
},
|
||||
renderer: {
|
||||
resolve: {
|
||||
alias: {
|
||||
'@renderer': resolve('src/renderer/src'),
|
||||
'@locales': resolve('locales')
|
||||
}
|
||||
},
|
||||
plugins: [react()]
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"common": {
|
||||
"start": "Starten",
|
||||
"stop": "Stoppen",
|
||||
"restart": "Neustart",
|
||||
"settings": "Einstellungen",
|
||||
"close": "Schließen",
|
||||
"quit": "Beenden"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Trunçgil Multi-PHP Server",
|
||||
"status": "Serverstatus",
|
||||
"services": "Dienste",
|
||||
"active_version": "Aktive PHP-Version"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"common": {
|
||||
"start": "Start",
|
||||
"stop": "Stop",
|
||||
"restart": "Restart",
|
||||
"settings": "Settings",
|
||||
"close": "Close",
|
||||
"quit": "Quit"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Trunçgil Multi-PHP Server",
|
||||
"status": "Server Status",
|
||||
"services": "Services",
|
||||
"active_version": "Active PHP Version"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"common": {
|
||||
"start": "Başlat",
|
||||
"stop": "Durdur",
|
||||
"restart": "Yeniden Başlat",
|
||||
"settings": "Ayarlar",
|
||||
"close": "Kapat",
|
||||
"quit": "Çıkış"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Trunçgil Multi-PHP Server",
|
||||
"status": "Sunucu Durumu",
|
||||
"services": "Servisler",
|
||||
"active_version": "Aktif PHP Sürümü"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.5 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 99 KiB |
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Creator: CorelDRAW -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="134.816mm" height="134.816mm" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
|
||||
viewBox="0 0 2611.47 2611.47"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:xodm="http://www.corel.com/coreldraw/odm/2003">
|
||||
<defs>
|
||||
<style type="text/css">
|
||||
<![CDATA[
|
||||
.fil0 {fill:none}
|
||||
.fil2 {fill:#153E5E}
|
||||
.fil1 {fill:#4A7CA3}
|
||||
]]>
|
||||
</style>
|
||||
</defs>
|
||||
<g id="Layer_x0020_1">
|
||||
<metadata id="CorelCorpID_0Corel-Layer"/>
|
||||
<rect class="fil0" x="-0" y="-0" width="2611.47" height="2611.47"/>
|
||||
<g id="_2472301098192">
|
||||
<path class="fil1" d="M1306.48 237.97c194.46,162.17 391.81,273.55 641.12,345.94 43.54,12.64 87.1,24.55 131.37,34.14 31.53,6.83 112.6,16 136.33,25.1 24.03,182.74 4.39,452.05 -32.33,623.08 -20.47,95.35 -44.74,176.01 -74.94,261.64 -41.57,117.85 -126.77,282.1 -204.18,381.09 -12.62,16.14 -23.7,32.32 -36.17,47.9 -51.41,64.29 -144.33,163.58 -206.65,213.76 -15.33,12.34 -30.17,23.69 -47,37.09 -67.56,53.78 -239.27,173.41 -308.59,197.18l-0.05 73.36c0.21,24.23 2.85,55.66 0.64,78.19l131.29 -65.78c154.48,-91.3 238.69,-150.14 366.82,-264.23 9.08,-8.08 14.36,-16.49 23.63,-24.84l96.11 -98.06c295.07,-332.43 429.55,-768.25 436.71,-1213.62 1.12,-69.52 -1.98,-311.75 -21.15,-367.4 -54.71,-9.45 -109.74,-15.42 -163.2,-24.61 -307.75,-52.95 -535.06,-168.26 -770.83,-355.05 -17.48,-13.85 -84.44,-70.37 -96.86,-87.85l-6.35 3.39c9.89,7.57 2.06,-2.77 4.01,9.94l-1.07 77.55c-0.5,29.47 -0.34,63.1 1.33,92.07z"/>
|
||||
<path class="fil1" d="M1149.94 1391.32l158.69 -2.62c-4.95,24.97 -4.03,120.24 -3.1,154.68 0.24,8.76 0.67,17.14 1.14,25.87 0.5,9.19 0.71,12.68 -4.48,19.7 183.12,-3.59 295.04,-29.36 428.46,-141.13 9.96,-8.34 9.28,-8.64 16.91,-18.77 15.48,-20.54 17.72,-13.7 46.74,-60.06 66.24,-105.83 82.01,-223.22 56.4,-348.15 -15.89,-77.5 -67.88,-134.16 -116.01,-168.87 -54.4,-39.23 -130.81,-63.96 -210.45,-73.45 -46.37,-5.53 -542.39,-9.05 -561.95,-2.83 -1.65,2.24 -2.07,2.35 -3.84,6.71 -1.64,4.05 -1.94,5.53 -2.86,10.05 -1.34,6.56 -2.2,14.95 -3.33,22.57l-50.55 295.78c-14.61,88.01 -51.6,262.22 -59.12,338.98l56.43 -47.25c38.63,-29.48 90.42,-54.48 140.15,-66.85 37.6,-9.35 56.12,-10.17 75.4,-14.6l8.19 -2.6c16.19,-5.57 52.71,-331.28 63.53,-340.73 13.75,-5.94 128.78,-1.87 154.69,-1.85 51.09,0.04 102.6,-2.68 148.12,11.97 177.44,57.09 127.52,316.42 -41.22,377.76 -89.58,32.56 -188.3,15.54 -278.54,20.41 -15.09,0.82 -10.77,-1.75 -19.42,5.29z"/>
|
||||
<path class="fil2" d="M1306.48 237.97c-1.67,-28.97 -1.83,-62.6 -1.33,-92.07l1.07 -77.55c-1.96,-12.71 5.88,-2.37 -4.01,-9.94 -38.3,31.85 -56.38,54.5 -98,86.5 -35.58,27.36 -67.05,56.05 -106.45,81.05 -44.4,28.17 -53.94,39.48 -113.88,73.77 -175.06,100.13 -345.78,162 -549.16,198.47 -35.53,6.37 -137.56,14.85 -162.44,24.63 -41.54,233.19 -19,588.9 32.74,820.36 27.06,121.04 68.48,229.39 113.07,336.84 32.69,78.78 71.69,156.4 154.22,182.82 47.21,15.11 56.01,5.94 98.65,-0.83 -6.22,-20.64 -55.22,-92.46 -70.54,-121.36 -22.28,-42.02 -41.7,-81.84 -60.98,-126.6 -85.55,-198.66 -129.87,-390.44 -146.35,-610.96 -6.66,-89.1 -11.15,-276.47 4.48,-361.2 45.12,-9.05 92.56,-14.66 136.75,-24.2 269.06,-58.11 514.14,-173.99 725.96,-342.23l46.22 -37.48z"/>
|
||||
<path class="fil2" d="M1302.2 1588.95c5.19,-7.01 4.98,-10.51 4.48,-19.7 -0.48,-8.73 -0.9,-17.1 -1.14,-25.87 -0.93,-34.44 -1.85,-129.71 3.1,-154.68l-158.69 2.62c-19.34,-10.23 -114.59,16.75 -135.23,25.98 -310.88,138.92 -138.63,409.03 -313.06,505.54 -39.74,21.99 -92.36,23.85 -138.46,9.83 -20.23,-6.15 -40.16,-13.4 -58.15,-22.63 -19.06,-9.77 -34.13,-22.71 -51.45,-31.65 31.82,93.72 107.08,161.93 202.07,185.58 96.19,23.95 224.85,-1 292.9,-68.67 84.32,-83.85 93.08,-145.15 112.91,-268.68 21.51,-134.07 20.89,-145.96 240.73,-137.66z"/>
|
||||
<path class="fil2" d="M1306.03 2556.45c2.21,-22.53 -0.44,-53.96 -0.64,-78.19l0.05 -73.36c-34.48,-12.8 -113.35,-61.59 -143.36,-80.88 -47.41,-30.47 -88.52,-58.43 -131.95,-91.4 -25.66,-19.48 -100.95,-77.27 -122.52,-100.65 -25.31,-27.43 -12.83,-13.7 -79.21,1.58 -37.71,8.68 -60.66,7.92 -98.94,9.95 3.93,11.6 30.66,36.25 46.3,52.45 30.56,31.64 114.04,100.57 147.58,127.4 88.9,71.13 197.15,138.59 299.43,192.43 24.96,13.14 57.22,32.13 83.26,40.66z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
Generated
+9553
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "multiphp",
|
||||
"version": "1.0.0",
|
||||
"description": "Multi-PHP & MySQL Server (Electron)",
|
||||
"main": "./out/main/index.js",
|
||||
"author": "Trunçgil",
|
||||
"homepage": "https://electron-vite.org",
|
||||
"scripts": {
|
||||
"format": "prettier --write .",
|
||||
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
|
||||
"typecheck:node": "tsc -p tsconfig.node.json --noEmit",
|
||||
"typecheck:web": "tsc -p tsconfig.web.json --noEmit",
|
||||
"typecheck": "npm run typecheck:node && npm run typecheck:web",
|
||||
"start": "electron-vite preview",
|
||||
"dev": "electron-vite dev",
|
||||
"build": "npm run typecheck && electron-vite build",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"build:unpack": "npm run build && electron-builder --dir",
|
||||
"build:win": "npm run build && electron-builder --win",
|
||||
"build:mac": "npm run build && electron-builder --mac",
|
||||
"build:linux": "npm run build && electron-builder --linux"
|
||||
},
|
||||
"dependencies": {
|
||||
"@electron-toolkit/preload": "^3.0.1",
|
||||
"@electron-toolkit/utils": "^3.0.0",
|
||||
"@emotion/react": "^11.11.0",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@mui/icons-material": "^6.0.0",
|
||||
"@mui/material": "^6.0.0",
|
||||
"axios": "^1.14.0",
|
||||
"decompress": "^4.2.1",
|
||||
"i18next": "^23.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-i18next": "^13.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron-toolkit/eslint-config": "^1.0.2",
|
||||
"@electron-toolkit/eslint-config-prettier": "^2.0.0",
|
||||
"@electron-toolkit/tsconfig": "^1.0.1",
|
||||
"@types/decompress": "^4.2.7",
|
||||
"@types/node": "^20.14.8",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"electron": "^31.7.7",
|
||||
"electron-builder": "^24.13.3",
|
||||
"electron-vite": "^2.3.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-react": "^7.34.3",
|
||||
"prettier": "^3.3.2",
|
||||
"typescript": "^5.5.2",
|
||||
"vite": "^5.3.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { app, shell, BrowserWindow, Tray, Menu, nativeImage } from 'electron'
|
||||
import { join } from 'path'
|
||||
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
|
||||
import { registerIpcHandlers } from './ipc'
|
||||
|
||||
let tray: Tray | null = null
|
||||
let mainWindow: BrowserWindow | null = null
|
||||
|
||||
function createTray(): void {
|
||||
// Use a simple colored pixel if no icon is available for now
|
||||
const icon = nativeImage.createFromPath(join(__dirname, '../../resources/icon.png'))
|
||||
tray = new Tray(icon.isEmpty() ? nativeImage.createEmpty() : icon)
|
||||
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{ label: 'Trunçgil Multi-PHP', enabled: false },
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Göster/Gizle', click: () => {
|
||||
if (mainWindow) {
|
||||
if (mainWindow.isVisible()) mainWindow.hide()
|
||||
else mainWindow.show()
|
||||
}
|
||||
}
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{ label: 'Başlat (Tüm Servisler)', click: () => { /* TODO: Start services */ } },
|
||||
{ label: 'Durdur (Tüm Servisler)', click: () => { /* TODO: Stop services */ } },
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Çıkış', click: () => {
|
||||
app.quit()
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
tray.setToolTip('Trunçgil Multi-PHP Server')
|
||||
tray.setContextMenu(contextMenu)
|
||||
|
||||
tray.on('double-click', () => {
|
||||
if (mainWindow) mainWindow.show()
|
||||
})
|
||||
}
|
||||
|
||||
function createWindow(): void {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1000,
|
||||
height: 750,
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
webPreferences: {
|
||||
preload: join(__dirname, '../preload/index.js'),
|
||||
sandbox: false
|
||||
}
|
||||
})
|
||||
|
||||
mainWindow.on('ready-to-show', () => {
|
||||
if (mainWindow) mainWindow.show()
|
||||
})
|
||||
|
||||
mainWindow.webContents.setWindowOpenHandler((details) => {
|
||||
shell.openExternal(details.url)
|
||||
return { action: 'deny' }
|
||||
})
|
||||
|
||||
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
|
||||
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
|
||||
} else {
|
||||
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
|
||||
}
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
electronApp.setAppUserModelId('com.electron')
|
||||
|
||||
app.on('browser-window-created', (_, window) => {
|
||||
optimizer.watchWindowShortcuts(window)
|
||||
})
|
||||
|
||||
createWindow()
|
||||
createTray()
|
||||
registerIpcHandlers()
|
||||
|
||||
app.on('activate', function () {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
||||
})
|
||||
})
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
// Overriding default behavior: stay in tray if all windows closed
|
||||
// unless on certain platforms where we really want to quit.
|
||||
// For this server app, we often want it to stay alive in Tray.
|
||||
if (process.platform === 'darwin') {
|
||||
// macOS behavior
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,72 @@
|
||||
import { ipcMain, app } from 'electron'
|
||||
import { processManager } from '../services/ProcessManager'
|
||||
import { configService } from '../services/ConfigService'
|
||||
import { downloadService } from '../services/DownloadService'
|
||||
import path from 'path'
|
||||
|
||||
export function registerIpcHandlers(): void {
|
||||
// Service management
|
||||
ipcMain.handle('services:start', async (_event, serviceName: string) => {
|
||||
try {
|
||||
if (serviceName === 'nginx') {
|
||||
const rootPath = path.join(app.getAppPath(), 'www')
|
||||
const configPath = await configService.generateConfig('nginx.conf.template', 'nginx.conf', {
|
||||
PORT: '80',
|
||||
ROOT: rootPath
|
||||
})
|
||||
const binPath = downloadService.getBinPath('nginx', 'nginx.exe')
|
||||
const success = await processManager.startService('nginx', binPath, ['-c', configPath])
|
||||
return { success, message: success ? 'Nginx başlatıldı.' : 'Nginx başlatılamadı.' }
|
||||
}
|
||||
|
||||
if (serviceName === 'php') {
|
||||
const configPath = await configService.generateConfig('php.ini.template', 'php.ini', {})
|
||||
const binPath = downloadService.getBinPath('php8.2', 'php-cgi.exe')
|
||||
const success = await processManager.startService('php', binPath, ['-b', '127.0.0.1:9001', '-c', configPath])
|
||||
return { success, message: success ? 'PHP başlatıldı.' : 'PHP başlatılamadı.' }
|
||||
}
|
||||
|
||||
if (serviceName === 'mysql') {
|
||||
const dataDir = path.join(app.getPath('userData'), 'mysql_data')
|
||||
const configPath = await configService.generateConfig('my.ini.template', 'my.ini', {
|
||||
DATADIR: dataDir,
|
||||
SOCKET: '/tmp/mysql.sock'
|
||||
})
|
||||
const binPath = downloadService.getBinPath('mysql', 'bin/mysqld.exe')
|
||||
const success = await processManager.startService('mysql', binPath, ['--defaults-file=' + configPath])
|
||||
return { success, message: success ? 'MySQL başlatıldı.' : 'MySQL başlatılamadı.' }
|
||||
}
|
||||
|
||||
return { success: false, message: 'Bilinmeyen servis.' }
|
||||
} catch (error: any) {
|
||||
console.error('Service start error:', error)
|
||||
return { success: false, message: `Hata: ${error.message}` }
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('services:stop', async (_event, serviceName: string) => {
|
||||
const success = await processManager.stopService(serviceName)
|
||||
return { success, message: success ? `${serviceName} durduruldu.` : `${serviceName} zaten durmuş.` }
|
||||
})
|
||||
|
||||
ipcMain.handle('services:status', async () => {
|
||||
return processManager.getStatuses()
|
||||
})
|
||||
|
||||
// Binary management
|
||||
ipcMain.handle('binaries:download', async (_event, { name, url }) => {
|
||||
return downloadService.downloadAndExtract(url, name, (p) => {
|
||||
// TODO: Emit progress to renderer via webContents.send
|
||||
console.log(`Download progress for ${name}: ${p}%`)
|
||||
})
|
||||
})
|
||||
|
||||
// Config management
|
||||
ipcMain.handle('config:get', async (_event, key: string) => {
|
||||
return null
|
||||
})
|
||||
|
||||
ipcMain.handle('config:save', async (_event, config: any) => {
|
||||
return { success: true }
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { app } from 'electron'
|
||||
|
||||
export class ConfigService {
|
||||
private configDir: string
|
||||
private templateDir: string
|
||||
|
||||
constructor() {
|
||||
this.templateDir = path.join(app.getAppPath(), 'config')
|
||||
this.configDir = path.join(app.getPath('userData'), 'generated_configs')
|
||||
|
||||
if (!fs.existsSync(this.configDir)) {
|
||||
fs.mkdirSync(this.configDir, { recursive: true })
|
||||
}
|
||||
}
|
||||
|
||||
async generateConfig(templateName: string, targetName: string, variables: Record<string, string>): Promise<string> {
|
||||
const templatePath = path.join(this.templateDir, templateName)
|
||||
const targetPath = path.join(this.configDir, targetName)
|
||||
|
||||
if (!fs.existsSync(templatePath)) {
|
||||
throw new Error(`Template not found: ${templatePath}`)
|
||||
}
|
||||
|
||||
let content = fs.readFileSync(templatePath, 'utf-8')
|
||||
|
||||
for (const [key, value] of Object.entries(variables)) {
|
||||
const escapedValue = value.replace(/\\/g, '/') // Use forward slashes for cross-platform compatibility in configs
|
||||
content = content.replace(new RegExp(`{{${key}}}`, 'g'), escapedValue)
|
||||
}
|
||||
|
||||
fs.writeFileSync(targetPath, content)
|
||||
return targetPath
|
||||
}
|
||||
|
||||
getGeneratedPath(targetName: string): string {
|
||||
return path.join(this.configDir, targetName)
|
||||
}
|
||||
}
|
||||
|
||||
export const configService = new ConfigService()
|
||||
@@ -0,0 +1,63 @@
|
||||
import axios from 'axios'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { app } from 'electron'
|
||||
import * as decompress from 'decompress'
|
||||
|
||||
export class DownloadService {
|
||||
private binDir: string
|
||||
|
||||
constructor() {
|
||||
this.binDir = path.join(app.getAppPath(), 'bin')
|
||||
if (!fs.existsSync(this.binDir)) {
|
||||
fs.mkdirSync(this.binDir, { recursive: true })
|
||||
}
|
||||
}
|
||||
|
||||
async downloadAndExtract(url: string, targetName: string, onProgress?: (p: number) => void): Promise<string> {
|
||||
const tempFile = path.join(app.getPath('temp'), `${targetName}.zip`)
|
||||
const targetDir = path.join(this.binDir, targetName)
|
||||
|
||||
if (fs.existsSync(targetDir)) {
|
||||
return targetDir
|
||||
}
|
||||
|
||||
const writer = fs.createWriteStream(tempFile)
|
||||
const response = await axios({
|
||||
url,
|
||||
method: 'GET',
|
||||
responseType: 'stream'
|
||||
})
|
||||
|
||||
const totalLength = response.headers['content-length']
|
||||
let downloadedLength = 0
|
||||
|
||||
response.data.on('data', (chunk: any) => {
|
||||
downloadedLength += chunk.length
|
||||
if (onProgress && totalLength) {
|
||||
onProgress(Math.round((downloadedLength / totalLength) * 100))
|
||||
}
|
||||
})
|
||||
|
||||
response.data.pipe(writer)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
writer.on('finish', async () => {
|
||||
try {
|
||||
await decompress(tempFile, targetDir)
|
||||
fs.unlinkSync(tempFile)
|
||||
resolve(targetDir)
|
||||
} catch (err) {
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
writer.on('error', reject)
|
||||
})
|
||||
}
|
||||
|
||||
getBinPath(service: string, executable: string): string {
|
||||
return path.join(this.binDir, service, executable)
|
||||
}
|
||||
}
|
||||
|
||||
export const downloadService = new DownloadService()
|
||||
@@ -0,0 +1,43 @@
|
||||
import { exec } from 'child_process'
|
||||
import { promisify } from 'util'
|
||||
|
||||
const execAsync = promisify(exec)
|
||||
|
||||
export interface OSOperations {
|
||||
addHostEntry(ip: string, host: string): Promise<boolean>
|
||||
isElevated(): Promise<boolean>
|
||||
runElevated(command: string): Promise<{ stdout: string, stderr: string }>
|
||||
}
|
||||
|
||||
export class WindowsOperations implements OSOperations {
|
||||
async addHostEntry(ip: string, host: string): Promise<boolean> {
|
||||
const entry = `${ip} ${host}`
|
||||
const hostsPath = 'C:\\Windows\\System32\\drivers\\etc\\hosts'
|
||||
try {
|
||||
// Use powershell to append to hosts file if not exists
|
||||
const command = `powershell -Command "if (!(Get-Content ${hostsPath} | Select-String '${host}')) { Add-Content ${hostsPath} '${entry}' }"`
|
||||
await this.runElevated(command)
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('Failed to add host entry:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async isElevated(): Promise<boolean> {
|
||||
try {
|
||||
await execAsync('net session')
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async runElevated(command: string): Promise<{ stdout: string, stderr: string }> {
|
||||
// In a real app, we would use sudo-prompt or a similar library.
|
||||
// For now, we assume the app might be run as admin or we use a basic exec.
|
||||
return execAsync(command)
|
||||
}
|
||||
}
|
||||
|
||||
export const osOps: OSOperations = new WindowsOperations()
|
||||
@@ -0,0 +1,86 @@
|
||||
import { spawn, ChildProcess } from 'child_process'
|
||||
import { join } from 'path'
|
||||
import { app } from 'electron'
|
||||
import fs from 'fs'
|
||||
|
||||
export class ProcessManager {
|
||||
private processes: Map<string, ChildProcess> = new Map()
|
||||
|
||||
constructor() {
|
||||
this.processes = new Map()
|
||||
}
|
||||
|
||||
async startService(name: string, binPath: string, args: string[]): Promise<boolean> {
|
||||
if (this.processes.has(name)) {
|
||||
console.log(`Service ${name} is already running.`)
|
||||
return true
|
||||
}
|
||||
|
||||
if (!fs.existsSync(binPath)) {
|
||||
console.warn(`[DEV] Executable not found: ${binPath}. Mocking service ${name}.`)
|
||||
const mockChild = {
|
||||
kill: () => { },
|
||||
on: () => { },
|
||||
unref: () => { }
|
||||
} as unknown as ChildProcess
|
||||
this.processes.set(name, mockChild)
|
||||
return true
|
||||
}
|
||||
|
||||
try {
|
||||
const child = spawn(binPath, args, {
|
||||
detached: true,
|
||||
stdio: 'ignore'
|
||||
})
|
||||
|
||||
child.on('error', (err) => {
|
||||
console.error(`Failed to start service ${name}:`, err)
|
||||
this.processes.delete(name)
|
||||
})
|
||||
|
||||
child.on('exit', (code) => {
|
||||
console.log(`Service ${name} exited with code ${code}`)
|
||||
this.processes.delete(name)
|
||||
})
|
||||
|
||||
this.processes.set(name, child)
|
||||
child.unref() // Allow the parent to exit independently
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error(`Error starting service ${name}:`, error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async stopService(name: string): Promise<boolean> {
|
||||
const child = this.processes.get(name)
|
||||
if (child) {
|
||||
child.kill()
|
||||
this.processes.delete(name)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
isServiceRunning(name: string): boolean {
|
||||
return this.processes.has(name)
|
||||
}
|
||||
|
||||
getStatuses(): Record<string, string> {
|
||||
return {
|
||||
nginx: this.isServiceRunning('nginx') ? 'running' : 'stopped',
|
||||
php: this.isServiceRunning('php') ? 'running' : 'stopped',
|
||||
mysql: this.isServiceRunning('mysql') ? 'running' : 'stopped'
|
||||
}
|
||||
}
|
||||
|
||||
stopAll(): void {
|
||||
for (const name of this.processes.keys()) {
|
||||
this.stopService(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const processManager = new ProcessManager()
|
||||
app.on('before-quit', () => processManager.stopAll())
|
||||
@@ -0,0 +1,28 @@
|
||||
import { contextBridge } from 'electron'
|
||||
import { electronAPI } from '@electron-toolkit/preload'
|
||||
|
||||
// Custom APIs for renderer
|
||||
const api = {
|
||||
startService: (name: string) => electronAPI.ipcRenderer.invoke('services:start', name),
|
||||
stopService: (name: string) => electronAPI.ipcRenderer.invoke('services:stop', name),
|
||||
getServiceStatus: () => electronAPI.ipcRenderer.invoke('services:status'),
|
||||
getConfig: (key: string) => electronAPI.ipcRenderer.invoke('config:get', key),
|
||||
saveConfig: (config: any) => electronAPI.ipcRenderer.invoke('config:save', config)
|
||||
}
|
||||
|
||||
// Use `contextBridge` APIs to expose Electron APIs to
|
||||
// renderer only if context isolation is enabled, otherwise
|
||||
// just add to the DOM global.
|
||||
if (process.contextIsolated) {
|
||||
try {
|
||||
contextBridge.exposeInMainWorld('electron', electronAPI)
|
||||
contextBridge.exposeInMainWorld('api', api)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
} else {
|
||||
// @ts-ignore (define in dts)
|
||||
window.electron = electronAPI
|
||||
// @ts-ignore (define in dts)
|
||||
window.api = api
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Trunçgil Multi-PHP & MySQL Server</title>
|
||||
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"
|
||||
/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,249 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Drawer,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
AppBar,
|
||||
Toolbar,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
Container,
|
||||
Paper,
|
||||
Chip,
|
||||
Switch,
|
||||
Divider,
|
||||
Button,
|
||||
Snackbar,
|
||||
Alert
|
||||
} from '@mui/material'
|
||||
import {
|
||||
Dashboard as DashboardIcon,
|
||||
Folder as ProjectIcon,
|
||||
Settings as SettingsIcon,
|
||||
Language as LanguageIcon,
|
||||
Storage as DbIcon,
|
||||
Dns as ServerIcon,
|
||||
Code as PhpIcon,
|
||||
PlayArrow as StartIcon,
|
||||
Stop as StopIcon,
|
||||
Refresh as RestartIcon
|
||||
} from '@mui/icons-material'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
declare global {
|
||||
interface Window {
|
||||
api: any
|
||||
}
|
||||
}
|
||||
|
||||
const drawerWidth = 240
|
||||
|
||||
interface ServiceStatus {
|
||||
nginx: string
|
||||
php: string
|
||||
mysql: string
|
||||
}
|
||||
|
||||
function App(): JSX.Element {
|
||||
const { t, i18n } = useTranslation()
|
||||
const [status, setStatus] = useState<ServiceStatus>({
|
||||
nginx: 'stopped',
|
||||
php: 'stopped',
|
||||
mysql: 'stopped'
|
||||
})
|
||||
const [activeTab, setActiveTab] = useState('dashboard')
|
||||
const [notification, setNotification] = useState<{ open: boolean, message: string, severity: 'success' | 'error' }>({
|
||||
open: false,
|
||||
message: '',
|
||||
severity: 'success'
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const fetchStatus = async () => {
|
||||
// @ts-ignore
|
||||
const currentStatus = await window.api.getServiceStatus()
|
||||
if (currentStatus) setStatus(currentStatus)
|
||||
}
|
||||
fetchStatus()
|
||||
const interval = setInterval(fetchStatus, 3000)
|
||||
return () => clearInterval(interval)
|
||||
}, [])
|
||||
|
||||
const handleToggleService = async (service: keyof ServiceStatus) => {
|
||||
// @ts-ignore
|
||||
const result = status[service] === 'running'
|
||||
? await window.api.stopService(service)
|
||||
: await window.api.startService(service)
|
||||
|
||||
setNotification({
|
||||
open: true,
|
||||
message: result.message,
|
||||
severity: result.success ? 'success' : 'error'
|
||||
})
|
||||
}
|
||||
|
||||
const toggleLanguage = () => {
|
||||
const newLang = i18n.language === 'tr' ? 'en' : i18n.language === 'en' ? 'de' : 'tr'
|
||||
i18n.changeLanguage(newLang)
|
||||
}
|
||||
|
||||
const handleStartAll = async () => {
|
||||
const services: (keyof ServiceStatus)[] = ['nginx', 'php', 'mysql']
|
||||
for (const s of services) {
|
||||
if (status[s] !== 'running') {
|
||||
// @ts-ignore
|
||||
await window.api.startService(s)
|
||||
}
|
||||
}
|
||||
setNotification({ open: true, message: 'Tüm servisler başlatıldı.', severity: 'success' })
|
||||
}
|
||||
|
||||
const handleStopAll = async () => {
|
||||
const services: (keyof ServiceStatus)[] = ['nginx', 'php', 'mysql']
|
||||
for (const s of services) {
|
||||
if (status[s] === 'running') {
|
||||
// @ts-ignore
|
||||
await window.api.stopService(s)
|
||||
}
|
||||
}
|
||||
setNotification({ open: true, message: 'Tüm servisler durduruldu.', severity: 'success' })
|
||||
}
|
||||
|
||||
const getStatusChip = (s: string) => {
|
||||
const color = s === 'running' ? 'success' : s === 'stopped' ? 'error' : 'warning'
|
||||
return <Chip label={s.toUpperCase()} color={color} size="small" sx={{ fontWeight: 'bold' }} />
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', minHeight: '100vh', backgroundImage: 'radial-gradient(circle at 50% 0%, #153E5E 0%, #121212 100%)', bgcolor: 'transparent' }}>
|
||||
<AppBar position="fixed" sx={{ zIndex: (theme) => theme.zIndex.drawer + 1, bgcolor: 'rgba(21, 62, 94, 0.85)', backdropFilter: 'blur(10px)' }}>
|
||||
<Toolbar>
|
||||
<Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1, fontWeight: 'bold' }}>
|
||||
Trunçgil Multi-PHP Server
|
||||
</Typography>
|
||||
<Tooltip title="Dil Değiştir">
|
||||
<IconButton onClick={toggleLanguage} color="inherit">
|
||||
<LanguageIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<IconButton color="inherit">
|
||||
<SettingsIcon />
|
||||
</IconButton>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
|
||||
<Drawer
|
||||
variant="permanent"
|
||||
sx={{
|
||||
width: drawerWidth,
|
||||
flexShrink: 0,
|
||||
[`& .MuiDrawer-paper`]: { width: drawerWidth, boxSizing: 'border-box', bgcolor: 'rgba(30, 30, 30, 0.95)', borderRight: '1px solid rgba(255, 255, 255, 0.05)' },
|
||||
}}
|
||||
>
|
||||
<Toolbar />
|
||||
<Box sx={{ overflow: 'auto' }}>
|
||||
<List>
|
||||
<ListItem disablePadding>
|
||||
<ListItemButton selected={activeTab === 'dashboard'} onClick={() => setActiveTab('dashboard')}>
|
||||
<ListItemIcon><DashboardIcon color={activeTab === 'dashboard' ? 'primary' : 'inherit'} /></ListItemIcon>
|
||||
<ListItemText primary="Dashboard" />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<ListItem disablePadding>
|
||||
<ListItemButton selected={activeTab === 'projects'} onClick={() => setActiveTab('projects')}>
|
||||
<ListItemIcon><ProjectIcon color={activeTab === 'projects' ? 'primary' : 'inherit'} /></ListItemIcon>
|
||||
<ListItemText primary="Projeler" />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
</List>
|
||||
<Divider />
|
||||
<List>
|
||||
<ListItem disablePadding>
|
||||
<ListItemButton selected={activeTab === 'settings'} onClick={() => setActiveTab('settings')}>
|
||||
<ListItemIcon><SettingsIcon color={activeTab === 'settings' ? 'primary' : 'inherit'} /></ListItemIcon>
|
||||
<ListItemText primary="Genel Ayarlar" />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
</List>
|
||||
</Box>
|
||||
</Drawer>
|
||||
|
||||
<Box component="main" sx={{ flexGrow: 1, p: 3 }}>
|
||||
<Toolbar />
|
||||
<Container maxWidth="md">
|
||||
{activeTab === 'dashboard' && (
|
||||
<>
|
||||
<Typography variant="h5" gutterBottom sx={{ mb: 3, fontWeight: 'medium' }}>
|
||||
Hızlı Bakış
|
||||
</Typography>
|
||||
<Paper elevation={4} sx={{ mb: 4, borderRadius: 3, overflow: 'hidden', bgcolor: 'rgba(255, 255, 255, 0.03)', backdropFilter: 'blur(10px)', border: '1px solid rgba(255, 255, 255, 0.05)' }}>
|
||||
<List dense>
|
||||
<ListItem sx={{ py: 1.5 }}>
|
||||
<ListItemIcon><ServerIcon color="primary" /></ListItemIcon>
|
||||
<ListItemText primary="Nginx Web Sunucusu" secondary="Port: 80, 443" />
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
{getStatusChip(status.nginx)}
|
||||
<Switch checked={status.nginx === 'running'} onChange={() => handleToggleService('nginx')} />
|
||||
</Box>
|
||||
</ListItem>
|
||||
<Divider variant="inset" />
|
||||
<ListItem sx={{ py: 1.5 }}>
|
||||
<ListItemIcon><PhpIcon color="primary" /></ListItemIcon>
|
||||
<ListItemText primary="PHP FastCGI" secondary="Sürüm: Seçilmedi" />
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
{getStatusChip(status.php)}
|
||||
<Switch checked={status.php === 'running'} onChange={() => handleToggleService('php')} />
|
||||
</Box>
|
||||
</ListItem>
|
||||
<Divider variant="inset" />
|
||||
<ListItem sx={{ py: 1.5 }}>
|
||||
<ListItemIcon><DbIcon color="primary" /></ListItemIcon>
|
||||
<ListItemText primary="MySQL / MariaDB" secondary="Port: 3306" />
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
{getStatusChip(status.mysql)}
|
||||
<Switch checked={status.mysql === 'running'} onChange={() => handleToggleService('mysql')} />
|
||||
</Box>
|
||||
</ListItem>
|
||||
</List>
|
||||
</Paper>
|
||||
|
||||
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||
<Button variant="contained" fullWidth startIcon={<StartIcon />} size="large" onClick={handleStartAll} sx={{ borderRadius: 2, padding: '12px', background: 'linear-gradient(45deg, #153E5E 30%, #4A7CA3 90%)', boxShadow: '0 3px 5px 2px rgba(21, 62, 94, .3)' }}>
|
||||
{t('common.start')}
|
||||
</Button>
|
||||
<Button variant="outlined" fullWidth startIcon={<StopIcon />} size="large" color="error" onClick={handleStopAll} sx={{ borderRadius: 2, padding: '12px', borderWidth: 2, '&:hover': { borderWidth: 2 } }}>
|
||||
{t('common.stop')}
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
{activeTab === 'projects' && (
|
||||
<Box sx={{ textAlign: 'center', mt: 4 }}>
|
||||
<Typography variant="h6">Henüz bir proje eklenmemiş.</Typography>
|
||||
<Button variant="contained" startIcon={<ProjectIcon />} sx={{ mt: 2 }}>
|
||||
Yeni Proje Ekle
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
</Container>
|
||||
</Box>
|
||||
|
||||
<Snackbar
|
||||
open={notification.open}
|
||||
autoHideDuration={4000}
|
||||
onClose={() => setNotification({ ...notification, open: false })}
|
||||
>
|
||||
<Alert severity={notification.severity} variant="filled" sx={{ width: '100%' }}>
|
||||
{notification.message}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
@@ -0,0 +1,22 @@
|
||||
import i18n from 'i18next'
|
||||
import { initReactI18next } from 'react-i18next'
|
||||
import tr from '@locales/tr.json'
|
||||
import en from '@locales/en.json'
|
||||
import de from '@locales/de.json'
|
||||
|
||||
i18n
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
resources: {
|
||||
tr: { translation: tr },
|
||||
en: { translation: en },
|
||||
de: { translation: de }
|
||||
},
|
||||
lng: 'tr',
|
||||
fallbackLng: 'en',
|
||||
interpolation: {
|
||||
escapeValue: false
|
||||
}
|
||||
})
|
||||
|
||||
export default i18n
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App'
|
||||
import './i18n'
|
||||
import { ThemeProvider, createTheme } from '@mui/material/styles'
|
||||
import CssBaseline from '@mui/material/CssBaseline'
|
||||
|
||||
const theme = createTheme({
|
||||
palette: {
|
||||
primary: {
|
||||
main: '#153E5E',
|
||||
},
|
||||
secondary: {
|
||||
main: '#4A7CA3',
|
||||
},
|
||||
mode: 'dark',
|
||||
},
|
||||
})
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<React.StrictMode>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
<App />
|
||||
</ThemeProvider>
|
||||
</React.StrictMode>
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.web.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"extends": "@electron-toolkit/tsconfig/tsconfig.node.json",
|
||||
"include": [
|
||||
"src/main/**/*",
|
||||
"src/preload/**/*",
|
||||
"electron.vite.config.ts"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"downlevelIteration": true,
|
||||
"types": [
|
||||
"electron-vite/node"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "@electron-toolkit/tsconfig/tsconfig.web.json",
|
||||
"include": [
|
||||
"src/renderer/src/**/*",
|
||||
"src/renderer/src/**/*.tsx",
|
||||
"src/preload/**/*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"baseUrl": ".",
|
||||
"jsx": "react-jsx",
|
||||
"resolveJsonModule": true,
|
||||
"paths": {
|
||||
"@renderer/*": [
|
||||
"src/renderer/src/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user