Factorizer/Cluster ERP WikiCluster ERP Türkçe
Home / Identity and access

Explanation

Operator console

The vendor operator console of a multi-tenant deployment, its separate accounts and tokens, its second factor, its routes, and its audit trail.

The operator console is the screen of the people who run a multi-tenant deployment. Operators are not tenant users: their accounts live in the control-plane database, they sign in through their own route, and their tokens are refused by every tenant route. The server side is server/operators.py and server/routes/vendor.py; the page is webapp/operator.html with webapp/js/operator.js. This page explains accounts, sign-in, the routes, the cross-country fleet view, and the audit trail.

In short

  • The console API exists only in multi-tenant mode, under /api/v1/vendor (src/cluster_erp/server/app.py:1440-1450, src/cluster_erp/server/routes/vendor.py:68-70).
  • Operator accounts live in control_operators; the first one is created at start-up from vendor_admin_username and vendor_admin_password when none exists (src/cluster_erp/core/tenancy.py:132-154, src/cluster_erp/server/operators.py:56-75).
  • Sign-in requires the password and, with vendor_require_totp on, an authenticator code (src/cluster_erp/server/operators.py:114-175, src/cluster_erp/config/settings.py:131).
  • Operator tokens carry the audience vendor and last a fixed number of hours; tenant tokens and operator tokens are refused at each other's routes (src/cluster_erp/server/operators.py:1-12, src/cluster_erp/server/operators.py:209-235).
  • Every change an operator makes to a customer is written to control_operator_actions (src/cluster_erp/server/operators.py:238-257).

How it works

Accounts

Command Effect Source
python -m cluster_erp.server.operators add <username> Create an operator src/cluster_erp/server/operators.py:78-90
python -m cluster_erp.server.operators list List operators src/cluster_erp/server/operators.py:323-366
python -m cluster_erp.server.operators disable <username> Deactivate an operator src/cluster_erp/server/operators.py:93-103
python -m cluster_erp.server.operators totp-enroll <username> Issue the authenticator seed src/cluster_erp/server/operators.py:178-195, src/cluster_erp/server/operators.py:352-353

The seed is stored sealed; see second factor and secret storage.

Sign-in and requests

POST /api/v1/vendor/login takes username, password, and an optional otp_code (src/cluster_erp/server/routes/vendor.py:101-138). authenticate returns one of five outcomes: ok, invalid, locked, totp_required, or totp_not_enrolled; an unknown name and a wrong password give the same answer (src/cluster_erp/server/operators.py:104-128). A wrong code counts as a failed attempt, and repeated failures lock the account for a period (src/cluster_erp/server/operators.py:110-111, src/cluster_erp/server/operators.py:147-152). Every other route depends on require_operator, which reads a Bearer token with the vendor audience and loads the active operator (src/cluster_erp/server/routes/vendor.py:84-96).

The setting vendor_allowed_ips limits the console API to listed addresses or networks; it applies before sign-in (src/cluster_erp/server/routes/vendor.py:38-70, src/cluster_erp/config/settings.py:134). When it is empty, the server logs a warning at start-up (src/cluster_erp/server/app.py:129-134).

Routes

Fifteen routes, counted from the @router decorators in server/routes/vendor.py:

