Skip to content

Release 14.0.89 - #5238

Merged
simlarsen merged 194 commits into
releasefrom
release-14.0.89
Oct 25, 2026
Merged

simlarsen merged 194 commits into
releasefrom
release-14.0.89

Conversation

@simlarsen

@simlarsen simlarsen commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Do not merge this PR. It is the CI gate for release 14.0.89. Its checks run on exactly the tree the release merge will build: this PR's merge ref is release plus the 14.0.89 bump. They run in a concurrency group that master pushes can't cancel. The release merge is pushed straight to release once these checks are green (14.0.88 published at 11:46Z on 10-24). GitHub then marks this PR merged.

🤖 Generated with Claude Code

nawazdhandala and others added 30 commits October 23, 2026 16:49
The AWS, Microsoft and Google Cloud usage reporters of a self-hosted
Enterprise install bought on a cloud marketplace were in
packages/Common/Server/Utils/Marketplace/SelfHosted, but only ee/ ever
called them (the usage job and the marketplace license). They are
behaviour, not schema, so they move to ee/Server/Marketplace/Reporters,
with their tests, and the Community image no longer ships them.

They reach core through Common/... specifiers only; the guides that name
their location follow.
…ccess controls are Enterprise features

Four license features, append-only like the rest:

- custom-roles: creating and changing CustomRole, CustomRolePermission
  and TeamCustomRole needs the license (@TableEditionAccessControl).
  What a team holds through a role is its own TeamPermission rows, so it
  keeps applying in every edition; reading and deleting always work.
- ip-allowlists: setting the ipWhitelist of a status page, dashboard or
  form. A list already set is enforced in every edition.
- status-page-access-controls: a status page's session length, only
  private users can subscribe, and subscribers' email domains. Also
  enforced as set in every edition.
- status-page-branding: hiding Powered by OneUptime. A runtime feature:
  the master page answers false unless the feature is active
  (StatusPagePoweredBy), so the Community Edition and a lapsed license
  show the line again, and the switch comes back on a renewal.

The column gate learns a value rule per column: retention overrides keep
theirs, every other enterprise column is cleared by its default
(PlanGatedColumnDefault, the rule the Cloud plans already use), so a
Community install can always remove what it configured. OneUptime Cloud
is unchanged: the plans gate these on Scale.
… billing hooks

OneUptime Cloud's billing is moving into the enterprise module. Core keeps
asking for it, through two new optional members of the module contract:

- getBillingProvider(): the payment provider (Stripe) BillingService will
  hand its calls to. Its interface, with the shared Invoice, PaymentMethod
  and PaymentIntentState types, is Server/Types/Billing/BillingProvider.ts,
  which loads nothing but Stripe's types.
- getProjectBillingHooks(): what the cloud marketplaces and Vercel do when
  a project is deleted or a marketplace-billed project changes plan.

EnterpriseEdition exposes both like getAuditLogRecorder: null on the
Community Edition, for a module without the member, for a null answer
(billing off) and for a module that throws (logged), never an error.
FakeEnterpriseModule can hand out either, or throw.
…ace billing live in ee/

Selling OneUptime Cloud on AWS Marketplace, Microsoft Marketplace, Google
Cloud Marketplace and Vercel Marketplace is OneUptime Cloud's billing, so it
moves to ee/Server/CloudMarketplace and the Community image no longer ships
it:

- the marketplace routes (MarketplaceAPI), the provider API Vercel calls
  (VercelMarketplaceProviderAPI) and sign-in from Vercel (VercelSignIn);
- each marketplace's client, adapter and inbound handling (AWS, Azure,
  Google Cloud, Vercel), the adapter registry, the claim token, the
  purchase claim and sync, the usage calculator, reporter and summarizer;
- the Marketplace:ReportUsage, Marketplace:SyncPurchases and
  Marketplace:VercelBilling jobs;
- recordPurchase, updatePurchase, issueClaimToken, findPurchaseByClaimToken,
  linkToProject and unlinkProject, now MarketplacePurchases. The CRUD
  service keeps its lookups.

The CloudMarketplace area contributes all of it only with billing on: the
routers through getApiRouters, sign-in from Vercel through
getIdentityRouters (after SSO, whose paths are unchanged), the jobs through
registerWorkerJobs (their names are now ee-owned, so core gives them
placeholders), and ProjectBillingHooks through getProjectBillingHooks.
ProjectService no longer imports the marketplaces: with billing on it tells
the hooks before and after a delete and after a marketplace plan change, and
a hook that fails never fails the change.

Every edition keeps what it uses: MarketplaceHttp and RemoteJwtVerifier
(Sign in with Microsoft) and VercelSignature (the Vercel log drain). The
Google Cloud client signs its assertion through JsonWebToken, so ee needs
no JWT library of its own.

