From dd80d49989060f8bcc859ef85eee4eaf4c023c0c Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Sun, 31 May 2026 03:52:42 +0100 Subject: [PATCH 1/5] Add viewable changelog --- packages/web_ui/global.d.ts | 5 ++ packages/web_ui/package.json | 1 + .../web_ui/src/components/ChangeLogModal.tsx | 63 +++++++++++++++++++ packages/web_ui/src/components/SiteLayout.tsx | 13 ++-- packages/web_ui/webpack.common.js | 4 ++ pnpm-workspace.yaml | 1 + 6 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 packages/web_ui/src/components/ChangeLogModal.tsx diff --git a/packages/web_ui/global.d.ts b/packages/web_ui/global.d.ts index 9ab63040..39e6ec20 100644 --- a/packages/web_ui/global.d.ts +++ b/packages/web_ui/global.d.ts @@ -3,6 +3,11 @@ declare module "*.png" { export default value; } +declare module "*.md" { + const value: string; + export default value; +} + declare const webRoot: string; declare const staticRoot: string; diff --git a/packages/web_ui/package.json b/packages/web_ui/package.json index 514da6d6..769e221f 100644 --- a/packages/web_ui/package.json +++ b/packages/web_ui/package.json @@ -37,6 +37,7 @@ "rc-field-form": "^1.37.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-markdown": "catalog:", "react-router-dom": "^6.29.0", "stream-browserify": "^3.0.0", "style-loader": "^3.3.3", diff --git a/packages/web_ui/src/components/ChangeLogModal.tsx b/packages/web_ui/src/components/ChangeLogModal.tsx new file mode 100644 index 00000000..b1d08b32 --- /dev/null +++ b/packages/web_ui/src/components/ChangeLogModal.tsx @@ -0,0 +1,63 @@ +import React, { useState, ReactNode } from "react"; +import { Modal, Tooltip } from "antd"; +import ReactMarkdown from "react-markdown"; + +import webUiPackage from "../../package.json"; +import changelog from "../../../../CHANGELOG.md"; + +const cleanedChangelog = changelog + .replace(//g, "") // remove comments + .replace(/^#\s+Changelog\s*\n?/i, ""); // remove top-level title + +type Props = { + /** Optional tooltip text */ + tooltip?: string; + /** Optional class for the clickable wrapper */ + className?: string; + /** Custom clickable content */ + children?: ReactNode; +}; + +/** + * Displays a clickable element which opens a modal containing the changelog. + * Defaults to showing the current version if no children are provided. + */ +export default function ChangeLogModal({ + tooltip = "View changelog", + className = "site-version", + children = webUiPackage.version, +}: Props) { + const [open, setOpen] = useState(false); + + return ( + <> + + setOpen(true)} + > + {children} + + + + setOpen(false)} + footer={null} + width={800} + > +
+ + {cleanedChangelog} + +
+
+ + ); +} diff --git a/packages/web_ui/src/components/SiteLayout.tsx b/packages/web_ui/src/components/SiteLayout.tsx index f430f801..62ee2ab4 100644 --- a/packages/web_ui/src/components/SiteLayout.tsx +++ b/packages/web_ui/src/components/SiteLayout.tsx @@ -1,18 +1,19 @@ import React, { Fragment, useContext, useEffect, useState } from "react"; import { Route, Routes, useNavigate } from "react-router-dom"; import { Button, Dropdown, Layout, Menu, MenuProps, Tooltip } from "antd"; -import UserOutlined from "@ant-design/icons/UserOutlined"; -import webUiPackage from "../../package.json"; +import { UserOutlined, DownloadOutlined } from "@ant-design/icons"; -import { useAccount } from "../model/account"; import ErrorBoundary from "./ErrorBoundary"; import ErrorPage from "./ErrorPage"; import ControlContext from "./ControlContext"; +import ChangeLogModal from "./ChangeLogModal"; + import { pages } from "../pages"; -import { DraggingContext } from "../model/is_dragging"; import { saveJson } from "../util/save_file"; +import { useAccount } from "../model/account"; +import { DraggingContext } from "../model/is_dragging"; + import { ControlConfig } from "@clusterio/lib"; -import { DownloadOutlined } from "@ant-design/icons"; type MenuItem = Required["items"][number]; @@ -145,7 +146,7 @@ export default function SiteLayout() {
Clusterio - {webUiPackage.version} + ({ test: /\.png$/, type: "asset/resource", }, + { + test: /\.md$/, + type: "asset/source", + }, ], }, resolve: { diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 2af79422..fefcb0b9 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -22,6 +22,7 @@ catalog: jszip: ^3.10.1 react: ^18.2.0 react-dom: ^18.2.0 + react-markdown: ^10.1.0 react-router-dom: ^6.29.0 set-blocking: ^2.0.0 typescript: ^5.7.3 From f7d3516c2494d0f354803832923670edee76aac2 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Wed, 10 Jun 2026 20:18:05 +0100 Subject: [PATCH 2/5] Remove internal div from changelog --- packages/web_ui/src/components/ChangeLogModal.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/web_ui/src/components/ChangeLogModal.tsx b/packages/web_ui/src/components/ChangeLogModal.tsx index b1d08b32..21f7dd6f 100644 --- a/packages/web_ui/src/components/ChangeLogModal.tsx +++ b/packages/web_ui/src/components/ChangeLogModal.tsx @@ -52,11 +52,9 @@ export default function ChangeLogModal({ footer={null} width={800} > -
- - {cleanedChangelog} - -
+ + {cleanedChangelog} + ); From 0e36df8ae38d2adb12603dbec5fbee4ea15fa92d Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Wed, 10 Jun 2026 22:35:00 +0100 Subject: [PATCH 3/5] Add AboutModal --- packages/web_ui/src/components/AboutModal.tsx | 188 ++++++++++++++++++ .../web_ui/src/components/ChangeLogModal.tsx | 65 ++---- packages/web_ui/src/components/SiteLayout.tsx | 41 +++- packages/web_ui/webpack.common.js | 15 ++ 4 files changed, 258 insertions(+), 51 deletions(-) create mode 100644 packages/web_ui/src/components/AboutModal.tsx diff --git a/packages/web_ui/src/components/AboutModal.tsx b/packages/web_ui/src/components/AboutModal.tsx new file mode 100644 index 00000000..39e7db2c --- /dev/null +++ b/packages/web_ui/src/components/AboutModal.tsx @@ -0,0 +1,188 @@ +import React, { useContext, useEffect, useMemo, useState } from "react"; +import { Modal, Button, Space, Typography, message, Row, Col, Card } from "antd"; +import { ReloadOutlined, CopyOutlined, InfoCircleOutlined } from "@ant-design/icons"; + +import { ControlContext, useAccount, useHosts, useInstances, useUsers } from "@clusterio/web_ui"; +import { PluginWebApi } from "@clusterio/lib"; +import webUiPackage from "../../package.json"; + +const { Text } = Typography; + +function formatOS(os?: string) { + switch (os) { + case "win32": return "Windows"; + case "darwin": return "macOS"; + case "linux": return "Linux"; + default: return os; + } +} + +export default function AboutModal({ open, onClose, onOpenChangelog }: any) { + const control = useContext(ControlContext); + const account = useAccount(); + const [hosts] = useHosts(); + const [instances] = useInstances(); + const [users] = useUsers(); + + const [pluginList, setPluginList] = useState([]); + + useEffect(() => { + (async () => { + let response = await fetch(`${webRoot}api/plugins`); + if (response.ok) { + setPluginList(await response.json()); + } + })(); + }, [webRoot]); + + const browserInfo = useMemo(() => ({ + userAgent: navigator.userAgent, + platform: navigator.platform, + }), []); + + const debugText = useMemo(() => { + const permissions = new Set( + account.roles.flatMap(role => role.permissions) + ); + + const date = new Date(); + const lines: string[] = [ + "Clusterio Debug Info", + `- Timestamp: ${date.toISOString()}`, + `- Local Time: ${date.toString()}`, + "", + + "Build", + `- Version: ${webUiPackage.version}`, + `- Build Date: ${process.env.BUILD_DATE}`, + `- Commit Hash: ${process.env.COMMIT_HASH}`, + `- Environment: ${process.env.BUILD_ENV}`, + `- System: ${process.env.BUILD_OS}`, + "", + + "Runtime", + `- Platform: ${browserInfo.platform}`, + `- User Agent: ${browserInfo.userAgent}`, + `- Screen: ${window.screen.width}x${window.screen.height}`, + `- Viewport: ${window.innerWidth}x${window.innerHeight}`, + "", + + "Cluster", + `- Url: ${new URL(webRoot, window.location.origin)}`, + `- Hosts: ${hosts.size}`, + `- Instances: ${instances.size}`, + `- Plugins: ${pluginList.length}`, + `- Users: ${users.size}`, + "", + + "Account", + `- Name: ${account.name}`, + `- Roles: ${account.roles.map(r => r.name).sort().join(", ") || "None"}`, + `- Permissions: ${permissions.size}`, + "", + + "Permission List", + ...[...permissions].sort().map(p => `- ${p}`), + "", + ]; + + lines.push("Plugin List"); + for (let plugin of pluginList) { + lines.push(`- ${plugin.name}`); + lines.push(` Version: ${plugin.version ?? "unknown"}`); + lines.push(` Loaded: ${plugin.loaded ? "yes" : "no"}`); + lines.push(` Controller: ${plugin.enabled ? "enabled" : "disabled"}`); + lines.push(` Web: ${control.plugins.get(plugin.name) ? "enabled" : "disabled"}`); + if (plugin.npmPackage) { + lines.push(` Package: ${plugin.npmPackage}`); + } + } + + return lines.join("\n"); + }, [account, hosts, instances, users, pluginList, browserInfo]); + + async function copyDebug() { + await navigator.clipboard.writeText(debugText); + message.success("Copied debug info"); + } + + function forceRefresh() { + window.location.reload(); + } + + return + + + + + + + Version: {`${webUiPackage.version} (${process.env.COMMIT_HASH})`} + Date: {new Date(process.env.BUILD_DATE ?? "").toLocaleString()} + Environment: {process.env.BUILD_ENV} + System: {formatOS(process.env.BUILD_OS)} + + + + + + + + Hosts: {hosts.size} + Instances: {instances.size} + Plugins: {pluginList.length} + Users: {users.size} + + + + + + + + + + + + + + + + + + + + + + + + ; +} diff --git a/packages/web_ui/src/components/ChangeLogModal.tsx b/packages/web_ui/src/components/ChangeLogModal.tsx index 21f7dd6f..78b23594 100644 --- a/packages/web_ui/src/components/ChangeLogModal.tsx +++ b/packages/web_ui/src/components/ChangeLogModal.tsx @@ -1,61 +1,26 @@ -import React, { useState, ReactNode } from "react"; -import { Modal, Tooltip } from "antd"; +import React from "react"; +import { Modal } from "antd"; import ReactMarkdown from "react-markdown"; -import webUiPackage from "../../package.json"; import changelog from "../../../../CHANGELOG.md"; const cleanedChangelog = changelog .replace(//g, "") // remove comments .replace(/^#\s+Changelog\s*\n?/i, ""); // remove top-level title -type Props = { - /** Optional tooltip text */ - tooltip?: string; - /** Optional class for the clickable wrapper */ - className?: string; - /** Custom clickable content */ - children?: ReactNode; -}; - /** - * Displays a clickable element which opens a modal containing the changelog. - * Defaults to showing the current version if no children are provided. + * Displays a modal element which contains the change log */ -export default function ChangeLogModal({ - tooltip = "View changelog", - className = "site-version", - children = webUiPackage.version, -}: Props) { - const [open, setOpen] = useState(false); - - return ( - <> - - setOpen(true)} - > - {children} - - - - setOpen(false)} - footer={null} - width={800} - > - - {cleanedChangelog} - - - - ); +export default function ChangeLogModal({ open, onClose }: any) { + return + + {cleanedChangelog} + + ; } diff --git a/packages/web_ui/src/components/SiteLayout.tsx b/packages/web_ui/src/components/SiteLayout.tsx index 62ee2ab4..76338056 100644 --- a/packages/web_ui/src/components/SiteLayout.tsx +++ b/packages/web_ui/src/components/SiteLayout.tsx @@ -7,11 +7,13 @@ import ErrorBoundary from "./ErrorBoundary"; import ErrorPage from "./ErrorPage"; import ControlContext from "./ControlContext"; import ChangeLogModal from "./ChangeLogModal"; +import AboutModal from "./AboutModal"; import { pages } from "../pages"; import { saveJson } from "../util/save_file"; import { useAccount } from "../model/account"; import { DraggingContext } from "../model/is_dragging"; +import webUiPackage from "../../package.json"; import { ControlConfig } from "@clusterio/lib"; @@ -44,6 +46,9 @@ export default function SiteLayout() { let plugins = useContext(ControlContext).plugins; const control = useContext(ControlContext); const [dragging, setDragging] = useState(0); + const [aboutOpen, setAboutOpen] = useState(false); + const [wasAboutOpen, setWasAboutOpen] = useState(false); + const [changeLogOpen, setChangeLogOpen] = useState(false); function SetSidebar(props: { path: string | null }) { useEffect(() => { @@ -62,6 +67,8 @@ export default function SiteLayout() { "control.controller_url": (new URL(webRoot, document.location.href)).href, }); saveJson("config-control.json", config.toJSON()); + } else if (key === "about") { + setAboutOpen(true); } else if (key === "logOut") { account.logOut(); } @@ -74,6 +81,7 @@ export default function SiteLayout() { , key: "ctlConfig", }, + { label: "About", key: "about" }, { type: "divider" }, { label: "Log out", danger: true, key: "logOut" }, ], @@ -142,11 +150,42 @@ export default function SiteLayout() { } }} > + { + setAboutOpen(false); + setWasAboutOpen(false); + }} + onOpenChangelog={() => { + setChangeLogOpen(true); + setWasAboutOpen(aboutOpen); + setAboutOpen(false); + }} + /> + { + setChangeLogOpen(false); + setAboutOpen(wasAboutOpen); + }} + /> 0}>
Clusterio - + + setChangeLogOpen(true)} + > + {webUiPackage.version} + + ({ mode: argv.mode ?? "development", devtool: argv.mode === "production" ? "source-map" : "eval-source-map", @@ -29,6 +40,10 @@ module.exports = (env = {}, argv = {}) => ({ new webpack.DefinePlugin({ "process.env.APP_ENV": JSON.stringify("browser"), + "process.env.COMMIT_HASH": JSON.stringify(getCommitHash()), + "process.env.BUILD_DATE": JSON.stringify(new Date().toISOString()), + "process.env.BUILD_ENV": JSON.stringify(process.env.NODE_ENV ?? "dev"), + "process.env.BUILD_OS": JSON.stringify(os.platform()), }), // Make sure ant-design icons use the ES variant From dab727f90f13a0cf0b7aa7665523384162d3fccb Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Wed, 10 Jun 2026 22:39:26 +0100 Subject: [PATCH 4/5] Fix import in AboutModal --- packages/web_ui/src/components/AboutModal.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/web_ui/src/components/AboutModal.tsx b/packages/web_ui/src/components/AboutModal.tsx index 39e7db2c..9c066da2 100644 --- a/packages/web_ui/src/components/AboutModal.tsx +++ b/packages/web_ui/src/components/AboutModal.tsx @@ -2,9 +2,14 @@ import React, { useContext, useEffect, useMemo, useState } from "react"; import { Modal, Button, Space, Typography, message, Row, Col, Card } from "antd"; import { ReloadOutlined, CopyOutlined, InfoCircleOutlined } from "@ant-design/icons"; -import { ControlContext, useAccount, useHosts, useInstances, useUsers } from "@clusterio/web_ui"; import { PluginWebApi } from "@clusterio/lib"; + import webUiPackage from "../../package.json"; +import ControlContext from "./ControlContext"; +import { useAccount } from "../model/account"; +import { useHosts } from "../model/host"; +import { useInstances } from "../model/instance"; +import { useUsers } from "../model/user"; const { Text } = Typography; From b160fecac4957484423b9ec85599c3ecd947e8f4 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Fri, 12 Jun 2026 16:04:39 +0100 Subject: [PATCH 5/5] Make icons local to package --- packages/web_ui/global.d.ts | 5 +++++ packages/web_ui/src/components/AboutModal.tsx | 14 +++++--------- packages/web_ui/src/components/ErrorPage.tsx | 7 +++++-- packages/web_ui/src/images/discord.svg | 3 +++ packages/web_ui/src/images/github.svg | 3 +++ packages/web_ui/webpack.common.js | 4 ++++ 6 files changed, 25 insertions(+), 11 deletions(-) create mode 100644 packages/web_ui/src/images/discord.svg create mode 100644 packages/web_ui/src/images/github.svg diff --git a/packages/web_ui/global.d.ts b/packages/web_ui/global.d.ts index 39e6ec20..475b5037 100644 --- a/packages/web_ui/global.d.ts +++ b/packages/web_ui/global.d.ts @@ -3,6 +3,11 @@ declare module "*.png" { export default value; } +declare module "*.svg" { + const value: string; + export default value; +} + declare module "*.md" { const value: string; export default value; diff --git a/packages/web_ui/src/components/AboutModal.tsx b/packages/web_ui/src/components/AboutModal.tsx index 9c066da2..7a167858 100644 --- a/packages/web_ui/src/components/AboutModal.tsx +++ b/packages/web_ui/src/components/AboutModal.tsx @@ -5,6 +5,9 @@ import { ReloadOutlined, CopyOutlined, InfoCircleOutlined } from "@ant-design/ic import { PluginWebApi } from "@clusterio/lib"; import webUiPackage from "../../package.json"; +import DiscordLogo from "../images/discord.svg"; +import GitHubLogo from "../images/github.svg"; + import ControlContext from "./ControlContext"; import { useAccount } from "../model/account"; import { useHosts } from "../model/host"; @@ -153,10 +156,7 @@ export default function AboutModal({ open, onClose, onOpenChangelog }: any) { target="_blank" href="https://github.com/clusterio/clusterio/issues" icon={ - + } > GitHub Issues @@ -165,11 +165,7 @@ export default function AboutModal({ open, onClose, onOpenChangelog }: any) { target="_blank" href="https://discord.gg/mzAsgnm" icon={ - + } > Support Server diff --git a/packages/web_ui/src/components/ErrorPage.tsx b/packages/web_ui/src/components/ErrorPage.tsx index 92876107..99542079 100644 --- a/packages/web_ui/src/components/ErrorPage.tsx +++ b/packages/web_ui/src/components/ErrorPage.tsx @@ -2,6 +2,9 @@ import React from "react"; import { Button, Card, Flex, Image, Typography } from "antd"; import example from "../images/console-error-edge.png"; +import DiscordLogo from "../images/discord.svg"; +import GitHubLogo from "../images/github.svg"; + import PageLayout from "./PageLayout"; import type { ErrorProps } from "./App"; @@ -38,12 +41,12 @@ export default function ErrorPage(props: ErrorProps & { throw?: boolean }) {
diff --git a/packages/web_ui/src/images/discord.svg b/packages/web_ui/src/images/discord.svg new file mode 100644 index 00000000..0cccf665 --- /dev/null +++ b/packages/web_ui/src/images/discord.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/web_ui/src/images/github.svg b/packages/web_ui/src/images/github.svg new file mode 100644 index 00000000..9065f17a --- /dev/null +++ b/packages/web_ui/src/images/github.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/web_ui/webpack.common.js b/packages/web_ui/webpack.common.js index 15db5692..26545081 100644 --- a/packages/web_ui/webpack.common.js +++ b/packages/web_ui/webpack.common.js @@ -129,6 +129,10 @@ module.exports = (env = {}, argv = {}) => ({ test: /\.png$/, type: "asset/resource", }, + { + test: /\.svg$/, + type: "asset/resource", + }, { test: /\.md$/, type: "asset/source",