300 Commits

Author SHA1 Message Date
cjw b7640e9be4 x
CI / changes (push) Has been cancelled
CI / Build (push) Has been cancelled
Deploy Docs / build (push) Has been cancelled
Docker Build and Test / Build and Test Docker Image (push) Has been cancelled
Docker Publish (release, main, dry-run) / Pre-publish Tests (push) Has been cancelled
CI / Frontend — svelte-check, ESLint, Stylelint, Prettier (push) Has been cancelled
CI / Message-bus spec — AsyncAPI + TypeScript DTO drift (push) Has been cancelled
CI / Migration ordering (new migrations postdate target branch) (push) Has been cancelled
CI / Rustfmt (push) Has been cancelled
CI / Clippy (push) Has been cancelled
CI / Wasm — fmt + clippy (push) Has been cancelled
CI / Wasm — release tests (push) Has been cancelled
CI / Plugins — fixtures + runtime tests (push) Has been cancelled
CI / Server Unit and Functionnal Tests (push) Has been cancelled
CI / Security Audit (push) Has been cancelled
CI / API, WebDAV & OIDC tests (push) Has been cancelled
CI / Bundled-assets binary — embed + SPA-serve integration (push) Has been cancelled
CI / WebDAV RFC 4918 — litmus (59/59) (push) Has been cancelled
CI / CalDAV + CardDAV — python-caldav (push) Has been cancelled
CI / Frontend end-to-end tests (via Playwright) (push) Has been cancelled
Deploy Docs / deploy (push) Has been cancelled
Docker Publish (release, main, dry-run) / Build & Push Multi-Arch (push) Has been cancelled
2026-09-20 00:28:52 +08:00
cjw 6ee26e46e6 feat(share): inline media preview on single-file share landing
The public share page only rendered media previews for FOLDER shares —
a single-file share got a bare icon + download button, even for images
and videos the browser can play natively.

Backend: resolve the shared file's mime_type + size at read time and
expose them on ShareDto (meta + password-verify endpoints, one shared
enrichment helper). Display-only enrichment: a failed file lookup
leaves the fields None instead of failing the response — the download
endpoint still surfaces the real error.

Frontend: the 'file' view now reuses the folder grid's lazyVideo
(poster-seek + retry) for video and imageRetry for images, with
Range-aware streaming already provided by /api/s/{token}/file/{id}.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-14 16:00:30 +08:00
Edouard Vanbelle c4dfa9ccf2 feat(notification): enrich notifications, resources are clicable 2026-09-13 01:58:40 +02:00
Edouard Vanbelle ebe467ee92 feat(notification): recover notification since last known event on client resume 2026-09-12 00:16:06 +02:00
Edouard Vanbelle 617ae4b424 feat(notification): add persistent notification 2026-09-11 23:14:44 +02:00
Edouard Vanbelle a6138aa4d9 feat(msg-bus): wire jobs follow up 2026-09-11 22:06:09 +02:00
Edouard Vanbelle 84ea005b65 feat(msg-bus): free WS if tab is not active since 1min 2026-09-11 14:10:29 +02:00
Edouard Vanbelle 8d1fde2747 feat(config + admin panel): handle features activated
- review admin dashboard to reflect features enabled/disabled
- hide mount option if feature is disabled
- remove QUOTA option as it is not wired
2026-09-11 13:18:49 +02:00
Edouard Vanbelle 5083eaeaba feat(config): add server config + can disable message-bus
- server now provide it's config via /api/config (possibility to feature flag)
- client use /api/config to enable / disable some features
- capability to disable the message bus, somme OPS may not want this feature and
  consume persistent connections from server (websocket):
  OXICLOUD_MESSAGEBUS_ENABLE (true by default)
