mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-13 07:04:53 +09:00
Add AboutModal
This commit is contained in:
4 files changed
+258
-51
No files matched your search
@@ -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<PluginWebApi[]>([]);
|
||||
|
||||
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 <Modal open={open} onCancel={onClose} footer={null} title="About Clusterio" width={800}>
|
||||
|
||||
<Space direction="vertical" style={{ width: "100%" }} size="large">
|
||||
<Row gutter={[24, 16]}>
|
||||
<Col xs={24} md={12}>
|
||||
<Card title="Build Info">
|
||||
<Space direction="vertical">
|
||||
<Text>Version: {`${webUiPackage.version} (${process.env.COMMIT_HASH})`}</Text>
|
||||
<Text>Date: {new Date(process.env.BUILD_DATE ?? "").toLocaleString()}</Text>
|
||||
<Text>Environment: {process.env.BUILD_ENV}</Text>
|
||||
<Text>System: {formatOS(process.env.BUILD_OS)}</Text>
|
||||
</Space>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
<Col xs={24} md={12}>
|
||||
<Card title="Cluster Info">
|
||||
<Space direction="vertical">
|
||||
<Text>Hosts: {hosts.size}</Text>
|
||||
<Text>Instances: {instances.size}</Text>
|
||||
<Text>Plugins: {pluginList.length}</Text>
|
||||
<Text>Users: {users.size}</Text>
|
||||
</Space>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row>
|
||||
<Col span={24}>
|
||||
<Space style={{ width: "100%", justifyContent: "space-between" }} wrap>
|
||||
<Space wrap>
|
||||
<Button icon={<InfoCircleOutlined />} onClick={onOpenChangelog}>
|
||||
Changelog
|
||||
</Button>
|
||||
<Button
|
||||
target="_blank"
|
||||
href="https://github.com/clusterio/clusterio/issues"
|
||||
icon={<span style={{ display: "flex", alignItems: "center" }}>
|
||||
<img
|
||||
src="https://github.githubassets.com/favicons/favicon-dark.svg"
|
||||
style={{ width: 16 }}
|
||||
/>
|
||||
</span>}
|
||||
>
|
||||
GitHub Issues
|
||||
</Button>
|
||||
<Button
|
||||
target="_blank"
|
||||
href="https://discord.gg/mzAsgnm"
|
||||
icon={<span style={{ display: "flex", alignItems: "center" }}>
|
||||
<img
|
||||
// eslint-disable-next-line max-len
|
||||
src="https://cdn.prod.website-files.com/6257adef93867e50d84d30e2/66e3d7f4ef6498ac018f2c55_Symbol.svg"
|
||||
style={{ width: 16}}
|
||||
/>
|
||||
</span>}
|
||||
>
|
||||
Support Server
|
||||
</Button>
|
||||
</Space>
|
||||
|
||||
<Space wrap>
|
||||
<Button icon={<ReloadOutlined />} onClick={forceRefresh}>
|
||||
Reload
|
||||
</Button>
|
||||
<Button icon={<CopyOutlined />} onClick={copyDebug} type="primary">
|
||||
Copy Debug
|
||||
</Button>
|
||||
</Space>
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
</Space>
|
||||
|
||||
</Modal>;
|
||||
}
|
||||
@@ -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(/<!--[\s\S]*?-->/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 (
|
||||
<>
|
||||
<Tooltip
|
||||
title={tooltip}
|
||||
placement="bottom"
|
||||
align={{ offset: [0, -10] }}
|
||||
>
|
||||
<span
|
||||
className={className}
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
<Modal
|
||||
title="Changelog"
|
||||
open={open}
|
||||
onCancel={() => setOpen(false)}
|
||||
footer={null}
|
||||
width={800}
|
||||
>
|
||||
<ReactMarkdown>
|
||||
{cleanedChangelog}
|
||||
</ReactMarkdown>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
export default function ChangeLogModal({ open, onClose }: any) {
|
||||
return <Modal
|
||||
title="Changelog"
|
||||
open={open}
|
||||
onCancel={onClose}
|
||||
footer={null}
|
||||
width={800}
|
||||
>
|
||||
<ReactMarkdown>
|
||||
{cleanedChangelog}
|
||||
</ReactMarkdown>
|
||||
</Modal>;
|
||||
}
|
||||
@@ -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() {
|
||||
</Tooltip>,
|
||||
key: "ctlConfig",
|
||||
},
|
||||
{ label: "About", key: "about" },
|
||||
{ type: "divider" },
|
||||
{ label: "Log out", danger: true, key: "logOut" },
|
||||
],
|
||||
@@ -142,11 +150,42 @@ export default function SiteLayout() {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<AboutModal
|
||||
open={aboutOpen}
|
||||
onClose={() => {
|
||||
setAboutOpen(false);
|
||||
setWasAboutOpen(false);
|
||||
}}
|
||||
onOpenChangelog={() => {
|
||||
setChangeLogOpen(true);
|
||||
setWasAboutOpen(aboutOpen);
|
||||
setAboutOpen(false);
|
||||
}}
|
||||
/>
|
||||
<ChangeLogModal
|
||||
open={changeLogOpen}
|
||||
onClose={() => {
|
||||
setChangeLogOpen(false);
|
||||
setAboutOpen(wasAboutOpen);
|
||||
}}
|
||||
/>
|
||||
<DraggingContext.Provider value={dragging > 0}>
|
||||
<Header className="header">
|
||||
<div className="site-logo" />
|
||||
<span className="site-name">Clusterio</span>
|
||||
<ChangeLogModal />
|
||||
<Tooltip
|
||||
title="View changelog"
|
||||
placement="bottom"
|
||||
align={{ offset: [0, -10] }}
|
||||
>
|
||||
<span
|
||||
className="site-version"
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() => setChangeLogOpen(true)}
|
||||
>
|
||||
{webUiPackage.version}
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Menu
|
||||
theme="dark"
|
||||
mode="horizontal"
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
"use strict";
|
||||
const os = require("os");
|
||||
const { execSync } = require("child_process");
|
||||
|
||||
const webpack = require("webpack");
|
||||
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
|
||||
const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
|
||||
function getCommitHash() {
|
||||
try {
|
||||
return execSync("git rev-parse --short HEAD").toString().trim();
|
||||
} catch {
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = (env = {}, argv = {}) => ({
|
||||
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
|
||||
|
||||
Reference in new issue
Block a user