Compare commits
3 Commits
2ce9bffb1d
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b31b3c6d31 | ||
|
|
fc9500f99b | ||
|
|
c84fd2a7e9 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -23,5 +23,4 @@ dist-ssr
|
|||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
public/webftp-backend.exe
|
public/webftp-backend.exe
|
||||||
public/WebZilla.exe
|
|
||||||
public/webftp.exe
|
public/webftp.exe
|
||||||
|
|||||||
121
App.tsx
121
App.tsx
@@ -12,6 +12,28 @@ import ConflictModal from './components/ConflictModal';
|
|||||||
import DownloadModal from './components/DownloadModal';
|
import DownloadModal from './components/DownloadModal';
|
||||||
import { formatBytes } from './utils/formatters';
|
import { formatBytes } from './utils/formatters';
|
||||||
|
|
||||||
|
const AdSenseBanner: React.FC = () => {
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
// @ts-ignore
|
||||||
|
(window.adsbygoogle = window.adsbygoogle || []).push({});
|
||||||
|
} catch (e) {
|
||||||
|
console.error("AdSense error", e);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full h-full flex items-center justify-center overflow-hidden">
|
||||||
|
<ins className="adsbygoogle"
|
||||||
|
style={{ display: "block", width: "100%", height: "100%" }}
|
||||||
|
data-ad-client="ca-pub-4444852135420953"
|
||||||
|
data-ad-slot="7799405796"
|
||||||
|
data-ad-format="auto"
|
||||||
|
data-full-width-responsive="true"></ins>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
// --- State ---
|
// --- State ---
|
||||||
const savedPref = localStorage.getItem('save_connection_info') !== 'false';
|
const savedPref = localStorage.getItem('save_connection_info') !== 'false';
|
||||||
@@ -37,7 +59,7 @@ const App: React.FC = () => {
|
|||||||
const [showConnectionHelp, setShowConnectionHelp] = useState(false);
|
const [showConnectionHelp, setShowConnectionHelp] = useState(false);
|
||||||
const [showSiteManager, setShowSiteManager] = useState(false);
|
const [showSiteManager, setShowSiteManager] = useState(false);
|
||||||
const [showHelp, setShowHelp] = useState(false);
|
const [showHelp, setShowHelp] = useState(false);
|
||||||
const [showDownloadModal, setShowDownloadModal] = useState(false); // New state for Download Modal
|
const [showDownloadModal, setShowDownloadModal] = useState(false);
|
||||||
const [helpInitialTab, setHelpInitialTab] = useState<'sites' | 'connection' | 'files' | 'backend'>('sites');
|
const [helpInitialTab, setHelpInitialTab] = useState<'sites' | 'connection' | 'files' | 'backend'>('sites');
|
||||||
const [savedSites, setSavedSites] = useState<SiteConfig[]>([]);
|
const [savedSites, setSavedSites] = useState<SiteConfig[]>([]);
|
||||||
|
|
||||||
@@ -145,55 +167,6 @@ const App: React.FC = () => {
|
|||||||
ws!.send(JSON.stringify({ command: 'LOCAL_LIST', path: storedLocalPath }));
|
ws!.send(JSON.stringify({ command: 'LOCAL_LIST', path: storedLocalPath }));
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onmessage = (event) => {
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(event.data);
|
|
||||||
|
|
||||||
switch (data.type) {
|
|
||||||
// ... (no change to inside of switch) ...
|
|
||||||
case 'status':
|
|
||||||
if (data.status === 'connected') {
|
|
||||||
setConnection(prev => ({ ...prev, connected: true, connecting: false }));
|
|
||||||
addLog('success', data.message || 'FTP 연결 성공');
|
|
||||||
|
|
||||||
// Use Ref for latest state (especially initialPath from Site Manager)
|
|
||||||
const currentConn = connectionRef.current;
|
|
||||||
|
|
||||||
// 1. Initial Directory from Site Config
|
|
||||||
if (currentConn.initialPath) {
|
|
||||||
ws?.send(JSON.stringify({ command: 'LIST', path: currentConn.initialPath }));
|
|
||||||
}
|
|
||||||
// 2. Last Visited Path (Persistence)
|
|
||||||
else {
|
|
||||||
const lastRemote = localStorage.getItem(`last_remote_path_${currentConn.host}`);
|
|
||||||
const initialPath = lastRemote || '/';
|
|
||||||
ws?.send(JSON.stringify({ command: 'LIST', path: initialPath }));
|
|
||||||
}
|
|
||||||
} else if (data.status === 'disconnected') {
|
|
||||||
setConnection(prev => ({ ...prev, connected: false, connecting: false }));
|
|
||||||
setRemote(prev => ({ ...prev, files: [], path: '/' }));
|
|
||||||
addLog('system', 'FTP 연결 종료');
|
|
||||||
} else if (data.status === 'error') {
|
|
||||||
setConnection(prev => ({ ...prev, connecting: false }));
|
|
||||||
addLog('error', data.message);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'list':
|
|
||||||
// ... (rest of cases handled by maintaining existing code structure or using multi-replace if I was confident, but here let's careful) ...
|
|
||||||
// Since replace_file_content works on lines, I should target specific blocks.
|
|
||||||
// BE CAREFUL: attempting to replace too large block blindly.
|
|
||||||
// I should stick to smaller replaces.
|
|
||||||
}
|
|
||||||
|
|
||||||
// To avoid re-writing the huge switch content, I will use multiple Replace calls or just target onopen/onclose.
|
|
||||||
} catch (e) {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
}; // This close brace is problematic if I don't include the switch logic.
|
|
||||||
// Better: just replace onopen and onclose separately.
|
|
||||||
|
|
||||||
|
|
||||||
ws.onmessage = (event) => {
|
ws.onmessage = (event) => {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
@@ -226,11 +199,7 @@ const App: React.FC = () => {
|
|||||||
|
|
||||||
case 'error':
|
case 'error':
|
||||||
addLog('error', data.message);
|
addLog('error', data.message);
|
||||||
window.alert(data.message); // Show error dialog
|
|
||||||
setConnection(prev => ({ ...prev, connecting: false }));
|
setConnection(prev => ({ ...prev, connecting: false }));
|
||||||
setRemote(prev => ({ ...prev, isLoading: false }));
|
|
||||||
setLocal(prev => ({ ...prev, isLoading: false }));
|
|
||||||
setRefreshKey(prev => prev + 1); // Revert invalid inputs
|
|
||||||
|
|
||||||
// Check for disconnection messages
|
// Check for disconnection messages
|
||||||
if (
|
if (
|
||||||
@@ -284,7 +253,14 @@ const App: React.FC = () => {
|
|||||||
addLog('success', `[로컬] 이동 완료: ${data.path}`);
|
addLog('success', `[로컬] 이동 완료: ${data.path}`);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'error':
|
||||||
|
addLog('error', data.message);
|
||||||
|
window.alert(data.message); // Show error dialog
|
||||||
|
setConnection(prev => ({ ...prev, connecting: false }));
|
||||||
|
setRemote(prev => ({ ...prev, isLoading: false }));
|
||||||
|
setLocal(prev => ({ ...prev, isLoading: false }));
|
||||||
|
setRefreshKey(prev => prev + 1); // Revert invalid inputs
|
||||||
|
break;
|
||||||
|
|
||||||
case 'success':
|
case 'success':
|
||||||
addLog('success', data.message);
|
addLog('success', data.message);
|
||||||
@@ -916,10 +892,6 @@ const App: React.FC = () => {
|
|||||||
onSkip={handleConflictSkip}
|
onSkip={handleConflictSkip}
|
||||||
onClose={() => handleConflictSkip(false)} // Treat close as skip single
|
onClose={() => handleConflictSkip(false)} // Treat close as skip single
|
||||||
/>
|
/>
|
||||||
<DownloadModal
|
|
||||||
isOpen={showDownloadModal}
|
|
||||||
onClose={() => setShowDownloadModal(false)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<header className="bg-white border-b border-slate-200 p-2 shadow-sm z-20">
|
<header className="bg-white border-b border-slate-200 p-2 shadow-sm z-20">
|
||||||
@@ -1005,13 +977,15 @@ const App: React.FC = () => {
|
|||||||
{connection.connecting ? <div className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" /> : (connection.connected ? <><WifiOff size={16} /> 연결 해제</> : '빠른 연결')}
|
{connection.connecting ? <div className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" /> : (connection.connected ? <><WifiOff size={16} /> 연결 해제</> : '빠른 연결')}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
{!(window as any).__IS_STANDALONE__ && (
|
||||||
onClick={() => setShowDownloadModal(true)}
|
<button
|
||||||
className="h-[30px] flex items-center justify-center px-3 rounded bg-emerald-600 hover:bg-emerald-500 text-white border border-emerald-500 shadow-md shadow-emerald-500/20 transition-all"
|
onClick={() => setShowDownloadModal(true)}
|
||||||
title="다운로드 센터"
|
className={`h-[30px] flex items-center justify-center px-3 rounded bg-emerald-600 hover:bg-emerald-500 text-white border border-emerald-500 shadow-md shadow-emerald-500/20 transition-all ${!connection.connected ? 'animate-pulse ring-2 ring-emerald-400/50' : ''}`}
|
||||||
>
|
title="다운로드 센터"
|
||||||
<Download size={14} className="mr-1.5" /> 다운로드
|
>
|
||||||
</button>
|
<Download size={14} className="mr-1.5" /> 다운로드
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowHelp(true)}
|
onClick={() => setShowHelp(true)}
|
||||||
@@ -1037,13 +1011,10 @@ const App: React.FC = () => {
|
|||||||
<div className="w-1/2 min-w-0 h-full">
|
<div className="w-1/2 min-w-0 h-full">
|
||||||
<LogConsole logs={logs} />
|
<LogConsole logs={logs} />
|
||||||
</div>
|
</div>
|
||||||
<div className="w-1/2 min-w-0 h-full bg-white border border-slate-300 rounded-lg shadow-sm flex flex-col items-center justify-center relative overflow-hidden group cursor-pointer">
|
<div className="w-1/2 min-w-0 h-full bg-white border border-slate-300 rounded-lg shadow-sm flex flex-col items-center justify-center relative overflow-hidden">
|
||||||
|
{/* Placeholder for Sponsored tag if needed, or remove it */}
|
||||||
<div className="absolute top-0 right-0 bg-slate-100 text-[9px] text-slate-500 px-1.5 py-0.5 rounded-bl border-b border-l border-slate-200 z-10">SPONSORED</div>
|
<div className="absolute top-0 right-0 bg-slate-100 text-[9px] text-slate-500 px-1.5 py-0.5 rounded-bl border-b border-l border-slate-200 z-10">SPONSORED</div>
|
||||||
<div className="text-slate-300 flex flex-col items-center gap-1 group-hover:text-slate-400 transition-colors">
|
<AdSenseBanner />
|
||||||
<div className="font-bold text-lg tracking-widest flex items-center gap-2"><MousePointerClick size={20} /> GOOGLE ADSENSE</div>
|
|
||||||
<div className="text-xs">Premium Ad Space Available</div>
|
|
||||||
</div>
|
|
||||||
<div className="absolute inset-0 -z-0 opacity-30" style={{ backgroundImage: 'radial-gradient(#cbd5e1 1px, transparent 1px)', backgroundSize: '10px 10px' }}></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1119,6 +1090,12 @@ const App: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Download Modal - Standalone Only */}
|
||||||
|
<DownloadModal
|
||||||
|
isOpen={showDownloadModal}
|
||||||
|
onClose={() => setShowDownloadModal(false)}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<footer className="bg-white border-t border-slate-200 px-3 py-1 text-[10px] text-slate-500 flex justify-between shadow-[0_-2px_10px_rgba(0,0,0,0.02)]">
|
<footer className="bg-white border-t border-slate-200 px-3 py-1 text-[10px] text-slate-500 flex justify-between shadow-[0_-2px_10px_rgba(0,0,0,0.02)]">
|
||||||
<span>WebZilla v1.3.0 <span className="mx-2 text-slate-300">|</span> © SIMP</span>
|
<span>WebZilla v1.3.0 <span className="mx-2 text-slate-300">|</span> © SIMP</span>
|
||||||
|
|||||||
@@ -18,26 +18,6 @@ const path = require('path');
|
|||||||
const os = require('os');
|
const os = require('os');
|
||||||
const { exec } = require('child_process');
|
const { exec } = require('child_process');
|
||||||
const readline = require('readline');
|
const readline = require('readline');
|
||||||
const http = require('http');
|
|
||||||
|
|
||||||
// MIME Types for static serving
|
|
||||||
const MIME_TYPES = {
|
|
||||||
'.html': 'text/html',
|
|
||||||
'.js': 'text/javascript',
|
|
||||||
'.css': 'text/css',
|
|
||||||
'.json': 'application/json',
|
|
||||||
'.png': 'image/png',
|
|
||||||
'.jpg': 'image/jpeg',
|
|
||||||
'.gif': 'image/gif',
|
|
||||||
'.svg': 'image/svg+xml',
|
|
||||||
'.ico': 'image/x-icon',
|
|
||||||
'.woff': 'font/woff',
|
|
||||||
'.woff2': 'font/woff2',
|
|
||||||
'.ttf': 'font/ttf',
|
|
||||||
'.eot': 'application/vnd.ms-fontobject',
|
|
||||||
'.otf': 'font/otf',
|
|
||||||
'.wasm': 'application/wasm'
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- 로컬 저장소 경로 설정 (AppData 구현) ---
|
// --- 로컬 저장소 경로 설정 (AppData 구현) ---
|
||||||
function getConfigDir() {
|
function getConfigDir() {
|
||||||
@@ -58,84 +38,97 @@ if (!fs.existsSync(configDir)) {
|
|||||||
|
|
||||||
const PORT = 8090;
|
const PORT = 8090;
|
||||||
|
|
||||||
// --- 서버 시작 함수 (재시도 로직 포함) ---
|
|
||||||
// --- 서버 시작 함수 (재시도 로직 포함) ---
|
// --- 서버 시작 함수 (재시도 로직 포함) ---
|
||||||
function startServer() {
|
function startServer() {
|
||||||
|
const http = require('http'); // HTTP server module required
|
||||||
|
|
||||||
const server = http.createServer((req, res) => {
|
const server = http.createServer((req, res) => {
|
||||||
// Basic Static File Server
|
// Parse URL to handle query strings and decoding
|
||||||
// pkg friendly path: use path.join(__dirname, 'dist')
|
const parsedUrl = new URL(req.url, `http://${req.headers.host}`);
|
||||||
// When packaged with pkg, __dirname points to the virtual filesystem inside the executable
|
const pathname = decodeURIComponent(parsedUrl.pathname);
|
||||||
|
|
||||||
// Special handling for executables (Download Center)
|
// Special handling for executables (Download Center)
|
||||||
// Serve from the host filesystem (where the .exe is running)
|
if (pathname === '/webftp.exe' || pathname === '/webftp-backend.exe') {
|
||||||
// This prevents the need to bundle the executables inside the pkg bundle itself
|
const exeDir = path.dirname(process.execPath);
|
||||||
if (req.url === '/webftp.exe' || req.url === '/webftp-backend.exe') {
|
// Fallback for dev mode -> serve from public
|
||||||
const exeDir = path.dirname(process.execPath); // Directory of the running executable
|
const targetPath = process.pkg ? path.join(exeDir, pathname) : path.join(__dirname, 'public', pathname);
|
||||||
// Fallback for dev mode (node backend_proxy.cjs) -> serve from public or current dir
|
|
||||||
const targetPath = process.pkg ? path.join(exeDir, req.url) : path.join(__dirname, 'public', req.url);
|
|
||||||
|
|
||||||
if (fs.existsSync(targetPath)) {
|
if (fs.existsSync(targetPath)) {
|
||||||
const stat = fs.statSync(targetPath);
|
const stat = fs.statSync(targetPath);
|
||||||
|
|
||||||
// Handle HEAD request for size check
|
|
||||||
if (req.method === 'HEAD') {
|
|
||||||
res.writeHead(200, {
|
|
||||||
'Content-Type': 'application/octet-stream',
|
|
||||||
'Content-Length': stat.size
|
|
||||||
});
|
|
||||||
res.end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.writeHead(200, {
|
res.writeHead(200, {
|
||||||
'Content-Type': 'application/octet-stream',
|
'Content-Type': 'application/octet-stream',
|
||||||
'Content-Length': stat.size,
|
'Content-Length': stat.size,
|
||||||
'Content-Disposition': `attachment; filename="${path.basename(targetPath)}"`
|
'Content-Disposition': `attachment; filename="${path.basename(targetPath)}"`
|
||||||
});
|
});
|
||||||
|
|
||||||
const readStream = fs.createReadStream(targetPath);
|
const readStream = fs.createReadStream(targetPath);
|
||||||
readStream.pipe(res);
|
readStream.pipe(res);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let filePath = path.join(__dirname, 'dist', req.url === '/' ? 'index.html' : req.url);
|
// Static File Serving
|
||||||
|
// Handle /ftp base path (strip it)
|
||||||
|
let normalizedPath = pathname;
|
||||||
|
if (normalizedPath.startsWith('/ftp')) {
|
||||||
|
normalizedPath = normalizedPath.replace(/^\/ftp/, '') || '/';
|
||||||
|
}
|
||||||
|
|
||||||
// Prevent directory traversal
|
let filePath = path.join(__dirname, 'dist', normalizedPath === '/' ? 'index.html' : normalizedPath);
|
||||||
|
|
||||||
|
// Prevent traversal
|
||||||
if (!filePath.startsWith(path.join(__dirname, 'dist'))) {
|
if (!filePath.startsWith(path.join(__dirname, 'dist'))) {
|
||||||
res.writeHead(403);
|
res.writeHead(403); res.end('Forbidden'); return;
|
||||||
res.end('Forbidden');
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const extname = path.extname(filePath);
|
const extname = path.extname(filePath);
|
||||||
|
// Basic MIME types
|
||||||
|
const MIME_TYPES = {
|
||||||
|
'.html': 'text/html', '.js': 'text/javascript', '.css': 'text/css',
|
||||||
|
'.json': 'application/json', '.png': 'image/png', '.jpg': 'image/jpeg',
|
||||||
|
'.svg': 'image/svg+xml', '.ico': 'image/x-icon'
|
||||||
|
};
|
||||||
let contentType = MIME_TYPES[extname] || 'application/octet-stream';
|
let contentType = MIME_TYPES[extname] || 'application/octet-stream';
|
||||||
|
|
||||||
fs.readFile(filePath, (err, content) => {
|
fs.readFile(filePath, (err, content) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
if (err.code === 'ENOENT') {
|
if (err.code === 'ENOENT') {
|
||||||
// SPA fallback: serve index.html for unknown paths (if not an asset)
|
// SPA fallback
|
||||||
if (!extname || extname === '.html') {
|
if (!extname || extname === '.html') {
|
||||||
fs.readFile(path.join(__dirname, 'dist', 'index.html'), (err2, content2) => {
|
fs.readFile(path.join(__dirname, 'dist', 'index.html'), (err2, content2) => {
|
||||||
if (err2) {
|
if (err2) {
|
||||||
res.writeHead(404);
|
// If index.html is missing (Backend Only Mode), return 404
|
||||||
res.end('404 Not Found');
|
console.log(`[404] Backend Only Mode - Missing: ${pathname}`);
|
||||||
|
res.writeHead(404); res.end('Backend Only Mode - No Frontend Assets Found');
|
||||||
} else {
|
} else {
|
||||||
|
let html = content2.toString('utf-8');
|
||||||
|
if (process.pkg) {
|
||||||
|
html = html.replace('</head>', '<script>window.__IS_STANDALONE__ = true;</script></head>');
|
||||||
|
}
|
||||||
res.writeHead(200, { 'Content-Type': 'text/html' });
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
||||||
res.end(content2, 'utf-8');
|
res.end(html);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
res.writeHead(404);
|
console.log(`[404] File Not Found: ${pathname}`);
|
||||||
res.end('File Not Found');
|
res.writeHead(404); res.end('File Not Found');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
res.writeHead(500);
|
console.error(`[500] Server Error for ${pathname}:`, err.code);
|
||||||
res.end(`Server Error: ${err.code}`);
|
res.writeHead(500); res.end(`Server Error: ${err.code}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
res.writeHead(200, { 'Content-Type': contentType });
|
// Determine if we need to inject script for main index.html access
|
||||||
res.end(content, 'utf-8');
|
if (filePath.endsWith('index.html') || extname === '.html') {
|
||||||
|
let html = content.toString('utf-8');
|
||||||
|
if (process.pkg) {
|
||||||
|
html = html.replace('</head>', '<script>window.__IS_STANDALONE__ = true;</script></head>');
|
||||||
|
}
|
||||||
|
res.writeHead(200, { 'Content-Type': contentType });
|
||||||
|
res.end(html);
|
||||||
|
} else {
|
||||||
|
res.writeHead(200, { 'Content-Type': contentType });
|
||||||
|
res.end(content, 'utf-8');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -153,13 +146,20 @@ function startServer() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.listen(PORT, () => {
|
server.listen(PORT, () => {
|
||||||
console.log(`\n🚀 WebFTP Proxy Server [v${APP_VERSION}] 가 http://localhost:${PORT} 에서 실행 중입니다.`);
|
console.log(`\n🚀 WebZilla Server [v${APP_VERSION}] running at http://localhost:${PORT}`);
|
||||||
console.log(`📂 설정 폴더: ${configDir}`);
|
console.log(`📂 Config: ${configDir}`);
|
||||||
|
|
||||||
// Open Browser
|
// Check if Frontend Assets exist (dist/index.html)
|
||||||
openBrowser(`http://localhost:${PORT}`);
|
const frontendExists = fs.existsSync(path.join(__dirname, 'dist', 'index.html'));
|
||||||
|
if (frontendExists) {
|
||||||
|
console.log("✨ Frontend detected. Launching browser...");
|
||||||
|
openBrowser(`http://localhost:${PORT}/ftp/`);
|
||||||
|
} else {
|
||||||
|
console.log("🔧 Backend Only Mode. Waiting for connections...");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// WSS Connection Handling (Existing Logic)
|
||||||
wss.on('connection', (ws) => {
|
wss.on('connection', (ws) => {
|
||||||
const client = new ftp.Client();
|
const client = new ftp.Client();
|
||||||
|
|
||||||
@@ -185,6 +185,7 @@ function startServer() {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
||||||
case 'LIST':
|
case 'LIST':
|
||||||
if (client.closed) {
|
if (client.closed) {
|
||||||
ws.send(JSON.stringify({ type: 'error', message: 'FTP 연결이 끊어져 있습니다.' }));
|
ws.send(JSON.stringify({ type: 'error', message: 'FTP 연결이 끊어져 있습니다.' }));
|
||||||
@@ -486,6 +487,7 @@ function askToKill(pid) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- 브라우저 열기 ---
|
||||||
function openBrowser(url) {
|
function openBrowser(url) {
|
||||||
const start = (process.platform == 'darwin' ? 'open' : process.platform == 'win32' ? 'start' : 'xdg-open');
|
const start = (process.platform == 'darwin' ? 'open' : process.platform == 'win32' ? 'start' : 'xdg-open');
|
||||||
exec(`${start} ${url}`, (err) => {
|
exec(`${start} ${url}`, (err) => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Download, Server, Package } from 'lucide-react';
|
import { Download, Package, Server, X } from 'lucide-react';
|
||||||
import { formatBytes } from '../utils/formatters';
|
import { formatBytes } from '../utils/formatters';
|
||||||
|
|
||||||
interface DownloadModalProps {
|
interface DownloadModalProps {
|
||||||
@@ -8,98 +8,105 @@ interface DownloadModalProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DownloadModal: React.FC<DownloadModalProps> = ({ isOpen, onClose }) => {
|
const DownloadModal: React.FC<DownloadModalProps> = ({ isOpen, onClose }) => {
|
||||||
if (!isOpen) return null;
|
const [sizes, setSizes] = useState({ full: 0, backend: 0 });
|
||||||
|
|
||||||
const baseUrl = import.meta.env.BASE_URL;
|
|
||||||
const [fileSizes, setFileSizes] = useState<{ [key: string]: string }>({});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isOpen) return;
|
if (isOpen) {
|
||||||
|
// Fetch sizes
|
||||||
const fetchSize = async (filename: string) => {
|
const fetchSize = async (url: string, key: 'full' | 'backend') => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${baseUrl}${filename}`, { method: 'HEAD' });
|
const res = await fetch(url, { method: 'HEAD' });
|
||||||
const size = response.headers.get('Content-Length');
|
const len = res.headers.get('content-length');
|
||||||
if (size) {
|
if (len) setSizes(prev => ({ ...prev, [key]: parseInt(len, 10) }));
|
||||||
setFileSizes(prev => ({ ...prev, [filename]: formatBytes(parseInt(size, 10)) }));
|
} catch (e) {
|
||||||
|
console.error("Failed to fetch size", e);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
};
|
||||||
console.error(`Failed to fetch size for ${filename}`, e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchSize('webftp.exe');
|
fetchSize(`${import.meta.env.BASE_URL}webftp.exe`, 'full');
|
||||||
fetchSize('webftp-backend.exe');
|
fetchSize(`${import.meta.env.BASE_URL}webftp-backend.exe`, 'backend');
|
||||||
}, [isOpen, baseUrl]);
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/20 backdrop-blur-sm animate-in fade-in duration-200">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
|
||||||
<div
|
<div className="bg-white rounded-lg shadow-xl w-[500px] max-w-full m-4 overflow-hidden animate-in fade-in zoom-in duration-200">
|
||||||
className="fixed inset-0"
|
|
||||||
onClick={onClose}
|
|
||||||
></div>
|
|
||||||
<div className="bg-white/90 backdrop-blur-md rounded-xl shadow-2xl border border-white/50 w-full max-w-md p-6 relative animate-in zoom-in-95 duration-200">
|
|
||||||
|
|
||||||
<h2 className="text-xl font-bold text-slate-800 mb-2 flex items-center gap-2">
|
{/* Header */}
|
||||||
<Download className="text-blue-600" />
|
<div className="flex items-center justify-between px-4 py-3 border-b border-slate-100 bg-slate-50">
|
||||||
다운로드 센터
|
<h3 className="font-bold text-slate-800 flex items-center gap-2">
|
||||||
</h2>
|
<Download size={20} className="text-blue-600" />
|
||||||
<p className="text-sm text-slate-500 mb-6">
|
WebZilla 다운로드 센터
|
||||||
사용 환경에 맞는 실행 파일을 선택하여 다운로드하세요.
|
</h3>
|
||||||
</p>
|
<button onClick={onClose} className="text-slate-400 hover:text-slate-600 transition-colors">
|
||||||
|
<X size={20} />
|
||||||
<div className="space-y-4">
|
</button>
|
||||||
<a
|
|
||||||
href={`${baseUrl}webftp.exe`}
|
|
||||||
download="webftp.exe"
|
|
||||||
className="flex items-start gap-4 p-4 rounded-lg border border-slate-200 bg-white hover:bg-blue-50 hover:border-blue-300 transition-all group shadow-sm hover:shadow-md"
|
|
||||||
onClick={onClose}
|
|
||||||
>
|
|
||||||
<div className="w-12 h-12 rounded-full bg-blue-100/50 flex items-center justify-center shrink-0 group-hover:bg-blue-200/50 transition-colors">
|
|
||||||
<Package className="text-blue-600 w-6 h-6" />
|
|
||||||
</div>
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="font-bold text-slate-800 mb-1 flex items-center justify-between">
|
|
||||||
WebFTP (권장)
|
|
||||||
WebFTP (권장)
|
|
||||||
<span className="flex gap-1">
|
|
||||||
{fileSizes['webftp.exe'] && <span className="text-[10px] bg-slate-100 text-slate-500 px-1.5 py-0.5 rounded opacity-80">{fileSizes['webftp.exe']}</span>}
|
|
||||||
<span className="text-[10px] font-bold bg-blue-100 text-blue-700 px-1.5 py-0.5 rounded">ALL-IN-ONE</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-slate-500 leading-relaxed">
|
|
||||||
백엔드와 프론트엔드가 포함된 단일 실행 파일입니다. <br />
|
|
||||||
<span className="font-semibold text-slate-600">다운로드 후 바로 실행하여 사용하세요.</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a
|
|
||||||
href={`${baseUrl}webftp-backend.exe`}
|
|
||||||
download="webftp-backend.exe"
|
|
||||||
className="flex items-start gap-4 p-4 rounded-lg border border-slate-200 bg-white hover:bg-slate-50 hover:border-slate-300 transition-all group shadow-sm hover:shadow-md"
|
|
||||||
onClick={onClose}
|
|
||||||
>
|
|
||||||
<div className="w-12 h-12 rounded-full bg-slate-100/50 flex items-center justify-center shrink-0 group-hover:bg-slate-200/50 transition-colors">
|
|
||||||
<Server className="text-slate-600 w-6 h-6" />
|
|
||||||
</div>
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="font-bold text-slate-800 mb-1 flex items-center justify-between">
|
|
||||||
Backend Only
|
|
||||||
{fileSizes['webftp-backend.exe'] && <span className="text-[10px] bg-slate-100 text-slate-500 px-1.5 py-0.5 rounded opacity-80">{fileSizes['webftp-backend.exe']}</span>}
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-slate-500 leading-relaxed">
|
|
||||||
백엔드 서버 파일만 다운로드합니다. <br />
|
|
||||||
<span className="font-semibold text-slate-600">백엔드를 실행하시면 현재 사이트에서 백엔드가 자동 연결 됩니다</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-6 flex justify-end">
|
{/* Content */}
|
||||||
|
<div className="p-6 space-y-4">
|
||||||
|
<p className="text-sm text-slate-500 mb-4">
|
||||||
|
사용 환경에 맞는 실행 파일을 선택하여 다운로드하세요.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="grid gap-4">
|
||||||
|
{/* Option 1: Full Version */}
|
||||||
|
<a
|
||||||
|
href={`${import.meta.env.BASE_URL}webftp.exe`}
|
||||||
|
download="webftp.exe"
|
||||||
|
className="flex items-start gap-4 p-4 rounded-lg border border-slate-200 hover:border-blue-400 hover:bg-blue-50 transition-all group relative"
|
||||||
|
>
|
||||||
|
<div className="p-3 bg-blue-100 text-blue-600 rounded-lg group-hover:bg-blue-200 transition-colors">
|
||||||
|
<Package size={24} />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h4 className="font-bold text-slate-800">단일 실행 파일 (All-in-One)</h4>
|
||||||
|
<span className="text-xs font-mono bg-slate-100 px-2 py-0.5 rounded text-slate-500">
|
||||||
|
{sizes.full > 0 ? formatBytes(sizes.full) : 'Loading...'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-slate-500 mt-1">
|
||||||
|
설치가 필요 없으며, 프론트엔드와 백엔드가 하나로 통합되어 있습니다.
|
||||||
|
<span className="block text-blue-600 mt-1 font-semibold">가장 추천하는 방식입니다.</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Download size={16} className="absolute top-4 right-4 text-slate-300 group-hover:text-blue-500" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{/* Option 2: Backend Only */}
|
||||||
|
<a
|
||||||
|
href={`${import.meta.env.BASE_URL}webftp-backend.exe`}
|
||||||
|
download="webftp-backend.exe"
|
||||||
|
className="flex items-start gap-4 p-4 rounded-lg border border-slate-200 hover:border-emerald-400 hover:bg-emerald-50 transition-all group relative"
|
||||||
|
>
|
||||||
|
<div className="p-3 bg-emerald-100 text-emerald-600 rounded-lg group-hover:bg-emerald-200 transition-colors">
|
||||||
|
<Server size={24} />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h4 className="font-bold text-slate-800">백엔드 전용 (Backend Only)</h4>
|
||||||
|
<span className="text-xs font-mono bg-slate-100 px-2 py-0.5 rounded text-slate-500">
|
||||||
|
{sizes.backend > 0 ? formatBytes(sizes.backend) : 'Loading...'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-slate-500 mt-1">
|
||||||
|
정적 파일 호스팅을 별도로 하거나, 서버 리소스를 최소화해야 할 때 사용하세요.
|
||||||
|
(프론트엔드 파일 미포함)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Download size={16} className="absolute top-4 right-4 text-slate-300 group-hover:text-emerald-500" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<div className="px-4 py-3 bg-slate-50 border-t border-slate-100 text-right">
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="px-4 py-2 text-sm text-slate-600 hover:text-slate-900 font-medium transition-colors"
|
className="px-4 py-2 bg-white border border-slate-300 text-slate-700 rounded hover:bg-slate-50 text-sm font-medium shadow-sm"
|
||||||
>
|
>
|
||||||
닫기
|
닫기
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -5,6 +5,9 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>WebFTP by SIMP</title>
|
<title>WebFTP by SIMP</title>
|
||||||
|
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-4444852135420953"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<style>
|
<style>
|
||||||
/* Custom scrollbar for a more native app feel - Light Theme */
|
/* Custom scrollbar for a more native app feel - Light Theme */
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import './index.css';
|
|
||||||
|
|
||||||
const rootElement = document.getElementById('root');
|
const rootElement = document.getElementById('root');
|
||||||
if (!rootElement) {
|
if (!rootElement) {
|
||||||
|
|||||||
2
make.bat
2
make.bat
@@ -1 +1 @@
|
|||||||
call npm run build
|
pkg backend_proxy.cjs --targets node18-win-x64 --output .\public\webftp-backend.exe
|
||||||
|
|||||||
665
package-lock.json
generated
665
package-lock.json
generated
@@ -15,35 +15,15 @@
|
|||||||
"react-dom": "^19.2.3",
|
"react-dom": "^19.2.3",
|
||||||
"ws": "^8.19.0"
|
"ws": "^8.19.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
|
||||||
"webzilla": "backend_proxy.cjs"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/postcss": "^4.1.18",
|
|
||||||
"@types/node": "^22.14.0",
|
"@types/node": "^22.14.0",
|
||||||
"@vitejs/plugin-react": "^5.0.0",
|
"@vitejs/plugin-react": "^5.0.0",
|
||||||
"autoprefixer": "^10.4.23",
|
|
||||||
"pkg": "^5.8.1",
|
"pkg": "^5.8.1",
|
||||||
"postcss": "^8.5.6",
|
|
||||||
"rimraf": "^6.1.2",
|
"rimraf": "^6.1.2",
|
||||||
"tailwindcss": "^4.1.18",
|
|
||||||
"typescript": "~5.8.2",
|
"typescript": "~5.8.2",
|
||||||
"vite": "^6.2.0"
|
"vite": "^6.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@alloc/quick-lru": {
|
|
||||||
"version": "5.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
|
||||||
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
"version": "7.28.6",
|
"version": "7.28.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz",
|
||||||
@@ -1428,277 +1408,6 @@
|
|||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/node": {
|
|
||||||
"version": "4.1.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz",
|
|
||||||
"integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@jridgewell/remapping": "^2.3.4",
|
|
||||||
"enhanced-resolve": "^5.18.3",
|
|
||||||
"jiti": "^2.6.1",
|
|
||||||
"lightningcss": "1.30.2",
|
|
||||||
"magic-string": "^0.30.21",
|
|
||||||
"source-map-js": "^1.2.1",
|
|
||||||
"tailwindcss": "4.1.18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide": {
|
|
||||||
"version": "4.1.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz",
|
|
||||||
"integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"@tailwindcss/oxide-android-arm64": "4.1.18",
|
|
||||||
"@tailwindcss/oxide-darwin-arm64": "4.1.18",
|
|
||||||
"@tailwindcss/oxide-darwin-x64": "4.1.18",
|
|
||||||
"@tailwindcss/oxide-freebsd-x64": "4.1.18",
|
|
||||||
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18",
|
|
||||||
"@tailwindcss/oxide-linux-arm64-gnu": "4.1.18",
|
|
||||||
"@tailwindcss/oxide-linux-arm64-musl": "4.1.18",
|
|
||||||
"@tailwindcss/oxide-linux-x64-gnu": "4.1.18",
|
|
||||||
"@tailwindcss/oxide-linux-x64-musl": "4.1.18",
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi": "4.1.18",
|
|
||||||
"@tailwindcss/oxide-win32-arm64-msvc": "4.1.18",
|
|
||||||
"@tailwindcss/oxide-win32-x64-msvc": "4.1.18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-android-arm64": {
|
|
||||||
"version": "4.1.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz",
|
|
||||||
"integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"android"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-darwin-arm64": {
|
|
||||||
"version": "4.1.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz",
|
|
||||||
"integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-darwin-x64": {
|
|
||||||
"version": "4.1.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz",
|
|
||||||
"integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-freebsd-x64": {
|
|
||||||
"version": "4.1.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz",
|
|
||||||
"integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"freebsd"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
|
|
||||||
"version": "4.1.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz",
|
|
||||||
"integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==",
|
|
||||||
"cpu": [
|
|
||||||
"arm"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
|
|
||||||
"version": "4.1.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz",
|
|
||||||
"integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
|
|
||||||
"version": "4.1.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz",
|
|
||||||
"integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
|
|
||||||
"version": "4.1.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz",
|
|
||||||
"integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
|
|
||||||
"version": "4.1.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz",
|
|
||||||
"integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
|
|
||||||
"version": "4.1.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz",
|
|
||||||
"integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==",
|
|
||||||
"bundleDependencies": [
|
|
||||||
"@napi-rs/wasm-runtime",
|
|
||||||
"@emnapi/core",
|
|
||||||
"@emnapi/runtime",
|
|
||||||
"@tybys/wasm-util",
|
|
||||||
"@emnapi/wasi-threads",
|
|
||||||
"tslib"
|
|
||||||
],
|
|
||||||
"cpu": [
|
|
||||||
"wasm32"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@emnapi/core": "^1.7.1",
|
|
||||||
"@emnapi/runtime": "^1.7.1",
|
|
||||||
"@emnapi/wasi-threads": "^1.1.0",
|
|
||||||
"@napi-rs/wasm-runtime": "^1.1.0",
|
|
||||||
"@tybys/wasm-util": "^0.10.1",
|
|
||||||
"tslib": "^2.4.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
|
||||||
"version": "4.1.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz",
|
|
||||||
"integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
|
|
||||||
"version": "4.1.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz",
|
|
||||||
"integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/postcss": {
|
|
||||||
"version": "4.1.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.18.tgz",
|
|
||||||
"integrity": "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@alloc/quick-lru": "^5.2.0",
|
|
||||||
"@tailwindcss/node": "4.1.18",
|
|
||||||
"@tailwindcss/oxide": "4.1.18",
|
|
||||||
"postcss": "^8.4.41",
|
|
||||||
"tailwindcss": "4.1.18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/babel__core": {
|
"node_modules/@types/babel__core": {
|
||||||
"version": "7.20.5",
|
"version": "7.20.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||||
@@ -1834,43 +1543,6 @@
|
|||||||
"node": ">= 4.0.0"
|
"node": ">= 4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/autoprefixer": {
|
|
||||||
"version": "10.4.23",
|
|
||||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz",
|
|
||||||
"integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==",
|
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/postcss/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "tidelift",
|
|
||||||
"url": "https://tidelift.com/funding/github/npm/autoprefixer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/ai"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"browserslist": "^4.28.1",
|
|
||||||
"caniuse-lite": "^1.0.30001760",
|
|
||||||
"fraction.js": "^5.3.4",
|
|
||||||
"picocolors": "^1.1.1",
|
|
||||||
"postcss-value-parser": "^4.2.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"autoprefixer": "bin/autoprefixer"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "^10 || ^12 || >=14"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"postcss": "^8.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
@@ -2255,20 +1927,6 @@
|
|||||||
"once": "^1.4.0"
|
"once": "^1.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/enhanced-resolve": {
|
|
||||||
"version": "5.18.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz",
|
|
||||||
"integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"graceful-fs": "^4.2.4",
|
|
||||||
"tapable": "^2.2.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.13.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.25.12",
|
"version": "0.25.12",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
|
||||||
@@ -2446,20 +2104,6 @@
|
|||||||
"node": ">=12.20.0"
|
"node": ">=12.20.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fraction.js": {
|
|
||||||
"version": "5.3.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
|
|
||||||
"integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": "*"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/rawify"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/from2": {
|
"node_modules/from2": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
|
||||||
@@ -2937,16 +2581,6 @@
|
|||||||
"@pkgjs/parseargs": "^0.11.0"
|
"@pkgjs/parseargs": "^0.11.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jiti": {
|
|
||||||
"version": "2.6.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
|
|
||||||
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"bin": {
|
|
||||||
"jiti": "lib/jiti-cli.mjs"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/js-tokens": {
|
"node_modules/js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
@@ -3023,267 +2657,6 @@
|
|||||||
"safe-buffer": "^5.0.1"
|
"safe-buffer": "^5.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss": {
|
|
||||||
"version": "1.30.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
|
|
||||||
"integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"detect-libc": "^2.0.3"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/parcel"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"lightningcss-android-arm64": "1.30.2",
|
|
||||||
"lightningcss-darwin-arm64": "1.30.2",
|
|
||||||
"lightningcss-darwin-x64": "1.30.2",
|
|
||||||
"lightningcss-freebsd-x64": "1.30.2",
|
|
||||||
"lightningcss-linux-arm-gnueabihf": "1.30.2",
|
|
||||||
"lightningcss-linux-arm64-gnu": "1.30.2",
|
|
||||||
"lightningcss-linux-arm64-musl": "1.30.2",
|
|
||||||
"lightningcss-linux-x64-gnu": "1.30.2",
|
|
||||||
"lightningcss-linux-x64-musl": "1.30.2",
|
|
||||||
"lightningcss-win32-arm64-msvc": "1.30.2",
|
|
||||||
"lightningcss-win32-x64-msvc": "1.30.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lightningcss-android-arm64": {
|
|
||||||
"version": "1.30.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz",
|
|
||||||
"integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"android"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/parcel"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lightningcss-darwin-arm64": {
|
|
||||||
"version": "1.30.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz",
|
|
||||||
"integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/parcel"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lightningcss-darwin-x64": {
|
|
||||||
"version": "1.30.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz",
|
|
||||||
"integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/parcel"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lightningcss-freebsd-x64": {
|
|
||||||
"version": "1.30.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz",
|
|
||||||
"integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"freebsd"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/parcel"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lightningcss-linux-arm-gnueabihf": {
|
|
||||||
"version": "1.30.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz",
|
|
||||||
"integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==",
|
|
||||||
"cpu": [
|
|
||||||
"arm"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/parcel"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lightningcss-linux-arm64-gnu": {
|
|
||||||
"version": "1.30.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz",
|
|
||||||
"integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/parcel"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lightningcss-linux-arm64-musl": {
|
|
||||||
"version": "1.30.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz",
|
|
||||||
"integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/parcel"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lightningcss-linux-x64-gnu": {
|
|
||||||
"version": "1.30.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz",
|
|
||||||
"integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/parcel"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lightningcss-linux-x64-musl": {
|
|
||||||
"version": "1.30.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz",
|
|
||||||
"integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/parcel"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lightningcss-win32-arm64-msvc": {
|
|
||||||
"version": "1.30.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz",
|
|
||||||
"integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/parcel"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lightningcss-win32-x64-msvc": {
|
|
||||||
"version": "1.30.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz",
|
|
||||||
"integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/parcel"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/long": {
|
"node_modules/long": {
|
||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
|
||||||
@@ -3309,16 +2682,6 @@
|
|||||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/magic-string": {
|
|
||||||
"version": "0.30.21",
|
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
|
||||||
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/merge2": {
|
"node_modules/merge2": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||||
@@ -3853,13 +3216,6 @@
|
|||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss-value-parser": {
|
|
||||||
"version": "4.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
|
||||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/prebuild-install": {
|
"node_modules/prebuild-install": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz",
|
||||||
@@ -4430,27 +3786,6 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tailwindcss": {
|
|
||||||
"version": "4.1.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
|
|
||||||
"integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/tapable": {
|
|
||||||
"version": "2.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
|
|
||||||
"integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/webpack"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/tar-fs": {
|
"node_modules/tar-fs": {
|
||||||
"version": "2.1.4",
|
"version": "2.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
"clean": "rimraf public/webftp.exe public/webftp-backend.exe dist/webftp.exe dist/webftp-backend.exe",
|
"clean": "rimraf public/webftp.exe public/webftp-backend.exe dist/webftp.exe dist/webftp-backend.exe",
|
||||||
"build": "npm run clean && vite build && npm run build:backend && npm run build:full",
|
"build": "npm run clean && vite build && npm run build:backend && npm run build:full",
|
||||||
"build:full": "pkg . --output ./public/webftp.exe",
|
"build:full": "pkg . --output ./public/webftp.exe",
|
||||||
"build:backend": "pkg backend_proxy.cjs --targets node18-win-x64 --output ./public/webftp-backend.exe",
|
"build:backend": "pkg backend_proxy.cjs -c pkg-backend.json --output ./public/webftp-backend.exe",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"proxy": "node backend_proxy.cjs"
|
"proxy": "node backend_proxy.cjs"
|
||||||
},
|
},
|
||||||
@@ -22,14 +22,10 @@
|
|||||||
"ws": "^8.19.0"
|
"ws": "^8.19.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/postcss": "^4.1.18",
|
|
||||||
"@types/node": "^22.14.0",
|
"@types/node": "^22.14.0",
|
||||||
"@vitejs/plugin-react": "^5.0.0",
|
"@vitejs/plugin-react": "^5.0.0",
|
||||||
"autoprefixer": "^10.4.23",
|
|
||||||
"pkg": "^5.8.1",
|
"pkg": "^5.8.1",
|
||||||
"postcss": "^8.5.6",
|
|
||||||
"rimraf": "^6.1.2",
|
"rimraf": "^6.1.2",
|
||||||
"tailwindcss": "^4.1.18",
|
|
||||||
"typescript": "~5.8.2",
|
"typescript": "~5.8.2",
|
||||||
"vite": "^6.2.0"
|
"vite": "^6.2.0"
|
||||||
},
|
},
|
||||||
@@ -42,4 +38,4 @@
|
|||||||
"node18-win-x64"
|
"node18-win-x64"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
12
pkg-backend.json
Normal file
12
pkg-backend.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "webzilla-backend",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"bin": "backend_proxy.cjs",
|
||||||
|
"pkg": {
|
||||||
|
"scripts": "backend_proxy.cjs",
|
||||||
|
"assets": [],
|
||||||
|
"targets": [
|
||||||
|
"node18-win-x64"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
export default {
|
|
||||||
plugins: {
|
|
||||||
'@tailwindcss/postcss': {},
|
|
||||||
autoprefixer: {},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
|
||||||
export default {
|
|
||||||
content: [
|
|
||||||
"./index.html",
|
|
||||||
"./src/**/*.{js,ts,jsx,tsx}",
|
|
||||||
"./**/*.{js,ts,jsx,tsx}",
|
|
||||||
],
|
|
||||||
theme: {
|
|
||||||
extend: {},
|
|
||||||
},
|
|
||||||
plugins: [],
|
|
||||||
}
|
|
||||||
@@ -3,22 +3,22 @@ import { defineConfig, loadEnv } from 'vite';
|
|||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
|
|
||||||
export default defineConfig(({ mode }) => {
|
export default defineConfig(({ mode }) => {
|
||||||
const env = loadEnv(mode, '.', '');
|
const env = loadEnv(mode, '.', '');
|
||||||
return {
|
return {
|
||||||
base: './',
|
base: '/ftp/',
|
||||||
server: {
|
server: {
|
||||||
port: 3000,
|
port: 3000,
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
},
|
},
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
define: {
|
define: {
|
||||||
'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),
|
'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),
|
||||||
'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY)
|
'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY)
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': path.resolve(__dirname, '.'),
|
'@': path.resolve(__dirname, '.'),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user