2026-09-11 12:14:50 +02:00
Edouard Vanbelle 758b1e0d6e feat(msg-bus): notify the deleted folder himself
cas where a client is browsing a folder being deleted
2026-09-11 03:31:31 +02:00
Edouard Vanbelle 899bbd13a6 fix(msg-bus): prevent race on reconnect
and update plan
2026-09-11 03:20:45 +02:00
Edouard Vanbelle 41d25d3a3e feat(msg-bus): resubscribe topics on reconnect 2026-09-11 03:13:27 +02:00
Edouard Vanbelle 75a123ae6c feat(msg-bus): add DPoP support, fix floow from client, correct deletion 2026-09-11 03:06:28 +02:00
Edouard Vanbelle 821f76b471 feat(msg-bus): wire message bus on frontend 2026-09-11 00:59:49 +02:00
Edouard Vanbelle ad9eab6f92 refactor(msg-bus): prefer explicit enum on AsyncAPI error 2026-09-11 00:39:11 +02:00
Edouard Vanbelle 7918fff47b refactor(msg-bus): prefer MessageBus as Realtime 2026-09-11 00:28:04 +02:00
Edouard Vanbelle 1d280c161c feat(asyncapi): generate ts types according asyncapi 2026-09-10 21:50:15 +02:00
Edouard Vanbelle 3b8a1828d8 fix(mounts): add owner in admin panel
- add missing owner on personal drive
- review syle
- apply i18n
2026-09-08 20:12:44 +02:00
Edouard Vanbelle 0cdb2bb0a9 fix(admin): separate a job's run STATE from its OUTCOME
Ed's diagnosis, and it is the root of three symptoms I had been patching
one at a time: a job has two independent statuses, and the panel was
collapsing them into one column.

  * STATE — where the run is in its lifecycle: running, paused,
    cancelled, completed, failed.
  * OUTCOME — how the work turned out: ok, issues, notices, err.

They are orthogonal. A paused run has no outcome yet. A completed run's
outcome may still be "issues". Conflating them produced, in order:

  1. a paused migration rendering as a green "ok" — the outcome was
     genuinely ok, the STATE was Paused, and only the outcome was shown;
  2. my first fix, which put "blocked" into the OUTCOME column — a
     category error, encoding lifecycle into the result axis;
  3. a cancelled job still reading "blocked", because that outcome was
     cached in memory while the cancel had flipped the row in SQL.

The layout already had both columns. State just never rendered anything
but "running" or "—", so the status axis had no home and the information
leaked into Outcome.

Now:

  * State renders `last_run_status`, sourced from the run ROW. Memory
    cannot answer this — it is empty after a restart and stale after a
    cancel, both of which the row gets right. The retryable reason, when
    there is one, is the pill's tooltip.
  * Outcome goes back to describing only the work: ok / issues /
    notices / err. No lifecycle in it.

`JobSummary` gains `last_run_status`, and `last_run_at` falls back to
the row's `started_at` when memory has none — a restart left the column
reading "never" for a job whose last run was hours earlier.

"never" is now reserved for jobs that genuinely never ran. With a run
row present but no cached outcome the cell reads "—": the honest "no
outcome recorded", rather than a claim the run history immediately
contradicts.

The enrichment query generalises rather than multiplying — it already
fetched Paused rows for the Resume button, so it now takes the latest
row per job via `DISTINCT ON` and derives state, timestamp and paused
brief from it. Sound as "the current run" because the
`one_active_run_per_job` partial unique index permits one non-terminal
row per job and a resume reuses it, so a non-terminal row is always
newest.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 06:23:25 +02:00
Edouard Vanbelle efb9723787 fix(admin): a run blocked on an unreachable backend must not render "ok"
Ed pointed a broken S3 entry at 127.0.0.1 with nothing listening. The
classification worked end to end — the run paused with
`target backend init: Transient Backend: … ConnectionRefused` — but the
job row showed a green **ok** pill and the reason was only visible after
expanding it.

`PausedRetryable` reports `outcome: 'ok'` on the wire, and that is
correct: the run did not fail, and a Resume continues it. But rendering
it as plain "ok" hides the one thing worth acting on. A paused
`backend_migration` is still holding `migration_readonly` and refusing
writes across the whole application, presented as a healthy job.

The row now reads **blocked**, in amber, with the reason as the pill's
title so it is legible without unfolding anything.

