Commit Graph
431 Commits
Author SHA1 Message Date
bbassie db84455c34 Merge remote-tracking branch 'upstream/master' into refactor/plugin-hooks-v2
# Conflicts:
#	packages/lib/src/data/messages_plugin.ts
#	packages/web_ui/src/components/PluginViewPage.tsx
#	test/lib/plugin.js
2026-09-12 18:07:12 +00:00
bbassieandClaude Fable 5.1 8363b221bb Address review on plugin docs and web plugins map
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>
2026-09-12 08:29:16 +00:00
Cooldude2606 c2325a8b6e Add missing as const for permission string 2026-09-12 02:12:49 +01:00
Cooldude2606 06a6a7edf3 Merge branch 'master' into feature/update-all 2026-09-12 02:07:54 +01:00
BastiaanandClaude Fable 5.1 1a10b38743 Only report a missing plugin web build when one is expected (#1013)
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>
2026-09-12 01:59:16 +01:00
7122cbccf8 Add typed hook system for plugins
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>
2026-09-11 22:49:09 +00:00
8c03ac15ab Define permissions on PluginDeclaration (#988)
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>
2026-09-11 23:28:30 +01:00
bbassieandClaude Fable 5.1 7ff2092547 Add update all for the controller, hosts and plugins
Updating a single plugin can leave the installation with multiple
versions of @clusterio/lib. Passing the core package and every npm
installed plugin to a single npm update call lets npm resolve them
together.

UpdateAllRequest targets the controller or a host, requires both the
core update permission and core.plugin.update, and is gated by both
allow_remote_updates and allow_plugin_updates. The web UI Updates modal
gets an Update All action and ctl gets --all on the update commands.

Closes #910

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 22:40:32 +00:00
bbassieandClaude Opus 5 9b17fa2b93 Add host, server name and start options to the create instance modal
Accounts with core.instance.assign get a host selector, defaulting to
unassigned. Picking a host offers "Start after creation", which creates a
new save on first run when no save is copied, and together with a config
to copy it offers "Copy Save". Copying a config also offers a server
name, prefilled from the source, which overwrites factorio.settings.name
so the clone does not show up under the source name in the server
browser.

useInstanceConfig now clears the config while loading and drops errors,
so an account without core.instance.get_config just does not get the
server name field.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 09:40:00 +00:00
bbassieandClaude Fable 5.1 c997fd8ea7 Add save copying and start options when creating an instance
The create instance modal can now copy the active save of the instance
whose config is copied, optionally saving the running game first, and
start the new instance once the copy is done. The new instance is
assigned to the host of the source so the save can be transferred.

Saving a running game is a new InstanceSaveGameRequest handled by the
host through /server-save, which waits for the save to finish. It is
also exposed as clusterioctl instance save-game.

Closes #405

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 22:19:24 +00:00
bbassieandClaude Fable 5 2f4e0c4418 Make staticRoot include the static/ prefix and use it for plugin bundles
staticRoot was set to the same value as webRoot, so the static/ prefix
ended up scattered across consumers: index.html relied on the manifest
path containing it, the export stores hardcoded `${staticRoot}static/`,
and bootstrap loaded plugin bundles from webRoot instead. Now staticRoot
is `${webRoot}static/` and the controller strips the static/ prefix from
bundle paths before handing them to the page and to /api/plugins.

Plugins that fetch assets with `${staticRoot}static/...` need to drop the
static/ part.

Fixes #980

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 09:50:08 +00:00
BastiaanandClaude Fable 5 7bd408b45e Restore pagination on the instance save list (#973)
useTableQueryState returns pagination=false unless a pagination config
is passed, and SavesList did not pass one after #930, so the saves table
rendered all saves on one page.

Fixes #967

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 23:22:57 +01:00
Cooldude2606 03f2616f30 Merge pull request #955 from bbassie/feature/console-log-filter-labels
Fix the instance console hiding Factorio diagnostics
2026-08-13 00:20:44 +01:00
Cooldude2606 244d637fac Reduce tooltip length 2026-08-12 22:25:31 +01:00
bbassieandClaude Opus 5 71db8e57f5 Label Factorio output in the console with its log level
The Web UI console rendered server level entries as bare parsed Factorio
output, while every other level was prefixed with its name. Nothing in a
line distinguished what the game wrote from what Clusterio wrote about it,
which matters more now that both modes of the filter carry a mix of the
two.

Emit the [server] prefix for those entries as well, matching what
TerminalFormat already does for the host and controller logs, and style the
level the same bold the terminal colourises it with.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 10:58:30 +00:00
bbassieandClaude Opus 4.8 16092ec967 Make the instance console log filter discoverable
The filter was a Switch showing only the label of the mode it was already
in, so with the default of chat mode it read as a button labelled "Chat"
with no hint that a Log mode existed or that anything was being hidden.

Swap it for a Segmented control listing both Chat and Log, so the mode that
is not active is always visible, and add a tooltip describing what each one
covers.

Fixes #853

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-12 10:58:21 +00:00
bbassieandClaude Opus 4.8 42dc83df80 Stop the instance console filter swallowing diagnostics
The console's chat filter kept only Factorio output parsed as an action,
which dropped the server's replies to those actions. Running a console
command with a typo in it echoed the command and then appeared to print
nothing, because "Cannot execute command. Error: ..." is a plain message
rather than an [ACTION] line. Factorio's engine level errors were dropped
the same way, so a failed multiplayer connection left no trace either.

Filter by where a line came from instead. Factorio interleaves two streams
on stdout: the date stamped console log, which carries what was done to the
server and the replies to it, and the seconds stamped engine log, which is
verbose diagnostic output. Keep the console log whole and take only
warnings and errors from the engine log.

Apply the same rule to Clusterio's own entries, which were previously kept
in full and drowned the Factorio output they were mixed with. Keep those at
warn or worse and drop the routine bookkeeping. Replayed over a development
cluster's history this drops 415 informational lines and surfaces engine
errors that were never visible in either mode.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-12 10:58:19 +00:00
Cooldude2606 15a8215a4f Merge pull request #956 from bbassie/feature/plugin-change-refresh
Reload the web interface when the controller's plugins change
2026-08-10 16:57:30 +01:00
Cooldude2606 499d219933 Remove overly verbose comments 2026-08-10 16:45:56 +01:00
bbassieandClaude Opus 4.8 1e4b586fa3 Distinguish a plugin that was never loaded from one that failed
The plugin list reported "Error loading module" for anything without a
loaded package, which includes plugins the controller has picked up since
the page was opened. Nothing had been tried for those, so reporting a
failure was both wrong and unhelpful: the fix is to reload, not to
investigate.

Report those as needing a reload instead. Plugins the controller cannot
serve at all are excluded during startup before they reach the loaded
plugin map, so those are checked for first and still count as an error.

Fixes #729

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-10 10:01:38 +00:00
bbassie e3c18db7b8 Reload the web interface when the controller's plugins change
The web interface loads plugin bundles, registers their messages and
adds their config fields once in bootstrap, and has no way to undo any
of it.  A controller restarted to pick up a plugin being installed,
enabled or disabled comes back running a different set, and a controller
started in recovery mode runs none of them while still accepting control
connections, so open pages carry on sending messages it no longer
handles.

The controller already states the plugins it is running in the handshake
it sends on every connection, which is what hosts track as serverPlugins
and use to decide what to forward and which instance plugins to load.
Compare that against the set the page loaded and stop rendering on a
mismatch, asking to be reloaded instead.

The comparison is against loaded rather than enabled because loaded is
what the handshake reports and what the controller is actually running;
enabled also takes load_plugin into account, which only applies from the
next restart and would otherwise latch a permanent mismatch.

Closes #804
2026-08-10 10:01:35 +00:00
Loi Nguyen dfe52df7bb Show informational mod recommendations 2026-07-26 17:05:44 +07:00
Cooldude2606 58f2ad9810 Merge pull request #930 from bbassie/feature/table-url-state
Persist table sort/filter state in the URL
2026-07-10 23:13:53 +01:00
Cooldude2606 3cc99a1ea6 Fix commit filter logic for empty search strings 2026-07-10 23:10:54 +01:00
bbassieandClaude Opus 4.8 40407e52d6 Fix clearing a live-but-uncommitted search re-committing the old value
commitFilter now reads the live overrides from a ref that setFilter updates
synchronously, so committing within the same event that changed a filter no
longer sees a stale render closure. antd's Input.Search fires onSearch (before
onChange) when the clear button is clicked, so onSearch now commits the cleared
state instead of closing the dropdown, which had re-committed the still-live
typed value.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 22:02:25 +00:00
bbassieandClaude Opus 4.8 2898fdf5d3 Write the URL immediately when a search filter is cleared
The search box clear (X) button cleared the filter live but only wrote the URL
when the dropdown closed. commitFilter now accepts an explicit value so the
clear handlers can commit right away (avoiding the stale-state race of reading
the live override in the same event), for both useColumnSearch and the Users
filter.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 21:37:32 +00:00
bbassieandClaude Opus 4.8 bb4c82ea5a Make all table search live and only write the URL on close
Add a live filter override to useTableQueryState: setFilter updates the filter
immediately (so every table's search filters as you type) without touching the
URL, and commitFilter writes it to the URL when the filter dropdown closes.
useColumnSearch and useUserFilter now filter live via setFilter and commit on
close, so the Users table no longer rewrites the URL on every keystroke and the
other tables gain live search.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 00:14:32 +00:00
bbassieandClaude Opus 4.8 1f2730a64f Add name-column search to the Mods and Mod Packs tables
These were the only tables without the column search filter; wire useColumnSearch
into both name columns with URL-persisted filteredValue, matching the other tables.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 23:58:16 +00:00
Cooldude2606 43238feada Merge pull request #941 from bbassie/feature/lenient-mod-version-parsing
Parse mod versions leniently like the game does
2026-07-02 00:50:06 +01:00
Cooldude2606 573274875c Rename to SourceVersion 2026-07-02 00:27:05 +01:00
Cooldude2606 2e27c4f499 Maintain strict tagging of GameVersion 2026-07-02 00:08:09 +01:00
bbassieandClaude Opus 4.8 8029ad3b2d Resolve GameVersion type as a plain-string supertype
Cooldude's GameVersion brand was disjoint from the clean version types in
both directions, forcing safeVersion/safeFactorioVersion companions and
breaking widening from FullVersion at file-name call sites.

Make GameVersion a plain string alias: the raw, un-normalised version as the
game reads it. The stricter template-literal types (FullVersion,
MajorMinorVersion, PartialVersion) now widen into it for free, while a
GameVersion still can't be used where a normalised version is required
without going through normaliseGameVersion/integerGameVersion. Runtime
validation stays in GameVersionSchema.

- Drop safeVersion/safeFactorioVersion; ModInfo.version stays raw (verbatim so
  it matches the mod's file name), integerVersion normalises via the new
  integerGameVersion. factorioVersion reverts to a normalised MajorMinorVersion
  since it is never part of a file name.
- ModRecord.version becomes a GameVersion so mod packs round-trip the exact
  file-name version.
- ModVersionEquality.testVersion / ModVersionRange.testVersion accept a raw
  GameVersion and normalise it; the equality bound stays strictly validated.
- Migration only pads clean partial versions; raw versions are kept verbatim.
- Cover the leading-zeros case: version kept verbatim in the file name while
  comparing equal to its normalised form.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 13:54:06 +00:00
Cooldude2606 eca5cdcd9e Merge pull request #939 from bbassie/feature/resizable-console
Make the Web UI console resizable
2026-07-01 00:25:28 +01:00
Cooldude2606 523293d4d9 Manual adjustments to GameVersion and ReleaseChannel types
Through the creation of this commit, it was discovered that ModInfo.version must be typed as GameVersion due to file names. This results in wide spread propagation of the unsafe type.  The full impact including on ModRecord and ModVersion.testVersion have no been implemented until a path forward is decided.
2026-06-30 23:39:08 +01:00
bbassie e27ad5fdf9 Merge remote-tracking branch 'upstream/master' into feature/lenient-mod-version-parsing
# Conflicts:
#	packages/lib/src/data/version.ts
2026-06-29 22:57:48 +00:00
bbassie 0874c26208 Merge remote-tracking branch 'upstream/master' into feature/table-url-state
# Conflicts:
#	packages/web_ui/src/components/HostsPage.tsx
#	packages/web_ui/src/components/InstanceList.tsx
#	packages/web_ui/src/components/ModsPage.tsx
#	packages/web_ui/src/components/PluginsPage.tsx
#	packages/web_ui/src/components/RolesPage.tsx
#	packages/web_ui/src/components/UserViewPage.tsx
#	packages/web_ui/src/components/UsersTable.tsx
#	packages/web_ui/src/index.ts
2026-06-29 22:37:03 +00:00
bbassieandClaude Opus 4.8 1e7b8f08cf Address review: tidy pagination handling and comments
Let useTableQueryState accept a full TablePaginationConfig (only defaultPageSize
is read) so UsersTable passes its pagination prop straight through instead of
extracting and casting it. Drop the redundant pagination: false from the tables
that just want it disabled (that is already the default). Clarify the comment on
the Last Seen online-only default to mention the onlyOnline prop, and explain why
onChange only uses the first sorter.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 22:26:39 +00:00
bbassieandClaude Opus 4.8 7c2793752d Add MajorMinorVersion type and dedupe the game version regex
Address review feedback: introduce a MajorMinorVersion type with
isMajorMinorVersion, rename majorMinorVersion to normaliseMajorMinorVersion,
and type ModInfo.factorioVersion as MajorMinorVersion so already-reduced
values are not re-reduced (ModPackViewPage uses mod.info.factorioVersion
directly). Reuse the single gameVersionRegExp in both GameVersionSchema and
normaliseGameVersion instead of duplicating the pattern.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 22:12:36 +00:00
bbassieandClaude Opus 4.8 5cde390c24 Use a spacer element for console scroll headroom
Replace the padding-top scroll headroom with a dedicated .console-spacer
element so the pin-to-bottom keeps working without padding inflating the
measured height, and trim the style.css comments.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 22:01:06 +00:00
bbassieandClaude Opus 4.8 2dedb44372 Fix being unable to shrink the console after enlarging it
The resize observer measured offsetHeight, which on the content-box console
includes the pin-to-bottom padding-top. Feeding that back into the height
(and thus the padding) meant the padding inflated the measured height, so
the console could be enlarged but never dragged smaller again.

Make the console border-box so its height already includes the padding and
offsetHeight matches the dragged height, breaking the feedback. The default
height is bumped to 600 so the rendered size stays the same as the previous
content-box height + padding.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 16:38:35 +00:00
bbassieandClaude Opus 4.8 97a6c32838 Parse mod versions leniently like the game does
The mod portal "ApiVersion" enum was validated strictly when loading a
mod's info.json, which rejected versions the game itself accepts. Versions
are now normalised the same lenient way the game reads them: leading
whitespace is allowed, the major and minor are required, the patch is
optional, any trailing content is ignored, and numbers are read as integers.

This applies to both a mod's version and its factorio_version, validated by
a regex schema rather than the fixed enum. The enum is no longer used to
validate versions at all - the mod portal is the source of truth, so its
error is now surfaced when a version is unsupported. The known-version list
kept for the web interface fallback gains 2.1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 16:24:43 +00:00
BastiaanandClaude Opus 4.8 a56fcbd785 Fix antd deprecation and TreeSelect duplicate-value warnings (#940)
Rename the deprecated Modal `destroyOnClose` prop to `destroyOnHidden`
(antd 5.25+) across the web UI.

Skip a version-tree child equal to its major.minor group key in
InputVersion, which triggered "Same value exist in the tree" warnings when
the version list contained bare major.minor entries (the ApiVersions
fallback shown before the version fetch resolves).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 17:21:52 +01:00
Cooldude2606 e16603bbd0 Merge pull request #937 from bbassie/feature/instance-version-channels
Add release channels (stable/experimental) as instance version targets
2026-06-26 17:00:44 +01:00
Cooldude2606 ce8bddad8f Merge pull request #931 from bbassie/feature/open-in-new-tab
Make navigation open in new tabs (real links)
2026-06-26 15:47:33 +01:00
bbassieandClaude Opus 4.8 3fe0c85055 Make the Web UI console resizable
The instance console was locked to a fixed 300px height. It can now be
drag-resized vertically (native resize handle), and the chosen height is
persisted with useLocalStorage so it is restored on the next visit.

Both the height and the scroll-anchor padding are driven by a
--console-height CSS variable, so the pin-to-bottom behaviour keeps working
at any size.

Closes #938

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 14:47:32 +00:00
bbassieandClaude Opus 4.8 9da48d5387 Add release channels as instance version targets
An instance's factorio.version can now target a release channel such as
"stable" or "experimental" (any key from factorio.com/api/latest-releases)
in addition to "latest" and specific X.Y / X.Y.Z versions.

The controller proxies the latest-releases API through a ValueCache and
persists the last successful response to latest-releases.json, serving that
copy when the API is unreachable. On instance start the host resolves a
channel to the concrete version it points to and feeds it into the existing
checkForUpdates/findVersion path, so a channel auto-installs and runs like a
pinned version. The version selector in the web UI lists the available
channels alongside "latest".

Also clarifies the host log/error when a newer version cannot be downloaded
automatically because the host uses a direct (single-version) Factorio
install rather than a versioned directory.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 14:19:34 +00:00
bbassieandClaude Opus 4.8 2bd9bc6138 Fill table link cells with a block link instead of an overlay
Replace the absolutely-positioned a::after overlay with display:block on
the link. The overlay covered sibling cell content (the restart-required
icon), swallowing its hover so the tooltip never triggered. A block link
fills the cell width without overlapping siblings.

Also skip rendering the assigned-host link when an instance has no
assigned host, avoiding a dead /hosts/undefined/view link.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 12:24:34 +00:00
Daniel ea5c35198f Merge pull request #924 from bbassie/feature/sidebar-layout
Rework site layout into a fixed sidebar
2026-06-26 10:06:23 +02:00
bbassieandClaude Opus 4.8 448eb9223d Make table link cells fill the cell and fix focus colour
Stretch the navigation link over the whole table cell (a position:relative
cell with an ::after overlay) so the entire name cell - and the Instances
Assigned Host cell - is clickable and right-click "open in new tab"-able,
not just the text. Also force color:inherit on the sidebar and account links
so the focus left behind by a middle-click no longer shows them blue.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 20:55:29 +00:00
bbassieandClaude Opus 4.8 cc0be69019 Enlarge the sidebar logo to 56px
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 18:41:25 +00:00