The tests of the moved code move to ee/Tests/Server/CloudMarketplace. New
tests cover the area with billing on and off, the hooks on their own and
from ProjectService with the real module, ProjectService calling them only
with billing on, and a core guard that no core file loads the moved code or
serves a marketplace route. The guides name the new paths.
…tatus page branding and access controls are Enterprise screens

The custom role pages, the IP allowlist card (status pages, public
dashboards and forms), a status page's reader session limit and subscriber
restrictions, and its Powered by OneUptime switch move into ee/Dashboard
(CustomRoles, IpAllowlists, StatusPageAccessControls, StatusPageBranding).
Core keeps a shell for each that draws the Enterprise card, or the upsell.

What a record already has keeps applying in every edition, so under the
upsell each shell lists it with the one move every edition allows:
removing it. On OneUptime Cloud below the Scale plan the same card shows.
Powered by OneUptime is runtime: the Community Edition shows the line on
every status page, so there is nothing to remove there.

Under a lapsed or excluding license the Enterprise cards say so and offer
only what the server accepts (remove, delete), via a shared notice.

Docs (all 17 languages): the edition matrix, what stops and what keeps
applying on a lapse or a switch to Community, the custom roles section,
each feature's own section, the status page Powered by card, and an
upgrade note. The Home self-hosted page and claims list the moved
features. Tests cover the shells, the ee cards and the docs.
…ervice is a facade

The Stripe client is OneUptime Cloud's card billing, so it moves to
ee/Server/Billing/StripeBillingProvider.ts (the BillingProvider interface)
and the Community image no longer ships it. The new Billing area builds the
one provider of the process on first use, and only with billing on; the
enterprise module hands it to core through getBillingProvider.

Core keeps BillingService with the same singleton, method names and
signatures, so its callers and the core tests that spy on it are unchanged.
Each method hands its call to the provider. Without one - the Community
Edition, billing off, a module that answers null or throws - it answers as
a server without billing does and never reaches a payment provider:
BILLING_NOT_ENABLED for what reads or changes a payment provider's records,
shouldSendInvoicesByEmail false, handleWebhookEvent ignores the event.
isBillingEnabled, isSubscriptionActive, isTestEnvironment and the metered
price ids stay in core; ProjectService.getCurrentPlan reads
SubscriptionStatusUtil. Core loads Stripe's types only (import type), and
stripe is now an ee dependency.

The Stripe tests and their helpers move to ee/Tests/Server/Billing and test
the provider; the Stripe mock no longer needs jest-mock-extended. New tests
cover the facade with and without a provider (every method, exact
arguments, refusals passed through), the Billing area with billing on and
off (no Stripe client is built with billing off), the facade with the real
module, and a core guard that no core server file loads stripe at runtime.
…ommunity-image-split-625da3

# Conflicts:
#	packages/App/FeatureSet/Dashboard/src/Locales/da.json
#	packages/App/FeatureSet/Dashboard/src/Locales/de.json
#	packages/App/FeatureSet/Dashboard/src/Locales/en.json
#	packages/App/FeatureSet/Dashboard/src/Locales/es.json
#	packages/App/FeatureSet/Dashboard/src/Locales/fa.json
#	packages/App/FeatureSet/Dashboard/src/Locales/fr.json
#	packages/App/FeatureSet/Dashboard/src/Locales/hi.json
#	packages/App/FeatureSet/Dashboard/src/Locales/it.json
#	packages/App/FeatureSet/Dashboard/src/Locales/ja.json
#	packages/App/FeatureSet/Dashboard/src/Locales/ko.json
#	packages/App/FeatureSet/Dashboard/src/Locales/nl.json
#	packages/App/FeatureSet/Dashboard/src/Locales/no.json
#	packages/App/FeatureSet/Dashboard/src/Locales/pt.json
#	packages/App/FeatureSet/Dashboard/src/Locales/ru.json
#	packages/App/FeatureSet/Dashboard/src/Locales/sv.json
#	packages/App/FeatureSet/Dashboard/src/Locales/zh-CN.json
#	packages/App/FeatureSet/Dashboard/src/Locales/zh-TW.json
#	packages/App/FeatureSet/Dashboard/src/Pages/Teams/RoleView.tsx
#	packages/App/FeatureSet/Dashboard/src/Pages/Teams/Roles.tsx
#	packages/App/FeatureSet/Docs/Content/da/permissions/index.md
#	packages/App/FeatureSet/Docs/Content/de/permissions/index.md
#	packages/App/FeatureSet/Docs/Content/en/permissions/index.md
#	packages/App/FeatureSet/Docs/Content/es/permissions/index.md
#	packages/App/FeatureSet/Docs/Content/fa/permissions/index.md
#	packages/App/FeatureSet/Docs/Content/fr/permissions/index.md
#	packages/App/FeatureSet/Docs/Content/hi/permissions/index.md
#	packages/App/FeatureSet/Docs/Content/it/permissions/index.md
#	packages/App/FeatureSet/Docs/Content/ja/permissions/index.md
#	packages/App/FeatureSet/Docs/Content/ko/permissions/index.md
#	packages/App/FeatureSet/Docs/Content/nl/permissions/index.md
#	packages/App/FeatureSet/Docs/Content/no/permissions/index.md
#	packages/App/FeatureSet/Docs/Content/pt/permissions/index.md
#	packages/App/FeatureSet/Docs/Content/ru/permissions/index.md
#	packages/App/FeatureSet/Docs/Content/sv/permissions/index.md
#	packages/App/FeatureSet/Docs/Content/zh-CN/permissions/index.md
#	packages/App/FeatureSet/Docs/Content/zh-TW/permissions/index.md
#	packages/App/Tests/FeatureSet/Docs/CustomRolesDocs.test.ts
…ve in ee/

