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
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
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
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
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
6a11036d96
feat(admin): show active session/users on dashboard
2026-08-21 23:00:49 +02:00
xalares
915d13ad8e
Miscellaneous french translation corrections
2026-08-20 19:27:32 +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
5a034d390e
i18n: apply missing translation on context menu
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
35f4984463
i18n: add missing key in admin > users section
2026-07-19 16:26:11 +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
moduvoice
a3801f5836
i18n: complete Korean (ko) locale
...
The ko.json locale existed but was missing 377 of 1392 keys (~27%),
covering entire feature areas added since the initial translation:
admin plugin management, storage/OIDC/SMTP settings, photos, music
playlists, device pairing, search filters, share dialogs, and more.
Filled in all missing keys with natural Korean translations matching
the existing tone and terminology in the file (파일/폴더/공유/업로드
등), verified full key parity with en.json (1392/1392) and matching
{{placeholder}} interpolation tokens on every translated string.
2026-07-11 23:10:35 +07:00