Settings stored in a mod pack that no setting prototype in the export
data defines are now listed in a collapsed "Unknown settings" panel
under each scope instead of under an "Unknown Fields" heading. Each one
has a remove link, and a revert link once removed, matching what
`clusterioctl mod-pack edit --remove-setting` already allows. They are
never removed automatically.
Without export data nothing is called unknown and stored settings are
listed as before, without a mod heading.
Fixes#773
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
InstanceDetails now carries factorio.mod_pack_id as modPackId, so the
instance list and pages can show it without fetching each config. The
web UI resolves an unset value to the controller default and tags it
"Default". The name links to the mod pack page.
Closes#852
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Keep a syncing spectator where its body stood
A player waiting for player_sync data is a spectator who could walk away,
so restore_position teleported them back at the end. That also undid
teleports by other mods made during the sync, such as universal_edges
placing an arriving player.
The spectator is now pulled back on every tile change, and a jump of more
than 4 tiles or to another surface counts as another mod's teleport and
moves where the body comes back. A failed acquire that takes the body
keeps its place too.
Closes#908
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Rename body place to controller location
Review asked for location and controller over place and body.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Let a syncing spectator look around
Review asked for the spectator to move freely while it waits. Only a jump
of more than 16 tiles from where it was last seen, or to another surface,
counts as a teleport and moves the restore location. The body place names
from #1156 become restore_location, keep_restore_location and
controller_taken.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Review asked for the spectator to move freely while it waits. Only a jump
of more than 16 tiles from where it was last seen, or to another surface,
counts as a teleport and moves the restore location. The body place names
from #1156 become restore_location, keep_restore_location and
controller_taken.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A player waiting for player_sync data is a spectator who could walk away,
so restore_position teleported them back at the end. That also undid
teleports by other mods made during the sync, such as universal_edges
placing an arriving player.
The spectator is now pulled back on every tile change, and a jump of more
than 4 tiles or to another surface counts as another mod's teleport and
moves where the body comes back. A failed acquire that takes the body
keeps its place too.
Closes#908
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Add base instances for instance config inheritance
Instances can be marked as a base with instance.is_base and other
instances inherit config values from it through instance.base_id.
The controller copies base values into inheriting configs and keeps
track of inherited fields in the hidden instance.inherited_fields, so
hosts receive resolved configs and need no changes. Setting a field
stops inheriting it, `instance config inherit` undoes that.
Bases cannot be assigned to a host, are hidden from `instance list`
unless --bases is given, and cannot be deleted or unmarked while
other instances inherit from them. Chains of bases are allowed and
cycles are rejected.
Part of #200
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Rename bases to templates and add per property inheritance
Review changes for #1142:
- base becomes template everywhere (is_template, template_id,
--template, --is-template, --templates, InstanceDetails.isTemplate).
- Object fields inherit per property. inherited_fields lists the
overridden properties for object fields and the template object is
merged with them.
- Linking a template also inherits fields that hold the default value.
The server name stays per instance.
- Ports are never inherited.
- InstanceDetails carries templateId.
Web UI: inherited values are locked until unlocked, overridden values
can be inherited again, templates are listed separately, the create
modal can create templates and instances from templates, and the
instance page has "Create template" which can link the source.
Part of #200
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Give templates their own table and keep rcon_password per instance
The templates table shows name, inheritor count and a create instance
action instead of the instance status and start/stop columns.
factorio.rcon_password joins the ports as never inherited.
Part of #200
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Use link and disconnect icons for template inheritance
Overridden values show a disconnect icon. Clicking it stages a link,
shown with a link icon, which is applied together with the other
changes instead of right away.
Part of #200
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Show the template value while a link is staged
Part of #200
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Address review of template inheritance
- Unlinking a field enables Apply on its own and sends the current value.
- The link button no longer toggles the checkbox of its label.
- Link and disconnect icons for all four states, pending ones highlighted.
- instance.is_template is hidden.
- ctl create takes --as-template.
- Clearer names for the InstanceManager inheritance helpers.
Part of #200
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- Unlinking a field enables Apply on its own and sends the current value.
- The link button no longer toggles the checkbox of its label.
- Link and disconnect icons for all four states, pending ones highlighted.
- instance.is_template is hidden.
- ctl create takes --as-template.
- Clearer names for the InstanceManager inheritance helpers.
Part of #200
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Overridden values show a disconnect icon. Clicking it stages a link,
shown with a link icon, which is applied together with the other
changes instead of right away.
Part of #200
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The templates table shows name, inheritor count and a create instance
action instead of the instance status and start/stop columns.
factorio.rcon_password joins the ports as never inherited.
Part of #200
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review changes for #1142:
- base becomes template everywhere (is_template, template_id,
--template, --is-template, --templates, InstanceDetails.isTemplate).
- Object fields inherit per property. inherited_fields lists the
overridden properties for object fields and the template object is
merged with them.
- Linking a template also inherits fields that hold the default value.
The server name stays per instance.
- Ports are never inherited.
- InstanceDetails carries templateId.
Web UI: inherited values are locked until unlocked, overridden values
can be inherited again, templates are listed separately, the create
modal can create templates and instances from templates, and the
instance page has "Create template" which can link the source.
Part of #200
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Instances can be marked as a base with instance.is_base and other
instances inherit config values from it through instance.base_id.
The controller copies base values into inheriting configs and keeps
track of inherited fields in the hidden instance.inherited_fields, so
hosts receive resolved configs and need no changes. Setting a field
stops inheriting it, `instance config inherit` undoes that.
Bases cannot be assigned to a host, are hidden from `instance list`
unless --bases is given, and cannot be deleted or unmarked while
other instances inherit from them. Chains of bases are allowed and
cycles are rejected.
Part of #200
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"safe" suggested the data being written survives a crash, it only
guarantees the old file is either kept or fully replaced. The new name
says what it does. No alias, plugin API breaks are allowed in minor
versions and the 2.1 migration guide lists the rename.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com>
* Report missing base game icons when exporting from headless Factorio
The headless build has no graphics, so export-data found none of the
base game's icon files. Plain icons were skipped silently and layered
icons were drawn blank and counted as exported.
Skip a layered icon when any of its layers is missing, and log one
warning with the number of base game icon files that were not found.
Add host.factorio_graphics_directory to read builtin mod graphics from
a full install when the instance runs the headless build.
Fixes#1068
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Export without the graphics directory when it has no matching install
If host.factorio_graphics_directory had no install matching the major and
minor version of the instance, or did not exist, findVersion threw and the
whole export failed. Without the setting the same instance exports everything
except the base game icons, so it now logs a warning that names the setting
and exports without it.
* Find a full install in host.factorio_directory instead of a setting
Per review, drop host.factorio_graphics_directory. findVersion takes a
headless option that filters installs by build, telling them apart by
data/base/graphics/icons. When the instance runs the headless build the
export looks for a full install of the same major.minor version in the
host's factorio directory. Server startup is unchanged.
If none is found the export still runs, and the reason is included in
the missing icons warning.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Address review on findVersion and the export graphics lookup
findVersion takes headless as a plain optional argument, the helper is
checkInstallType, the warning is checkMissingBuiltinIcons and the full
install lookup is inlined into exportData.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Danielv123 <danielv@danielv.no>
Factorio encodes an empty Lua table as {}, so a player with no cleared
recipes sent {} with the download request. new Set({}) threw, the
instance dropped the recipe notifications from the download, and the
next upload replaced the stored list on the controller. An empty stored
list was dropped the same way. Decode both lists treating {} as empty.
The mod pack view gave the settings form its values through the Form's
initialValues, which rc-field-form only applies when the form is
created. Settings added by a data export while the page was open were
shown empty, and color settings black, until the page was reloaded.
Each setting field now gets its saved value as its own initialValue,
which is applied when the field is created and when the form is reset.
findVersion takes headless as a plain optional argument, the helper is
checkInstallType, the warning is checkMissingBuiltinIcons and the full
install lookup is inlined into exportData.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Per review, drop host.factorio_graphics_directory. findVersion takes a
headless option that filters installs by build, telling them apart by
data/base/graphics/icons. When the instance runs the headless build the
export looks for a full install of the same major.minor version in the
host's factorio directory. Server startup is unchanged.
If none is found the export still runs, and the reason is included in
the missing icons warning.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
If host.factorio_graphics_directory had no install matching the major and
minor version of the instance, or did not exist, findVersion threw and the
whole export failed. Without the setting the same instance exports everything
except the base game icons, so it now logs a warning that names the setting
and exports without it.
inquirer 8 pulls in 50 packages and 7.4 MB (rxjs, old chalk) for every
user running npm init @clusterio. The standalone select, input, confirm
and checkbox packages from the same project cover all prompts used and
cut the create install from 70 packages / 8.1 MB to 31 / 0.94 MB.
The Windows rl.close workaround now patches readline.Interface instead
of inquirer's Prompt class, since the new prompts create their own
readline interface. Ctrl+C at a prompt exits with code 130 instead of
0, and closed stdin no longer crashes with ERR_USE_AFTER_CLOSE.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every view page handled a resource that can't be shown its own way, and
users without the subscribe permission were left on a spinner forever.
Adds useResourceState and a ResourceState component, used by the host,
instance, mod pack, icon reference, user, role and plugin pages and
exported for plugin pages. The single item hooks now also return the
subscription error.
Resolves#1165
An icon that could not be read or decoded, such as a truncated PNG, a
JPEG or a layered icon with no layers, threw out of exportIcons and
failed the whole data export. Log a warning naming the prototype and
export it without an icon instead. Also warn when a mod's zip is
missing while exporting the locale, which was skipped silently.
With --dev-plugin and no --dev the page kept the production import map,
while the development server gave the plugin its own copies of the
shared modules. The plugin then failed to load with "Attempt to import
duplicate copy of @clusterio/lib". Use the development server's web
interface and import map whenever it runs, so the plugin and the web
interface share the same modules.
Revoking a host now unassigns its instances instead of being refused
while it has any, and removes its system metrics.
Approving and revoking no longer force a save. Hosts are saved by
autosave and on exit like everything else, which also removes the
approving state from pairing requests.
The approval code and link are logged as warnings so they show up in
the systemd service, which only prints warnings and above. The pairing
limits are now the controller.host_pairing_timeout and
controller.host_pairing_max_pending config values, 0 removes the limit.
Subscribers take an optional permitted function, used for the pairing
requests instead of overrides in a subclass.
A standalone install no longer adds its host to the controller database
by signing a token and writing hosts.json itself. The host asks to join
when it first starts and is approved once with the link it prints.
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.
- Saves of a datastore run one at a time, and changes made while saving
are saved by the next save. Overlapping saves from approving and
revoking hosts could write an older snapshot after a newer one.
- Map subscribers fetch everything again when a new session starts, as
the controller only sends values that exist. A host revoked while a
browser was disconnected stayed in its list.
- The host token check accepts the same pre-rename tokens as the
WebSocket, so upgrading a host with such a token does not make it ask
to join again.
* Answer the disconnect prepare in the close old connections test
The test's first connector had no Link, so the controller's disconnect
prepare went unanswered and it waited the full 10 s timeout.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Start the controller and host CLIs faster
Like ctl, the controller and host now compile schema validators on first
use instead of at import, which cost about 120 ms per invocation. The
Controller and Host classes are only imported by the commands that
start them, so config, bootstrap and the other commands no longer load
express, ws and the rest of the servers. clusteriocontroller config set
goes from 413 ms to 231 ms and clusteriohost config set from 412 ms to
225 ms.
The integration tests run the bins, like npx does, instead of the
package directories.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Splitting the test jobs doubled them to 14, over GitHub's concurrent
job limit together with the other jobs. Every Factorio version now runs
on the latest Node.js only, and the other Node.js versions only with
the latest Factorio, which is 10 test jobs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The clusterioctl, plugin system and instance integration tests take
about half of the test time, so they run on one runner and the rest of
the tests on another, each half with its own controller and host.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* 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>