编排添加系统信息

This commit is contained in:
fofolee 2025-01-05 12:10:23 +08:00
parent e94118881a
commit 00ddba20ec
7 changed files with 499 additions and 4 deletions

View File

@ -1,5 +1,7 @@
const exec = require("./exec");
const os = require("./os");
module.exports = {
exec,
os,
};

View File

@ -0,0 +1,84 @@
const os = require("os");
// 获取系统架构
function arch() {
return os.arch();
}
// 获取CPU信息
function cpus({ format = "full" } = {}) {
const cpuInfo = os.cpus();
if (format === "simple") {
return cpuInfo.map(({ model, speed }) => ({ model, speed }));
}
return cpuInfo;
}
// 获取内存信息
function memory({ type = "totalmem" } = {}) {
switch (type) {
case "totalmem":
return os.totalmem();
case "freemem":
return os.freemem();
default:
throw new Error("不支持的内存信息类型");
}
}
// 获取网络信息
function network({ type = "hostname", internal = false } = {}) {
switch (type) {
case "hostname":
return os.hostname();
case "networkInterfaces": {
const interfaces = os.networkInterfaces();
if (!internal) {
// 过滤掉内部接口
Object.keys(interfaces).forEach((key) => {
interfaces[key] = interfaces[key].filter((iface) => !iface.internal);
if (interfaces[key].length === 0) {
delete interfaces[key];
}
});
}
return interfaces;
}
default:
throw new Error("不支持的网络信息类型");
}
}
// 获取平台信息
function platform({ type = "platform" } = {}) {
switch (type) {
case "platform":
return os.platform();
case "type":
return os.type();
case "release":
return os.release();
case "arch":
return os.arch();
case "endianness":
return os.endianness();
case "tmpdir":
return os.tmpdir();
case "homedir":
return os.homedir();
case "uptime":
return os.uptime();
case "userInfo":
return os.userInfo();
default:
throw new Error("不支持的平台信息类型");
}
}
module.exports = {
arch,
cpus,
memory,
network,
platform,
};

View File

