The list of waiting hosts opens the approval dialog instead of having its
own name input and buttons, so hosts are approved in one place. Removes
the limit on requests per minute, the limits on pending requests already
cap what an address can do.
Also updates text that still said the installer makes the requests.
* Take FieldData from antd instead of rc-field-form
web_ui depended on rc-field-form 1.x for a single type import, while
antd 5 uses rc-field-form 2.x. Derive the type from antd's FormProps
and drop the dependency.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Move typescript to web_ui devDependencies
web_ui only needs typescript to build its own types. The runtime
dependency is a leftover from webpack's ts-loader, Vite doesn't use it,
and it pulls in a 30 MB native compiler for anyone installing web_ui.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Use native FormData in controller route tests
Node's FormData and Blob do what the form-data package did here, and
fetch sets the multipart Content-Type header itself.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Remove leftover typedoc.json files
TypeDoc was dropped in #1147, but these configs still ship in the
published packages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A host waiting for approval logs a link to the Hosts page with the code
in it. Opening it shows a dialog with the code, the address of the host,
a name prefilled with the requested one and a large Approve button. A
code that is no longer waiting shows a message saying so.
The installer only writes the controller URL and host name. Running the
host requests to join the cluster if it has not been approved yet, or if
the controller no longer accepts its token, and waits until an admin
approves it. Once approved it saves its id and token and starts as
normal. A rejected host exits with code 8.
clusteriohost run --user-token approves the host right away with the
token of a user allowed to approve hosts. The token is not saved.
This moves the pairing client into the host package, where it uses the
same certificate setup as the host's connection to the controller, and
removes --no-pair and the rerun handling from the installer.
web_ui only needs typescript to build its own types. The runtime
dependency is a leftover from webpack's ts-loader, Vite doesn't use it,
and it pulls in a 30 MB native compiler for anyone installing web_ui.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
web_ui depended on rc-field-form 1.x for a single type import, while
antd 5 uses rc-field-form 2.x. Derive the type from antd's FormProps
and drop the dependency.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hosts join the cluster by running the installer, which shows a code and
waits until the request is approved under Hosts > Add host in the web
interface or with clusterioctl host approve. An admin can pass their
user token to the installer to approve the host right away. The token
is only used for that and is not saved.
The controller only accepts hosts that have a record, and only
approving a host creates one. Revoking a host deletes its record, so
its token can no longer be used to reconnect. Hosts that have connected
before keep working.
Removes host generate-token, revoke-token and create-config from
clusterioctl, bootstrap generate-host-token, the host token messages and
the core.host.generate_token, revoke_token and create_config
permissions, which are also stripped from stored roles. Adds
clusterioctl host pending, approve, reject and revoke, and the
core.host.approve and core.host.revoke permissions.
Standalone installs add the host to the controller database directly,
since the controller is not running during the install.
loaded, enabled, pendingInstall, removed, noEntrypoint and nameTaken
allowed far more combinations than can happen, and every consumer had
to know which took priority. PluginDetails now has one state, with
loaded, loadsOnRestart and removed as getters over it.
2.0.3 web UIs and ctl send PluginListRequest straight to hosts and
require loaded and enabled, so those are still sent next to the
optional state. Plugins listed by 2.0.3 hosts get their state from
loaded and enabled.
The web UI planner gets the same treatment: MachinePlugin has one
state in place of disabled, noEntrypoint, dependencyTaken and
nameTaken. ctl host plugin list shows the state.
Closes#1134
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The button offered Stop while the instance was starting whenever the
start came from somewhere else, such as Load save, another tab or ctl.
It now shows the action in progress as loading until the status
settles. Kill stays available from the dropdown.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Users don't need the web UI load state apart from the plugin's own state now
that the web UI reloads itself. A web load error now shows under the plugin's
versions and on its view page, only for enabled plugins.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Vite serves any file under fs.allow through /@fs/, and the default
allow list is the workspace root. In a clusterio checkout that is also
the usual data directory, so with --dev any client that could reach the
controller could fetch config-controller.json, config-ctl.json and the
database without logging in. webpack-dev-middleware only served the
compiled bundles.
Allow the controller, web_ui, lib and dev plugin directories and the
node_modules directories Node.js would resolve their imports from.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lib and plugins import Node.js modules such as events and buffer, which
the build swaps for browser packages. Vite resolves an aliased import
from the importing package, and pnpm only lets a package see its own
dependencies, so the polyfills had to be dependencies of lib.
Resolve them from web_ui with a small resolver plugin instead, also
used by the dependency optimizer. Hosts no longer install the browser
packages, and plugins get the same polyfills webpack gave them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace ESLint, typescript-eslint, eslint-plugin-node and the flat
config helpers with vite-plus, which runs Oxlint for vp lint. The rules
are carried over from eslint.config.mjs by @oxlint/migrate into the lint
section of the root vite.config.ts. The formatting rules ESLint
deprecated run as @stylistic rules through Oxlint's JS plugin support,
and disable comments naming them are updated to the @stylistic names.
@stylistic understands TypeScript syntax that the core rules skipped,
so the autofix adds missing semicolons and spaces in type literals and
aliases. Leading | and & in multiline types stay where they were.
Oxlint's node/no-sync also reports identifiers ending in Sync that are
not calls. The test helper techSync is renamed and the two other names
are ignored.
Refs #1127
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace webpack and Module Federation with Vite 8. The controller build
emits one entry per shared module (lib, web_ui, typebox, antd, react,
react-dom, react-router) and lists them in manifest.json. The controller
turns that into an import map in index.html, and plugins are built as
ES modules that import the shared modules by name and are loaded with
import().
The shared Vite setup lives in @clusterio/web_ui/vite.mjs. Plugins use
clusterioPlugin(), which exposes index, package.json and web/index under
the same keys the webpack template used. The --dev and --dev-plugin
flags now run the Vite dev server in middleware mode with hot module
replacement.
Browser polyfills move from web_ui to lib, which is the code that needs
them, and lib sets the process and Buffer globals itself.
Refs #1127
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add a LogEntry type for log entries sent over links and returned by
log queries. It names the fields every entry has (level, message,
timestamp) and leaves the rest as unknown.
The console now prefixes each line with the entry's time in the
browser's local time zone, which makes it possible to tell when an
error happened after the fact.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Notes in the web UI now render through ReactMarkdown. A mention like
@instance/778528005 or @user/cooldude2606 turns into a link to that
resource's page, labelled with its name if the client has it and the id
otherwise. The stored note keeps the mention as written. Line breaks
are kept as hard breaks so existing plain text notes look the same.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Raise the catalog and package.json floors to the latest release within
each major, and move typebox to 0.34, react-router-dom to 7,
winston-daily-rotate-file to 5, webpack-dev-middleware to 8 and
webpack-manifest-plugin to 6. None of these need code changes.
express 5.3.0 requires proxy-addr ^2.0.8 and qs ^6.16.0, which puts a
floor under the critical proxy-addr and high qs advisories for anyone
installing the published packages. webpack-dev-middleware 8 fixes a
high advisory in the controller's dev mode, and react-router 7 fixes two
moderate ones.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The web interface registered the hook's listener directly on the
connector, so handlers got the connector's emit arguments (the hello
data for connect, nothing for the rest) instead of the event name.
Fixes#1083
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Since the move to ES modules and pnpm 12, packages installed from npm
fail to start:
- The package imports of @clusterio/lib point Node at TypeScript
sources, which Node refuses to strip types from inside node_modules.
- The controller, host and ctl import their package.json from next to
the compiled entrypoint, a copy pnpm leaves out of the package.
- ctl, host and web_ui have no .npmignore, so pnpm packs them using the
repository .gitignore and leaves out nearly all of dist/.
Resolve the package imports to the compiled files at runtime while
keeping the sources for type checking, import package.json by package
name so it resolves to the real file, and add .npmignore files so pnpm
packs the same files as npm.
Below the lg breakpoint the list is not rendered and the permission
groups take the full width. At md the site sider already takes 250px,
so a 200px list next to it left too little room for the permissions.
The list keeps its sticky position but is capped at the viewport height
minus its top offset and scrolls on its own when there are more groups
than fit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A sticky list of permission groups sits left of the permissions. Clicking
an entry scrolls to that group, and its dot is blue when any permission
in the group is granted, grey otherwise. Same layout as the ExpCluster
scenario features page.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Not every plugin arrives through PluginInstallRequest. Plugins checked
out into external_plugins or installed by hand on the machine never
flag a restart, so the web UI shows nothing and tooling that acts on
the flag never fires.
HostMarkRestartRequiredRequest and ControllerMarkRestartRequiredRequest
set config.restartRequired, guarded by the existing restart
permissions. They are exposed as "Mark Restart Required" in the control
dropdown of the host and controller pages, hidden once the flag is set,
and as mark-restart-required ctl commands. The mark shows up on the
next system metrics poll.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Ctl ended up calling its main class for "Control" for historical
reasons, along with its config file and plugin entrypoint.
The plugin entrypoint was renamed to ctl in 808eda0513. This
finishes that renaming by also renaming the main class, default config
file, config-control command, and main entrypoint.
The config-control command is renamed to config to be consistent with
the host and controller's config commands.
The term "control" is still kept in the config and message locations as
a common specifier for both ctl and web_ui.
The Map Settings block parsed values.mapGenSettings, so filling in only
Map Settings failed with "undefined" is not valid JSON and the modal
would not submit, while filling in both sent the map gen settings JSON
to Factorio as --map-settings.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Deleting your own account now terminates the session immediately, so the
web UI received a SessionLost rejection and reported "Error deleting
user" for a delete that had succeeded. Use the same branch revoke does.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
yargs, @types/yargs, semver and @types/semver are used by several packages at the same version. Also point the remaining direct references to @swc/core and react-router-dom at their existing catalog entries.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Raise every caret range to the current latest within its major, in the
root package.json, the pnpm-workspace.yaml catalog, the per-package
package.json files and the create plugin template.
Majors taken: eslint 10, @eslint/js 10, globals 17, nyc 18,
@types/express 5, @types/node 24, finalhandler 2, pidusage 4,
style-loader 4, css-loader 7, yargs 18 (all packages), chalk 6.
Majors skipped: mocha 12 (integration suite fails under it, 59 tests,
starting with a 30 s timeout in clusteriocontroller run; kept at
^11.8.0), inquirer (10+ is a rewrite on @inquirer/prompts where
ui.Prompt.prototype.close no longer exists, so the win32 readline
workaround in create.js would have to go and cannot be verified here),
react/antd/react-router-dom/@types/react, typescript 7,
@sinclair/typebox, webpack-merge (exact pin), webpack-dev-middleware,
webpack-manifest-plugin, winston-daily-rotate-file.
Code adaptations: the two /api/stream/:id handlers in routes.ts are
typed Request<{ id: string }> because @types/express 5 types
req.params values as string | string[]; eslint.config.mjs adds
newIsCapExceptions: ["constructor"] because eslint 10.10 new-cap now
reports `new this.constructor()`.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Point the invalid configuration section at the features flags and
config validators instead of throwing from the entrypoint, use const in
the examples, add a metrics hook example including a returned registry,
and mark Control.plugins as deprecated.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Plugins with only host, instance or ctl code have no reason to ship a
webpack build, yet the plugin list showed "Missing dist/web/manifest.json"
as an error for them and the controller logged a warning on every start.
Decide whether a build is expected from the plugin declaration, using the
same rule the create tool uses to decide when to generate one: a web or
controller entrypoint, or any config fields. Plugins outside that rule get
no bundle path and no error, the web UI skips loading them, and the plugin
pages show them as having no web module instead of an error or a prompt to
reload.
Fixes#1004
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Replace invokeHook and the per plugin on* methods with AsyncHook
collections on the controller, host, instance, clusterioctl and web
control objects. Plugin entrypoints export a default function that
receives a load context and attaches handlers to the hooks it needs.
Class exports still load through fromContext with a deprecation
warning. The create templates and docs use the entrypoint function.
This is clusterio/clusterio#921 recreated on top of master after the
ESM migration and the PluginDeclaration permissions change.
Closes#730
Co-Authored-By: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Adds a permissions array to PluginDeclaration, a type-level Permissions
registry that plugins extend with module augmentation, and
registerPluginPermissions() called from all entrypoints. checkPermission
and the web UI hasPermission helpers now take PermissionName so typos are
compile errors. Core definitions move into a corePermissions array that
the interface derives its keys from.
Closes#987
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com>
Migrate the usage of CommonJS specific code and imports to ESM. Roughly
summarised this:
- Changes all require() calls to import statement or expressions.
- Changes usage of module.exports to export statements.
- Replaces require.resolve with import.meta.resolve.
- Replaces conditional imports with conditional subpath imports.
- Replaces module === require.main with import.meta.main.
- Adds the full filename with extention to all relative imports.
- Changes all usage of __dirname and __filename to their import.meta
equivalents.
- Sets "type": "module" in package.json files.
- Removes usage of "use strict".
- Adds .ts an an alias of .js for the webpack build.
Due to differences in how require and import resolves files the plugin
entrypoints will have to include the filename and extension after this
migration in order to work.