Skip to content

PMM-15299 Open Manager - #5872

Draft
ademidoff wants to merge 56 commits into
mainfrom
PMM-15299-open-manager
Draft

PMM-15299 Open Manager#5872
ademidoff wants to merge 56 commits into
mainfrom
PMM-15299-open-manager

Conversation

@ademidoff

Copy link
Copy Markdown
Member

Ticket number: PMM-15299

Feature build: SUBMODULES-0

If this PR adds, removes or alters one or more API endpoints, please review and update the relevant API documentation as well:

  • API Docs updated

If this PR is related to other PRs, contributions, or ongoing work in this or other repositories, please reference them here:

  • Links to related work items (optional).

ademidoff and others added 30 commits July 27, 2026 02:23
Add an opt-in integration that lets SEP, running in a side container on a
shared Docker bridge network, use PMM's embedded PostgreSQL for its
persistence layer.

When PMM_ENABLE_SEP is set, the entrypoint appends marker-delimited blocks to
postgresql.conf (listen_addresses) and pg_hba.conf (one scram-sha-256 rule per
attached Docker subnet, scoped to the sep database and role), then provisions a
non-superuser sep role owning a dedicated sep database. Nothing is published on
the host, and the postgres, pmm-managed and grafana accounts remain unreachable
over the network. Unsetting the variable reverts the configuration on the next
start and leaves the role and database intact.

The postgres data directory, password file and binary directory are now
declared once in the entrypoint and passed to the helper scripts via a
subshell-scoped export, replacing the /usr/pgsql-14 literals that were
duplicated across them.
Replaces yarn 1 with pnpm 11 across the ui/ workspace and swaps
ESLint/Prettier for oxlint/oxfmt, then brings the runtime libraries up to
the versions the workspace is pinned against.

Toolchain:
- pnpm-workspace.yaml replaces the yarn "workspaces"/"resolutions" block;
  yarn.lock is dropped for pnpm-lock.yaml. Overrides, packageExtensions
  and allowBuilds carry over the constraints yarn resolutions encoded.
- oxlintrc.json / .oxfmtrc.json replace the per-package .eslintrc and
  .prettierrc files; every package's lint/format scripts point at them.
- tsconfig.base.json centralizes the compiler options each package used
  to repeat.

Libraries: React 18 -> 19, React Router 6 -> 7, Vite 5 -> 8, Vitest 2 -> 4,
TypeScript 5 -> 6, @testing-library/react 15 -> 16, jsdom 24 -> 29,
@percona/percona-ui 1.0.23 -> 1.0.24.

Two dependencies that yarn's flat node_modules provided implicitly are now
declared, since pnpm's isolated store does not hoist them:
material-react-table (apps/pmm) and @jest/globals (apps/pmm-compat).

vitest.config.ts drops the hardcoded ../../node_modules React aliases,
which do not exist under pnpm, in favour of resolve.dedupe.

Build and dev environments learn pnpm via corepack, which resolves the
version from the packageManager field in ui/package.json so it is pinned
in exactly one place: the rpmbuild images, the devcontainer setup and the
UI CI workflow. yarn stays installed for dashboards/pmm-app, which is
still a yarn 1 project.

Signed-off-by: Ignacio Durand <nachodurand@gmail.com>
- ui/tsconfig.json extends tsconfig.base.json instead of reaching into
  packages/shared/tsconfig.json. The old target also dragged in that
  package's `include: ["src"]`, which is meaningless at the workspace root.
- @pmm/shared: `private` was the string "true" rather than a boolean, so
  npm's schema validation ignores it; and `typesVersions` pointed at
  ./src/index.tsx, which does not exist (the entry is index.ts). Consumers
  resolve through `exports`, so the stale block is dropped rather than
  repointed.
- ui/Makefile declares its command targets .PHONY so a stray file or
  directory named after one cannot make GNU Make skip the recipe.

Signed-off-by: Ignacio Durand <nachodurand@gmail.com>
Oxlint enables only `eslint`, `typescript`, `unicorn` and `oxc` by default —
`--react-plugin` and `--import-plugin` are opt-in, and the config-file
equivalent is the `plugins` array. The migration carried the rule list over
from the ESLint config without it, so every `react/*`, `react-hooks/*` and
`import/*` rule in `oxlintrc.json` was silently inert: oxlint parses them,
matches no plugin, and reports nothing. Verified against oxlint 1.76 with a
fixture that violates `jsx-key`, `rules-of-hooks` and `exhaustive-deps` —
zero diagnostics before, all three after.