Amber rather than red, deliberately: nothing is broken and no data was
lost — the run is waiting for the backend to return. Red reads as "this
job is failing" and invites a Cancel, which for a migration also
discards the copy already done and is the one action that cannot be
undone.

Checked BEFORE the findings branches, too. A run that never finished has
nothing meaningful to say about findings, and "0 issues" on an aborted
scan is a worse answer than "blocked".

`JobOutcome.extra` was typed `unknown`, so the panel could not read the
`retryable` flag the backend already sends. Now a narrow
`JobOutcomeExtra` exposing just the three keys that describe the RUN's
shape rather than its work — the rest stay per-job counters nothing
generic should switch on.

Same class of defect as the known "Ok despite findings" issue: an
outcome that looks like success while hiding the state an operator needs
to see.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 06:23:25 +02:00
Edouard Vanbelle a4101743e0 feat(jobs): jobs declare their own run parameters
`JobRunArgs` was a fixed struct — `force`, `deep`, `storage`, `repair` —
and six places hardcoded that same list: the engine's persist/restore,
the trigger endpoint's query type, the OXICLOUD_STARTUP_JOBS parser, the
frontend API wrapper, the panel's checkboxes, and `StartupTrigger` on
the wire.

Two costs. Adding a parameter meant editing all six, and forgetting one
dropped it silently — most damagingly in persist/restore, where a
resumed run lost it and a `?repair=true` migration came back as
discovery-only after a restart. And the panel offered the same knobs on
every job: only two jobs read `deep`, six read `repair`, so most of
those controls did nothing with no way to tell which.

Now `JobHandler::parameters()` returns `&'static [JobParam]` — name,
type (boolean/string/number), default, and the job's own description of
what it does. `JobRunArgs` holds a map keyed by those names.

Everything reads the declaration:

* `run_or_resume` iterates it to persist and restore, replacing
  `const FLAGS` plus a `storage` special case. `storage` stops being
  special — it was the one Option<String> among three bools.
* `dispatch` normalises every run against it, which is what makes "a
  handler sees its declared parameters with their declared defaults"
  true rather than usual. The periodic tick passes an empty
  `JobRunArgs::default()`, so a `default: true` parameter would
  otherwise read false on every scheduled run.
* The trigger endpoint takes free-form query params and rejects
  undeclared ones with a 400 naming the real set, instead of ignoring
  them.
* OXICLOUD_STARTUP_JOBS keeps raw pairs (config is parsed before the
  registry exists) and validates at dispatch, where the error can name
  the job's actual parameters. Still a boot panic, same as an unknown
  job name — a typo'd `?repare=true` must not leave a migration
  importing forever in discovery mode.
* `JobSummary.parameters` carries it to the panel, whose `supportsDeep`
  was a hardcoded name allowlist (`consistency_batch ||
  backend_consistency`). A job gaining a deep mode needed a frontend
  release; one losing it left a button that silently did nothing. The
  menu now renders from the declaration, so a newly-declared boolean
  appears with no frontend change.

Three consistency tenants were hand-rolling persist-on-fresh /
restore-on-resume for their own flag, under the same `params` key the
engine already used. Deleted — they read `args.get_bool(…)` now.

Fresh runs also filter to the declaration. `consistency_batch` forwards
its args verbatim to sub-jobs, so a tenant's `params` row could grow
`deep` with no deep mode, and the run-detail view would claim a mode the
job never had.

Two things found while wiring it, both worth knowing:

`RecoverableAdapter` bridges the two traits, and `parameters` has to be
forwarded there or the registry sees `&[]`. Both traits have defaults,
so omitting it compiled cleanly — and the trigger endpoint then rejected
`?repair=true` on the very jobs that declare it, with
OXICLOUD_STARTUP_JOBS panicking at boot. Now covered by
`adapter_forwards_job_metadata_from_inner_handler`.

`TriggerJobQuery` was briefly a newtype over the map. `serde_urlencoded`
cannot deserialize a newtype struct at the top level, so axum's `Query`
rejected EVERY trigger with a 400 — even one with no query string —
before the handler ran. It reads exactly like the new validation
rejecting something, which sent the first diagnosis to the wrong layer.
Now covered by `trigger_query_extracts_from_every_url_shape`.