@ -0,0 +1,381 @@
<template>
<div class="os-editor">
<!-- 操作类型选择 -->
<div class="operation-cards">
<div
v-for="op in operations"
:key="op.name"
:class="['operation-card', { active: argvs.operation === op.name }]"
@click="updateArgvs('operation', op.name)"
>
<div
class="row items-center justify-center q-gutter-x-xs q-px-sm q-py-xs"
>
<q-icon
:name="op.icon"
size="16px"
:color="argvs.operation === op.name ? 'primary' : 'grey'"
/>
<div class="text-caption">{{ op.label }}</div>
</div>
</div>
</div>
<!-- 操作配置 -->
<div class="operation-options q-mt-sm" v-if="hasOptions">
<div class="bubble-pointer" :style="pointerStyle"></div>
<!-- CPU信息配置 -->
<div v-if="argvs.operation === 'cpus'" class="options-container">
<div class="row items-center q-gutter-x-sm">
<div
v-for="opt in formatOptions"
:key="opt.value"
:class="['custom-btn', { active: argvs.format === opt.value }]"
@click="updateArgvs('format', opt.value)"
>
{{ opt.label }}
</div>
</div>
</div>
<!-- 内存信息配置 -->
<div v-if="argvs.operation === 'memory'" class="options-container">
<div class="row items-center q-gutter-x-sm">
<div
v-for="opt in memoryOptions"
:key="opt.value"
:class="['custom-btn', { active: argvs.type === opt.value }]"
@click="updateArgvs('type', opt.value)"
>
{{ opt.label }}
</div>
</div>
</div>
<!-- 网络信息配置 -->
<div v-if="argvs.operation === 'network'" class="options-container">
<div class="row items-center q-gutter-x-sm">
<div
v-for="opt in networkOptions"
:key="opt.value"
:class="['custom-btn', { active: argvs.type === opt.value }]"
@click="updateArgvs('type', opt.value)"
>
{{ opt.label }}
</div>
</div>
<div class="q-mt-xs" v-if="argvs.type === 'networkInterfaces'">
<q-checkbox
:model-value="argvs.internal"
@update:model-value="(val) => updateArgvs('internal', val)"
label="包含内部接口"
dense
class="text-caption"
/>
</div>
</div>
<!-- 平台信息配置 -->
<div v-if="argvs.operation === 'platform'" class="options-container">
<div class="row items-center q-gutter-x-sm wrap">
<div
v-for="opt in platformOptions"
:key="opt.value"
:class="['custom-btn', { active: argvs.type === opt.value }]"
@click="updateArgvs('type', opt.value)"
>
{{ opt.label }}
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { defineComponent } from "vue";
import { stringifyObject, parseFunction } from "js/composer/formatString";
export default defineComponent({
name: "OsEditor",
props: {
modelValue: {
type: Object,
required: true,
},
},
emits: ["update:modelValue"],
data() {
return {
operations: [
{ name: "arch", label: "系统架构", icon: "memory" },
{ name: "cpus", label: "CPU信息", icon: "developer_board" },
{ name: "memory", label: "内存信息", icon: "storage" },
{ name: "network", label: "网络信息", icon: "wifi" },
{ name: "platform", label: "平台信息", icon: "computer" },
],
formatOptions: [
{ label: "完整信息", value: "full" },
{ label: "仅型号和速度", value: "simple" },
],
memoryOptions: [
{ label: "总内存", value: "totalmem" },
{ label: "空闲内存", value: "freemem" },
],
networkOptions: [
{ label: "主机名", value: "hostname" },
{ label: "网络接口", value: "networkInterfaces" },
],
platformOptions: [
{ label: "操作系统名称", value: "platform" },
{ label: "操作系统类型", value: "type" },
{ label: "操作系统版本", value: "release" },
{ label: "操作系统架构", value: "arch" },
{ label: "CPU字节序", value: "endianness" },
{ label: "系统临时目录", value: "tmpdir" },
{ label: "主目录", value: "homedir" },
{ label: "系统正常运行时间", value: "uptime" },
{ label: "用户信息", value: "userInfo" },
],
defaultArgvs: {
operation: "arch",
format: "full",
type: "platform",
internal: false,
},
};
},
computed: {
argvs: {
get() {
return (
this.modelValue.argvs ||
this.parseCodeToArgvs(this.modelValue.code) || {
...this.defaultArgvs,
}
);
},
set(value) {
//
const newValue = { ...value };
if (value.operation !== this.argvs.operation) {
switch (value.operation) {
case "cpus":
newValue.format = "full";
delete newValue.type;
delete newValue.internal;
break;
case "memory":
newValue.type = "totalmem";
delete newValue.format;
delete newValue.internal;
break;
case "network":
newValue.type = "hostname";
delete newValue.format;
break;
case "platform":
newValue.type = "platform";
delete newValue.format;
delete newValue.internal;
break;
default:
delete newValue.format;
delete newValue.type;
delete newValue.internal;
}
}
this.$emit("update:modelValue", {
...this.modelValue,
code: this.generateCode(newValue),
argvs: newValue,
});
},
},
hasOptions() {
return ["cpus", "memory", "network", "platform"].includes(
this.argvs.operation
);
},
pointerStyle() {
const activeIndex = this.operations.findIndex(
(op) => op.name === this.argvs.operation
);
if (activeIndex === -1) return {};
//
const cardWidth = 80; //
const gap = 4; //
const pointerWidth = 12; //
//
// 1. (cardWidth + gap) * activeIndex
// 2. cardWidth / 2
// 3. pointerWidth / 2
const leftOffset =
(cardWidth + gap) * activeIndex + cardWidth / 2 - pointerWidth / 2;
return {
left: `${leftOffset}px`,
};
},
},
methods: {
generateCode(argvs = this.argvs) {
const params = {};
//
switch (argvs.operation) {
case "cpus":
params.format = argvs.format;
break;
case "memory":
params.type = argvs.type;
break;
case "network":
params.type = argvs.type;
if (argvs.type === "networkInterfaces") {
params.internal = argvs.internal;
}
break;
case "platform":
params.type = argvs.type;
break;
}
//
if (Object.keys(params).length === 0) {
return `${this.modelValue.value}.${argvs.operation}()`;
}
return `${this.modelValue.value}.${argvs.operation}(${stringifyObject(
params
)})`;
},
parseCodeToArgvs(code) {
if (!code) return null;
try {
// 使 parseFunction
const result = parseFunction(code);
if (!result) return this.defaultArgvs;
//
const operation = result.name.split(".").pop();
const [params = {}] = result.args;
return {
...this.defaultArgvs,
operation,
...params,
};
} catch (e) {
console.error("解析OS参数失败:", e);
return this.defaultArgvs;
}
},
updateArgvs(key, value) {
this.argvs = {
...this.argvs,
[key]: value,
};
},
},
mounted() {
if (!this.modelValue.argvs && !this.modelValue.code) {
this.$emit("update:modelValue", {
...this.modelValue,
code: this.generateCode(this.defaultArgvs),
argvs: { ...this.defaultArgvs },
});
}
},
});
</script>
<style scoped>
.os-editor {
display: flex;
flex-direction: column;
}
.operation-cards {
display: flex;
justify-content: flex-start;
gap: 4px;
}
.operation-card {
cursor: pointer;
transition: all 0.2s ease;
border: 1px solid transparent;
border-radius: 4px;
min-width: 80px;
}
.operation-card:hover {
background: var(--q-primary-opacity-5);
}
.operation-card.active {
border-color: var(--q-primary);
background: var(--q-primary-opacity-5);
}
.operation-options {
position: relative;
background: #f8f8f8;
border-radius: 4px;
padding: 12px;
margin-top: 12px !important;
z-index: 0;
}
.options-container {
min-height: 32px;
display: flex;
flex-direction: column;
justify-content: center;
}
.bubble-pointer {
position: absolute;
top: -6px;
width: 12px;
height: 12px;
background: #f8f8f8;
transform: rotate(45deg);
transition: left 0.3s ease;
z-index: 1;
}
.body--dark .operation-options,
.body--dark .bubble-pointer {
background: rgba(0, 0, 0, 0.1);
}
.custom-btn {
display: inline-flex;
align-items: center;
justify-content: center;
height: 24px;
padding: 0 8px;
font-size: 12px;
border-radius: 3px;
cursor: pointer;
transition: all 0.2s ease;
color: var(--q-primary);
background: transparent;
white-space: nowrap;
}
.custom-btn:hover {
background: var(--q-primary-opacity-1);
}
.custom-btn.active {
color: white;
background: var(--q-primary);
}
</style>

