style
ci / docker (backend, backend/Dockerfile, backend) (push) Successful in 3m24s
ci / docker (frontend, frontend/Dockerfile, frontend) (push) Successful in 3m43s

This commit is contained in:
2025-10-16 16:15:36 +02:00
parent 41f17733a5
commit 9405f07f06
4 changed files with 43 additions and 17 deletions
+9
View File
@@ -26,6 +26,15 @@ The compose service uses tmpfs storage, giving each test run a clean database.
- `ocrmypdf` (optional but recommended): Used by the OCR worker to extract text from PDFs when no embedded text layer is available. Ensure it is installed and available on the worker hosts if OCR is desired.
- Quickwit (optional): The Quickwit indexer is used to ingest extracted text for search. Set `QUICKWIT_ENDPOINT` and `QUICKWIT_INDEX` in the environment when running workers if you want indexing jobs to run. The local compose file starts a Quickwit instance on `http://localhost:7280` and seeds the `documents` index automatically.
## Configuration
The backend reads its settings from environment variables (see `backend/.env` for local defaults). In particular:
- `DATABASE_URL` connection string for the primary Postgres database (required).
- `DATABASE_MAX_POOL_SIZE` optional override for the r2d2 connection pool size. Defaults to `2`; increase it in staging/production to match expected concurrency.
On startup each binary logs the effective configuration with secrets redacted (for example, the database password is masked). This makes it easier to confirm the runtime settings in staging without exposing credentials.
## Running Migrations in Kubernetes
The backend container image ships the `diesel` CLI, so schema migrations can be executed as a short-lived Job (or Helm hook) before rolling out new pods. Example manifest:
+17 -2
View File
@@ -76,6 +76,7 @@ const DocumentsTable = ({
onDocumentTagDrop,
viewMode = 'list',
onViewModeChange,
onClearSelection,
}) => {
const showingSearchResults = searchResults !== null;
const rows = showingSearchResults ? searchResults : documents;
@@ -213,6 +214,16 @@ const DocumentsTable = ({
[isTagDragEvent, onDocumentTagDrop],
);
const handleGridBackgroundClick = useCallback(
(event) => {
if (event.target !== event.currentTarget) {
return;
}
onClearSelection?.();
},
[onClearSelection],
);
return (
<section
className={`documents-panel column documents-panel--view-${isGridView ? 'grid' : 'list'}`}
@@ -307,7 +318,11 @@ const DocumentsTable = ({
Drop files anywhere or onto a folder to upload documents.
</div>
) : isGridView ? (
<div className="documents-grid" role="list">
<div
className="documents-grid"
role="list"
onClick={handleGridBackgroundClick}
>
{!showingSearchResults &&
subfolders.map((folder) => {
const canDragFolder = folder.id !== 'root';
@@ -353,7 +368,7 @@ const DocumentsTable = ({
}}
>
<div className="folder-card__icon">
<FolderIcon className="icon-inline" size={32} />
<FolderIcon className="folder-card__icon-svg" size={128} />
</div>
<div className="folder-card__meta">
<div className="folder-card__name" title={folder.name}>
+6
View File
@@ -1246,6 +1246,11 @@ const AppLayout = () => {
[handleRowSelection],
);
const clearDocumentSelection = useCallback(() => {
setFocusedRowKey(null);
applySelection([], { anchor: null, interactedKeys: [] });
}, [applySelection]);
const handleFolderRowClick = useCallback(
(folderId, event) => {
const rowKey = resolveFolderRowKey(folderId);
@@ -4522,6 +4527,7 @@ const AppLayout = () => {
onDocumentTagDrop: handleDocumentTagDrop,
viewMode: documentsViewMode,
onViewModeChange: handleDocumentsViewModeChange,
onClearSelection: clearDocumentSelection,
};
const detailPanelProps = {
+10 -14
View File
@@ -1309,7 +1309,7 @@ button.icon-button.ghost:hover:not([disabled]) {
.documents-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(128px, 1fr));
gap: 0.2rem 1.3rem;
gap: 0.3rem 1.3rem;
padding: 0;
}
@@ -1317,7 +1317,7 @@ button.icon-button.ghost:hover:not([disabled]) {
padding: 0rem;
display: flex;
flex-direction: column;
gap: 0.1rem;
gap: 0.3rem;
cursor: pointer;
align-items: center;
text-align: center;
@@ -1364,6 +1364,7 @@ button.icon-button.ghost:hover:not([disabled]) {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
justify-content: center;
}
.folder-card {
@@ -1375,11 +1376,16 @@ button.icon-button.ghost:hover:not([disabled]) {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
aspect-ratio: 1;
width: 128px;
height: 128px;
color: var(--accent);
}
.folder-card__icon-svg {
width: 100%;
height: 100%;
}
.folder-card__meta {
display: flex;
flex-direction: column;
@@ -1396,16 +1402,6 @@ button.icon-button.ghost:hover:not([disabled]) {
white-space: nowrap;
}
.folder-card__badge {
display: inline-flex;
align-self: center;
padding: 0.12rem 0.45rem;
font-size: 0.72rem;
border-radius: 999px;
background: var(--surface-soft);
color: var(--muted);
border: 1px solid var(--border);
}
.doc-name__title {
font-weight: 600;