Wire names are a compatibility surface: `params` rows are keyed by them
and the panel switches on them, so a rename breaks existing run history
the same way renaming a `Mutates` variant does. The JSON shape is pinned
in `snapshot_carries_job_metadata`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 22:23:30 +02:00
Dionisio Pozo ff286f8159 Merge pull request #713 from BCNelson/fix/685-drive-scoped-external-mounts 2026-09-07 21:38:49 +02:00
Dionisio Pozo 8984eeec89 Merge pull request #715 from Xalares/french_translation 2026-09-07 21:38:35 +02:00
Xalares c78db6ec6c Merge branch 'main' into french_translation 2026-09-07 15:07:36 +02:00
xalares 7abb66c19f Miscellaneous french translation corrections 2026-09-07 15:02:48 +02:00
Bradley Nelson 39a5ef4fad fix(mounts): scope external mounts to drives 2026-09-07 00:28:23 -06:00
Edouard Vanbelle 289f408d23 perf(dpop): collapse the SW nonce stampede to a single challenge
A DPoP proof carries a server-issued nonce. With none cached the server
answers `401 use_dpop_nonce`, the client harvests the nonce and retries.
`signAndFetch` already absorbed that, so it was invisible — but it did
it PER REQUEST, with no coordination.

The Service Worker always cold-starts without a nonce. Both mechanisms
that pre-seed the page are unreachable from worker scope: workers have
no `sessionStorage`, and `seedNonceFromCookie` early-returns on
`typeof document === 'undefined'`. The SW also skips requests that
already carry a `DPoP` header, so it never observes the page's
responses and cannot harvest from them either. Browsers terminate idle
workers after ~30s, so this happens routinely, not once.

Uncoordinated, every request issued in that window discovers the nonce
independently: N parallel requests → N challenges → 2N requests. That is
precisely the photo grid, and serving `<img src>` is the SW's main job —
those requests cannot sign themselves, which is why the worker exists.
Each wasted challenge also costs the server a full ECDSA P-256 verify,
because `verify_proof` runs before the nonce check.

Now the first request through owns the discovery and the rest await it,
so N challenges collapse to 1. The wait is capped (5s) and released in a
`finally`: the SW is on the critical path for every thumbnail, so a hung
discovery must degrade to the old behaviour rather than stall the grid
behind a promise that never settles.

Measured on a 763-line e2e server log: 115 `dpop.nonce_challenged`
events across 97 logins.

Scope, deliberately: this does NOT remove the one challenge per worker
lifetime. Doing that needs the nonce persisted where a worker can read
it — IndexedDB already holds the keypair — and it can never replace the
challenge path anyway, since a persisted nonce can be stale. Left out
until the audit line shows it is worth it; the numbers above are now
legible enough to tell.

Not a correctness fix. Nothing was broken and no test failed over this.
The argument is waste, plus signal: 115 challenges per run is noise that
would bury a real one.

`hasNonce()` is exported for the gate — deliberately "will the next
proof carry a nonce", not "is it still valid", since only the server
knows the latter and the challenge path already handles it. Its tests
assert it agrees with what `buildDpopProof` actually emits, because a
wrong answer either reinstates the stampede or stalls every request
behind a bootstrap that is not happening.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-05 23:01:53 +02:00
Edouard Vanbelle ce4354f497 fix(thumbnails): neither import job may tear down the shared directory
Found on a sandbox restore. `thumb_derived_import` ran first, imported
and deleted its own hash-named sidecars, then found `remove_dir` refused
because the `ext-*.jpg` previews were still there — those belong to
`thumb_attached_import`. The rename fallback fired, moving the tree to
`.thumbnails.migrated`; the attached job then looked in `.thumbnails/`,
found nothing, and reported zeros.

That stranded the user-uploaded previews, which are the one class of
file here with no render path to rebuild them. The rename exists for
files NEITHER job claims — a `.DS_Store` blocking removal forever — and
it fired for the sibling's work in progress instead. Inverting the job
order does not help: once the tree is renamed, both jobs look at
`.thumbnails/` and find nothing, whatever order they run in.