The five jobs that keep projects' subscriptions and metered usage in step
with Stripe are OneUptime Cloud's billing, and did nothing without it, so
they move from core's Workers to ee/Server/Billing/Jobs:

- PaymentProvider:CheckSubscriptionStatus
- PaymentProvider:PopulatePlanNameInProject
- PaymentProvider:UpdateTeamMembersIfNull
- PaymentProvider:SendDailyEmailsToOwnersIfSubscriptionIsOverdue
- MeteredPlan:ReportTelemetryMeteredPlan

The Billing area registers them through registerWorkerJobs, only with
billing on, with their names and schedules unchanged. Their names are now
ee-owned, so a repeatable definition an earlier release left in Redis gets
core's no-op placeholder wherever ee does not register it.

The seat-count job's test moves with it. New tests cover the area
registering all five on their schedules with billing on, none with billing
off, nothing twice, and a registered job doing nothing once billing is off;
the core guard pins that core's Workers no longer has or loads them. With
no billing email left in core, the email subject check drops its billing
exemption.
The ee README names the Billing and CloudMarketplace areas, and a new
section says what OneUptime Cloud bills from ee/ (the Stripe client, the
subscription and metered plan jobs, the cloud marketplaces and Vercel
Marketplace), how core reaches it (getBillingProvider and the BillingService
facade, getProjectBillingHooks, the area hooks and the ee-owned job names),
and what every edition keeps in core.
…d's billing jobs

The DefaultExport fixture registers none of OneUptime Cloud's billing
jobs, so under real node + ts-node the loader gives each its no-op
placeholder: the three marketplace jobs and the five subscription and
metered plan jobs, now that ee owns them.
… failures

toPaymentProviderReadFailure and writePaymentProvider moved with the
Stripe client to ee/Server/Billing/StripeBillingProvider.ts; the E2E
billing helper and a payment method API test still named them on
BillingService.
…on teams

Master gave API keys custom roles while this branch made custom roles an
Enterprise feature. Bring the two together:

- ApiKeyCustomRole is gated like TeamCustomRole: a key is given a role only
  on the Enterprise Edition with a license that includes custom roles (or
  on OneUptime Cloud's Scale plan); taking a role from a key always works,
  and what a key already holds keeps applying (its own ApiKeyPermission
  copies).
- The key's Custom Roles card and the role page's API Keys card move to
  ee/Dashboard/CustomRoles, read-only under a lapsed license. Core keeps a
  shell on the key's page (ApiKeyCustomRolesSettings) that lists the roles a
  key still has, with Remove, on the Community Edition and below the plan.
- Create API Key, like Create Team, offers custom roles only where the
  project may use them.
- The custom roles browser fixture resolves the ee plugin as the
  Dashboard's build does and skips its Enterprise-only specs without ee/.
- Docs: the edition matrix and the custom roles paragraph name API keys.

Also pins master's ApiKeyCustomRole.apiKey relation in the workflow
credential-settings survey, which master's own test had not caught up with.
…ommunity-image-split-625da3

# Conflicts:
#	packages/App/FeatureSet/Dashboard/src/Locales/da.json
#	packages/App/FeatureSet/Dashboard/src/Locales/de.json
#	packages/App/FeatureSet/Dashboard/src/Locales/en.json
#	packages/App/FeatureSet/Dashboard/src/Locales/es.json
#	packages/App/FeatureSet/Dashboard/src/Locales/fa.json
#	packages/App/FeatureSet/Dashboard/src/Locales/fr.json
#	packages/App/FeatureSet/Dashboard/src/Locales/hi.json
#	packages/App/FeatureSet/Dashboard/src/Locales/it.json
#	packages/App/FeatureSet/Dashboard/src/Locales/ja.json
#	packages/App/FeatureSet/Dashboard/src/Locales/ko.json
#	packages/App/FeatureSet/Dashboard/src/Locales/nl.json
#	packages/App/FeatureSet/Dashboard/src/Locales/no.json
#	packages/App/FeatureSet/Dashboard/src/Locales/pt.json
#	packages/App/FeatureSet/Dashboard/src/Locales/ru.json
#	packages/App/FeatureSet/Dashboard/src/Locales/sv.json
#	packages/App/FeatureSet/Dashboard/src/Locales/zh-CN.json
#	packages/App/FeatureSet/Dashboard/src/Locales/zh-TW.json
…in Git repositories

CodeRepositoryType gains Forgejo and Git beside GitHub and GitLab. A new
internal model, CodeRepositoryConnection, holds how a project reaches a
Git server that is not GitHub: its address, the account its token acts
as, and the encrypted, write-only credentials. CodeRepository names its
connection (server-written only).

The server reads those servers through one guarded transport (egress
guard, pinned addresses, no redirects, size and time caps): ForgejoApi
(Forgejo, Gitea, Codeberg), GitLabApi, and GitSmartHttp for any Git
repository's branches over smart HTTP. CodeHostAccess turns a repository
into one reader, whatever its host, and says which are reachable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Code's connect routes (/code-repository-connection/*): list the
  repositories a token reaches, connect the ones picked (each read again
  from the server by id), connect a Git repository by its HTTPS address
  (checked over smart HTTP, or saved for a project Runner when OneUptime
  cannot reach it), replace a connection's token, list connections. Who
  may connect is what connecting GitHub asks.
- Reachability everywhere reads CodeHostAccess: Code's gate (new blockers
  RepositoryTokenRefused and RepositoryNeedsOwnRunner), the repository
  JSON (provider and connection, never a token), the branch picker, stack
  trace resolution, source links, OneUptime AI reading code, profile
  fixes, fix readiness and SubjectCodeFixRun.hasConnectedRepository.
