mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-11 23:04:52 +09:00
Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e39b587df | ||
|
|
e28789c1c0 | ||
|
|
4caf06aafc | ||
|
|
6b0dd4e9cf | ||
|
|
a2c38ad4e8 | ||
|
|
3a823bee01 | ||
|
|
98650b9bfb | ||
|
|
a781af5b6a | ||
|
|
f202a0975a | ||
|
|
ea4280d184 | ||
|
|
c519ee1389 | ||
|
|
c61b9dbe60 |
No files matched your search
@@ -120,7 +120,7 @@ There's also the `add_libraries` function exported by event_handler, which accep
|
||||
## Communicating with Clusterio
|
||||
|
||||
Clusterio uses a homebrew protocol based on sending JSON payloads over a WebSocket connection.
|
||||
See [socket.md](socket.md) for the implementation details of it.
|
||||
See [protocol.md](devs/protocol.md) for the implementation details of it.
|
||||
|
||||
It's also possible to write a plugin for Clusterio that exposes a custom interface over HTTP, WebSocket or any other technology supported by Node.js.
|
||||
|
||||
|
||||
@@ -134,6 +134,12 @@ function getPlugins(req: Request, res: Response) {
|
||||
res.send(plugins);
|
||||
}
|
||||
|
||||
function getClusterName(req: Request, res: Response) {
|
||||
const controller: Controller = req.app.locals.controller;
|
||||
const clusterName = controller.config.get("controller.name");
|
||||
res.json({ name: clusterName });
|
||||
}
|
||||
|
||||
function validateHostToken(req: Request, res: Response, next: any) {
|
||||
let token = req.header("x-access-token");
|
||||
if (!token) {
|
||||
@@ -662,6 +668,7 @@ async function uploadMod(req: Request, res: Response) {
|
||||
export function addRouteHandlers(app: Application) {
|
||||
app.get("/metrics", (req:Request, res:Response, next:any) => getMetrics(req, res, next).catch(next));
|
||||
app.get("/api/plugins", getPlugins);
|
||||
app.get("/api/cluster-name", getClusterName);
|
||||
app.put("/api/upload-export",
|
||||
validateHostToken,
|
||||
(req:Request, res:Response, next:any) => uploadExport(req, res).catch(next)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"description": "Installer for clusterio",
|
||||
"author": "Hornwitser",
|
||||
"license": "MIT",
|
||||
"version": "2.0.0-alpha.22",
|
||||
"version": "2.0.0-alpha.22b",
|
||||
"main": "create.js",
|
||||
"bin": {
|
||||
"clusteriocreate": "./create.js"
|
||||
|
||||
@@ -70,7 +70,6 @@ function serialize.serialize_equipment_grid(grid)
|
||||
if equipment.to_be_removed then
|
||||
entry.t = equipment.to_be_removed
|
||||
end
|
||||
log(entry)
|
||||
table.insert(serialized, entry)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import zlib from "zlib";
|
||||
|
||||
type FactorioVersion = [number, number, number, number];
|
||||
|
||||
class MapReaderState {
|
||||
pos = 0;
|
||||
last_position = { x: 0, y: 0 };
|
||||
/** True when a version greater than 2.0.0 is detected */
|
||||
v2 = false;
|
||||
version: FactorioVersion | null = null;
|
||||
constructor(
|
||||
public buf: Buffer
|
||||
) { }
|
||||
@@ -110,7 +113,7 @@ function readDict<K, V>(
|
||||
return mapping;
|
||||
}
|
||||
|
||||
function readVersion(state: MapReaderState) {
|
||||
function readVersion(state: MapReaderState): FactorioVersion {
|
||||
let major = readUInt16(state);
|
||||
let minor = readUInt16(state);
|
||||
let patch = readUInt16(state);
|
||||
@@ -118,6 +121,24 @@ function readVersion(state: MapReaderState) {
|
||||
return [major, minor, patch, developer];
|
||||
}
|
||||
|
||||
function isVersionAtLeast(state: MapReaderState, major: number, minor: number, patch: number, developer: number) {
|
||||
if (!state.version) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const [vMajor, vMinor, vPatch, vDeveloper] = state.version;
|
||||
if (vMajor !== major) {
|
||||
return vMajor > major;
|
||||
}
|
||||
if (vMinor !== minor) {
|
||||
return vMinor > minor;
|
||||
}
|
||||
if (vPatch !== patch) {
|
||||
return vPatch > patch;
|
||||
}
|
||||
return vDeveloper >= developer;
|
||||
}
|
||||
|
||||
function readFrequencySizeRichness(state: MapReaderState) {
|
||||
return {
|
||||
frequency: readFloat(state),
|
||||
@@ -262,7 +283,8 @@ function readEnemyEvolution(state: MapReaderState) {
|
||||
}
|
||||
|
||||
function readEnemyExpansion(state: MapReaderState) {
|
||||
return {
|
||||
let evolutionGroupSizeFactor: number | null | undefined;
|
||||
const settings = {
|
||||
enabled: readOptional(state, readBool),
|
||||
max_expansion_distance: readOptional(state, readUInt32),
|
||||
friendly_base_influence_radius: readOptional(state, readUInt32),
|
||||
@@ -274,9 +296,36 @@ function readEnemyExpansion(state: MapReaderState) {
|
||||
max_colliding_tiles_coefficient: readOptional(state, readDouble),
|
||||
settler_group_min_size: readOptional(state, readUInt32),
|
||||
settler_group_max_size: readOptional(state, readUInt32),
|
||||
min_expansion_cooldown: readOptional(state, readUInt32),
|
||||
max_expansion_cooldown: readOptional(state, readUInt32),
|
||||
min_expansion_cooldown: null,
|
||||
max_expansion_cooldown: null,
|
||||
} as {
|
||||
enabled: boolean | null;
|
||||
max_expansion_distance: number | null;
|
||||
friendly_base_influence_radius: number | null;
|
||||
enemy_building_influence_radius: number | null;
|
||||
building_coefficient: number | null;
|
||||
other_base_coefficient: number | null;
|
||||
neighbouring_chunk_coefficient: number | null;
|
||||
neighbouring_base_chunk_coefficient: number | null;
|
||||
max_colliding_tiles_coefficient: number | null;
|
||||
settler_group_min_size: number | null;
|
||||
settler_group_max_size: number | null;
|
||||
min_expansion_cooldown: number | null;
|
||||
max_expansion_cooldown: number | null;
|
||||
evolution_group_size_factor?: number;
|
||||
};
|
||||
|
||||
if (isVersionAtLeast(state, 2, 1, 0, 0)) {
|
||||
evolutionGroupSizeFactor = readOptional(state, readDouble);
|
||||
}
|
||||
settings.min_expansion_cooldown = readOptional(state, readUInt32);
|
||||
settings.max_expansion_cooldown = readOptional(state, readUInt32);
|
||||
|
||||
if (evolutionGroupSizeFactor !== undefined) {
|
||||
settings.evolution_group_size_factor = evolutionGroupSizeFactor ?? 4.0;
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
function readUnitGroup(state: MapReaderState) {
|
||||
@@ -427,6 +476,7 @@ export function readMapExchangeString(exchangeString: string) {
|
||||
try {
|
||||
const version = readVersion(state);
|
||||
state.v2 = version[0] >= 2;
|
||||
state.version = version;
|
||||
data = {
|
||||
version: version,
|
||||
unknown: readUInt8(state),
|
||||
|
||||
@@ -275,6 +275,9 @@ function keyToLabels(key: string) {
|
||||
if (key !== "") {
|
||||
for (let pair of key.split(",")) {
|
||||
let [name, value] = pair.split("=", 2);
|
||||
if (!name || value === undefined) {
|
||||
continue;
|
||||
}
|
||||
labels.set(name, value
|
||||
.slice(1, -1)
|
||||
.replace(/\\"/g, "\"")
|
||||
|
||||
@@ -106,7 +106,7 @@ export default function HostViewPage() {
|
||||
</PageLayout>;
|
||||
}
|
||||
|
||||
let hostButtons = <Space>
|
||||
let hostButtons = <Space wrap>
|
||||
<HostControlButton hostId={hostId} canRestart={system?.canRestart} restartRequired={system?.restartRequired}/>
|
||||
{
|
||||
account.hasPermission("core.host.revoke_token")
|
||||
|
||||
@@ -141,7 +141,7 @@ function InstanceButtons(props: { instance: lib.InstanceDetails }) {
|
||||
}
|
||||
},
|
||||
};
|
||||
return <Space>
|
||||
return <Space wrap>
|
||||
{
|
||||
account.hasAnyPermission(...InstanceControlButtonPermissions)
|
||||
&& <InstanceControlButton instance={instance} />
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { Fragment, useContext } from "react";
|
||||
import React, { Fragment, useContext, useEffect, useState } from "react";
|
||||
import { Button, Card, Divider, Form, Image, Input, Space, Typography } from "antd";
|
||||
import LockOutlined from "@ant-design/icons/LockOutlined";
|
||||
|
||||
import notify from "../util/notify";
|
||||
import logo from "../images/logo.png";
|
||||
import ControlContext from "./ControlContext";
|
||||
|
||||
@@ -36,6 +37,23 @@ function TokenAuth(props: SetTokenProps) {
|
||||
|
||||
export default function LoginForm(props: SetTokenProps) {
|
||||
let plugins = useContext(ControlContext).plugins;
|
||||
const [clusterName, setClusterName] = useState<string>("Clusterio");
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
let response = await fetch(`${webRoot}api/cluster-name`);
|
||||
if (response.ok) {
|
||||
let data = await response.json();
|
||||
if (data.name) {
|
||||
setClusterName(data.name);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
notify("Failed to load cluster name");
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
let loginForms = [];
|
||||
for (let plugin of plugins.values()) {
|
||||
@@ -50,7 +68,7 @@ export default function LoginForm(props: SetTokenProps) {
|
||||
|
||||
return <Card style={{ maxWidth: "30em" }}>
|
||||
<Space style={{ width: "100%" }} direction="vertical" align="center">
|
||||
<h1>Clusterio</h1>
|
||||
<h1>{clusterName}</h1>
|
||||
<Image width={128} height={123} src={logo} />
|
||||
Log in to the cluster using one of the methods below.
|
||||
</Space>
|
||||
@@ -60,4 +78,3 @@ export default function LoginForm(props: SetTokenProps) {
|
||||
</Fragment>)}
|
||||
</Card>;
|
||||
}
|
||||
|
||||
@@ -664,7 +664,7 @@ function ModsTable(props: ModsTableProps) {
|
||||
return <>
|
||||
<SectionHeader
|
||||
title="Mods"
|
||||
extra={<Space>
|
||||
extra={<Space wrap>
|
||||
<Button
|
||||
icon={<ToolOutlined />}
|
||||
disabled={!mods.some(m => m.enabled && m.warning && m.warning !== "wrong_factorio_version")}
|
||||
|
||||
@@ -561,7 +561,7 @@ export default function ModsPage() {
|
||||
<PageHeader title="Mods" />
|
||||
<SectionHeader
|
||||
title="Mod Packs"
|
||||
extra={<Space>
|
||||
extra={<Space wrap>
|
||||
{account.hasPermission("core.mod_pack.create") && <ImportModPackButton />}
|
||||
{account.hasPermission("core.mod_pack.create") && <CreateModPackButton />}
|
||||
</Space>}
|
||||
@@ -594,7 +594,7 @@ export default function ModsPage() {
|
||||
},
|
||||
})}
|
||||
/>
|
||||
<SectionHeader title="Stored Mods" extra={<Space>
|
||||
<SectionHeader title="Stored Mods" extra={<Space wrap>
|
||||
<SearchModsButton />
|
||||
{uploadButton}
|
||||
</Space>} />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useContext, useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { Button, Checkbox, Form, Input, Popconfirm, Spin } from "antd";
|
||||
import { Button, Checkbox, Form, Input, Popconfirm, Space, Spin } from "antd";
|
||||
import DeleteOutlined from "@ant-design/icons/DeleteOutlined";
|
||||
|
||||
import * as lib from "@clusterio/lib";
|
||||
@@ -71,7 +71,7 @@ export default function RoleViewPage() {
|
||||
>
|
||||
<PageHeader
|
||||
title={role.name}
|
||||
extra={<>
|
||||
extra={<Space wrap>
|
||||
{canUpdate && <Button type={edited ? "primary" : "default"} htmlType="submit">Apply</Button>}
|
||||
{account.hasPermission("core.role.delete") && <Popconfirm
|
||||
title="Delete role?"
|
||||
@@ -90,7 +90,7 @@ export default function RoleViewPage() {
|
||||
<DeleteOutlined />
|
||||
</Button>
|
||||
</Popconfirm>}
|
||||
</>}
|
||||
</Space>}
|
||||
/>
|
||||
<Form.Item name="name" label="Name">
|
||||
<Input disabled={!canUpdate}/>
|
||||
|
||||
@@ -345,7 +345,7 @@ export default function SavesList(props: { instance: lib.InstanceDetails }) {
|
||||
};
|
||||
|
||||
return <div>
|
||||
<SectionHeader title="Saves" extra={<Space>
|
||||
<SectionHeader title="Saves" extra={<Space wrap>
|
||||
{account.hasPermission("core.instance.save.upload") && <Upload {...uploadProps} >
|
||||
<UploadButton disabled={hostOffline}>
|
||||
Upload save
|
||||
|
||||
@@ -106,7 +106,7 @@ export default function UserViewPage() {
|
||||
{user.isBanned && <Tag color="red">Banned</Tag>}
|
||||
</span>
|
||||
</Space>}
|
||||
extra={<>
|
||||
extra={<Space wrap>
|
||||
{account.hasPermission("core.user.revoke_token") && (
|
||||
account.name === userName || account.hasPermission("core.user.revoke_other_token")
|
||||
) && <Button
|
||||
@@ -146,7 +146,7 @@ export default function UserViewPage() {
|
||||
<DeleteOutlined />
|
||||
</Button>
|
||||
</Popconfirm>}
|
||||
</>}
|
||||
</Space>}
|
||||
/>
|
||||
<Form
|
||||
form={form}
|
||||
|
||||
@@ -249,6 +249,7 @@ export default function UsersTable({ instanceId, onlyOnline = false, pagination,
|
||||
rowKey={(user) => user.name}
|
||||
pagination={defaultPagination}
|
||||
size={size}
|
||||
scroll={{ x: "max-content" }}
|
||||
onRow={(user) => ({
|
||||
onClick: (event) => {
|
||||
if ((event.target as HTMLElement).closest("a")) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@clusterio/plugin-global_chat",
|
||||
"version": "2.0.0-alpha.22",
|
||||
"version": "2.0.0-alpha.22b",
|
||||
"description": "Clusterio plugin forwarding between Factorio servers",
|
||||
"author": "Hornwitser <github@hornwitser.no>",
|
||||
"homepage": "https://github.com/clusterio/clusterio#readme",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@clusterio/plugin-inventory_sync",
|
||||
"version": "2.0.0-alpha.22",
|
||||
"version": "2.0.0-alpha.22b",
|
||||
"description": "Clusterio plugin forwarding between Factorio servers",
|
||||
"author": "Danielv <danielv@danielv.no>",
|
||||
"homepage": "https://github.com/clusterio/factorioClusterio#readme",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@clusterio/plugin-player_auth",
|
||||
"version": "2.0.0-alpha.22",
|
||||
"version": "2.0.0-alpha.22b",
|
||||
"description": "Clusterio plugin authenticating logged in players to the web interface",
|
||||
"author": "Hornwitser <github@hornwitser.no>",
|
||||
"homepage": "https://github.com/clusterio/clusterio#readme",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@clusterio/plugin-research_sync",
|
||||
"version": "2.0.0-alpha.22",
|
||||
"version": "2.0.0-alpha.22b",
|
||||
"description": "Clusterio plugin for synchronizing research between Factorio servers",
|
||||
"author": "Hornwitser <github@hornwitser.no>",
|
||||
"homepage": "https://github.com/clusterio/clusterio#readme",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@clusterio/plugin-statistics_exporter",
|
||||
"version": "2.0.0-alpha.22",
|
||||
"version": "2.0.0-alpha.22b",
|
||||
"description": "Clusterio plugin for gathering statistics from Factorio servers",
|
||||
"author": "Hornwitser <github@hornwitser.no>",
|
||||
"homepage": "https://github.com/clusterio/clusterio#readme",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@clusterio/plugin-subspace_storage",
|
||||
"version": "2.0.0-alpha.22",
|
||||
"version": "2.0.0-alpha.22b",
|
||||
"description": "Clusterio plugin for sharing storage between Factorio servers",
|
||||
"author": "Hornwitser <github@hornwitser.no>",
|
||||
"homepage": "https://github.com/clusterio/clusterio#readme",
|
||||
|
||||
@@ -74,6 +74,19 @@ describe("controller/src/routes", function() {
|
||||
assert.equal(await responses[1].text(), "test content");
|
||||
});
|
||||
});
|
||||
describe("/api/cluster-name", function() {
|
||||
let endpoint;
|
||||
beforeEach(function() {
|
||||
endpoint = `http://localhost:${port}/api/cluster-name`;
|
||||
});
|
||||
it("should return the cluster name from config", async function() {
|
||||
controller.mockConfigEntries.set("controller.name", "Test Cluster");
|
||||
let response = await fetch(endpoint);
|
||||
assert.equal(response.status, 200);
|
||||
let data = await response.json();
|
||||
assert.deepEqual(data, { name: "Test Cluster" });
|
||||
});
|
||||
});
|
||||
describe("/api/upload-save", function() {
|
||||
let endpoint;
|
||||
beforeEach(function() {
|
||||
|
||||
Reference in new issue
Block a user