Teardown is now shared and refuses to act while anything remains that
either job would claim. Both jobs call it, so whichever finishes last
removes the tree in the same boot rather than leaving an empty
directory until the next one. The rename survives for its original
purpose, and now only fires when the remaining files are genuinely
nobody's.

Also drops the daily tick on both imports — they are on-demand now. The
boot run in repair mode IS the migration: nothing has written a sidecar
since step 10d2, so the tail cannot grow afterwards, and a tick could
not finish the job anyway because ticks never pass `repair`. Once
drained it was a `read_dir` returning nothing, every day, forever.

UX: the "at boot" badge moves from beside the job name into the cadence
column. It answers WHEN a job runs, which is what that column is for —
next to the name it read as a property of the job, and the row could
show "on-demand" beside a badge saying otherwise.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-30 16:19:45 +02:00
Edouard Vanbelle 577ecb7cef feat(jobs): run the thumbnail migration at startup, by default
A migration nobody triggers never finishes. Scheduled ticks deliberately
never pass `repair`, so a deployment whose operator never opens the
admin panel re-imported the same sidecars forever and never drained the
directory — and relying on operators to edit `.env` has the same failure
mode one level up.

`OXICLOUD_STARTUP_JOBS` dispatches named jobs once, in the background,
after the scheduler is ready. Entries use the syntax operators already
type at the trigger URL (`name?repair=true`), so the value is literally
the request they would otherwise make by hand. It defaults to both
migration jobs in repair mode, so an untouched deployment migrates and
drains itself.

That is a destructive default and a real exception to
no-silent-auto-repair, so the guard it rests on had to get stronger:
`verify_and_unlink` now compares CONTENT, not length. A blob of the
right size and the wrong bytes used to pass — a key-mapping bug handing
back another file's preview at the same length would have deleted the
original and kept the impostor, and thumbnails cluster tightly enough in
size for that to be a real coincidence. The readback streams from the
backend with no cache in front, so it proves durability rather than that
a write was acknowledged.

