python migration
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
dist
|
||||
@@ -0,0 +1,21 @@
|
||||
# ---- Build stage ----
|
||||
FROM node:20 AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package-lock.json* ./
|
||||
RUN npm install
|
||||
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# ---- Production stage ----
|
||||
FROM nginx:alpine
|
||||
|
||||
# Copy built React app
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||
|
||||
# Replace nginx config
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
EXPOSE 80
|
||||
@@ -0,0 +1,20 @@
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
try_files $uri /index.html;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
# Keep the /api prefix when forwarding (Flask routes are /api/*).
|
||||
proxy_pass http://backend:3001;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
# SSE support (important for your EventSource)
|
||||
proxy_set_header Connection '';
|
||||
proxy_buffering off;
|
||||
chunked_transfer_encoding off;
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,6 @@ export default function ConfigModal({ onClose }) {
|
||||
const [scanResult, setScanResult] = useState(null) // { testCount, completedCount } | null
|
||||
const [saveError, setSaveError] = useState(null)
|
||||
|
||||
// Directories are locked once both are saved — restart server to change them
|
||||
const dirsLocked = !!(config?.target_dir && config?.results_dir)
|
||||
|
||||
useEffect(() => {
|
||||
if (config) {
|
||||
setForm({
|
||||
@@ -104,9 +101,6 @@ export default function ConfigModal({ onClose }) {
|
||||
<section>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h3 className="text-slate-300 text-xs uppercase tracking-widest">Directories</h3>
|
||||
{dirsLocked && (
|
||||
<span className="text-xs text-slate-500">🔒 Restart server to change directories</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{[
|
||||
@@ -116,30 +110,19 @@ export default function ConfigModal({ onClose }) {
|
||||
<div key={key}>
|
||||
<label className="text-slate-400 text-xs block mb-1">{label}</label>
|
||||
<div className="flex gap-2">
|
||||
{dirsLocked ? (
|
||||
<div
|
||||
title={form[key] ?? ''}
|
||||
className="flex-1 border border-slate-700/50 bg-slate-800/40 text-slate-500 text-sm rounded-lg px-3 py-2 truncate cursor-default select-all font-mono"
|
||||
>
|
||||
{(form[key] ?? '').replace(/^(.+[/\\])([^/\\]+[/\\][^/\\]*)$/, '…$2')}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<input
|
||||
type="text"
|
||||
value={form[key] ?? ''}
|
||||
onChange={e => setForm(f => ({ ...f, [key]: e.target.value }))}
|
||||
placeholder="C:\path\to\folder"
|
||||
className="flex-1 bg-slate-800 border border-slate-700 text-slate-200 text-sm rounded-lg px-3 py-2 focus:outline-none focus:border-blue-500"
|
||||
/>
|
||||
<button
|
||||
onClick={() => setBrowser(key)}
|
||||
className="px-3 py-2 text-sm rounded-lg border border-slate-700 text-slate-300 hover:bg-slate-800 transition-colors whitespace-nowrap"
|
||||
>
|
||||
Browse
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<input
|
||||
type="text"
|
||||
value={form[key] ?? ''}
|
||||
onChange={e => setForm(f => ({ ...f, [key]: e.target.value }))}
|
||||
placeholder="C:\path\to\folder"
|
||||
className="flex-1 bg-slate-800 border border-slate-700 text-slate-200 text-sm rounded-lg px-3 py-2 focus:outline-none focus:border-blue-500"
|
||||
/>
|
||||
<button
|
||||
onClick={() => setBrowser(key)}
|
||||
className="px-3 py-2 text-sm rounded-lg border border-slate-700 text-slate-300 hover:bg-slate-800 transition-colors whitespace-nowrap"
|
||||
>
|
||||
Browse
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -29,6 +29,15 @@ export default function DirectoryBrowser({ onSelect, onClose }) {
|
||||
}
|
||||
|
||||
const breadcrumbs = current ? current.replace(/\\/g, '/').split('/').filter(Boolean) : []
|
||||
const isUnixPath = !!current && current.startsWith('/')
|
||||
|
||||
function breadcrumbPathAt(index) {
|
||||
const parts = breadcrumbs.slice(0, index + 1)
|
||||
if (isUnixPath) {
|
||||
return `/${parts.join('/')}`
|
||||
}
|
||||
return parts.join('\\') + (index === 0 ? '\\' : '')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60">
|
||||
@@ -43,7 +52,7 @@ export default function DirectoryBrowser({ onSelect, onClose }) {
|
||||
<div className="px-4 py-2 border-b border-slate-800 flex items-center gap-1 text-xs text-slate-400 flex-wrap min-h-[36px]">
|
||||
<button onClick={() => navigate(null)} className="hover:text-slate-200">Drives</button>
|
||||
{breadcrumbs.map((part, i) => {
|
||||
const path = breadcrumbs.slice(0, i + 1).join('\\') + (i === 0 ? '\\' : '')
|
||||
const path = breadcrumbPathAt(i)
|
||||
return (
|
||||
<span key={i} className="flex items-center gap-1">
|
||||
<span>/</span>
|
||||
|
||||
Reference in New Issue
Block a user