Edouard Vanbelle
fd103c46e6
feat(manifests-consistency): add safe repair mode
2026-08-23 23:50:39 +02:00
Edouard Vanbelle
06e4df5318
fix(upload): fix race condition in front-end
2026-08-22 08:09:21 +02:00
Edouard Vanbelle
6a11036d96
feat(admin): show active session/users on dashboard
2026-08-21 23:00:49 +02:00
Edouard Vanbelle
2049535516
feat(session): admin UI showing online sessions
2026-08-20 10:42:09 +02:00
Edouard Vanbelle
0d5a726ef4
feat(oidc): explicit rejection reason
...
Show explicitly login rejection (for example when a user does not have a valid
email reported from OIDC but email verification is set)
2026-08-14 13:38:24 +02:00
Edouard Vanbelle
27ab392a35
i18n: fix uncontexted strings
...
For example jobs where translated into "employment" in other language
Remove english text in other languages
2026-08-13 00:30:55 +02:00
Edouard Vanbelle
2be2928ddc
i18n: update all languages
2026-08-09 20:37:09 +02:00
Edouard Vanbelle
a7df46f8f8
feat(sessions): show session origin in admin panel + test
2026-08-09 16:36:42 +02:00
Edouard Vanbelle
321f3ad733
i18n(session): add missing translations
2026-08-09 14:04:42 +02:00
Edouard Vanbelle
bbef8afb7b
fix(logout): reduce unwanted API call during logout
...
- stop trying to refresh session
- display "successfully signed out" rather "your session is expired"
2026-08-09 13:51:35 +02:00
Edouard Vanbelle
9d80bddb5f
feat(oidc): apply i18n
2026-08-08 20:49:09 +02:00
Edouard Vanbelle
21607e3e7f
feat(opaque): improve password change
...
- rebuild the opaque envoloppe
- revoke all other useer's sessions
- send a security email to user
2026-08-06 20:52:17 +02:00
Edouard Vanbelle
015f2da0f7
refactor(backend): normalize naming convention to backend rather storage
...
no ambiguity with the backend rather storage
2026-08-02 14:56:29 +02:00
Edouard Vanbelle
e12d8bcb90
feat(job): show if a job is recverable
2026-08-02 02:40:33 +02:00
Edouard Vanbelle
a58351b7ad
feat(storage key rot): add admin panel
2026-08-02 02:40:33 +02:00
Edouard Vanbelle
bbfb106a32
feat(maintenance): add a maintenance notification during backend migration
2026-08-01 18:46:17 +02:00
Edouard Vanbelle
48cbec8fae
feat(admin ui): too many tabs, change display
2026-07-30 01:14:35 +02:00
Edouard Vanbelle
9fe47a53ea
feat(jobs): add admin call to purge jubs result
2026-07-30 00:39:11 +02:00
Edouard Vanbelle
5527d09618
feat(recoverable-job): check if old blob (no cdc) still remains: notice only
2026-07-29 22:47:07 +02:00
Edouard Vanbelle
0f12399a48
feat(recoverable-job): fix files_consistency to check blob chunk consistency
2026-07-29 22:17:15 +02:00
Edouard Vanbelle
5881968f50
feat(recoverable-job): add progress view
2026-07-29 08:41:19 +02:00
Edouard Vanbelle
4336eca4d1
feat(recoverable-job): add admin page
2026-07-29 08:03:43 +02:00
Edouard Vanbelle
3b31b8911b
feat(breadcrumb): build breadcrumb in 1 API call
...
add /api/folders/{id}/ancestors
this API to iterate parent up to the drive root or the shared folder
this will help UI to build the breadcrumb in 1 API call
and to identify the root element (is it a drive users has access to or
a shared folder ?)
ui: now only 1 API call is now required to build the breadcrumb
2026-07-27 01:33:09 +02:00
Edouard Vanbelle
7718970e75
i18n(search): apply i18n on all filters
2026-07-26 18:18:15 +02:00
Edouard Vanbelle
0cc77f7a36
feat(ui): show a notification if user try to drop a file in another section than /files
2026-07-20 23:19:44 +02:00
Edouard Vanbelle
c286eed3b2
feat(ui): add a dropzone when uploading files from system
...
- add a dropzone on the whole screen
- correct z-index according design system
2026-07-20 23:19:12 +02:00
Edouard Vanbelle
a520afcf7c
feat(ui:items): uploading an item with a swimlane display
...
restore the legacy display with new element uploaded, when swimlane is in place
as the sort is done by server side just add new element in a "new elements" swimlane.
if user continue to scroll down in cursor/pages and item is found from server,
UI remove it from the new element and restore the position
other option: is user refresh it's page, server will restore the natural order
2026-07-20 22:11:00 +02:00
Edouard Vanbelle
5b8fb68b30
feat(items): clarify column names
2026-07-20 21:25:52 +02:00
Edouard Vanbelle
63589e595e
refactor(ui): recent: add a quick button to remove item from recent
2026-07-20 12:26:45 +02:00
Edouard Vanbelle
75ae837f1a
refator(ui): merge of ResourceList part1
2026-07-20 12:26:45 +02:00
Edouard Vanbelle
2a08fe83ae
feat(user): admin can promote external user + security on deletion
...
promotion by admin of external user into internal possible
deletion of a user request admin to enter it's email, this is to prevent any miss click
2026-07-19 16:26:11 +02:00
Edouard Vanbelle
e003a8c55b
feat(admin): display external users for security reasons
2026-07-19 16:26:11 +02:00
Edouard Vanbelle
5982efd783
i18n(drive): correct locales for drive sections
2026-07-19 16:26:07 +02:00
Edouard Vanbelle
05dfda9aa3
feat(drive): add quota update handler
...
per today: admin only can update quota
shared drive can have quota updated (personal drive's quota belong to user's quota)
2026-07-19 16:25:41 +02:00
Edouard Vanbelle
a6427fc028
feat(drive): add readonly policy
...
permmit admin to freeze a drive, trash janitor background job is also disabled for this drive
2026-07-16 01:02:15 +02:00
Edouard Vanbelle
1fa1966fbe
feat(upgrate): add i18n for account upgade
2026-07-14 11:40:16 +02:00
Dionisio Pozo
5ae551a93d
Merge pull request #577 from EdouardVanbelle/feat/users-perfs-and-filter-dotfiles
...
feat: users prefs server side + filter dotfiles + filter shares by resource type
2026-07-14 09:07:09 +02:00
Edouard Vanbelle
e94063d96a
test(login/register): via password or magic-link
...
Password login
┌─────┬────────────────────────────────────────────────────┬────────────────────────┬─────────────────────────────────────────────────────────────────────────────────────────────┐
│ # │ Case │ Where │ Assertion │
├─────┼────────────────────────────────────────────────────┼────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ L1 │ Login by username │ auth_login.hurl Case 1 │ 200 + access_token, user.email match │
├─────┼────────────────────────────────────────────────────┼────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ L2 │ Login by email (dispatch on @) │ auth_login.hurl Case 2 │ 200, same session shape as L1 │
├─────┼────────────────────────────────────────────────────┼────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ L3 │ Bad password on username path │ auth_login.hurl Case 3 │ 403 anti-enum │
├─────┼────────────────────────────────────────────────────┼────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ L4 │ Bad password on email path │ auth_login.hurl Case 4 │ 403 anti-enum (same shape as L3) │
├─────┼────────────────────────────────────────────────────┼────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ L5 │ Unknown username │ auth_login.hurl Case 5 │ 403 anti-enum (same shape as L3) │
├─────┼────────────────────────────────────────────────────┼────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ L6 │ Unknown email │ auth_login.hurl Case 6 │ 403 anti-enum (same shape as L3) │
├─────┼────────────────────────────────────────────────────┼────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ L7 │ /api/auth/oidc/providers reports methods correctly │ auth_login.hurl Case 7 │ password_login_enabled: true, magic_link_login_enabled: true, require_verified_email: false │
└─────┴────────────────────────────────────────────────────┴────────────────────────┴─────────────────────────────────────────────────────────────────────────────────────────────┘
Password registration
┌─────┬───────────────────────────────────────────────────┬──────────────────────────────┬─────────────────────────────────────────────────────────┐
│ # │ Case │ Where │ Assertion │
├─────┼───────────────────────────────────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────────┤
│ R1 │ Classic username + email + password → uniform 200 │ registration.hurl Step 2 │ anti-enum message contains "request received" │
├─────┼───────────────────────────────────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────────┤
│ R2 │ Login after register works │ registration.hurl Step 2b │ 200 + session for the new user │
├─────┼───────────────────────────────────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────────┤
│ R3 │ Email collision → uniform 200 (no rewrite) │ registration.hurl Steps 8-10 │ attacker password doesn't work; original account intact │
├─────┼───────────────────────────────────────────────────┼──────────────────────────────┼────────────────────────────┤
│ R4 │ Username collision → uniform 200 │ registration.hurl Step 11 │ same anti-enum shape │
├─────┼───────────────────────────────────────────────────┼──────────────────────────────┼────────────────────────────┤
│ R5 │ Off-domain rejection │ registration.hurl Step 12 │ 403 RegistrationDomainNotAllowed │
├─────┼───────────────────────────────────────────────────┼──────────────────────────────┼────────────────────────────┤
│ R6 │ Case-insensitive domain match │ registration.hurl Step 12b │ uniform 200 on charlie@EXAMPLE.COM │
└─────┴───────────────────────────────────────────────────┴──────────────────────────────┴────────────────────────────┘
Magic-link registration (email-only signup)
┌─────┬──────────────────────────────────────────────────────────────────────────────────────────────────┬───────────────────────────────────────────────────┐
│ # │ Case │ Where │ Assertion │
├─────┼──────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────┤
│ MR1 │ Email-only signup → welcome mail queued │ registration.hurl Step 3 │ uniform 200 + browser-binding cookie set │
├─────┼──────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────┤
│ MR2 │ Welcome mail contains magic-link URL │ registration.hurl Step 4 │ captured from mock SMTP │
├─────┼──────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────┤
│ MR3 │ PR 22 cross-browser confirmation page │ registration.hurl Step 5a │ 200 HTML "different browser" │
├─────┼──────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────┤
│ MR4 │ Cookie-bound redemption lands on SPA │ registration.hurl Step 5b │ 302 → /files (SvelteKit route, post-migration) │
├─────┼──────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────┤
│ MR5 │ email_verified_at stamped after redemption │ registration.hurl Step 6 │ field present on /api/auth/me │
├─────┼──────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────┤
│ MR6 │ Second magic-link post-signup │ registration.hurl Step 7 │ uniform 200 │
├─────┼──────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────┤
│ MR7 │ Profile PATCH — no-op, name set, empty-string rejected, username-taken 409, claim-once 409, etc. │ registration.hurl Steps 6a–6i │ full profile lifecycle │
└─────┴──────────────────────────────────────────────────────────────────────────────────────────────────┴───────────────────────────────────────────────────┘
Magic-link login (existing account)
┌─────┬──────────────────────────────────────────────────────────┬──────────────────────────────────────┬───────────────────────────────────────┐
│ # │ Case │ Where │ Assertion │
├─────┼──────────────────────────────────────────────────────────┼──────────────────────────────────────┼───────────────────────────────────────┤
│ ML1 │ Baseline password login still works │ auth_magic_link_login.hurl Steps 1-2 │ 200 │
├─────┼──────────────────────────────────────────────────────────┼──────────────────────────────────────┼───────────────────────────────────────┤
│ ML2 │ magic-link/send with email identifier │ auth_magic_link_login.hurl Step 3 │ uniform 200 + cookie │
├─────┼──────────────────────────────────────────────────────────┼──────────────────────────────────────┼───────────────────────────────────────┤
│ ML3 │ magic-link/send with username identifier (dispatch on @) │ auth_magic_link_login.hurl Step 4 │ uniform 200 │
├─────┼──────────────────────────────────────────────────────────┼──────────────────────────────────────┼───────────────────────────────────────┤
│ ML4 │ Password-user policy: mail actually sent │ auth_magic_link_login.hurl Step 5 │ SMTP capture proves permit_magic_link_for_password_users in effect │
├─────┼──────────────────────────────────────────────────────────┼──────────────────────────────────────┼───────────────────────────────────────┤
│ ML5 │ Redemption creates a session │ auth_magic_link_login.hurl Steps 6-7 │ 302 → /files, /api/auth/me returns the same user │
├─────┼──────────────────────────────────────────────────────────┼──────────────────────────────────────┼───────────────────────────────────────┤
│ ML6 │ Anti-enum on unknown identifier │ auth_magic_link_login.hurl Step 8 │ same uniform 200 shape as ML3 │
└─────┴──────────────────────────────────────────────────────────┴──────────────────────────────────────┴───────────────────────────────────────┘
OIDC
┌─────┬────────────────────────────────────────────────────────────────────────┬───────────────────┬────────────────────────────────────────────────────────────────────────────────────────────┐
│ # │ Case │ Where │ Assertion │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O1 │ Setup local admin (bootstrap) │ oidc.hurl Step 1 │ 201 │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O2 │ Providers endpoint — OIDC visible │ oidc.hurl Step 2 │ enabled: true, provider_name: MockSSO, password_login_enabled: true, magic_link_login_enabled: false (OIDC-master rule) │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O2b │ Magic-link/send refused (endpoint layer) │ oidc.hurl Step 2b │ 403 MagicLinkLoginDisabled — proves the policy gate fires, not a 503 SMTP-unwired │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O3 │ Authorize redirect includes PKCE + state │ oidc.hurl Step 3 │ 307 to fake IdP │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O4 │ IdP round-trip + JIT provisioning │ oidc.hurl Step 4 │ Callback lands on /login?oidc_code=… │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O5 │ Code exchange → session cookies │ oidc.hurl Step 5 │ 200 + all three cookies │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O6 │ JIT profile mapping (name, given/family, picture, groups → admin role) │ oidc.hurl Step 6 │ every claim reflected on /api/auth/me │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O7 │ Refresh rotation on OIDC session │ oidc.hurl Step 7 │ new access/refresh/CSRF cookies │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O8 │ Refreshed cookies authenticate │ oidc.hurl Step 8 │ 200 on /api/auth/me │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O9 │ Repeat login = same local user (no dup) │ oidc.hurl Step 9 │ user_id stable │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O10 │ Anti-takeover: unverified email → refused │ oidc.hurl Step 10 │ 401/403 │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O11 │ One-time code replay refused │ oidc.hurl Step 11 │ second /exchange → 401 │
└─────┴────────────────────────────────────────────────────────────────────────┴───────────────────┴────────────────────────────────────────────────────────────────────────────────────────────┘
test
2026-07-14 03:16:25 +02:00
Edouard Vanbelle
5aaf49859e
feat(user-perf): add ui user-perf + dotfile filter
...
- add resource kind filter (file, folder, drive) in shared section (localStorage stored)
- add user preferences serverside store
- add client side dotfile filter (show/hide dotfiles) (user perf stored, default: dotfiles are shown)
for security trashed dotfile are always displayed
protection added: if a folder has only hidden items, a notification invite user to display it
if a user rename or create a hidden item, a notification tells it to user
2026-07-13 20:27:52 +02:00
Edouard Vanbelle
09339ea63f
feat(drive): UI: show policiesto drive's members
...
and add tests
2026-07-01 22:35:08 +02:00
Edouard Vanbelle
26d3c692ba
feat(drive): policies management from UI
2026-06-26 18:57:35 +02:00
DioCrafts
db97a88956
feat(upload): skip unreadable files (FIFOs/sockets) + auto-reload on new deploy
...
Two robustness fixes behind the recurring "folder upload stuck at ~93%" reports.
1. Skip non-regular files up front. A copied s6/runit service tree contains
FIFOs (e.g. supervise/control named pipes) that report a size but BLOCK
FOREVER when the browser reads them — the deterministic ~8-files-short that
no retry/watchdog tweak could fix. uploadBatch/uploadTree now probe each
file's first chunk against a 3 s timeout (partitionReadable), upload only the
readable ones, and report the rest: "N uploaded · M skipped (not regular
files)". Progress runs over the uploadable count, so it reaches 100% instead
of parking at 93% while a lane hangs on a pipe.
2. Auto-reload on a new deploy. svelte.config.js polls _app/version.json
(60 s); the root layout reloads itself when the deployed build changes —
unless an upload is in flight — so an open tab can't keep running stale code
after a rebuild (the recurring "my fix isn't applied" trap).
npm run check: 0 errors, 58 tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-21 12:08:29 +02:00
DioCrafts
6123843dd0
fix
2026-06-20 19:13:18 +02:00
DioCrafts
2ebc4e82b2
feat(upload): batch dedup-check + instant uploads + resilient parallel uploads
...
Backend:
- POST /api/dedup/check-batch — returns the subset of submitted whole-file
BLAKE3 hashes the caller already owns, in one query (user-scoped,
anti-enumeration via idx_files_blob_hash). Lets a client learn which of N
files it can skip with a single round trip.
(dedup_service::user_owned_blob_references, dedup_handler, routes) + tests.
Frontend — upload pipeline:
- Instant ("by-hash") upload for content the caller already owns: hash every
in-band file, ONE /api/dedup/check-batch, create the owned ones with zero
content bytes, upload only the rest. Covers all sizes below the 8 MB delta
threshold (delta handles larger files). vendor/hashWasm computes the
whole-file BLAKE3 on the main thread.
- Resilient parallel uploads: bounded concurrency (4) + a per-file deadline,
so one stuck/slow/failing file no longer freezes the whole batch — it blocks
only its own lane and times out / is skipped while the rest proceed. Quota
exhaustion stops the run early; partial results are reported ("N uploaded,
M failed").
- Folder uploads (uploadTree) show live bell progress + a final result and go
through the same dedup + parallel pipeline.
- Storage bar ("Almacenamiento") refreshes after uploads/deletes
(session.refresh) instead of showing the stale login value.
Frontend — i18n / UI fixes:
- Fix literal {{count}} and {{percentage}}/{{used}}/{{total}} (param-name
mismatches) in the selection toolbar and storage line; add es strings.
- Remove the underline on user-menu link rows.
Benchmark (uploadStrategies.bench.test.ts) compares baseline / per-file / batch:
the batch collapses N per-file probes into one check (e.g. a WAN 1000-file run
drops from 1700 to 1001 round trips) while matching per-file's byte savings.
Also includes in-progress group virtual-description i18n work present in the
working tree (groups.ts, ResourceList, locale `groups` keys).
Verified: cargo clippy -D warnings (clean), backend 448 tests; frontend
npm run check (clean), 58 unit tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-20 17:03:30 +02:00
Claude
5494efea35
feat(frontend): port Places, People & photo tabs to the Svelte app
...
Bring the Photos/People/Places UI that main added (in the legacy vanilla
frontend) into the SvelteKit rewrite, wired to the now-merged backend
(/api/photos/geo, /api/people/*).
Photos page (routes/photos/+page.svelte):
- Moments | Places | People sub-tabs (the People tab appears only when the
faces feature is enabled, via a /api/people capability probe), mirroring
the vanilla photos sub-nav.
- Square ↔ justified layout toggle. Justified uses a Flickr-style
row-packer over the width/height the photos list endpoint returns
(PhotoItem), falling back to 1:1 when dimensions are missing.
New components:
- PhotoLightbox.svelte — the lightbox extracted from the photos page into a
reusable component (items + bindable index, onDelete callback) so the
grid, People and Places all share one implementation (no duplication).
- PlacesMap.svelte — MapLibre GL map with server-clustered markers; the
vector basemap is optional (probed at /basemaps/basemap.pmtiles, themed
fallback otherwise). Cluster click zooms in or opens the lightbox.
- PeopleView.svelte — identity-cluster grid → per-person photo grid, with
rename via the in-app prompt dialog.
Supporting:
- api/endpoints/people.ts (+ peopleEnabled probe); photos.ts gains
fetchPhotosGeo + GeoCluster + PhotoItem; fileThumbnailUrl takes a size.
- lib/vendor/maplibre.ts — minimal typings + lazy loader for the vendored
MapLibre GL + pmtiles globals (kept any-free for ESLint).
- utils/media.ts — shared isVideo / photoTimestamp / minimalPhotoItem.
- Vendored maplibre-gl 5.24.0 + pmtiles 4.4.1 under static/vendors and an
optional static/basemaps dir, matching the PR's vendored-asset pattern.
- New photos.tab_*/layout_*/map_* + people.* keys in en.json.
Verified: npm run check (svelte-check + eslint + stylelint + prettier),
npm run test:unit (36 pass), and npm run build all green.
2026-06-19 12:59:16 +00:00
Bradley Nelson
4ddadb5d0f
update i18n
2026-06-17 22:52:06 -06:00
Bradley Nelson
daa3010458
init new frontend
2026-06-17 17:06:30 -06:00