From d9a36a99a7f5e83876be2ec3fc2c172da626abed Mon Sep 17 00:00:00 2001 From: Nils Schneider Date: Sun, 12 Oct 2025 01:59:26 +0200 Subject: [PATCH] dockerfiles, TagsWorkspace --- backend/Dockerfile | 48 +++++ frontend/Dockerfile | 19 ++ frontend/nginx.conf | 11 ++ frontend/src/index.jsx | 412 ++++++++++++++++++---------------------- frontend/src/styles.css | 10 +- 5 files changed, 271 insertions(+), 229 deletions(-) create mode 100644 backend/Dockerfile create mode 100644 frontend/Dockerfile create mode 100644 frontend/nginx.conf diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..153e474 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,48 @@ +# syntax=docker/dockerfile:1 + +FROM rust:1-slim AS builder +WORKDIR /app + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + build-essential \ + pkg-config \ + libssl-dev \ + libpq-dev \ + libjpeg-dev \ + libpng-dev \ + && rm -rf /var/lib/apt/lists/* + +COPY Cargo.toml Cargo.lock ./ +COPY src ./src +COPY migrations ./migrations +COPY tests ./tests +COPY diesel.toml ./ + +RUN cargo build --release --bin paperless-backend --bin worker +RUN cargo install diesel_cli --no-default-features --features postgres + +FROM debian:bookworm-slim AS runtime +WORKDIR /app + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates \ + libssl3 \ + libpq5 \ + libjpeg62-turbo \ + libpng16-16 \ + && rm -rf /var/lib/apt/lists/* \ + && useradd --system --create-home --uid 10001 appuser + +COPY --from=builder /app/target/release/paperless-backend /usr/local/bin/paperless-backend +COPY --from=builder /app/target/release/worker /usr/local/bin/paperless-worker +COPY --from=builder /usr/local/cargo/bin/diesel /usr/local/bin/diesel +COPY migrations ./migrations +COPY diesel.toml ./ + +ENV RUST_LOG=info +USER appuser +EXPOSE 3000 + +ENTRYPOINT ["/usr/local/bin/paperless-backend"] diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..e38281d --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,19 @@ +# syntax=docker/dockerfile:1 + +FROM node:20-alpine AS build +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci --no-audit --no-fund + +COPY . . +RUN npm run build + +FROM nginx:alpine +WORKDIR /usr/share/nginx/html + +COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=build /app/dist ./ + +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..9ba98f1 --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,11 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri /index.html; + } +} diff --git a/frontend/src/index.jsx b/frontend/src/index.jsx index 3a7c5d9..39ac048 100644 --- a/frontend/src/index.jsx +++ b/frontend/src/index.jsx @@ -4,6 +4,7 @@ import React, { useMemo, useRef, useState, + useContext, } from 'react'; import { createRoot } from 'react-dom/client'; import axios from 'axios'; @@ -12,8 +13,11 @@ import { Navigate, Route, Routes, + Outlet, + useLocation, + useMatch, useNavigate, - useParams, + matchPath, } from 'react-router-dom'; import './styles.css'; @@ -1174,7 +1178,13 @@ const Sidebar = ({ onFolderDragStart, onFolderDragEnd, draggedFolderId, + onShowTags, + tags = [], }) => { + const handleShowTags = onShowTags || (() => {}); + const tagsRouteMatch = useMatch('/tags'); + const isTagsRoute = Boolean(tagsRouteMatch); + const renderNodes = useCallback( (ids, depth) => ids.map((id) => { @@ -1249,8 +1259,8 @@ const Sidebar = ({ @@ -1282,6 +1292,17 @@ const TagsPanel = ({ tags, onRefresh, onUpdateTag }) => { setError(null); }, []); + const colorPickerValue = useMemo(() => { + if (!draftColor) { + return '#3366ff'; + } + const match = HEX_COLOR_PATTERN.exec(draftColor.trim()); + if (!match) { + return '#3366ff'; + } + return `#${match[1].toLowerCase()}`; + }, [draftColor]); + const handleSave = useCallback(async () => { if (!editingId) return; @@ -1391,13 +1412,12 @@ const TagsPanel = ({ tags, onRefresh, onUpdateTag }) => { {isEditing ? (
setDraftColor(event.target.value)} - onKeyDown={handleKeyDown} - placeholder="#3366ff" - spellCheck="false" disabled={saving} + aria-label="Pick tag color" /> {draftColor && ( - -
- - -
- {previewActive && previewWorkspaceDocument ? ( - - ) : ( - <> - - { - const selection = selectedDocumentIds.includes(documentId) - ? selectedDocumentIds - : [documentId]; - if (!selectedDocumentIds.includes(documentId)) { - applySelection([documentId], { - anchor: documentId, - interactedIds: [documentId], - }); - } - setDraggedDocumentIds(selection); - event.dataTransfer.effectAllowed = 'move'; - try { - event.dataTransfer.setData( - 'application/x-paperless-doc-list', - JSON.stringify(selection), - ); - } catch ( - // eslint-disable-next-line no-empty - error - ) {} - event.currentTarget.classList.add('dragging'); - }} - onDocumentDragEnd={(event) => { - setDraggedDocumentIds([]); - event.currentTarget.classList.remove('dragging'); - }} - filterBar={filterBar} - /> - - - )} -
- - ) : ( - - )} - + const contextValue = useMemo( + () => ({ + token, + status, + dropOverlayState, + handleBulkReanalyze, + handleLogout, + sidebarProps, + tags, + refreshTags, + handleTagUpdate, + previewActive, + previewWorkspaceDocument, + previewWorkspaceDetail, + previewWorkspaceEntry, + closeDocumentPreview, + handleThumbnailRegeneration, + documentsTableProps, + detailPanelProps, + }), + [ + token, + status, + dropOverlayState, + handleBulkReanalyze, + handleLogout, + sidebarProps, + tags, + refreshTags, + handleTagUpdate, + previewActive, + previewWorkspaceDocument, + previewWorkspaceDetail, + previewWorkspaceEntry, + closeDocumentPreview, + handleThumbnailRegeneration, + documentsTableProps, + detailPanelProps, + ], ); -} -const RoutedDocumentsApp = () => { - const navigate = useNavigate(); - const params = useParams(); - const folderId = params.folderId ?? null; - const documentId = params.documentId ?? null; + if (!token) { + return ( +
+ +
+ ); + } return ( - + +
+ +
+
+
+

Paperless-NEO

+ + {previewActive + ? 'Viewing document preview. Press ← Back to return to the library.' + : 'Drag files here to upload.'} + +
+ {status && ( +
+ +
+ )} +
+ + +
+
+
+ +
+
); }; -const RoutedTagsApp = () => { - const navigate = useNavigate(); - return ; +const useAppShell = () => { + const context = useContext(AppShellContext); + if (!context) { + throw new Error('AppShellContext not found. Ensure routes are nested under AppLayout.'); + } + return context; +}; + +const DocumentsRoute = () => { + const { + sidebarProps, + previewActive, + previewWorkspaceDocument, + previewWorkspaceDetail, + previewWorkspaceEntry, + closeDocumentPreview, + handleThumbnailRegeneration, + documentsTableProps, + detailPanelProps, + } = useAppShell(); + + if (previewActive && previewWorkspaceDocument) { + return ( +
+ +
+ ); + } + + return ( + + + + + ); +}; + +const TagsRoute = () => { + const { sidebarProps, tags, refreshTags, handleTagUpdate } = useAppShell(); + return ( + + + + ); }; const AppRouter = () => ( - } /> - } /> - } /> - } /> - } - /> - } /> - } /> + }> + } /> + } /> + } /> + } /> + } + /> + } /> + } /> + ); diff --git a/frontend/src/styles.css b/frontend/src/styles.css index bfe0fa0..6f80a24 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -361,9 +361,13 @@ button.icon-button.ghost:hover:not([disabled]) { gap: 0.4rem; } -.tags-table__color-field { - width: 7rem; - font-family: var(--monospace, 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace); +.tags-table__color-picker { + width: 2.25rem; + height: 2.25rem; + padding: 0; + border: none; + background: none; + cursor: pointer; } .tags-table__edit-controls {