Deletion of `.thumbnails/` is attempted first and only falls back to
renaming it `.thumbnails.migrated` when `remove_dir` refuses because a
non-sidecar file is inside (Finder's `.DS_Store`). Either way the
directory stops existing, which lets the read-path probe go back to a
single `stat` on the root instead of walking the size directories.

Validation is fail-fast: an unknown job name or flag panics at boot. A
silently dropped `?repare=true` would leave the job in discovery-only
mode while the operator believed the tier was draining, surfacing months
later as "the migration never finished" with nothing pointing at the
config line.

Interrupted runs resume. Boot recovery flips abandoned rows to Paused
with their cursor, so `run_or_resume` continues rather than rescanning —
a long migration completes across however many restarts it takes. That
is a scoped exception to "we do not auto-resume": here somebody did ask,
in configuration, and not having to ask again is the point.

`StartupJob` holds a `JobRunArgs` rather than re-listing its four
fields, so a fifth flag cannot be added to the scheduler and silently
ignored in configuration.

Jobs named here are ordinary registered jobs — visible in the panel,
triggerable by hand, same runs and findings. Their rows now carry a
`startup` object so an operator can see that a job deletes on every boot
rather than only when someone clicks Run.

Adds docs/config/thumbnail-migration.md: what runs on first boot, how to
snapshot database and storage together beforehand, and how to verify
afterwards with satellites_consistency plus backend_consistency
?deep=true.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-30 13:41:05 +02:00
Edouard Vanbelle f1f327a6c4 refactor(consistency): blobs_consistency reads only the database
`blobs_consistency` probed `blob_exists` once per row and, under
`?deep=true`, read and re-hashed every blob. `backend_consistency`
already reports the same `blob_missing_from_backend` from its
merge-join — so the probe was duplicated work that found strictly less
(a DB walk cannot see backend-only orphans by construction) at N round
-trips instead of one enumeration. Every scheduled sweep paid for it.

All three physical checks move to `backend_consistency`:

* `blob_missing_from_backend` was already there; the duplicate is gone.
* `blob_corrupted` / `blob_unreadable` hook the matched arm of the
  merge-join, which holds exactly the key pairs worth reading. Guarded
  by `in_range` so a pair past the horizon is not read twice, and
  `params.deep` is persisted on a fresh run and read back on resume so
  a paused deep scan does not silently continue shallow.

Deep mode belongs there because it is backend work end to end: the
only DB input is the hash. Keeping it in `blobs_consistency` forced
that tenant to carry a backend for one flag.

What remains is the half that needs no backend: `refcount_mismatch`
and its repair. The constructor drops from five parameters to two —
no backend, no storage_entries, no storage_path_fallback — and
`?storage=<name>` / `?deep=true` are now inert there, which the
job description says outright.

`affected_files` is needed by both tenants, so it moves to a shared
`blob_diagnostics` module rather than being copied.
`PROBED_STORAGE_PARAM` moves to `backend_consistency`: it was defined
in `blobs_consistency` and re-exported, which is backwards once the
DB-only tenant has no entry to scope. The create-grace window goes
with the probe — it existed to avoid flagging a blob whose bytes had
landed before its row, and the refcount comparison reads one
consistent snapshot.

Known cost: `backend_consistency` returns `backend_unenumerable` on
Azure and mid-migration, so on those configs missing bytes now go
unreported where the per-row probe caught them. That argues for the
Azure enumeration impl, not for keeping the probe.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-30 13:41:05 +02:00
Edouard Vanbelle 1ea3826660 feat(jobs): jobs describe themselves — description, mutates, repair_description
The admin panel had no repair toggle wired to anything but a hardcoded
name list naming the two refcount tenants, so `thumb_derived_import` and
`thumb_attached_import` could not be run in repair mode from the UI at
all despite supporting it. And nothing in the job list said what any
given job does or whether clicking Run on production writes anything.

Three defaulted methods on `JobHandler` and `RecoverableJobHandler`:

    fn description(&self) -> &'static str
    fn mutates(&self) -> Mutates          // Never | Always | OnRepairOnly
    fn repair_description(&self) -> Option<&'static str>

`RecoverableAdapter` forwards them — the registry only holds
`dyn JobHandler`, so a tenant's metadata is invisible otherwise, and
falling back to the defaults would report every recoverable job as
read-only, including the ones that delete files.

Three values rather than a boolean because a job can be read-only by
default and destructive under `?repair=true`; a boolean answers wrongly
for one of its two modes, and `false` on something that unlinks files is
the dangerous direction to be wrong in. `repair_description` returning
`Option` collapses "does it repair" and "what does repair do" into one
method: presence gates the toggle, content is the confirmation text —
which the frontend cannot invent, since correcting a counter and
deleting sidecars are not the same warning.

`OnRepairOnly` with no `repair_description` is rejected at registration:
it claims to mutate only under a flag it does not support.

All 17 registered jobs declare all three. The panel now renders the
description under each name, badges read-only jobs, confirms before a
plain run of a mutating one, and offers the repair variant off the
backend flag instead of the name list.

Descriptions are English in the trait, next to the behaviour: one in
`locales/*.json` rots invisibly the moment a job changes, and a
translator cannot know what `manifests_consistency` reconciles. i18n can
layer on later keyed by job name with these as the fallback.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-30 13:41:05 +02:00
Edouard Vanbelle fd103c46e6 feat(manifests-consistency): add safe repair mode 2026-08-23 23:50:39 +02:00
Edouard Vanbelle d57400f7d3 fix(i18n): fix too literal translation with jobs 2026-08-23 13:27:09 +02:00
Edouard Vanbelle 06e4df5318 fix(upload): fix race condition in front-end 2026-08-22 08:09:21 +02:00
Edouard Vanbelle 537e7f15ef fix(users): /api/admin/users always returns a FullUserDto[] 2026-08-22 00:14:37 +02:00
Edouard Vanbelle a8fa281a02 refactor(user): apply chanoges to hurl tests 2026-08-21 23:56:25 +02:00
Edouard Vanbelle c583b26355 refactor(user): apply change on update entries 2026-08-21 23:18:23 +02:00
Edouard Vanbelle 6a11036d96 feat(admin): show active session/users on dashboard 2026-08-21 23:00:49 +02:00
Edouard Vanbelle 117815ef4d feat(user): show if user is online 2026-08-21 19:34:11 +02:00
Edouard Vanbelle ec9b5087f3 refactor(User): apply changes on frontend 2026-08-21 17:10:02 +02:00
xalares 915d13ad8e Miscellaneous french translation corrections 2026-08-20 19:27:32 +02:00
Edouard Vanbelle 543a1a88eb feat(admin > users): UI: correct oidc badge 2026-08-20 10:42:09 +02:00
Edouard Vanbelle 2049535516 feat(session): admin UI showing online sessions 2026-08-20 10:42:09 +02:00
Edouard Vanbelle 1a8306f3db feat(login): prevent login form flash on OIDC callback 2026-08-14 13:38:24 +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 4154019603 feat(ui:delta): add heartbeat on worker 2026-08-14 01:52:53 +02:00
Edouard Vanbelle 24da7443de feat(ui delta upload): user can change oxi.UPLOAD_BATCH_BYTES
```
oxi.UPLOAD_BATCH_BYTES // get current value
oxi.UPLOAD_BATCH_BYTES=1024*1024 // change values
```

values are stored in localstorage, default is 8*1024*1024
2026-08-14 01:52:14 +02:00
Edouard Vanbelle ef465b99de feat(ui:delta upload): warn user if refresh page during upload 2026-08-14 01:52:14 +02:00
Edouard Vanbelle e357caf43b feat(ui): add a logger to change log level and permit better diagnostics
in this case logs are added in the delta upload worker

you can increase verbosity from console via:

```javascript
// Usage:
oxi.setLogLevel('oxi:upload', 'debug')    // deep dive
oxi.setLogLevel('oxi:upload', 'warn')     // quiet
oxi.log.setLevel('debug')                  // everything to debug
```

values are stored in localstorage

example:

```
oxi.setLogLevel('oxi:upload', 'debug');
'oxi:upload → debug'
deltaUpload.ts:120 [f32ce1] delta start {file: 'Revue de presseg.odp', size: 23658927}
deltaUpload.ts:185 [f32ce1] worker: worker start {file: 'Revue de presseg.odp', size: 23658927}
deltaUpload.ts:186 [f32ce1] worker: wasm loaded
deltaUpload.ts:186 [f32ce1] worker: hashed — blake3=150edbc35f3475a0bdeecd15fe5278dda305ce3b27e53e9c40515a69f0b883f3 (76 chunks)
deltaUpload.ts:186 [f32ce1] worker: negotiate: 76 hashes → 76 missing, 0 dedup'd
deltaUpload.ts:186 [f32ce1] worker: chunk PUT: 28 chunks, 8825338 bytes
deltaUpload.ts:186 [f32ce1] worker: chunk PUT: 29 chunks, 8950230 bytes
deltaUpload.ts:186 [f32ce1] worker: chunk PUT: 19 chunks, 5883663 bytes
deltaUpload.ts:186 [f32ce1] worker: ✅ committed — uploaded 23 658 927 B (no dedup, blake3=150edbc35f3475a0bdeecd15fe5278dda305ce3b27e53e9c40515a69f0b883f3)
deltaUpload.ts:185 [f32ce1] worker: commit HTTP 201 {blake3: '150edbc35f3475a0bdeecd15fe5278dda305ce3b27e53e9c40515a69f0b883f3', uploadedBytes: 23658927, reusedBytes: 0, totalBytes: 23658927, attempt: 0}
deltaUpload.ts:213 [f32ce1] delta done {file: 'Revue de presseg.odp', blake3: '150edbc35f3475a0bdeecd15fe5278dda305ce3b27e53e9c40515a69f0b883f3', savedBytes: 0, uploadedBytes: 23658927}
```
2026-08-14 01:52:03 +02:00
Dionisio Pozo b1493e2d0e Merge pull request #671 from EdouardVanbelle/i18n/context-translation 2026-08-13 16:58:42 +02:00