Route Purpose Source
POST /login Operator sign-in src/cluster_erp/server/routes/vendor.py:113
GET /me The signed-in operator src/cluster_erp/server/routes/vendor.py:141
GET /_meta Column and action metadata for the page src/cluster_erp/server/routes/vendor.py:149
GET /overview Counts across tenants src/cluster_erp/server/routes/vendor.py:332
GET /tenants Tenants with membership, provisioning state, region, schema revision src/cluster_erp/server/routes/vendor.py:394
GET /fleet Minimised tenant list for the cross-country view src/cluster_erp/server/routes/vendor.py:456
GET /tenants/{key} One tenant with database size and latest backup src/cluster_erp/server/routes/vendor.py:497
GET /signups The signup queue src/cluster_erp/server/routes/vendor.py:540
GET /actions The audit trail src/cluster_erp/server/routes/vendor.py:570
POST /tenants/{key}/membership/extend Extend the paid period src/cluster_erp/server/routes/vendor.py:607
POST /tenants/{key}/membership/suspend Suspend membership and tenant src/cluster_erp/server/routes/vendor.py:626
POST /tenants/{key}/membership/activate Reactivate src/cluster_erp/server/routes/vendor.py:639
POST /tenants/{key}/refresh-seeds Refresh the tenant's seed catalogue in the background src/cluster_erp/server/routes/vendor.py:652
POST /tenants/{key}/backup Take a backup in the background src/cluster_erp/server/routes/vendor.py:690
GET /tenants/{key}/backups List the tenant's backups src/cluster_erp/server/routes/vendor.py:740

One console across countries

The page can show the tenants of several country nodes together. The page itself asks every node listed in its metadata; there is no central copy. The /fleet route returns only the fields in _FLEET_FIELDS, and the server allows cross-origin calls to that one route only from the origins in console_origins, without credentials (src/cluster_erp/server/app.py:1354-1389, src/cluster_erp/server/routes/vendor.py:443-494, src/cluster_erp/config/settings.py:115-122). Each read of the fleet is recorded as an action (src/cluster_erp/server/routes/vendor.py:478-488).

The page

webapp/js/operator.js renders four views (overview, tenants, signup queue, audit trail) from GET /vendor/_meta and knows only column kinds, never specific columns (webapp/js/operator.js:1-8). The operator's token is kept in the browser tab's session storage, and signing out clears the tokens of every country node (webapp/js/operator.js:17-52). All page text comes from the operator keys of the language files, for example Operator Console (webapp/i18n/en.json key operator.title). The web app folder is served under /app (src/cluster_erp/server/app.py:1633-1643).

Why it is built this way

  • Operators and tenant users are separate worlds so that they cannot be confused by accident or on purpose (src/cluster_erp/server/operators.py:1-8).
  • Every operator action is recorded so that a change to a customer is never anonymous (src/cluster_erp/server/operators.py:10-12).
  • The cross-country view runs in the browser because a customer's data stays in that customer's country, with no central copy (src/cluster_erp/server/app.py:1354-1361).
  • Screens are metadata so that a new column or action is one server-side line (src/cluster_erp/server/routes/vendor.py:6-8).

Rules and boundaries

  • Membership actions share code with the command line in server/membership.py (src/cluster_erp/server/operators.py:260-320). See multi-tenancy.
  • The control-plane retention pass deletes old operator actions and anonymises long-disabled operator accounts after control_actions_retention_years (src/cluster_erp/server/control_retention.py:137-159).
  • The e2e suite is web-client/_e2e_operator_console.mjs.

Interaction with other parts

Limits and open points

  • Operator tokens have no revocation list in code; read_token checks signature, audience, type, and expiry, and require_operator checks that the account is active (src/cluster_erp/server/operators.py:222-235, src/cluster_erp/server/routes/vendor.py:84-96).
  • Operator roles or per-operator permissions are not determined from code; every active operator reaches every route.

Terms introduced

Term Code identifier Definition Turkish term Source
operator Operator A person who runs the deployment, with an account on the control plane. operatör src/cluster_erp/core/tenancy.py:132
vendor audience VENDOR_AUDIENCE The token audience that separates operator tokens from tenant tokens. satıcı hedef kitlesi src/cluster_erp/server/operators.py:49
operator action OperatorAction One audit record of what an operator did to which tenant. operatör eylemi src/cluster_erp/core/tenancy.py:157
fleet view vendor_fleet The minimised tenant list that the console merges across country nodes. filo görünümü src/cluster_erp/server/routes/vendor.py:457

Verified against commit 92f8051 on 2026-11-13.

Verified against commit 92f8051 · 2026-11-13