- The Runner's token route hands a GitLab, Forgejo or Git repository's
  token with where to use it (codeHost) only to a Runner that supports
  the host and can reach it, after the server checked the token.
- Pull/merge request state and CI sync for GitLab and Forgejo.
- Chat's code-writing tools and a deploy's commits stay GitHub's, and say
  so plainly for other hosts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Token requests say which hosts this Runner supports; a token for a
  repository that is not on GitHub comes with codeHost (server, API,
  username Git signs in with).
- Clone and push a repository that is not on GitHub through GitHostGuard:
  the egress guard (a global Runner reaches the internet only, never http),
  git pinned to the checked addresses (http.curloptResolve) and no
  redirects. Git signs in with the connection's username.
- PullRequestOpener opens the change's pull request on GitHub (as before),
  Forgejo and Gitea (pulls), or GitLab (merge requests). A Git repository
  has none: the branch is pushed and the run's outcome says where it is
  and where to open the pull request (PushedBranches), instead of no fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Connect repositories asks where the code is: GitHub (the App, as
  before), GitLab or Forgejo/Gitea (server address and an access token,
  then pick the repositories it can push to), or any Git server (HTTPS
  address and credentials; one only a project Runner reaches is asked
  about first). The empty Code page offers the other hosts under Connect
  GitHub, and is the way to connect on a server with no GitHub App.
- Each host's mark everywhere a repository is named (list, picker, task
  rows, task header, turns); a self-hosted server is named beside its
  organization/name; a refused token shows as Token refused.