`plugins` replaces the default set rather than extending it, so the list
names typescript, unicorn and oxc as well.

Turning the rules on surfaced one real error, now fixed:
`import(no-duplicates)` in `pages/updates/Updates.tsx`, which imported
`@mui/material` twice.

Also from the review:

- `$schema` pointed at the Oxc `main` branch while the workspace pins oxlint
  1.76.0, so editor validation could drift from the CLI. Now the
  package-local schema, which always matches the installed version.
- `*.config.ts` / `*.config.js` were excluded from linting, which hid real
  code — `vite.config.ts` carries dev-server and proxy logic. The patterns
  are gone and `pmm-compat` lints its package root, not just `src`, so its
  `webpack.config.ts` is covered too. `apps/pmm-compat/.config/` stays
  ignored: it is Grafana's auto-generated plugin scaffold, which upstream
  regenerates and marks "not intended to be changed".
- That new coverage caught a latent bug in `packages/shared/jest.config.js`:
  the transform key `'^.+\.tsx?$'` is a string, so `\.` collapsed to `.` and
  the pattern matched any character where a literal dot was meant. Escaped
  properly.

The guides were still describing the pre-migration toolchain, so they are
brought in line: `ui/AGENTS.md` (Yarn workspaces -> pnpm, Yarn prerequisite ->
Corepack, plus a Linting and Formatting section covering oxlint/oxfmt, the
`plugins` pitfall above, and what is in scope), the root `AGENTS.md` linting
rows (ESLint -> oxlint + oxfmt, and `make format-check` alongside `make
lint`), and one stale "Node 22 + Yarn" line in `ui/README.md`.

Gates: oxlint 0 errors, oxfmt clean, check-types clean, tests pass
(@pmm/shared 11, pmm-compat 4 suites, ui 330).

Signed-off-by: Ignacio Durand <nachodurand@gmail.com>
The linting and testing decision trees named only `ui/apps/pmm` and
`ui/packages/shared`, so a contributor touching `ui/apps/pmm-compat` could
read the rows as not applying to them — while `make lint` and `make test`
have always run Turborepo across every workspace package, and pmm-compat's
lint scope just widened to its package root.

Signed-off-by: Ignacio Durand <nachodurand@gmail.com>
Brings SEP's frontend packages into ui/ and mounts the migrated plugins as
native PMM routes, so SEP surfaces render inside the PMM shell instead of an
iframe. Builds on PMM-15288, which moved the workspace to pnpm and the
library versions SEP's code targets.

Packages, ported from SEP's frontend workspace:
- packages/sep/api       — typed API client, generated OpenAPI surfaces, hooks
- packages/sep/framework  — schema-driven form/list/task components
- packages/sep/shared     — shared primitives
- packages/plugins/atw    — Collect Diagnostic Data (ATW)

SEP's "app" vocabulary is renamed to "plugin" throughout the port, since "app"
already means a workspace app in ui/: SchemaDrivenApp -> SchemaDrivenPlugin,
useAppSchema -> usePluginSchema, useAppTasks -> usePluginTasks, app-schema.ts
-> plugin-schema.ts. Ported files also carry PMM's AGPL header.

Wiring in apps/pmm:
- router.tsx mounts the plugins under their own routes; SepPage gives them the
  standard PMM Page chrome (padding, width, auth gate, footer).
- navigation gates the SEP entries behind admin + the inventory settings flag.
- main.tsx calls initSepAuth, which points SEP's axios client at PMM's session.
  Auth is still the interim Option D: the dev proxy injects SEP_INTERNAL_TOKEN
  server-side, so no token reaches the browser.
- vite.config.ts proxies SEP's paths (/api, /sep_app, /stream-logs,
  /execution-events, /files) to SEP_BACKEND_URL, and lets PMM_SERVER_URL
  override the PMM target.
- A SyntaxHighlighter component backs the schema renderer's script/JSON fields.

Page gains maxWidth so SEP pages can opt into the full-width container from
@percona/percona-ui; Settings.tsx moves to it in place of the removed
fullWidth flag.

*.tsbuildinfo is gitignored; one had been committed by accident.

Signed-off-by: Ignacio Durand <nachodurand@gmail.com>
Catches the migrated packages up with SEP's frontend, which moved on after
the initial port. Ported commit by commit rather than by copying files, so
PMM's app -> plugin rename and license headers survive.