View File

@ -59,3 +59,7 @@ export const FileOperationEditor = defineAsyncComponent(() =>
export const SystemCommandEditor = defineAsyncComponent(() =>
import("components/composer/system/SystemCommandEditor.vue")
);
export const OsEditor = defineAsyncComponent(() =>
import("components/composer/system/OsEditor.vue")
);

View File

@ -25,9 +25,15 @@ export const systemCommands = {
value: "quickcomposer.system.exec",
label: "执行系统命令",
desc: "执行系统命令并返回输出结果",
config: [],
component: "SystemCommandEditor",
icon: "terminal",
},
{
value: "quickcomposer.system.os",
label: "系统信息",
desc: "获取操作系统相关信息",
component: "OsEditor",
icon: "computer",
},
],
};

View File

@ -91,7 +91,7 @@ const customComponentGuide = {
},
},
parseCodeToArgvs: {
description: "解析代码字符串为参数对象",
description: "解析代码字符串为参数对象严禁使用eval",
parameters: "code - 要解析的代码字符串",
implementation: {
steps: [

View File

@ -219,7 +219,22 @@ export const parseFunction = (functionStr, options = {}) => {
throw new Error("Not a valid function call");
}
const functionName = callExpression.callee.name;
// 处理函数名,支持成员方法调用
let name;
if (callExpression.callee.type === "MemberExpression") {
// 递归获取完整的成员访问路径
const getMemberPath = (node) => {
if (node.type === "Identifier") {
return node.name;
} else if (node.type === "MemberExpression") {
return `${getMemberPath(node.object)}.${node.property.name}`;
}
return "";
};
name = getMemberPath(callExpression.callee);
} else {
name = callExpression.callee.name;
}
// 递归处理AST节点
const processNode = (node, currentPath = "") => {
@ -260,6 +275,9 @@ export const parseFunction = (functionStr, options = {}) => {
);
case "ObjectProperty":
return processNode(node.value, currentPath);
case "MemberExpression":
// 处理成员表达式
return getMemberPath(node);
default:
console.warn("Unhandled node type:", node.type);
return null;
@ -271,7 +289,7 @@ export const parseFunction = (functionStr, options = {}) => {
);
return {
functionName,
name,
args,
};
} catch (e) {