- Settings: Repository Details says how a repository off GitHub connects
  (server, account, the token's last characters) with Replace access
  token, what it means when the token was refused or the server is only
  reachable by a project Runner, and plainly what works on GitHub only.
  More settings holds only what the host has: no GitHub commands,
  trigger label or production reviews off GitHub; a Git repository has
  Branches instead of Pull Requests.
- GitLab merge requests are named as GitLab names them (!12); a Git
  repository's footers say AI pushes a branch for the person to open the
  pull request from.
- Blockers for a refused token (Replace access token) and for a private
  Git server with no project Runner (Pick a Runner); a lost connection
  off GitHub is not told to connect GitHub. The branch picker never reads
  a server only a Runner reaches.
- Production Reviews leaves the menu off GitHub and says why if opened.
- 98 new strings, translated in all 16 languages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ify fixes on GitLab and Forgejo

- A repository's token now says whether the Runner may reach a private
  network (isPrivateNetworkAllowed): always for a project's own Runner,
  and for a global Runner only where OneUptime's server may too - a
  self-hosted OneUptime, whose bundled Runner runs on its operator's
  network. OneUptime Cloud's global Runners still reach the internet only,
  over https. GitHostGuard pins and checks with the server's answer.
- Fix verification in production asks GitLab (repository compare) and
  Forgejo or Gitea (compare base...head) whether a deploy contains the
  fix's merge commit, through the repository's connection, as GitHub's
  compare is asked; a plain Git server counts a deploy of the fix's own
  commit only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dashboard API (#5140)

A network controller connection holds a Meraki organization's read-only API
key (encrypted, never returned) and is read on a schedule: the
organization's uplink usage in five-minute windows - one call for every
appliance - and, every two hours, each network's traffic analytics
(applications, destinations, flows) and client usage. The reader paces
itself to a share of the organization's 10 requests a second, honours
429 Retry-After, follows Meraki's own redirects and page links only, and
reads incrementally from cursors; rows land in the new NetworkTrafficUsage
table, read through the device they belong to, and
/network-traffic/usage-summary answers a Traffic page's Meraki side.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… languages

- Code gets a Connect a repository section: the four places a repository
  can be, how each connects (the GitHub App, an access token with the
  scopes it needs, or a Git server's HTTPS address and credentials), what
  AI does in each - commands from comments and production reviews are
  GitHub's for now; a Git server gets a pushed branch and you open the
  pull request - tokens kept encrypted with Replace access token and
  Token refused, and a Git server on a private network (Connect anyway,
  for a project Runner; a self-hosted OneUptime reaches its own network).
- Before you begin, Open Code and the automatic fix and telemetry pull
  request requirements no longer say the GitHub App only; the GitLab
  integration and the exception page say AI works in GitLab repositories.
- The roadmap's GitLab support entry is gone.
- CodeRepositoryHostsDocsFacts holds the section to CodeHost and the
  connect dialog's words; the other facts suites follow the new lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…og and the connections card (#5140)

A Traffic page that has Meraki traffic gets a Flows | Meraki switch in its
header (kept in the URL); a page with Meraki traffic and no flow records
opens on Meraki. The Meraki side draws the same tiles and chart from the
appliances' uplinks (download and upload), Top networks or the uplinks,
and Top applications, clients and destinations from Meraki's traffic
analytics, with one line per connection saying how it is doing. The whole
network's page offers Connect Meraki in its set-up guide and lists the
connections at its foot: status, what the last read covered, Read now,
edit, turn off, delete.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Meraki side of the Traffic pages, the connect and edit dialogs and the
connections card, translated into the 16 Dashboard languages; brand and
technical words that read the same are recorded as such, and the uplink
rows and chart reuse the flow side's In / Out wording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…gation and fixing were off for an incident or alert

The incident and alert AI card used to fill the page with two cards that
both said nothing happened. When the record was not investigated for a
reason the project chose (AI off, automatic investigation off, no AI rule
that investigates matched, severity below the minimum) and fixing did
nothing for one either, the card is now one quiet line - what is off and
why - that opens to say what to turn on, where, and who can. Ask OneUptime
AI stays on the line while AI is on. The Remediation card leaves its
'nothing was fixed' lines to that line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oard API (#5140)

The client, the reader, the connector, the runner, the scheduler, the rows
and the connection service, against a fake Meraki dashboard API on a test
clock: pacing, 429s, redirects, paging, budgets, windows, cursors, failures.

What the tests found, fixed here:

- A read whose rows were stored but whose cursor was not recorded (the
  bookkeeping write failed, or the insert's answer was lost) read its first
  application window again from a new start, so it counted twice. The
  runner now moves the cursors up to where the connection's stored windows
  end before it reads, and a network's applications and clients of a window
  go in one insert, so its stored end is its cursor.
- Stored window starts and the freshness times are read as epoch
  milliseconds, never as text in the server's time zone.
- The longest window is 25 hours: a day's backfill ends when the read asked.
- Testing a saved connection keeps its own organization and region, as a
  save does, so its stored key is only tested where it reads.
- A count is a number or numeric text only; a cut never splits a character.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ay act, the conversation and the Remediation card beside it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…an do' where it was still English

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
valeriamustafaeva and others added 26 commits October 24, 2026 10:22
…longest word whole

Dutch's Aanmeldgebeurtenissen is 161px: the Data column now caps at 11rem
rather than 10rem, so it is never cut mid-word, and the Rule column gives
way down to 11rem rather than 12rem, so Dutch fits production's 1024 beside
the side menu as German and Swedish do (Dutch was 2px over). Measured in
English, German, Swedish, Dutch and Japanese at 1024 and 1280.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Audit log export (Enterprise): a project's or the whole instance's audit log to S3, GCS, Azure Blob, a webhook or syslog
…fix suggestion

AI rules now trigger on Incident, Alert, Profile and Trace, and
Common/Utils/Suggestions holds TelemetryAiFixSuggestion, Fix with AI's
offer on a telemetry lane's finding. The routing test passes a verdict's
classification as the string it is, for the strict test compile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-traces

Master's AddAuditLogExport took 1807110000000, so the Traces AI switch
migration is renumbered to 1807120000000. The locale files' conflict (both
sides appended keys) is merged by npm run i18n:extract.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, its adds beside the search, and a dialog that leaves the page where it was

UI review of #5233 (security-rule-library-overhaul).

- Filters: the three native selects (each as wide as its longest option,
  wrapping into a ragged stack beside the side menu) are now the filter row
  every list in OneUptime draws - FacetFilterBar's "Filter", the
  FilterChipDropdown chips Session Replay and the host lists use, "Clear
  all" - named Rule category, Data and Status, the value picked beside the
  name in the words the rows use (Data · Receiving, Status · Not added).
  How many rules are in sight follows the chips, and is not drawn while the
  rules are read or could not be. Clear all empties the chips, not the
  search, which has its own x.
- The Data chip is offered only while it can narrow the list: not when the
  project's events could not be read, nor when none arrived this week (both
  said under the chips, now with an icon); a data filter kept from the
  address meanwhile narrows nothing and is not counted
  (getEffectiveRuleLibraryFilters, countRuleLibraryChipFilters).
- The header's adds stay beside an opened search (keepButtonsWhileSearching):
  a model table folds its buttons while searching, which hid Add N Matching
  Rules the moment there was something to match. The opened search is 20rem,
  so on a 1280 laptop the header keeps the title's line instead of jumping
  under the description at the first letter.
- Common Modal takes its first focus with preventScroll. The Dashboard draws
  its Tailwind as classes first appear, so the first dialog a page opens was
  focused before its overlay was fixed, and the focus scrolled the page to
  the panel's place at its foot: Add 17 Recommended Rules left the reader
  4,500px down the list, behind the dialog and after it. Product-wide.
- A row's Add that cannot be pressed (a role that cannot add, another add on
  its way) looks it; the outline button has no disabled look of its own.
- A rule's details: "No events of this kind arrived..." sits in the box that
  says how to send them, not between it and the rule's events.
- Strings: "Rule category" in 17 languages ("Category" is still English in
  twelve, whose docs quote it as drawn); the select-only strings dropped.

Tests: the page's jsdom suite follows the chips (48 tests, 7 new: the chip
row, Clear all and a chip's x, the count while reading, a data filter set
aside, the search keeping the adds, the unavailable look while adding, the
sentence in the box); the model's (59, 2 new); Modal's first focus without
scrolling; the browser suite's library tests on the chips, plus the dialog
that leaves the page where it was (1280, 390) and the search's ⋯ adding what
it finds. Each new test fails with its fix taken out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s under its name

Side by side, a German "Anmeldeereignisse · Empfangen" left the rule names a
third of the dialog, three lines each. Each rule now reads as a phone draws a
library row: its name, and under it the events it reads and whether they
arrive. The browser suite checks the events sit under the name (it fails with
the side-by-side layout back), and a rule whose events do not arrive has no
"arrived" sentence in its details.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the evidence as written, Fix with AI in the rail

- Create Monitor on a trace finding was greyed out with "cannot be
  created from this kind of insight yet" on all three kinds. A failing
  operation now opens a Traces monitor on its failed spans (its span
  name, the Error status, its service, the past hour) at the detector's
  own floor of 20 failed spans; a slow operation keeps the button,
  disabled, with a reason of its own (no monitor watches how long an
  operation takes yet); missing tracing draws no button at all - a
  monitor could never count what a trace lacks. The operation's name
  reaches only the span filter, never the monitor's words.
- Monitor Create from any AI insight, link or template drew
  "props.initialValue.toJSON is not a function" and no status to pick:
  the archived-values read called toJSON() on steps that arrive as JSON.
  It reads them as text now, whatever their shape (MonitorSteps.tsx).
- The evidence a finding shows is the detector's own, built by a pure
  buildTraceInsightDetail the offline fixture now draws too (it showed
  made-up prose). Fixed in it: a service's look wrote "p50 0 ms", list
  items opened in lower case, "Request read" said nothing, code
  locations sat inside the repeated-pattern list, and "In 5 of the 5
  most recent failures" reads "In each of the 5".
- Fix with AI in a finding page's narrow rail: the repository picker and
  the button sat side by side and squeezed the button into "Fix / with
  / AI". Both strips (Traces' and the Profiler's) stack by a container
  query now, side by side only where the strip is wide enough.
- The Traces lane's summary lines promised a pull request for every
  problem; a failing operation's waits for the code to be found at
  fault, and a fix may find nothing to change: "for each one it can fix".
- 14 strings in all 16 languages; 4 replaced keys dropped everywhere.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
translateText answers string | undefined: each option falls back to its
English name, as the rest of the library does. Common compile-tests and the
Dashboard's tsc type-check again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…st for each problem it can fix - in 17 languages; tests for the review

- Traces Explorer: "On a failing operation, **Create Monitor** sets up a
  Traces monitor on its failed spans." and the AI rules line says a pull
  request opens "for each one it can fix", in all 17 languages, with the
  Dashboard's own label for Create Monitor in each. TracesAiDocsFacts
  pins both to the lane's copy and to the monitor prefill.
- The real Monitor Create page opens on the Traces monitor from a
  failing operation and says the slow operation's reason instead of a
  form (MonitorCreateFromAIInsight).
- The strip layout is keyed to its container, never the window, the
  button never wraps, and both strips use it (TelemetryAiFixCopy); no
  summary line promises a pull request for each problem (TelemetryAiCopy).
- Offline browser specs: the evidence as the detector writes it, Create
  Monitor on each kind of finding, the picker stacked in the rail and side
  by side at 1024; the fixture's notes and README say what it draws.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…final UI

The filter chips, the add dialog's stacked rules, the details' grouped box, a
new project's notice, German and Japanese - and a new frame of a search whose
⋯ adds the rules it finds. Taken from the offline fixture at production
widths, 2x, palette-compressed as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ackfill

Common Test (7/8) failed on eff0cfa: BackfillFileOwners1797900000000
"reads the inline images of every markdown column people write in" found
AutoRemediationRule.aiContext among the models' markdown columns but not
in the migration's list. #5230 added that column (migration
1807100000000), long after the backfill ran; as the test's own note says
of columns added later, its files are uploaded with their project and
need no backfill. It joins MARKDOWN_COLUMNS_ADDED_LATER beside the
security case columns. The suite passes (11/11) with CI's settings on
a7b661e.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d; /install.sh is the new installer

