mirror of
https://github.com/ether/etherpad-lite.git
synced 2026-07-18 00:57:55 +00:00
* fix(admin/pads): apply filter chip server-side, before pagination
Before: PadPage's filter chip (`active`/`recent`/`empty`/`stale`) ran
on the client AFTER the 12-row page slice was already on screen. On a
deployment with hundreds of pads it produced obviously wrong results
— click "empty pads" on page 1 with 100 empties and only the 0–12
empties within the current page passed the filter. thm reported this
on a 3.1.0 deployment.
Move the filter into `PadSearchQuery` so the `/settings` socket can
apply it before slicing:
1. pattern filter on names (cheap)
2. hydrate metadata for the matching pad universe iff a non-`all`
filter is set or a non-`padName` sort is requested
3. apply filter chip on the hydrated set
4. sort + slice → `total` reflects the filtered universe so the
pagination footer makes sense
The original handler also had a 4-way `if/else if` that duplicated the
hydrate-and-sort loop per `sortBy`. Folded those into one pipeline
with a single comparator switch.
Client side, `PadPage.tsx`:
- drop the client-side `filteredResults` filter (server already filters)
- chip click writes `filter` into searchParams (debounced refetch) and
resets `currentPage` to 0
- older clients that don't send `filter` keep working — server defaults
to `all`
Stats cards (totalUsers/activeCount/emptyCount) still count the visible
page only — that's a pre-existing UI limitation tracked separately.
Closes the regression thm reported.
Test plan
- `tsc --noEmit` clean (server + admin)
- New backend spec `padLoadFilter.ts` exercises filter:empty with
small `limit` to lock in the bug-fix, plus all/active/omitted cases
- `5 passing` locally on Node 25
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* address Qodo review on #7798
1. Functional setState updaters for every searchParams mutation
(Qodo bug 1). The debounced pattern handler captured a render-time
snapshot of searchParams; a faster chip click or sort change in
between would be silently reverted when the debounce fired. Now
every mutation merges against the latest state.
2. Concurrency-limited hydration (Qodo bug 3). The earlier draft
issued Promise.all over the full candidate set, fanning out to
thousands of in-flight padManager.getPad() reads on busy
deployments. New mapWithConcurrency() caps concurrent loads at 16
— empirically enough to saturate a single ueberDB driver without
pushing the event loop into back-pressure.
3. Test cleanup deletes the injected test-admin (Qodo bug 4). The
original snapshot/restore pattern saved `settings.users` by
reference; reassigning the same reference in after() left the
inserted key in place and could leak into later backend specs.
4. Document the new `filter` field on the `padLoad` socket query in
admin/README.md (Qodo rule violation 2).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
88 lines
4.4 KiB
Markdown
88 lines
4.4 KiB
Markdown
# Admin UI
|
|
|
|
Vite + React 19 single-page app served at `/admin`. Talks to the backend over
|
|
socket.io for the existing settings / plugins / pads pages, and (when
|
|
endpoints are added to the OpenAPI spec) over a typed REST client.
|
|
|
|
## Scripts
|
|
|
|
| Script | What it does |
|
|
| -------------------- | -------------------------------------------------------- |
|
|
| `pnpm dev` | `gen:api` + Vite dev server (expects backend on :9001). |
|
|
| `pnpm gen:api` | Regenerates `src/api/{schema.d.ts,version.ts}` from the OpenAPI spec. |
|
|
| `pnpm build` | `gen:api` + `tsc` + `vite build`. |
|
|
| `pnpm build-copy` | Same, but writes into `../src/templates/admin`. |
|
|
| `pnpm test` | `gen:api` + smoke tests for the API client wiring. |
|
|
| `pnpm lint` | ESLint. |
|
|
|
|
## Typed API client
|
|
|
|
The admin uses [`openapi-typescript`] to generate types from
|
|
`src/node/hooks/express/openapi.ts`, [`openapi-fetch`] for typed requests, and
|
|
[`openapi-react-query`] for TanStack Query bindings.
|
|
|
|
[`openapi-typescript`]: https://github.com/openapi-ts/openapi-typescript
|
|
[`openapi-fetch`]: https://github.com/openapi-ts/openapi-typescript/tree/main/packages/openapi-fetch
|
|
[`openapi-react-query`]: https://github.com/openapi-ts/openapi-typescript/tree/main/packages/openapi-react-query
|
|
|
|
### Generated files
|
|
|
|
`admin/src/api/schema.d.ts` and `admin/src/api/version.ts` are generated by
|
|
`gen:api` and gitignored — never commit them. They are produced by:
|
|
|
|
```sh
|
|
pnpm --filter admin gen:api
|
|
```
|
|
|
|
`admin/scripts/gen-api.mjs` loads `src/node/hooks/express/openapi.ts`, calls
|
|
`generateDefinitionForVersion` for the latest API version, pipes the JSON
|
|
through `openapi-typescript` to produce `schema.d.ts`, and emits a runtime
|
|
constant `LATEST_API_VERSION` (read from `info.version` in the spec) to
|
|
`version.ts` so `client.ts` can build the right `/api/<version>/` baseUrl.
|
|
|
|
`gen:api` runs as the first step of `dev`, `build`, `build-copy`, and
|
|
`test`, so a fresh checkout produces the generated files automatically when
|
|
any of those scripts is invoked. After modifying any of the following, the
|
|
next `pnpm <dev|build|test>` will refresh the generated files; you can also
|
|
run `gen:api` directly:
|
|
|
|
- `src/node/hooks/express/openapi.ts`
|
|
- `src/node/handler/APIHandler.ts` (changes to `latestApiVersion`)
|
|
- the resource definitions referenced by `openapi.ts`
|
|
|
|
### Using the client
|
|
|
|
```tsx
|
|
import { $api } from './api/client';
|
|
|
|
const SettingsPanel = () => {
|
|
const { data } = $api.useQuery('get', '/admin/settings'); // example
|
|
return <pre>{JSON.stringify(data, null, 2)}</pre>;
|
|
};
|
|
```
|
|
|
|
The admin endpoints are not yet present in the OpenAPI spec — this client is
|
|
in place to support upcoming work (see issue #7638 follow-up). For now, it is
|
|
exercised only by the smoke test.
|
|
|
|
## Socket.io: `padLoad` query shape
|
|
|
|
The admin `/settings` namespace's `padLoad` event accepts a `PadSearchQuery`
|
|
defined in `src/node/types/PadSearchQuery.ts`:
|
|
|
|
| field | type | required | notes |
|
|
| ------------ | ----------------------------------------------------------------- | -------- | ----- |
|
|
| `pattern` | `string` | yes | Substring match on pad name. |
|
|
| `offset` | `number` | yes | Pagination start, in items. Clamped server-side. |
|
|
| `limit` | `number` | yes | Page size. Capped at 12. |
|
|
| `ascending` | `boolean` | yes | Sort direction. |
|
|
| `sortBy` | `"padName" \| "lastEdited" \| "userCount" \| "revisionNumber"` | yes | Column to sort by. |
|
|
| `filter` | `"all" \| "active" \| "recent" \| "empty" \| "stale"` *(opt.)* | no | Filter chip; defaults to `"all"`. Applied **before** pagination so `total` and the page slice both reflect the filtered universe. Older clients that omit the field get the unchanged `"all"` behaviour. |
|
|
|
|
Filter semantics — applied after pattern matching, before sort + slice:
|
|
|
|
- `active`: `userCount > 0`
|
|
- `recent`: edited within the last 7 days
|
|
- `empty`: `revisionNumber === 0`
|
|
- `stale`: not edited in the last 365 days
|
|
- `all` / missing: no further filtering
|