main
Satur@it-depot.ru 2025-03-06 16:21:59 +03:00
parent 06c2feb619
commit 2a2dc6a30d
5 changed files with 94 additions and 4 deletions

View File

@ -6,6 +6,10 @@ RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
COPY templates/ ./templates/
# Создаем и копируем папку uploads (если нужно предзаполнение)
RUN mkdir -p /app/uploads
COPY uploads/ ./uploads/
# Экспонируем оба порта
EXPOSE 8001 8002

36
app.py
View File

@ -7,6 +7,9 @@ import markdown
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse, StreamingResponse
import io
import os
from fastapi.middleware.cors import CORSMiddleware
from werkzeug.utils import secure_filename
# Создаем два экземпляра FastAPI
web_app = FastAPI(title="Web Interface") # Для веб-интерфейса (порт 8001)
@ -59,6 +62,15 @@ if not unsorted_folder:
else:
unsorted_folder_id = unsorted_folder[0]
# Папка для загрузки изображений
UPLOAD_FOLDER = "/app/uploads"
if not os.path.exists(UPLOAD_FOLDER):
os.makedirs(UPLOAD_FOLDER)
# Монтируем папку uploads для доступа к изображениям
api_app.mount("/uploads", StaticFiles(directory=UPLOAD_FOLDER), name="uploads")
web_app.mount("/uploads", StaticFiles(directory=UPLOAD_FOLDER), name="uploads")
# Модели данных
class Folder(BaseModel):
name: str
@ -275,8 +287,30 @@ async def import_csv(file: UploadFile = File(...), folder_id: int | None = Query
except Exception as e:
raise HTTPException(status_code=400, detail=f"Ошибка импорта: {str(e)}")
# Новый эндпоинт для загрузки изображений
@api_app.post("/api/upload-image")
async def upload_image(install_id: int = Query(..., description="ID записи для ассоциации изображения"), file: UploadFile = File(...)):
if not file.filename:
raise HTTPException(status_code=400, detail="No file provided")
# Проверка расширения файла
allowed_extensions = {'png', 'jpg', 'jpeg', 'gif'}
if not secure_filename(file.filename).rsplit('.', 1)[1].lower() in allowed_extensions:
raise HTTPException(status_code=400, detail="Invalid file format. Use png, jpg, jpeg, or gif")
# Генерация безопасного имени файла
filename = secure_filename(f"{install_id}_{file.filename}")
file_path = os.path.join(UPLOAD_FOLDER, filename)
# Сохранение файла
with open(file_path, "wb") as buffer:
buffer.write(await file.read())
# Возвращаем URL изображения
url = f"/uploads/{filename}"
return {"url": url}
# CORS для API
from fastapi.middleware.cors import CORSMiddleware
api_app.add_middleware(
CORSMiddleware,
allow_origins=["http://10.0.0.10:8001", "http://localhost:8001"],

View File

@ -7,4 +7,5 @@ services:
- "8002:8002" # API (доступ для POST-запросов)
volumes:
- ./db:/db
- ./uploads:/app/uploads # Монтируем папку uploads
restart: unless-stopped

View File

@ -1,4 +1,5 @@
fastapi
uvicorn
python-multipart
markdown
markdown
werkzeug

View File

@ -267,6 +267,10 @@
background: #222;
}
#notes-content { margin-top: 10px; }
#notes-content img {
max-width: 100%; /* Ограничение ширины изображений */
height: auto;
}
#note-modal {
display: none;
position: fixed;
@ -293,6 +297,12 @@
color: #e0e0e0;
border: 1px solid #555;
}
#note-modal .image-upload {
margin-bottom: 10px;
}
#note-modal .image-upload input {
margin-right: 10px;
}
#note-modal button { margin-right: 10px; }
#note-modal.dark-theme button {
background: #007bff;
@ -407,6 +417,10 @@
<button onclick="formatItalic()">Курсив</button>
<button onclick="formatLink()">Ссылка</button>
</div>
<div class="image-upload">
<input type="file" id="image-upload-input" accept="image/*" onchange="uploadImage()">
<button onclick="document.getElementById('image-upload-input').click()">Добавить изображение</button>
</div>
<textarea id="note-textarea" placeholder="Введите заметку (Markdown поддерживается)"></textarea>
<button onclick="saveNote()">Сохранить</button>
<button onclick="closeNoteModal()">Закрыть</button>
@ -963,6 +977,41 @@
$('#note-textarea').val(currentNote || ''); // Устанавливаем текущую заметку или пустую строку
$('#note-modal').show();
$('#modal-overlay').show();
document.getElementById('image-upload-input').value = ''; // Сбрасываем поле загрузки
}
function uploadImage() {
const fileInput = document.getElementById('image-upload-input');
const file = fileInput.files[0];
if (!file) return;
const formData = new FormData();
formData.append('image', file);
formData.append('install_id', selectedInstallId);
$.ajax({
url: `${API_URL}/upload-image`,
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: function (response) {
console.log('Image uploaded:', response);
const imageUrl = response.url; // Предполагаем, что сервер возвращает { url: "path/to/image" }
const textarea = $('#note-textarea');
const cursorPos = textarea[0].selectionStart;
const text = textarea.val();
const altText = prompt('Введите альтернативный текст для изображения:', file.name);
const markdownImage = `![${altText || 'image'}](${imageUrl})`;
textarea.val(text.substring(0, cursorPos) + markdownImage + text.substring(cursorPos));
textarea[0].selectionStart = cursorPos + markdownImage.length;
textarea[0].selectionEnd = cursorPos + markdownImage.length;
},
error: function (xhr, status, error) {
console.error('Error uploading image:', status, error);
alert(`Не удалось загрузить изображение: ${xhr.responseJSON?.detail || error}`);
}
});
}
function saveNote() {
@ -1002,13 +1051,14 @@
$('#modal-overlay').hide();
$('#note-textarea').val(''); // Очищаем текстовое поле
selectedInstallId = null; // Сбрасываем выбранный ID
document.getElementById('image-upload-input').value = ''; // Сбрасываем поле загрузки
}
function updateNotesPanel() {
if (selectedInstallId) {
const install = allInstalls.find(i => i.id === selectedInstallId);
if (install && install.note) {
$('#notes-content').html(marked.parse(install.note));
$('#notes-content').html(marked.parse(install.note)); // Рендеринг Markdown с изображениями
} else {
$('#notes-content').html('<p>Нет заметок</p>');
}
@ -1056,7 +1106,7 @@
function formatItalic() {
const textarea = $('#note-textarea');
const start = textarea[0].selectionStart;
const end = textarea[0].selectionEnd;
end = textarea[0].selectionEnd;
const text = textarea.val();
const selected = text.substring(start, end);
const newText = `*${selected}*`;