diff --git a/packages/ctl/src/commands.js b/packages/ctl/src/commands.js index c5f3fe9d..a99e9eb7 100644 --- a/packages/ctl/src/commands.js +++ b/packages/ctl/src/commands.js @@ -867,6 +867,9 @@ modPackCommands.add(new libCommand.Command({ yargs.positional("mod-pack", { describe: "Mod pack to show", type: "string" }); }], handler: async function(args, control) { + let mods = new Map( + (await libLink.messages.listMods.send(control)).list.map(m => [`${m.name}_${m.version}`, m]) + ); let response = await libLink.messages.getModPack.send(control, { id: await libCommand.resolveModPack(control, args.modPack), }); @@ -875,8 +878,12 @@ modPackCommands.add(new libCommand.Command({ if (field === "mods") { print(`${field}:`); for (let entry of value) { + const mod = mods.get(`${entry.name}_${entry.version}`); + const missing = !mod && "? "; + const badChecksum = mod && entry.sha1 && entry.sha1 !== mod.sha1 && "! "; + const warn = missing || badChecksum || ""; const enabled = entry.enabled ? "" : "(disabled) "; - print(` ${enabled}${entry.name} ${entry.version}${entry.sha1 ? ` (${entry.sha1})` : ""}`); + print(` ${warn}${enabled}${entry.name} ${entry.version}${entry.sha1 ? ` (${entry.sha1})` : ""}`); } } else if (field === "settings") { print(`${field}:`); diff --git a/packages/web_ui/src/components/ModDetails.jsx b/packages/web_ui/src/components/ModDetails.jsx index af5ebd78..19487421 100644 --- a/packages/web_ui/src/components/ModDetails.jsx +++ b/packages/web_ui/src/components/ModDetails.jsx @@ -1,5 +1,6 @@ import React, { Fragment } from "react"; import { Descriptions, Grid, Typography } from "antd"; +import ExclamationCircleOutlined from "@ant-design/icons/ExclamationCircleOutlined"; import { libHelpers } from "@clusterio/lib"; @@ -11,6 +12,12 @@ export default function ModDetails(props) { const mod = props.mod; return + {mod.error && Error} span={2}> + {mod.error === "missing" && "The given version of this mod is missing from the master storage."} + {mod.error === "bad_checksum" + && "Checksum of the mod in the master storage does not match the checksum provided by this mod pack." + } + } {props.actions && !screens.lg && {props.actions(mod)} } diff --git a/packages/web_ui/src/components/ModPackViewPage.jsx b/packages/web_ui/src/components/ModPackViewPage.jsx index 4507f534..0d2a426f 100644 --- a/packages/web_ui/src/components/ModPackViewPage.jsx +++ b/packages/web_ui/src/components/ModPackViewPage.jsx @@ -6,6 +6,8 @@ import { } from "antd"; import InfoCircleOutlined from "@ant-design/icons/InfoCircleOutlined"; import ExportOutlined from "@ant-design/icons/ExportOutlined"; +import FileUnknownOutlined from "@ant-design/icons/FileUnknownOutlined"; +import FileExclamationOutlined from "@ant-design/icons/FileExclamationOutlined"; import CloseOutlined from "@ant-design/icons/CloseOutlined"; import DeleteOutlined from "@ant-design/icons/DeleteOutlined"; import PlusOutlined from "@ant-design/icons/PlusOutlined"; @@ -213,8 +215,16 @@ function ModsTable(props) { const mods = [...props.modPack.mods.values(), ...deletedMods.values()].map( mod => { const candidate = modListMap.get(`${mod.name}_${mod.version}`); - if (!candidate || mod.sha1 && candidate.sha1 !== mod.sha1) { - return mod; + if (!candidate) { + return { + ...mod, + error: "missing", + }; + } else if (mod.sha1 && candidate.sha1 !== mod.sha1) { + return { + ...mod, + error: "bad_checksum", + }; } return candidate; } @@ -289,7 +299,15 @@ function ModsTable(props) { { title: "Name", key: "name", - render: mod => mod.title || mod.name, + render: mod => <> + {mod.error === "missing" && + {" "} + } + {mod.error === "bad_checksum" && + {" "} + } + {mod.title || mod.name} + , defaultSortOrder: "ascend", sorter: (a, b) => strcmp(a.name, b.name), },