- SEP-1629 / SEP-1684 / SEP-1689 / SEP-1696 / SEP-1668: refresh the generated
  OpenAPI surface (specs/*.json + src/generated/*.ts) from SEP head. Two
  schema components are now namespaced — ConnectivityWarning and
  TaskExecuteWrite became framework__ConnectivityWarning and
  framework__TaskExecuteWrite — so their consumers move with them.
- SEP-1663: honor HostRef/HostField `allow_custom`. HostField passes it to
  HostSelector, which renders FreeSoloSelect instead of the closed
  AutoCompleteInput and commits a scalar id/string (including from cascade
  auto-select). FreeSoloSelect resolves a stored string against option ids,
  not just labels, so string host ids like "nomad-1" display as their option.
  SEP's multi-host half (FreeSoloMultiSelect, MultiHostField) is not ported —
  PMM's snapshot has no multi-host selector to extend.
- SEP-1653: hide the task-history Download files button unless the files API
  returns a non-empty listing. `has_logs` was the wrong signal: logs exist
  even when the output dir holds only the hidden .sep-run-result.json marker,
  which left a dead download action. Probes are cached for 30s so the history
  table's poll loop does not re-hit the files API every tick.
- SEP-1692: add postSession / postSessionExchange to @sep/api. The exchange
  endpoint trades PMM's session cookie for a short-lived SEP bearer, which is
  what replaces the interim SEP_INTERNAL_TOKEN wiring — that token's service
  principal hardcodes is_admin = False and so 403s every admin-gated surface.
  Only the client surface lands here; flipping bootstrap.ts over to it needs
  a SEP backend carrying the endpoint and is left to its own change.

Signed-off-by: Ignacio Durand <nachodurand@gmail.com>
The interim SEP dev proxy could never see SEP_INTERNAL_TOKEN, so every
call to the SEP backend from the migrated pages returned 401.

Two independent faults:

turbo.json declared no passThroughEnv, and Turbo 2.x defaults to
envMode strict, which strips undeclared variables before spawning a
task. vite was therefore started without the variable no matter how it
was exported. Declare the three variables vite.config.ts reads.

Vite exposes .env files to client code as import.meta.env but never to
the config file's own process.env, so the only working setup was an
export in the exact shell launching the dev server, and anything else
fell back to the defaults silently. Load the files explicitly with
loadEnv, keeping real environment variables ahead of file values so CI
and the devcontainer are unaffected.

PMM_SERVER_URL was broken the same way and is fixed by the same change.

Signed-off-by: Ignacio Durand <nachodurand@gmail.com>
…ch-up

SEP moved on again after the previous sync. Of the eleven commits touching
the mirrored packages, nine were already carried; the two that were not both
ship frontend code. The rest are spec/generated-only or belong to apps PMM has
not migrated.

- SEP-1666: page-scoped select-all on the ATW Results pane. The header toggle
  reuses `isSelectable`, so it can never disagree with the row checkboxes about
  which rows are eligible, and deselecting removes only the current page's ids
  — the selection deliberately outlives a page flip.
- SEP-1631: make a cascading RemoteChoices field usable with `allow_custom`.
  Free-text entry no longer needs the parent (or a successful fetch) before it
  is typable, a typed value survives the parent being set afterwards, and
  `required` now rejects whitespace-only input. SchemaFormRenderer defaults a
  `remote_choice` field to `null` rather than `''`, which the backend's
  NonEmptyStr rejects.

One deviation from SEP: SEP-1666's test asserts `aria-checked="mixed"` on the
select-all toggle, which MUI only emits after 7.3.7 — the version this repo's
lockfile resolves, against SEP's 7.3.11. The helper here also accepts the
`data-indeterminate` attribute MUI documents for 7.3.7, so the case covers the
same state on both. PMM-15296 tracks bumping MUI and dropping the shim.

Gates (node 22): @sep/plugins-atw 55 and @sep/framework 610 tests pass,
check-types clean across framework/atw/ui, oxfmt clean tree-wide.
Correctness / stability:
- client.ts: isolate a throwing _onRefreshed so a successful cookie
  rotation is not reported as a failed refresh.
- SchemaFormRenderer: gate a section-violation submit before
  react-hook-form runs, so the unsaved-changes guard stays armed.
- PluginTaskEditPage: normalize choice defaults through getAtPath /
  setAtPath so dotted one-of field names are covered.
- PluginListPage / PluginDetailPage: guard the optional list_view before
  dereferencing it on an unresolved entity route.
- SchemaListView: treat undefined cell values like null (em dash).
- HostSelector: path-aware error lookup for dotted field names.
- extractId: accept only decimal integers.
- validationMapper: reject non-finite and fractional numeric input
  instead of silently truncating it.
- useTaskLogs: keep stepless log lines (step: '') instead of dropping them.
- useExecutionEvents: bound transient stream retries and surface the
  failure instead of reconnecting forever in the loading state.
- StandaloneHostSelector: stay enabled on a failed hosts query so onOpen
  can retry.
- ScheduledTasksPanel: preserve kwargs on an enable/disable toggle.
- useCascadingField: use '' as the form's empty-value sentinel.

Contract / conventions:
- SepPage: enforce the PMM-admin gate at the route wrapper, not only in
  the navigation.
- SchemaDrivenPlugin: pass capabilities / submitError / fieldErrors to
  the renderEditForm slot, matching PluginTaskEditPage.
- Share SEP route constants between the router and the nav builder.
- Rename the dev-only proxy variables to PMM_DEV_SEP_BACKEND_URL and
  PMM_DEV_SEP_INTERNAL_TOKEN.
- SchemaListView: mode-aware opaque table surface instead of common.white.
- FileField: give the file picker button an accessible name.
- useResolvedServiceField: expose the services fetch error.
- Delete the orphaned framework test/setup.ts (no afterEach(cleanup)).

Signed-off-by: Ignacio Durand <nachodurand@gmail.com>
- extractId: hold the numeric branch to the same bar as the string one.
  `Number.isSafeInteger` rejects 1.5 and unsafe integers, which previously
  passed straight through and enabled a service lookup nothing can satisfy.
- ScheduledTasksPanel: the preserved `kwargs` now accepts either wire
  shape. `PeriodicTaskResponse` does not declare the field, so a decoded
  object was as likely as a JSON string, and only the string case was
  kept — the object case fell back to '{}' and wiped the arguments it was
  added to protect.
- client.ts: log when the injected `_onRefreshed` handler throws. The
  isolation is right, but swallowing it silently left the auth layer
  without the rotated token while this function returned it as applied,
  with no diagnostic. The trace carries neither token nor expiry.
- SchemaFormRenderer test: restore the `removeEventListener` spy through
  `onTestFinished` so a failing assertion cannot leak it into the rest of
  the file.

Two fixes of the same class as the reviewed ones, found while porting
this to SEP:

- HostSelector had the same wedge as StandaloneHostSelector: both its
  free-solo and standard branches disabled the control on a hosts-query
  failure while `onOpen` held the only `refetch()` trigger, so one failure
  blocked recovery until the page remounted.
- SchemaListView.formatCellValue now renders an absent `date` / `relative`
  value as an empty cell instead of an em dash, adopting SEP's variant of
  this guard: an em dash for a task that has never run reads as a
  placeholder for a time that exists. Keeps the mirrored file aligned with
  its source.

Declined, with reasons on the PR: removing `ctrl.abort()` from
useExecutionEvents' terminal path (the library disposes on the signal we
pass — its own controller is separate — and every other terminal path here
aborts the same way), and binding the ATW ResultsPane fixture to
ATW_PAGE_SIZE (trivial, and that file mirrors SEP's).

Gates: check-types clean, oxlint 0 errors, 1105 tests pass, oxfmt clean.

Signed-off-by: Ignacio Durand <nachodurand@gmail.com>
`Number('   ')` is 0, so a numeric field containing only whitespace passed
the validate rule and `coerceFormValues` submitted 0 for a value the user
never typed. RHF's built-in `required` rule does not fire on it either,
since the string is non-empty.

Both the validate rule and the coercion now trim string input and treat a
trimmed-empty string as empty: required fields report the required error,
optional fields serialise as absent.

Ported from SEP-1760 (percona/SEP#1283, 4bf7adcc4), where a Copilot review
caught it on the same code.

Signed-off-by: Ignacio Durand <nachodurand@gmail.com>
`refreshAccessToken()` hardcoded `POST /oauth/refresh` as the only way to
obtain a token. An embedded host that owns the session — PMM — has no
refresh cookie, so every recovery attempt would 401 there.

`setTokenMinter()` replaces just that call; the default is unchanged, so
the standalone SPA behaves exactly as before. Everything downstream is
minter-agnostic already: the single-flight coalescer, the axios 401
retry, and the `setOnRefreshed` notification.

Two supporting changes:

The 401 retry now skips `/oauth/session*` as well as `/oauth/refresh`.
Minting is single-flighted, so routing a mint's own 401 back through the
retry interceptor would hand it the very promise it is running inside —
an await on itself that never settles. The unauthorized handler still
fires for those endpoints: a rejected exchange means "not signed in" and
the auth layer needs to hear it.

The openapi-fetch transport gained the 401 retry the axios one already
had; it previously only reported unauthorized, so typed hooks could not
recover at all. `fetch` consumes a Request's body, so the middleware
stashes a clone taken before dispatch and replays that. The replay goes
through raw `fetch` so it cannot re-enter the middleware and loop.

Signed-off-by: Ignacio Durand <nachodurand@gmail.com>
The embedded SEP UI authenticated as SEP's internal service principal:
the token provider returned null and the proxy injected
PMM_DEV_SEP_INTERNAL_TOKEN server-side. That principal hardcodes
`is_admin = False`, so every admin-gated SEP surface answered 403.

It now authenticates as the actual PMM user. `sepTokenStore` exchanges
the ambient `pmm_session` cookie for a short-lived SEP bearer via
`POST /api/oauth/session/exchange` (SEP-1692) and holds it in memory
only — no localStorage, no sessionStorage, no query cache. It renews 30s
ahead of the 5-minute expiry, and the transports' 401 retry covers the
case where a throttled background tab misses that window. Concurrency is
delegated to `refreshAccessToken()`, so a burst of parallel SEP requests
triggers one exchange.

A 401 from the exchange itself is sticky: minting is refused until the
user retries, so a rejected session cannot drive an exchange loop.

`SepAuthGate` triggers the first exchange when a SEP route mounts rather
than at app startup — the UI has no PMM_ENABLE_SEP flag, so an eager
exchange would hit SEP on every page load for every PMM user. It also
closes a race the provider cannot: `setTokenProvider` is synchronous, so
a plugin's first queries would otherwise fire before the exchange
resolved.

The dev proxy no longer injects the internal token on `/api/oauth/*`.
Overwriting Authorization there would authenticate the exchange as the
service principal and mask whether the cookie path works at all.
Retiring the injection entirely is a follow-up.

Signed-off-by: Ignacio Durand <nachodurand@gmail.com>
`onRequest` cloned every outbound Request so a 401 could be replayed,
including the minting and login endpoints that `onResponse` explicitly
excludes from the retry. Cloning buffers the body, and those clones were
never going to be used.

Both call sites now share one `isReplayEligible` predicate, so the clone
and the retry cannot drift apart.

Raised by Copilot on #5739.

Signed-off-by: Ignacio Durand <nachodurand@gmail.com>
Reworks how the store reports failure, against the updated ACs. Two
rules now shape it, and they pull in opposite directions.

Fail closed. Every exchange failure drops the bearer, so no request can
proceed on a stale, expired, or unverified credential, and there is no
cached value to fall back on. A session SEP has rejected stays sticky:
minting is refused outright until the user retries, so a rejection can
never drive an exchange loop.

Never destroy user work. The failure now lands at one of two altitudes.
Before a bearer has ever been held the page does not exist yet, so a
bootstrap failure takes the page over — there is nothing to preserve.
Once mounted the page stays mounted and the failure becomes an inline
notice beside it. Previously a background renewal being rejected moved
the phase to `signedOut`, which unmounted the plugin and threw away
whatever was half-typed into it.

The two are reconciled by keeping the bearer and the reporting separate:
`failClosed` always drops the credential, then chooses between a phase
change and a notice based on whether the page is up.

A renewal that fails for a reason that may not repeat is now retried
quietly with backoff — 2s, 4s, 8s, 16s — and only surfaces if all four
attempts fail. A 401 skips the backoff: the session is genuinely gone
and retrying would only repeat the rejection, so the user is told at
once, non-destructively, that submissions from this page will fail.

`getSepAuthStatus()` is replaced by `getSepAuthState()`, returning a
cached `{ phase, notice }` snapshot so `useSyncExternalStore` does not
re-render subscribers on a no-op. The old `error` phase is renamed
`unreachable`, matching the notice of the same name.

Signed-off-by: Ignacio Durand <nachodurand@gmail.com>
pmm-server's nginx exposes the SEP side-car under a single /sep location
(PMM-15279) rather than the five top-level prefixes the dev proxy forwarded.
Introduce SEP_BASE_PATH in @sep/api as the one browser-facing mount point and
route the axios client, the openapi-fetch clients, and the file/log/event hooks
through it; forward only /sep from the dev server, unstripped, since SEP serves
the prefix itself via root_path.

Drop /sep_app, which matched no SEP route.

Signed-off-by: yyyyyyy <contact@yyyyyyyan.tech>
The base moved the SEP side-car behind a single `/sep` mount point
(SEP_BASE_PATH, PMM-15279), so the session exchange and everything the
401 retry matches on had to move with it.

Conflicts were textual, not behavioural — `client.ts` and `index.ts`
auto-merged with both the minter seam and SEP_BASE_PATH intact:

- `typed-client.ts` / both test files: import-block collisions, kept both
  sides.
- `bootstrap.ts`: the base edited the old doc comment that described the
  token exchange as future work. This branch is that work, so its text
  wins, with the base's `/sep/api/...` path correction applied.
- `vite.config.ts`: the base collapsed five proxied prefixes into `/sep`,
  forwarded unstripped. Both comment blocks kept, and `isSepAuthPath` now
  matches `/sep/api/oauth/` — the prefix is still on the URL when the
  proxy sees it, so the old `/api/oauth/` test silently stopped matching
  and would have let the internal token cover the exchange again.
- Tests: reworked my handlers onto the base's new `API` constant, which
  now carries the prefix.

Neither transport's mint guard needed changing: axios matches on a URL
relative to `baseURL`, and the typed client's `includes('/oauth/session')`
is unaffected by a prefix.

Signed-off-by: Ignacio Durand <nachodurand@gmail.com>
The proxy forwarded `/sep` unstripped on the grounds that SEP serves the
prefix itself via `root_path`. It does not: SEP carries no root_path
support at all - no flag, no setting, no `FastAPI(root_path=...)`, and
none on the shipped side-car's `python -m app.sep.main`. So both ways of
running it locally answer 404 to everything the proxy forwards.
`python -m app.main` serves at `/api/...`, and `uvicorn --root-path /sep`
prepends root_path to the path, so it sees `/sep/sep/...` instead.

PMM_DEV_SEP_STRIP_PREFIX=1 strips the prefix on the way out, which makes
the uvicorn form work while keeping `url_for()` links prefixed. It stays
off by default: the right default belongs to the server-side nginx
location, which does not exist in this repo yet.

The internal-token guard has to match both the prefixed and stripped
forms. Vite applies `rewrite` by mutating `req.url` before the proxyReq
handler runs, so with the strip enabled the old prefix-only test stopped
matching and would have injected the service-principal token onto the
OAuth routes it must never cover - masking whether the session exchange
works at all.

Signed-off-by: Ignacio Durand <nachodurand@gmail.com>
The previous commit's comment claimed SEP carries no root_path support
at all. That was true when it was written and stopped being true a day
later: SEP-1794 (percona/SEP#1325) added a `SEP.ROOT_PATH` setting,
passed to the `FastAPI(root_path=...)` constructor, so a SEP started
with `SEP__ROOT_PATH=/sep` serves the prefix and the proxy forwards it
untouched.

Verified against a local SEP carrying the change: with ROOT_PATH set and
nothing stripped, `/sep/api/oauth/session/exchange`,
`/sep/api/sep/admin/settings/`, `/sep/api/apps/atw/config/` and
`/sep/api/users/me` all resolve. Every one of them was a 404 before.

Keep the flag: it still covers a SEP that predates the change or runs
with ROOT_PATH unset. Reframe it as the fallback it now is, and warn
against pairing it with uvicorn's `--root-path`, which prepends the
prefix rather than declaring the mount - the two cancel out by accident
rather than by design.

Comment only; no behaviour change.

Signed-off-by: Ignacio Durand <nachodurand@gmail.com>
Regenerates specs/*.json and src/generated/*.ts from SEP head, catching the
mirrored client up with six backend contract changes that landed after the
last sync.

- SEP-1638: inventory list endpoints gain server-side sort/search and a
  filtered total on nodes/services/schemas/tables.
- SEP-1642 / SEP-1624: tasks-service list endpoints and the script-list seam
  move onto the Core list-query capability. `sort` replaces the endpoint-local
  SnippetSortKey/SnippetSortDirection pair and the separate `order` param;
  both enums are gone from the generated surface. Nothing in PMM calls that
  endpoint today, so this is a type-level change here.
- SEP-1617: opaque `Record<string, never>` payload shapes become open
  string-keyed maps, so a stored task form is no longer typed as uninhabited.
- SEP-1673: the MySQL backup catalog is keyed on the inventory service id, so
  a rename between recording a run and asking for it no longer empties the
  restore selector. BackupRecord gains service_id.
- SEP-1667: the ATW incident close/reopen endpoints appear.
- SEP-1631: /api/apps/mysql_backups/backup-sources/choices — the endpoint half
  of a change whose framework half was carried in 398a66d.

Regenerated with `pnpm --filter @sep/api codegen` over the copied specs, not
by hand. Gates: check-types clean across all seven workspace packages,
@sep/api 87 tests pass.

Signed-off-by: yyyyyyy <contact@yyyyyyyan.tech>
yyyyyyyan and others added 19 commits August 12, 2026 01:56
The merge of the base branch resolved typed-client.ts in favour of this
branch, which reinstated isHtmlLoginResponse and the 303 clause that
PMM-15216 had deleted with the SEP-1687 port. The Jinja login route that
could answer an API call with a 200 HTML body is gone, so content-type
sniffing can no longer mean "session expired" — under PMM it would only
fire on a proxy misconfiguration and report that as a lost session.

The axios transport in client.ts already took the deletion, and the tests
covering the removed behaviour are gone, so this restores parity between
the two transports. The token mint-and-replay path this branch adds is
untouched.

Signed-off-by: yyyyyyy <contact@yyyyyyyan.tech>
Second catch-up, covering the SEP backend contract changes that landed after
cda8cfe23. Regenerated with `pnpm --filter @sep/api codegen` over the copied
specs, then formatted at package level so the fixtures stay oxfmt-clean.

- SEP-1821: adds `GET /api/apps/atw/snippets/`, ATW's own snippet-search route
  (see the ATW commit for why this one matters here).
- SEP-1824: task history carries `log_capture`, so a finished run distinguishes
  drained logs from an allocation reclaimed before SEP could read them.
- SEP-1708: settings expose enum-typed fields with their allowed values, which
  is what lets the settings UI render them as dropdowns rather than free text.
- SEP-1815: HEALTH_REPORT moves to the report app's own settings class.
- SEP-1796: docstring only here — the unapproved-snippet exclusion it describes
  is enforced server-side in the ATW category listing.

Only SEP-1821 and SEP-1824 reach PMM's code; the other three land as generated
surface alone, since the apps that consume them are not mounted here.

Signed-off-by: Yan Orestes <yan.orestes@percona.com>
SEP-1821, and it fixes a defect this branch shipped four days ago. The search
added in 9c20007 called `GET /api/apps/snippets/`, a route contributed by the
**snippets app** — and SEP mounts an app's router only when that app is in the
activation list. PMM's embedded image activates `inventory`, `mysql_backups`
and `atw` alone, so the Collect pane's picker would have answered
"Snippet search failed: Not Found" on every deployment we ship. Category
browsing hid the problem: it reads the snippets *library* directly rather than
another app's HTTP surface.

SEP moved search onto the same footing with a new route on ATW's own router.
The approved-only filter is pinned server-side instead of travelling as a query
param, so a caller cannot widen the set to snippets the execute path would
reject — which also removes the `approval` param this client used to send.

Frontend side, carried here: `useAtwSnippetSearch` targets
`${ATW_BASE}/snippets/`; the route serves ATW's own summary shape, so
`AtwSnippetSearchRow` and the `toAtwSnippetSummary` projection are gone —
the projection had become an identity map — along with the
`SNIPPETS_PLUGINS_API_BASE` import that made this package reach into another
app's API surface. Neither removed symbol had a consumer outside the package.

Also carries SEP-1824's `log_capture` into the TaskHistoryTable fixture; PMM
has no Storybook copy, so only the test changes.

Two tests keep PMM's shape: the mock-call helpers avoid tuple-destructuring
`mock.calls`, which PMM's typecheck rejects because it includes `./tests` where
SEP's tsconfig covers `./src` alone.

Gates (node 22): @sep/plugins-atw 83 tests, @sep/api 86, framework
TaskHistoryTable green, check-types clean across all seven packages, oxlint 0
errors, format:check clean.

Signed-off-by: Yan Orestes <yan.orestes@percona.com>
Restore SEP-1552 (percona/SEP#1111), lost when the port stripped the
multi-value reference fields that shared its hunks.

A stringified inventory id persisted in an edit form stored body (e.g.
"7") was read as a free-typed custom value, so the dependent schema
options never loaded and the child rendered raw digits instead of the
schema name. This hits the MySQL restore form, whose service and schema
fields are both declared allow_custom.

The test asserting the inverted behaviour is replaced by the two SEP
tests that cover the edit-form case.

Signed-off-by: Ignacio Durand <nachodurand@gmail.com>
The periodic-task API field is now in the committed OpenAPI spec, so the
hand-written intersection on PeriodicTaskResponse is redundant and typed
the field differently from codegen.

Signed-off-by: Ignacio Durand <nachodurand@gmail.com>
The SEP auth gate named a "Smart Expert Platform" that does not exist.
Rephrase the blocked and notice copy around what the user can act on -
the page cannot load, their work is kept - and refer to the backend as
the support platform.

Also cancel the negative right margin MUI puts on an Alert's action slot,
which left Try again hanging past the alert's padding.

Signed-off-by: Ignacio Durand <nachodurand@gmail.com>
Ports the redactSecretEnvVar mechanism from ba67206 (#5747,
"PMM-15309 Add read-only ClickHouse datasource user") without the
unrelated ClickHouse datasource feature it shipped alongside. That
commit landed on main after PMM-15299-open-manager's last sync, and
PMM-15326-sep-env-secrets needs it to avoid reimplementing a parallel
redaction path for PMM_SEP_TOKEN.

ParseEnvVars previously traced every variable's raw value under
PMM_TRACE. It now redacts values whose key contains PASSWORD, SECRET,
TOKEN, or KEY before logging.

Signed-off-by: Pawel Lebioda <pawel.lebioda@percona.com>
Addresses 4nte's review on #5811: PMM_SEP_URL and PMM_SEP_TOKEN's values
reaching the trace log is the same problem redactSecretEnvVar (just ported
from main in the previous commit) already solves for every other
credential-bearing variable, so this extends that mechanism instead of
keeping a parallel secretEnvVars map. PMM_SEP_TOKEN is already covered by
the TOKEN marker; PMM_SEP_URL gets an explicit key check since a URL can
carry credentials in its userinfo and none of the generic markers match
"URL".

Also adds the switch case the two vars were missing, so they're classified
as "not a server setting" instead of falling through to the "unknown
environment variable" warning, which logs the full, unredacted key=value
assignment.

PMM-15326-sep-env-secrets and PMM-15326-pom-inventory each independently
reimplemented a version of this fix on top of the old base; both are being
rebased onto this commit so their duplicate copies drop out.

Signed-off-by: Pawel Lebioda <pawel.lebioda@percona.com>
managed/cmd/pmm-managed/main.go and agent/client/client.go were both
missing a blank line goimports now wants before a comment-only import
block (the grpc gzip encoding registration). Neither file's own recent
history touches this -- this is toolchain drift between whenever these
files were last formatted and the goimports version CI resolves today,
not a regression from any specific change.

Found while opening PMM-15360's PRs: CI's "Check files are formatted"
step failed on all three, none of which touch either file, and the
same failure reproduces against this branch's own tip with no other
changes applied. Every PR based on this branch was going to hit it.

No behavior change; gofumpt/goimports/gci only.

Signed-off-by: Pawel Lebioda <pawel.lebioda@percona.com>
@ademidoff
ademidoff requested review from a team as code owners September 1, 2026 13:05
@ademidoff
ademidoff requested review from 4nte, JiriCtvrtka, fabio-silva and matejkubinec and removed request for a team September 1, 2026 13:05
@ademidoff
ademidoff marked this pull request as draft September 1, 2026 13:05


# Conflicts:
#	.devcontainer/setup.sh
#	build/ansible/roles/postgres/files/postgres-migration
#	build/docker/rpmbuild/Dockerfile.hetzner-el9
#	build/docker/server/entrypoint.sh
#	managed/utils/envvars/parser.go
#	managed/utils/envvars/parser_test.go
#	ui/.vscode/settings.json
#	ui/README.md
#	ui/apps/pmm-compat/package.json
#	ui/apps/pmm/package.json
#	ui/apps/pmm/src/contexts/navigation/navigation.provider.tsx
#	ui/apps/pmm/vite.config.ts
#	ui/packages/shared/jest.config.js
#	ui/pnpm-lock.yaml
#	ui/pnpm-workspace.yaml
@codecov

codecov Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 46.98%. Comparing base (31318c7) to head (772fbbe).
⚠️ Report is 161 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5872      +/-   ##
==========================================
+ Coverage   43.59%   46.98%   +3.39%     
==========================================
  Files         415      430      +15     
  Lines       43134    45395    +2261     
==========================================
+ Hits        18804    21331    +2527     
+ Misses      22454    22004     -450     
- Partials     1876     2060     +184     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants