Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa0c998d0f | ||
|
|
3fc28af508 |
8
FTP-Server.code-workspace
Normal file
8
FTP-Server.code-workspace
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
}
|
||||||
111
public/app.js
111
public/app.js
@@ -65,18 +65,7 @@ document.getElementById('logout-btn').addEventListener('click', async () => {
|
|||||||
window.location.reload();
|
window.location.reload();
|
||||||
});
|
});
|
||||||
|
|
||||||
function showLogin() {
|
|
||||||
loginScreen.classList.remove('hidden');
|
|
||||||
appScreen.classList.add('hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
function showApp() {
|
|
||||||
loginScreen.classList.add('hidden');
|
|
||||||
appScreen.classList.remove('hidden');
|
|
||||||
document.getElementById('user-display').textContent = currentUser.username;
|
|
||||||
loadFiles();
|
|
||||||
updateViewIcon();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- View Mode ---
|
// --- View Mode ---
|
||||||
function toggleView() {
|
function toggleView() {
|
||||||
@@ -94,6 +83,27 @@ function updateViewIcon() {
|
|||||||
}
|
}
|
||||||
viewToggleBtn.addEventListener('click', toggleView);
|
viewToggleBtn.addEventListener('click', toggleView);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// --- Auth / UI State ---
|
||||||
|
|
||||||
|
function showLogin() {
|
||||||
|
// Force style to ensure hidden/visible
|
||||||
|
loginScreen.classList.remove('hidden');
|
||||||
|
loginScreen.style.display = 'flex';
|
||||||
|
appScreen.classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showApp() {
|
||||||
|
loginScreen.classList.add('hidden');
|
||||||
|
loginScreen.style.display = 'none'; // Force hide
|
||||||
|
appScreen.classList.remove('hidden');
|
||||||
|
document.getElementById('user-display').textContent = currentUser.username;
|
||||||
|
loadFiles();
|
||||||
|
updateViewIcon();
|
||||||
|
}
|
||||||
|
|
||||||
// --- Files ---
|
// --- Files ---
|
||||||
async function loadFiles(path = currentPath) {
|
async function loadFiles(path = currentPath) {
|
||||||
try {
|
try {
|
||||||
@@ -375,22 +385,87 @@ dropZone.addEventListener('drop', (e) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// File Input Change
|
||||||
|
document.getElementById('file-input').addEventListener('change', (e) => {
|
||||||
|
if (e.target.files.length > 0) {
|
||||||
|
uploadFiles(e.target.files);
|
||||||
|
// Reset input so same file can be selected again if needed
|
||||||
|
e.target.value = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
async function uploadFiles(files) {
|
async function uploadFiles(files) {
|
||||||
const statusDiv = document.getElementById('upload-status');
|
const statusDiv = document.getElementById('upload-status');
|
||||||
|
const progressContainer = document.getElementById('progress-container');
|
||||||
|
const progressBar = document.getElementById('progress-bar');
|
||||||
|
const progressText = document.getElementById('progress-text');
|
||||||
|
|
||||||
statusDiv.innerHTML = 'Uploading...';
|
statusDiv.innerHTML = 'Uploading...';
|
||||||
|
progressContainer.classList.remove('hidden');
|
||||||
|
progressBar.style.width = '0%';
|
||||||
|
progressText.textContent = '0%';
|
||||||
|
|
||||||
|
let totalSize = 0;
|
||||||
|
for (let file of files) totalSize += file.size;
|
||||||
|
|
||||||
|
// Avoid division by zero
|
||||||
|
if (totalSize === 0) totalSize = 1;
|
||||||
|
|
||||||
|
let previousFilesSize = 0;
|
||||||
|
|
||||||
for (let file of files) {
|
for (let file of files) {
|
||||||
const formData = new FormData();
|
try {
|
||||||
formData.append('file', file);
|
await new Promise((resolve, reject) => {
|
||||||
if (currentPath) {
|
const xhr = new XMLHttpRequest();
|
||||||
await fetch(`/api/upload?path=${encodeURIComponent(currentPath)}`, { method: 'POST', body: formData });
|
const formData = new FormData();
|
||||||
} else {
|
formData.append('file', file);
|
||||||
await fetch('/api/upload', { method: 'POST', body: formData });
|
|
||||||
|
const url = currentPath
|
||||||
|
? `/api/upload?path=${encodeURIComponent(currentPath)}`
|
||||||
|
: '/api/upload';
|
||||||
|
|
||||||
|
xhr.open('POST', url);
|
||||||
|
|
||||||
|
xhr.upload.onprogress = (e) => {
|
||||||
|
if (e.lengthComputable) {
|
||||||
|
const currentLoaded = e.loaded;
|
||||||
|
const totalLoaded = previousFilesSize + currentLoaded;
|
||||||
|
const percent = Math.min(100, Math.round((totalLoaded / totalSize) * 100));
|
||||||
|
progressBar.style.width = percent + '%';
|
||||||
|
progressText.textContent = percent + '%';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.onload = () => {
|
||||||
|
if (xhr.status >= 200 && xhr.status < 300) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
reject(new Error(`Upload failed: ${xhr.statusText}`));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.onerror = () => reject(new Error('Network Error'));
|
||||||
|
|
||||||
|
xhr.send(formData);
|
||||||
|
});
|
||||||
|
previousFilesSize += file.size;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
statusDiv.innerHTML = `<span style="color:var(--danger-color)">Error uploading ${file.name}</span>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
progressBar.style.width = '100%';
|
||||||
|
progressText.textContent = '100%';
|
||||||
statusDiv.innerHTML = 'Done!';
|
statusDiv.innerHTML = 'Done!';
|
||||||
setTimeout(() => statusDiv.innerHTML = '', 2000);
|
|
||||||
|
setTimeout(() => {
|
||||||
|
statusDiv.innerHTML = '';
|
||||||
|
progressContainer.classList.add('hidden');
|
||||||
|
progressBar.style.width = '0%';
|
||||||
|
progressText.textContent = '0%';
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
loadFiles();
|
loadFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,9 +40,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<span id="user-display"></span>
|
<span id="user-display"></span>
|
||||||
<button id="theme-toggle" class="btn secondary" aria-label="Toggle Dark Mode">
|
|
||||||
<span class="icon">🌓</span>
|
|
||||||
</button>
|
|
||||||
<button id="logout-btn" class="btn secondary">Logout</button>
|
<button id="logout-btn" class="btn secondary">Logout</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -69,6 +67,10 @@
|
|||||||
Files</button>
|
Files</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="upload-status"></div>
|
<div id="upload-status"></div>
|
||||||
|
<div id="progress-container" class="hidden">
|
||||||
|
<div id="progress-bar"></div>
|
||||||
|
<div id="progress-text">0%</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- File List -->
|
<!-- File List -->
|
||||||
<section class="files-section">
|
<section class="files-section">
|
||||||
|
|||||||
@@ -1,17 +1,4 @@
|
|||||||
:root {
|
:root {
|
||||||
--bg-color: #f8f9fa;
|
|
||||||
--card-bg: #ffffff;
|
|
||||||
--text-color: #1f2937;
|
|
||||||
--text-secondary: #6b7280;
|
|
||||||
--primary-color: #3b82f6;
|
|
||||||
--primary-hover: #2563eb;
|
|
||||||
--border-color: #e5e7eb;
|
|
||||||
--danger-color: #ef4444;
|
|
||||||
--modal-bg: rgba(0, 0, 0, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dark Mode Variables */
|
|
||||||
[data-theme="dark"] {
|
|
||||||
--bg-color: #111827;
|
--bg-color: #111827;
|
||||||
--card-bg: #1f2937;
|
--card-bg: #1f2937;
|
||||||
--text-color: #f9fafb;
|
--text-color: #f9fafb;
|
||||||
@@ -19,22 +6,10 @@
|
|||||||
--primary-color: #60a5fa;
|
--primary-color: #60a5fa;
|
||||||
--primary-hover: #3b82f6;
|
--primary-hover: #3b82f6;
|
||||||
--border-color: #374151;
|
--border-color: #374151;
|
||||||
|
--danger-color: #ef4444;
|
||||||
--modal-bg: rgba(0, 0, 0, 0.9);
|
--modal-bg: rgba(0, 0, 0, 0.9);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root:not([data-theme="light"]) {
|
|
||||||
--bg-color: #111827;
|
|
||||||
--card-bg: #1f2937;
|
|
||||||
--text-color: #f9fafb;
|
|
||||||
--text-secondary: #9ca3af;
|
|
||||||
--primary-color: #60a5fa;
|
|
||||||
--primary-hover: #3b82f6;
|
|
||||||
--border-color: #374151;
|
|
||||||
--modal-bg: rgba(0, 0, 0, 0.9);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: 'Inter', sans-serif;
|
font-family: 'Inter', sans-serif;
|
||||||
background-color: var(--bg-color);
|
background-color: var(--bg-color);
|
||||||
@@ -271,6 +246,40 @@ header {
|
|||||||
transform: scale(1.02);
|
transform: scale(1.02);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Progress Bar */
|
||||||
|
#progress-container {
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--card-bg);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
position: relative;
|
||||||
|
height: 1.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#progress-bar {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
width: 0%;
|
||||||
|
transition: width 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#progress-text {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: white;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
/* Modals */
|
/* Modals */
|
||||||
.modal {
|
.modal {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
FROM node:18-alpine
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY package*.json ./
|
|
||||||
RUN npm install
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Expose port 3000
|
|
||||||
EXPOSE 3000
|
|
||||||
|
|
||||||
# Create uploads directory
|
|
||||||
RUN mkdir -p uploads
|
|
||||||
|
|
||||||
CMD ["node", "server.js"]
|
|
||||||
2165
uploads/Think/all/package-lock.json
generated
2165
uploads/Think/all/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "simple-file-server",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"main": "server.js",
|
|
||||||
"scripts": {
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
|
||||||
"start": "node server.js"
|
|
||||||
},
|
|
||||||
"keywords": [],
|
|
||||||
"author": "",
|
|
||||||
"license": "ISC",
|
|
||||||
"description": "",
|
|
||||||
"dependencies": {
|
|
||||||
"adm-zip": "^0.5.16",
|
|
||||||
"archiver": "^7.0.1",
|
|
||||||
"bcryptjs": "^3.0.3",
|
|
||||||
"cors": "^2.8.6",
|
|
||||||
"dotenv": "^17.2.4",
|
|
||||||
"express": "^5.2.1",
|
|
||||||
"express-session": "^1.19.0",
|
|
||||||
"mariadb": "^3.4.5",
|
|
||||||
"multer": "^2.0.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
const express = require('express');
|
|
||||||
const cors = require('cors');
|
|
||||||
const path = require('path');
|
|
||||||
const session = require('express-session');
|
|
||||||
require('dotenv').config();
|
|
||||||
|
|
||||||
const { initUserTable } = require('./db/connection');
|
|
||||||
const authRoutes = require('./routes/auth');
|
|
||||||
const fileRoutes = require('./routes/files');
|
|
||||||
|
|
||||||
const app = express();
|
|
||||||
const PORT = process.env.PORT || 3000;
|
|
||||||
|
|
||||||
// Initialize DB
|
|
||||||
initUserTable();
|
|
||||||
|
|
||||||
// Middleware
|
|
||||||
app.use(cors());
|
|
||||||
app.use(express.json());
|
|
||||||
app.use(express.urlencoded({ extended: true }));
|
|
||||||
|
|
||||||
// Session
|
|
||||||
app.use(session({
|
|
||||||
secret: process.env.SESSION_SECRET || 'supersecretkey',
|
|
||||||
resave: false,
|
|
||||||
saveUninitialized: false,
|
|
||||||
cookie: { secure: false } // Set to true if using HTTPS
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Auth Middleware
|
|
||||||
const requireAuth = (req, res, next) => {
|
|
||||||
if (req.session.user) {
|
|
||||||
next();
|
|
||||||
} else {
|
|
||||||
res.status(401).json({ error: 'Unauthorized' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Routes
|
|
||||||
app.use('/api/auth', authRoutes);
|
|
||||||
app.use('/api', requireAuth, fileRoutes);
|
|
||||||
|
|
||||||
// Static files (public) - protect if needed, but for now let's allow loading the app
|
|
||||||
// We can protect specific assets if we want, but the API is protected.
|
|
||||||
// Actually, if we want to force login, we can serve a login page or handle it in specific separate file.
|
|
||||||
// The main `index.html` handles the login UI, so it should be public.
|
|
||||||
app.use(express.static('public'));
|
|
||||||
|
|
||||||
app.listen(PORT, () => {
|
|
||||||
console.log(`Server running on port ${PORT}`);
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user