mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-13 15:14:52 +09:00
Reimplement the Storage page
Reimplement the functionality of the Storage page which shows which items are stored in the shared subspace on the master server.
This commit is contained in:
1 parent
cebe83542f
commit
c16dc74ad9
5 files changed
+234
-3
No files matched your search
@@ -1,6 +1,7 @@
|
||||
"use strict";
|
||||
const libLink = require("@clusterio/lib/link");
|
||||
const libConfig = require("@clusterio/lib/config");
|
||||
const libUsers = require("@clusterio/lib/users");
|
||||
|
||||
class MasterConfigGroup extends libConfig.PluginConfigGroup {}
|
||||
MasterConfigGroup.defaultAccess = ["master", "slave", "control"];
|
||||
@@ -41,6 +42,13 @@ InstanceConfigGroup.define({
|
||||
});
|
||||
InstanceConfigGroup.finalize();
|
||||
|
||||
libUsers.definePermission({
|
||||
name: "subspace_storage.storage.view",
|
||||
title: "View Subspace Storage",
|
||||
description: "View the items and fluids stored in the shared subspace.",
|
||||
grantByDefault: true,
|
||||
});
|
||||
|
||||
// JSON schema for subspace storage items
|
||||
const items = {
|
||||
type: "array",
|
||||
@@ -65,6 +73,9 @@ module.exports = {
|
||||
masterEntrypoint: "master",
|
||||
MasterConfigGroup,
|
||||
|
||||
webEntrypoint: "./web",
|
||||
routes: ["/storage"],
|
||||
|
||||
messages: {
|
||||
// XXX this should be a request to be reliable
|
||||
place: new libLink.Event({
|
||||
@@ -90,7 +101,8 @@ module.exports = {
|
||||
}),
|
||||
getStorage: new libLink.Request({
|
||||
type: "subspace_storage:get_storage",
|
||||
links: ["instance-slave", "slave-master"],
|
||||
links: ["instance-slave", "slave-master", "control-master"],
|
||||
permission: "subspace_storage.storage.view",
|
||||
forwardTo: "master",
|
||||
responseProperties: {
|
||||
"items": items,
|
||||
@@ -98,11 +110,20 @@ module.exports = {
|
||||
}),
|
||||
updateStorage: new libLink.Event({
|
||||
type: "subspace_storage:update_storage",
|
||||
links: ["master-slave", "slave-instance"],
|
||||
links: ["master-slave", "slave-instance", "master-control"],
|
||||
broadcastTo: "instance",
|
||||
eventProperties: {
|
||||
"items": items,
|
||||
},
|
||||
}),
|
||||
|
||||
setStorageSubscription: new libLink.Request({
|
||||
type: "subspace_storage:set_storage_subscription",
|
||||
links: ["control-master"],
|
||||
permission: "subspace_storage.storage.view",
|
||||
requestProperties: {
|
||||
"storage": { type: "boolean" },
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
@@ -72,6 +72,8 @@ class MasterPlugin extends libPlugin.BaseMasterPlugin {
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
this.subscribedControlLinks = new Set();
|
||||
|
||||
routes.addApiRoutes(this.master.app, this.items, this.metrics.endpointHitCounter);
|
||||
}
|
||||
|
||||
@@ -90,6 +92,9 @@ class MasterPlugin extends libPlugin.BaseMasterPlugin {
|
||||
|
||||
itemsToUpdate = [...itemsToUpdate.entries()];
|
||||
this.broadcastEventToSlaves(this.info.messages.updateStorage, { items: itemsToUpdate });
|
||||
for (let link of this.subscribedControlLinks) {
|
||||
this.info.messages.updateStorage.send(link, { items: itemsToUpdate });
|
||||
}
|
||||
this.itemsLastUpdate = new Map(this.items._items.entries());
|
||||
}
|
||||
|
||||
@@ -181,6 +186,20 @@ class MasterPlugin extends libPlugin.BaseMasterPlugin {
|
||||
};
|
||||
}
|
||||
|
||||
async setStorageSubscriptionRequestHandler(message, request, link) {
|
||||
if (message.data.storage) {
|
||||
this.subscribedControlLinks.add(link);
|
||||
} else {
|
||||
this.subscribedControlLinks.delete(link);
|
||||
}
|
||||
}
|
||||
|
||||
onControlConnectionEvent(connection, event) {
|
||||
if (event === "close") {
|
||||
this.subscribedControlLinks.delete(connection);
|
||||
}
|
||||
}
|
||||
|
||||
onMetrics() {
|
||||
if (this.items) {
|
||||
for (let [key, count] of this.items._items) {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
.factorio-icon {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import { Input, Table, Typography } from "antd";
|
||||
|
||||
import libPlugin from "@clusterio/lib/plugin";
|
||||
import { notifyErrorHandler, useItemMetadata, useLocale, PageLayout, ControlContext } from "@clusterio/web_ui";
|
||||
import info from "../info";
|
||||
|
||||
import "./index.css";
|
||||
|
||||
const { Paragraph } = Typography;
|
||||
|
||||
|
||||
function useStorage(control) {
|
||||
let plugin = control.plugins.get("subspace_storage");
|
||||
let [storage, setStorage] = useState([...plugin.storage]);
|
||||
|
||||
useEffect(() => {
|
||||
function update() {
|
||||
setStorage([...plugin.storage]);
|
||||
}
|
||||
|
||||
plugin.onUpdate(update);
|
||||
return () => {
|
||||
plugin.offUpdate(update);
|
||||
};
|
||||
}, []);
|
||||
return storage;
|
||||
}
|
||||
|
||||
function StoragePage() {
|
||||
let control = useContext(ControlContext);
|
||||
let locale = useLocale();
|
||||
let itemMetadata = useItemMetadata();
|
||||
let storage = useStorage(control);
|
||||
let [filter, setFilter] = useState(null);
|
||||
|
||||
function getLocaleName(itemName) {
|
||||
let localeName = itemName;
|
||||
let meta = itemMetadata.get(itemName);
|
||||
if (meta && meta.localized_name) {
|
||||
// TODO: implement the locale to name conversion.
|
||||
localeName = locale.get(meta.localized_name[0]);
|
||||
} else {
|
||||
for (let section of ["item-name", "entity-name", "fluid-name", "equipment-name"]) {
|
||||
let name = locale.get(`${section}.${itemName}`);
|
||||
if (name) {
|
||||
localeName = name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return localeName;
|
||||
}
|
||||
|
||||
let numberFormat = new Intl.NumberFormat("en-US");
|
||||
|
||||
return <PageLayout nav={[{ name: "Storage" }]}>
|
||||
<h2>Storage</h2>
|
||||
<Paragraph>
|
||||
<Input
|
||||
placeholder="Search"
|
||||
onChange={(event) => {
|
||||
let search = event.target.value.trim();
|
||||
if (!search) {
|
||||
setFilter(null);
|
||||
return;
|
||||
}
|
||||
search = search.replace(/(^| )(\w)/g, "$1\\b$2");
|
||||
search = search.replace(/ +/g, ".*");
|
||||
let filterExpr = new RegExp(search, "i");
|
||||
setFilter(() => (item => {
|
||||
let name = getLocaleName(item[0]);
|
||||
return filterExpr.test(name) || filterExpr.test(item[0]);
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</Paragraph>
|
||||
<Table
|
||||
columns={[
|
||||
{
|
||||
title: "Resource",
|
||||
key: "resource",
|
||||
sorter: (a, b) => {
|
||||
let aName = getLocaleName(a[0]);
|
||||
let bName = getLocaleName(b[0]);
|
||||
if (aName < bName) { return -1; }
|
||||
if (aName > bName) { return 1; }
|
||||
return 0;
|
||||
},
|
||||
render: item => {
|
||||
let localeName = getLocaleName(item[0]);
|
||||
let hasMeta = itemMetadata.get(item[0]);
|
||||
|
||||
return <>
|
||||
<span className={`factorio-icon item-${hasMeta ? item[0] : "unknown-item"}`}/>
|
||||
{localeName}
|
||||
</>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Quantity",
|
||||
key: "quantity",
|
||||
align: "right",
|
||||
defaultSortOrder: "descend",
|
||||
sorter: (a, b) => a[1] - b[1],
|
||||
render: item => numberFormat.format(item[1]),
|
||||
},
|
||||
]}
|
||||
dataSource={filter ? storage.filter(filter) : storage}
|
||||
rowKey={item => item[0]}
|
||||
pagination={false}
|
||||
/>
|
||||
</PageLayout>;
|
||||
}
|
||||
|
||||
export class WebPlugin extends libPlugin.BaseWebPlugin {
|
||||
async init() {
|
||||
this.pages = [
|
||||
{ path: "/storage", sidebarName: "Storage", content: <StoragePage/> },
|
||||
];
|
||||
|
||||
this.storage = new Map();
|
||||
this.callbacks = [];
|
||||
}
|
||||
|
||||
onMasterConnectionEvent(event) {
|
||||
if (event === "connect") {
|
||||
this.updateSubscription();
|
||||
}
|
||||
}
|
||||
|
||||
async updateStorageEventHandler(message) {
|
||||
this.updateStorage(message.data.items);
|
||||
}
|
||||
|
||||
onUpdate(callback) {
|
||||
this.callbacks.push(callback);
|
||||
if (this.callbacks.length) {
|
||||
this.updateSubscription();
|
||||
}
|
||||
}
|
||||
|
||||
offUpdate(callback) {
|
||||
let index = this.callbacks.lastIndexOf(callback);
|
||||
if (index === -1) {
|
||||
throw new Error("callback is not registered");
|
||||
}
|
||||
|
||||
this.callbacks.splice(index, 1);
|
||||
if (!this.callbacks.length) {
|
||||
this.updateSubscription();
|
||||
}
|
||||
}
|
||||
|
||||
updateStorage(items) {
|
||||
for (let [name, amount] of items) {
|
||||
this.storage.set(name, amount);
|
||||
}
|
||||
for (let callback of this.callbacks) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
updateSubscription() {
|
||||
if (!this.control.connector.connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
info.messages.setStorageSubscription.send(
|
||||
this.control, { storage: Boolean(this.callbacks.length) }
|
||||
).catch(notifyErrorHandler("Error subscribing to storage"));
|
||||
|
||||
if (this.callbacks.length) {
|
||||
info.messages.getStorage.send(this.control).then(
|
||||
result => {
|
||||
this.updateStorage(result.items);
|
||||
}
|
||||
).catch(notifyErrorHandler("Error updating storage"));
|
||||
} else {
|
||||
this.storage.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,12 +21,14 @@ module.exports = (env = {}) => merge(common(env), {
|
||||
exposes: {
|
||||
"./info": "./info.js",
|
||||
"./package.json": "./package.json",
|
||||
"./web": "./web/index.jsx",
|
||||
},
|
||||
shared: {
|
||||
"@clusterio/lib/config": { import: false },
|
||||
"@clusterio/lib/link": { import: false },
|
||||
"@clusterio/lib/plugin": { import: false },
|
||||
"@clusterio/lib/users": { import: false },
|
||||
"@clusterio/web_ui": { import: false },
|
||||
"ajv": { import: false },
|
||||
"antd": { import: false },
|
||||
"react": { import: false },
|
||||
"react-dom": { import: false },
|
||||
|
||||
Reference in new issue
Block a user