File uploader
This commit is contained in:
@@ -0,0 +1,49 @@
|
|||||||
|
from fastapi import FastAPI, File, UploadFile
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
import dotenv
|
||||||
|
import os
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
|
||||||
|
dotenv.load_dotenv()
|
||||||
|
disable_docs = os.getenv("DISABLE_DOCS", "true").lower() == "true"
|
||||||
|
app: FastAPI = FastAPI(title="This OR That",
|
||||||
|
summary="OpenAPI schema for \"This OR That\" project!",
|
||||||
|
version="0.1",
|
||||||
|
contact={"GitHub": "https://github.com/IgorVolochay/thisORthat"},
|
||||||
|
docs_url=None if disable_docs else "/docs",
|
||||||
|
redoc_url=None if disable_docs else "/redoc",
|
||||||
|
openapi_url=None if disable_docs else "/openapi.json")
|
||||||
|
|
||||||
|
|
||||||
|
origins = ["*"]
|
||||||
|
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=origins,
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
def root():
|
||||||
|
return "Hello!"
|
||||||
|
|
||||||
|
@app.post("/upload_file")
|
||||||
|
@app.post("/upload_file")
|
||||||
|
async def create_file(file: Annotated[bytes, File()]):
|
||||||
|
return {"file_size": len(file)}
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
config = uvicorn.Config("main:app", port=5000, log_level="debug")
|
||||||
|
server = uvicorn.Server(config)
|
||||||
|
await server.serve()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[project]
|
||||||
|
name = "drop-me-files-analog"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = ""
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
dependencies = [
|
||||||
|
"dotenv>=0.9.9",
|
||||||
|
"fastapi>=0.121.1",
|
||||||
|
"python-multipart>=0.0.20",
|
||||||
|
"uvicorn>=0.38.0",
|
||||||
|
]
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Аналог DropMeFiles</title>
|
||||||
|
<link rel="stylesheet" href="./style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Аналог DropMeFiles</h1>
|
||||||
|
<div class="upload-container">
|
||||||
|
<form id="uploadForm">
|
||||||
|
<label class="dropzone" id="dropzone">
|
||||||
|
<input id="fileInput" type="file" multiple>
|
||||||
|
<p class="strong">Перетащите файлы сюда или нажмите, чтобы выбрать</p>
|
||||||
|
<p>Файлы будут отправлены на сервер после нажатия кнопки</p>
|
||||||
|
</label>
|
||||||
|
<button type="submit" id="uploadButton" disabled>Загрузить</button>
|
||||||
|
</form>
|
||||||
|
<div class="status" id="status"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="./script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
const fileInput = document.getElementById('fileInput');
|
||||||
|
const dropzone = document.getElementById('dropzone');
|
||||||
|
const uploadButton = document.getElementById('uploadButton');
|
||||||
|
const uploadForm = document.getElementById('uploadForm');
|
||||||
|
const statusDiv = document.getElementById('status');
|
||||||
|
|
||||||
|
const updateButtonState = () => {
|
||||||
|
uploadButton.disabled = !fileInput.files || fileInput.files.length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setStatus = (message, type = '') => {
|
||||||
|
statusDiv.textContent = message;
|
||||||
|
statusDiv.className = type ? `status ${type}` : 'status';
|
||||||
|
};
|
||||||
|
|
||||||
|
dropzone.addEventListener('dragover', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
dropzone.classList.add('dragover');
|
||||||
|
});
|
||||||
|
|
||||||
|
dropzone.addEventListener('dragleave', () => {
|
||||||
|
dropzone.classList.remove('dragover');
|
||||||
|
});
|
||||||
|
|
||||||
|
dropzone.addEventListener('drop', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
dropzone.classList.remove('dragover');
|
||||||
|
|
||||||
|
if (event.dataTransfer?.files?.length) {
|
||||||
|
fileInput.files = event.dataTransfer.files;
|
||||||
|
updateButtonState();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fileInput.addEventListener('change', () => {
|
||||||
|
updateButtonState();
|
||||||
|
});
|
||||||
|
|
||||||
|
uploadForm.addEventListener('submit', async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (!fileInput.files || fileInput.files.length === 0) {
|
||||||
|
setStatus('Выберите файлов для загрузки.', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
Array.from(fileInput.files).forEach((file) => {
|
||||||
|
formData.append('files', file);
|
||||||
|
});
|
||||||
|
|
||||||
|
uploadButton.disabled = true;
|
||||||
|
dropzone.classList.remove('dragover');
|
||||||
|
setStatus('Загрузка...', '');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('http://192.168.0.132:5000/upload_file', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Ошибка сервера: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.text();
|
||||||
|
setStatus(`Путь к файлу: ${data}`, 'success');
|
||||||
|
fileInput.value = '';
|
||||||
|
} catch (error) {
|
||||||
|
setStatus(`Не удалось загрузить файлы: ${error.message}`, 'error');
|
||||||
|
} finally {
|
||||||
|
uploadButton.disabled = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
updateButtonState();
|
||||||
|
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background: radial-gradient(circle at top, #f7f7fb, #eff1f8);
|
||||||
|
color: #111827;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: clamp(32px, 4vw, 48px);
|
||||||
|
margin-bottom: 32px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-container {
|
||||||
|
width: min(640px, 100%);
|
||||||
|
background: #ffffffdd;
|
||||||
|
border-radius: 20px;
|
||||||
|
box-shadow: 0 20px 45px -20px rgba(15, 23, 42, 0.65);
|
||||||
|
padding: 32px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone {
|
||||||
|
border: 2px dashed #6366f1;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 48px 24px;
|
||||||
|
text-align: center;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
background: rgba(99, 102, 241, 0.04);
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone.dragover {
|
||||||
|
background: rgba(99, 102, 241, 0.12);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 14px 28px -24px rgba(99, 102, 241, 0.65);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone input[type="file"] {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
opacity: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone p {
|
||||||
|
margin: 8px 0;
|
||||||
|
color: #4b5563;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone p.strong {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2937;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background: linear-gradient(135deg, #6366f1, #8b5cf6);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 14px 20px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover:not(:disabled) {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 16px 32px -24px rgba(99, 102, 241, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
background: #cbd5f5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
min-height: 24px;
|
||||||
|
font-size: 15px;
|
||||||
|
color: #374151;
|
||||||
|
line-height: 1.5;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status.error {
|
||||||
|
color: #dc2626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status.success {
|
||||||
|
color: #166534;
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user