Master's test-image E2E (run 40081137635 on eff0cfa) failed two tests,
both on what merged PRs changed on purpose:

- Forms D6 "an IP allowlist that leaves this network out shuts the form
  to it" (saas 3 and 4, self-hosted 2) waited 30 s for the Share page's
  Access card. #5216 made setting an IP allowlist an Enterprise feature:
  OneUptime Cloud sells it on Scale (this project is on Growth) and a
  self-hosted install needs the Enterprise Edition, so on both stacks the
  page now draws the "IP Allowlist" upsell instead. D6 waits for either
  card. Under the upsell it checks the card offers the right step
  ("Upgrade to Scale" with billing, "Learn about Enterprise Edition"
  without) and that nothing offers Edit IP Allowlist, then skips with the
  reason. Where the Access card is drawn, the save must go through, and a
  refusal fails the test with its text (it used to be read as the plan
  gate).
- Home "/install.sh serves the OneUptime installer" (saas 1 and 4)
  expected the old script's "#!/bin/bash" and "OneUptime Installation
  Script". #5229's installer (ba2bb05) starts "#!/usr/bin/env bash"
  and calls itself neither. The spec reads a bash shebang and "Installs
  OneUptime with Docker Compose", and Home's Redirect.test.ts now holds
  Home/Scripts/Install.sh to the same two marks on every commit
  (mutation-checked: a #!/bin/sh shebang fails it), so a rewrite fails
  there first.

The licensed Enterprise E2E stack, where an allowlist can be set, runs
only Enterprise/Licensed; covering D6's full path there is a follow-up.
E2E tsc, eslint and prettier are clean; Home's Redirect suite passes
(37/37).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e reflow after a resize

Master's self-hosted E2E (shard 2, run 40081137635) reported "registers
a security key on the separate two-factor page and keeps credential
lists distinct" flaky: right after setViewportSize to 390 px, one read
of documentElement.scrollWidth <= innerWidth came back false, and the
retry passed on the same build - the read caught the page still laid
out at the wider width. All three such checks in the spec (two-factor
page, passkeys page, sign-in page) now go through
expectNoHorizontalScroll, which polls the same comparison for up to
10 s, so a layout that really overflows still fails. E2E tsc, eslint
and prettier are clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Six more tests (the evidence as the detector writes it, Create Monitor on
each kind of finding, the picker in the narrow rail): 1.5 minutes with its
bundle locally, from 1.3 - about 21.3 for the group's ten suites.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-traces

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…overhaul

Security Events: the Rule Library as a list to scan, filter and add from
…-traces

Locale files merged by key: master's new keys and this branch's, no key
changed on both sides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Group 8 took 22.4 minutes on master's last green App Test run without
it, and group 1 14.9: with Traces AI's 1.5 minutes group 8 would have
passed the 22-minute mark the groups are kept under. About 16.5 for
group 1 now. Its screenshots and traces upload as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Traces gets an AI tab: find and fix slow, failing and under-traced operations with pull requests
…ule Library's Data filter

App Test (1/3, 2/3) failed on 6b55b79. #5233 gave the Rule Library a
Data filter whose options name the events a rule reads, so the
Dashboard's locales gained "Data" (translated in each language) and
"Okta System Log" (the same in every language). Two docs guards still
filed them as words no locale has:

- InfrastructureMonitorDocsTranslations listed the host monitor page's
  "**Data**" as a prose lead, which may not be a label. It is the
  comparison table's row head, beside Agent, Criteria and Setup, so it
  joins them in PROSE: a label by coincidence that a translation words
  as its language needs.
- IntegrationsDocsTranslations kept the Okta page's "Okta System Log" as
  written because no locale had it. It is a label now and every locale
  draws it as written, so it leaves KEPT_AS_WRITTEN and is held to the
  drawn label like any other; the note lists it with the earlier
  connection settings that became labels.

Both suites pass (8,307 tests), as do the ten other suites that import
these lists (1,746).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@simlarsen

Copy link
Copy Markdown
Contributor Author

✅ Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
✅ Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Comment on lines +180 to +182
!String(claims["scope"] || "").includes(
"https://www.googleapis.com/auth/devstorage",
) ||
): void {
this.router.post(
`${CODE_REPOSITORY_CONNECTION_API_PATH}${path}`,
UserMiddleware.getUserMiddleware,

this.router.post(
`${basePath}/test`,
UserMiddleware.getUserMiddleware,
Comment on lines +70 to +147
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
try {
const props: DatabaseCommonInteractionProps =
await CommonAPI.getDatabaseCommonInteractionProps(req);
const projectId: ObjectID =
CommonAPI.assertAuthenticatedProjectMember(props);
this.assertCanOperate(props);

const body: JSONObject =
req.body && typeof req.body === "object" && !Array.isArray(req.body)
? (req.body as JSONObject)
: {};

let toTest: { apiKey: string; config: NetworkControllerConfig };

try {
toTest = await this.readSettingsToTest({ body, projectId, props });
} catch (error) {
/*
* Settings the form can still fix (a key that cannot be one, a
* region that is not Meraki's) are the test's answer, shown in
* the form - not a failed request.
*/
if (!(error instanceof BadDataException)) {
throw error;
}

const result: NetworkControllerTestResult = {
provider: NetworkControllerProvider.Meraki,
status: "fail",
message: error.message,
failureKind: LlmConnectionFailureKind.Settings,
organizations: [],
durationMs: 0,
};

return Response.sendJsonObjectResponse(
req,
res,
result as unknown as JSONObject,
);
}

const result: NetworkControllerTestResult =
await NetworkControllerConnectorRegistry.getConnector(
NetworkControllerProvider.Meraki,
)
.testConnection(toTest, {
transport: DataSourceHttpFetch.fetch.bind(DataSourceHttpFetch),
})
.catch((error: unknown): NetworkControllerTestResult => {
// testConnection never throws; this is the belt to its braces.
const failure: LlmProviderError = LlmProviderError.from({
error,
step: "test",
providerTitle: "Meraki",
secretValues: [toTest.apiKey],
});

return {
provider: NetworkControllerProvider.Meraki,
status: "fail",
message: failure.message,
failureKind: failure.kind,
organizations: [],
durationMs: 0,
};
});

return Response.sendJsonObjectResponse(
req,
res,
result as unknown as JSONObject,
);
} catch (error) {
next(error);
}
},

this.router.post(
`${basePath}/:connectionId/read-now`,
UserMiddleware.getUserMiddleware,
Comment on lines +154 to +194
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
try {
const props: DatabaseCommonInteractionProps =
await CommonAPI.getDatabaseCommonInteractionProps(req);
const projectId: ObjectID =
CommonAPI.assertAuthenticatedProjectMember(props);
this.assertCanOperate(props);

const connectionId: string | undefined = req.params["connectionId"];

if (!connectionId || !ObjectID.isValidUUID(connectionId)) {
throw new BadDataException("A valid connection ID is required.");
}

const connection: NetworkControllerConnection | null =
await this.service.findOneBy({
query: { _id: connectionId, projectId },
select: { _id: true, projectId: true, isEnabled: true },
props,
});
CommonAPI.assertResourceBelongsToProject({
resourceProjectId: connection?.projectId,
projectId,
});

if (!connection?.isEnabled) {
throw new BadDataException(
"This connection is turned off. Turn it on to read it.",
);
}

await NetworkControllerScheduler.enqueue(
new ObjectID(connectionId),
new Date(),
);

return Response.sendJsonObjectResponse(req, res, { queued: true });
} catch (error) {
next(error);
}
},

router.post(
TELEMETRY_AI_READINESS_PATH,
UserMiddleware.getUserMiddleware,

router.post(
TELEMETRY_AI_LOGS_PATH,
UserMiddleware.getUserMiddleware,

router.post(
TELEMETRY_AI_FIX_SUGGESTION_PATH,
UserMiddleware.getUserMiddleware,

router.post(
TELEMETRY_AI_FIX_PATH,
UserMiddleware.getUserMiddleware,
@simlarsen
simlarsen merged commit 7df7e32 into release Oct 25, 2026
72 of 73 checks passed
@simlarsen
simlarsen deleted the release-14.0.89 branch October 25, 2026 03:08
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.

3 participants