@mqn00/file-manager-plugin-system-info 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -0
- package/dist/backend.d.ts +11 -0
- package/dist/backend.js +188 -0
- package/dist/frontend.d.ts +9 -0
- package/dist/frontend.js +546 -0
- package/dist/icons-types.d.ts +29 -0
- package/dist/icons-types.js +2 -0
- package/dist/page.d.ts +65 -0
- package/dist/page.js +187 -0
- package/dist/style.d.ts +6 -0
- package/dist/style.js +129 -0
- package/dist/vue-shim.d.ts +18 -0
- package/dist/vue-shim.js +8 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# file-manager-plugin-system-info
|
|
2
|
+
|
|
3
|
+
File Manager 系统信息插件:将内置「系统信息」页面抽取为独立插件,展示服务器的 OS / CPU / 内存 / 磁盘分区 / Node.js 信息。
|
|
4
|
+
|
|
5
|
+
## 功能
|
|
6
|
+
|
|
7
|
+
- **系统信息展示**:操作系统(类型/平台/架构/版本/主机名/运行时间)、CPU(型号/核心数/频率)、内存(总量/已用/可用)、硬盘(设备/制造商/型号/挂载点/文件系统/使用率进度条 + 分区明细)、Node.js(版本/进程 ID)
|
|
8
|
+
- **多磁盘切换**:检测到多块物理磁盘时,页面顶部下拉切换查看
|
|
9
|
+
- **顶栏导航入口**:启用插件后,文件浏览器顶栏出现「系统信息」图标按钮(主应用 `__fm_nav_actions` 挂载点),点击进入 `/plugin/system-info`(需登录)
|
|
10
|
+
- **主题适配**:样式使用主应用主题令牌(`--app-*`),明暗主题自动切换
|
|
11
|
+
|
|
12
|
+
## 使用
|
|
13
|
+
|
|
14
|
+
安装/启用插件(config.yml 的 `plugins.system-info.enabled: true`)后,顶栏出现系统信息按钮 → 点击进入页面;页面内可手动刷新,加载失败显示错误态 + 重试。
|
|
15
|
+
|
|
16
|
+
## 接口
|
|
17
|
+
|
|
18
|
+
需登录,挂在 `/api/plugin/system-info` 下(详见主项目 `API.md`「系统信息(system-info 插件)」):
|
|
19
|
+
|
|
20
|
+
| 接口 | 说明 |
|
|
21
|
+
|------|------|
|
|
22
|
+
| `GET /api/plugin/system-info/info` | 返回系统信息:`{ os, cpu, memory, disk, disks, node }`(结构与迁移前 `GET /api/system` 一致) |
|
|
23
|
+
|
|
24
|
+
## 构建与测试
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install --registry=https://registry.npmmirror.com # 安装依赖(systeminformation 等)
|
|
28
|
+
npm run build # tsc 编译后端 + esbuild 打包前端
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 实现说明
|
|
32
|
+
|
|
33
|
+
- **图标**:图标组件来自 `@element-plus/icons-vue`(devDependency,仅打包 import 到的图标,tree-shake);图标组件对 `vue` 的引用经构建期别名 `vue → src/vue-shim.ts` 转发到主应用暴露的 `window.Vue`,bundle 不含 vue runtime(避免双实例)
|
|
34
|
+
- **类型门面**:icons 2.3.2 的类型入口在插件 tsconfig(NodeNext)下无法解析出具名导出,tsconfig `paths` 将 `@element-plus/icons-vue` 映射到 `src/icons-types.ts`(类型经主项目 `frontend-types` 导入);esbuild 打包时显式 alias 到真实包入口
|
|
35
|
+
- **页面**:render 函数实现(插件不走 SFC),Element Plus 组件取自 `ctx.ElementPlus`
|
|
36
|
+
|
|
37
|
+
## 依赖
|
|
38
|
+
|
|
39
|
+
- 运行时:`systeminformation`
|
|
40
|
+
- 主项目:`file-manager`(peerDependencies,类型与平台能力:`ctx.router.addRoute`、`ctx.api.instance`、`window.__fm_nav_actions` 挂载点等)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 系统信息插件后端入口
|
|
3
|
+
*
|
|
4
|
+
* 注册路由(需登录):
|
|
5
|
+
* - GET /api/plugin/system-info/info :收集并返回系统信息(OS/CPU/内存/磁盘分区/Node)
|
|
6
|
+
*
|
|
7
|
+
* 逻辑自主应用内置 GET /api/system 原样移植(systeminformation 收集 + 磁盘按物理盘分组 +
|
|
8
|
+
* 存储根目录磁盘匹配 + CPU 频率逐级回退),保证响应结构与迁移前完全一致。
|
|
9
|
+
*/
|
|
10
|
+
import type { BackendPluginContext, PluginInstallFunction } from '@mqn00/file-manager/plugin';
|
|
11
|
+
export declare const install: PluginInstallFunction<BackendPluginContext>;
|
package/dist/backend.js
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.install = void 0;
|
|
7
|
+
const os_1 = __importDefault(require("os"));
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
9
|
+
const systeminformation_1 = __importDefault(require("systeminformation"));
|
|
10
|
+
function formatBytes(bytes) {
|
|
11
|
+
if (bytes === 0)
|
|
12
|
+
return '0 B';
|
|
13
|
+
const k = 1024;
|
|
14
|
+
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
15
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
16
|
+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
|
|
17
|
+
}
|
|
18
|
+
function formatUptime(seconds) {
|
|
19
|
+
const days = Math.floor(seconds / 86400);
|
|
20
|
+
const hours = Math.floor((seconds % 86400) / 3600);
|
|
21
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
22
|
+
const parts = [];
|
|
23
|
+
if (days > 0)
|
|
24
|
+
parts.push(`${days}天`);
|
|
25
|
+
if (hours > 0)
|
|
26
|
+
parts.push(`${hours}小时`);
|
|
27
|
+
if (minutes > 0)
|
|
28
|
+
parts.push(`${minutes}分钟`);
|
|
29
|
+
return parts.join(' ') || '0分钟';
|
|
30
|
+
}
|
|
31
|
+
/** 提取基础设备名: Linux /dev/sda1 → sda, Windows C: → C */
|
|
32
|
+
function getBaseDevice(devPath) {
|
|
33
|
+
if (process.platform === 'win32') {
|
|
34
|
+
return devPath.replace(/^([A-Z]):.*$/i, '$1');
|
|
35
|
+
}
|
|
36
|
+
return devPath.replace(/\/dev\//, '').replace(/\d+$/, '');
|
|
37
|
+
}
|
|
38
|
+
/** 构建磁盘列表,将 fsSize 条目按物理磁盘分组 */
|
|
39
|
+
function buildDiskList(fsSizes, diskLayouts) {
|
|
40
|
+
// 构建 vendor/model 查找表
|
|
41
|
+
const layoutMap = new Map();
|
|
42
|
+
for (const dl of diskLayouts) {
|
|
43
|
+
layoutMap.set(getBaseDevice(dl.device), dl);
|
|
44
|
+
}
|
|
45
|
+
// 按基础设备名分组,过滤虚拟文件系统
|
|
46
|
+
const groups = new Map();
|
|
47
|
+
for (const fs of fsSizes) {
|
|
48
|
+
if (['tmpfs', 'devtmpfs', 'overlay', 'squashfs', 'nsfs'].includes(fs.type))
|
|
49
|
+
continue;
|
|
50
|
+
const base = getBaseDevice(fs.fs);
|
|
51
|
+
if (!groups.has(base))
|
|
52
|
+
groups.set(base, []);
|
|
53
|
+
const group = groups.get(base);
|
|
54
|
+
if (group)
|
|
55
|
+
group.push(fs);
|
|
56
|
+
}
|
|
57
|
+
const disks = [];
|
|
58
|
+
for (const [base, partitions] of groups) {
|
|
59
|
+
const layout = layoutMap.get(base);
|
|
60
|
+
const mountpoints = partitions.map((p) => p.mount).filter(Boolean);
|
|
61
|
+
let total = 0;
|
|
62
|
+
let free = 0;
|
|
63
|
+
const partDetails = [];
|
|
64
|
+
for (const p of partitions) {
|
|
65
|
+
total += p.size;
|
|
66
|
+
free += p.available;
|
|
67
|
+
partDetails.push({
|
|
68
|
+
mountpoint: p.mount,
|
|
69
|
+
totalFormatted: formatBytes(p.size),
|
|
70
|
+
usedFormatted: formatBytes(p.used),
|
|
71
|
+
percent: Math.round(p.use),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
let device;
|
|
75
|
+
if (process.platform === 'win32') {
|
|
76
|
+
device = layout?.device || base + ':';
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
device = layout?.device || '/dev/' + base;
|
|
80
|
+
}
|
|
81
|
+
disks.push({
|
|
82
|
+
device,
|
|
83
|
+
vendor: (layout?.vendor || '').trim(),
|
|
84
|
+
model: (layout?.type || '').trim(),
|
|
85
|
+
mountpoint: mountpoints[0] || '未挂载',
|
|
86
|
+
mountpoints: mountpoints.length > 0 ? mountpoints : ['未挂载'],
|
|
87
|
+
fstype: partitions[0]?.type || 'unknown',
|
|
88
|
+
total,
|
|
89
|
+
free,
|
|
90
|
+
used: total - free,
|
|
91
|
+
totalFormatted: total > 0 ? formatBytes(total) : 'Unknown',
|
|
92
|
+
freeFormatted: free > 0 ? formatBytes(free) : '--',
|
|
93
|
+
usedFormatted: total > 0 ? formatBytes(total - free) : '--',
|
|
94
|
+
partitions: partDetails,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return disks;
|
|
98
|
+
}
|
|
99
|
+
/** 获取 CPU 频率 (MHz),systeminformation 在 WSL/容器环境可能返回 0,逐级回退 */
|
|
100
|
+
function getCpuSpeed(cpuInfo) {
|
|
101
|
+
if (cpuInfo.speed > 0)
|
|
102
|
+
return Math.round(cpuInfo.speed * 1000);
|
|
103
|
+
if (cpuInfo.speedMax > 0)
|
|
104
|
+
return Math.round(cpuInfo.speedMax * 1000);
|
|
105
|
+
// Linux: 尝试 lscpu 命令行获取
|
|
106
|
+
if (process.platform === 'linux') {
|
|
107
|
+
try {
|
|
108
|
+
const output = (0, child_process_1.execSync)('lscpu | grep "CPU MHz"', { encoding: 'utf-8' });
|
|
109
|
+
const match = output.match(/CPU MHz:\s*([\d.]+)/);
|
|
110
|
+
if (match)
|
|
111
|
+
return parseFloat(match[1]);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// lscpu 不可用,继续回退
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const cpus = os_1.default.cpus();
|
|
118
|
+
return cpus.length > 0 ? cpus[0].speed : 0;
|
|
119
|
+
}
|
|
120
|
+
const install = (ctx) => {
|
|
121
|
+
const router = ctx.express.Router();
|
|
122
|
+
router.get('/info', ctx.middleware.auth, async (_req, res) => {
|
|
123
|
+
try {
|
|
124
|
+
const [cpuInfo, currentLoad, mem, fsSizes, diskLayouts] = await Promise.all([
|
|
125
|
+
systeminformation_1.default.cpu(),
|
|
126
|
+
systeminformation_1.default.currentLoad(),
|
|
127
|
+
systeminformation_1.default.mem(),
|
|
128
|
+
systeminformation_1.default.fsSize(),
|
|
129
|
+
systeminformation_1.default.diskLayout(),
|
|
130
|
+
]);
|
|
131
|
+
const disks = buildDiskList(fsSizes, diskLayouts);
|
|
132
|
+
const storageRoot = ctx.utils.path.getStorageRoot();
|
|
133
|
+
const defaultDisk = disks.find((d) => d.mountpoint === storageRoot) ||
|
|
134
|
+
disks.find((d) => d.mountpoints.includes(storageRoot)) ||
|
|
135
|
+
disks[0];
|
|
136
|
+
const cpus = os_1.default.cpus();
|
|
137
|
+
res.json({
|
|
138
|
+
os: {
|
|
139
|
+
type: os_1.default.type(),
|
|
140
|
+
platform: os_1.default.platform(),
|
|
141
|
+
arch: os_1.default.arch(),
|
|
142
|
+
release: os_1.default.release(),
|
|
143
|
+
hostname: os_1.default.hostname(),
|
|
144
|
+
uptime: os_1.default.uptime(),
|
|
145
|
+
uptimeFormatted: formatUptime(os_1.default.uptime()),
|
|
146
|
+
},
|
|
147
|
+
cpu: {
|
|
148
|
+
model: cpuInfo.brand || (cpus.length > 0 ? cpus[0].model.replace(/\s+/g, ' ').trim() : 'Unknown'),
|
|
149
|
+
cores: cpuInfo.cores,
|
|
150
|
+
physicalCores: cpuInfo.physicalCores,
|
|
151
|
+
speed: getCpuSpeed(cpuInfo),
|
|
152
|
+
usage: Math.round(currentLoad.currentLoad * 10) / 10,
|
|
153
|
+
},
|
|
154
|
+
memory: {
|
|
155
|
+
total: mem.total,
|
|
156
|
+
free: mem.free,
|
|
157
|
+
used: mem.used,
|
|
158
|
+
usagePercent: Math.round((mem.used / mem.total) * 1000) / 10,
|
|
159
|
+
totalFormatted: formatBytes(mem.total),
|
|
160
|
+
freeFormatted: formatBytes(mem.free),
|
|
161
|
+
usedFormatted: formatBytes(mem.used),
|
|
162
|
+
},
|
|
163
|
+
disk: defaultDisk || {
|
|
164
|
+
device: 'Unknown',
|
|
165
|
+
mountpoint: '/',
|
|
166
|
+
fstype: 'unknown',
|
|
167
|
+
total: 0,
|
|
168
|
+
free: 0,
|
|
169
|
+
used: 0,
|
|
170
|
+
totalFormatted: '0 B',
|
|
171
|
+
freeFormatted: '0 B',
|
|
172
|
+
usedFormatted: '0 B',
|
|
173
|
+
},
|
|
174
|
+
disks,
|
|
175
|
+
node: {
|
|
176
|
+
version: process.version,
|
|
177
|
+
pid: process.pid,
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
catch (e) {
|
|
182
|
+
ctx.utils.logger.log('ERROR', 'system-info', `获取系统信息失败: ${e?.message || '未知错误'}`);
|
|
183
|
+
res.status(500).json({ message: e?.message || '获取系统信息失败' });
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
ctx.app.use('/api/plugin/system-info', router);
|
|
187
|
+
};
|
|
188
|
+
exports.install = install;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 系统信息插件前端入口
|
|
3
|
+
*
|
|
4
|
+
* 1. 注册页面路由 /plugin/system-info(requiresAuth,由主应用路由守卫兜底登录)
|
|
5
|
+
* 2. 向主应用导航注册表(window.__fm_nav_actions,类型契约由
|
|
6
|
+
* @mqn00/file-manager/plugin/frontend 发布)注册顶栏入口按钮
|
|
7
|
+
*/
|
|
8
|
+
import type { FrontendPluginInstallFunction } from '@mqn00/file-manager/plugin/frontend';
|
|
9
|
+
export declare const install: FrontendPluginInstallFunction;
|
package/dist/frontend.js
ADDED
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
// src/vue-shim.ts
|
|
2
|
+
var V = window.Vue;
|
|
3
|
+
var defineComponent = V.defineComponent;
|
|
4
|
+
var openBlock = V.openBlock;
|
|
5
|
+
var createElementBlock = V.createElementBlock;
|
|
6
|
+
var createElementVNode = V.createElementVNode;
|
|
7
|
+
|
|
8
|
+
// node_modules/@element-plus/icons-vue/dist/index.js
|
|
9
|
+
var _sfc_main8 = /* @__PURE__ */ defineComponent({
|
|
10
|
+
name: "ArrowLeft",
|
|
11
|
+
__name: "arrow-left",
|
|
12
|
+
setup(__props) {
|
|
13
|
+
return (_ctx, _cache) => (openBlock(), createElementBlock("svg", {
|
|
14
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
15
|
+
viewBox: "0 0 1024 1024"
|
|
16
|
+
}, [
|
|
17
|
+
createElementVNode("path", {
|
|
18
|
+
fill: "currentColor",
|
|
19
|
+
d: "M609.408 149.376 277.76 489.6a32 32 0 0 0 0 44.672l331.648 340.352a29.12 29.12 0 0 0 41.728 0 30.59 30.59 0 0 0 0-42.752L339.264 511.936l311.872-319.872a30.59 30.59 0 0 0 0-42.688 29.12 29.12 0 0 0-41.728 0"
|
|
20
|
+
})
|
|
21
|
+
]));
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
var arrow_left_default = _sfc_main8;
|
|
25
|
+
var _sfc_main24 = /* @__PURE__ */ defineComponent({
|
|
26
|
+
name: "Box",
|
|
27
|
+
__name: "box",
|
|
28
|
+
setup(__props) {
|
|
29
|
+
return (_ctx, _cache) => (openBlock(), createElementBlock("svg", {
|
|
30
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
31
|
+
viewBox: "0 0 1024 1024"
|
|
32
|
+
}, [
|
|
33
|
+
createElementVNode("path", {
|
|
34
|
+
fill: "currentColor",
|
|
35
|
+
d: "M317.056 128 128 344.064V896h768V344.064L706.944 128zm-14.528-64h418.944a32 32 0 0 1 24.064 10.88l206.528 236.096A32 32 0 0 1 960 332.032V928a32 32 0 0 1-32 32H96a32 32 0 0 1-32-32V332.032a32 32 0 0 1 7.936-21.12L278.4 75.008A32 32 0 0 1 302.528 64"
|
|
36
|
+
}),
|
|
37
|
+
createElementVNode("path", {
|
|
38
|
+
fill: "currentColor",
|
|
39
|
+
d: "M64 320h896v64H64z"
|
|
40
|
+
}),
|
|
41
|
+
createElementVNode("path", {
|
|
42
|
+
fill: "currentColor",
|
|
43
|
+
d: "M448 327.872V640h128V327.872L526.08 128h-28.16zM448 64h128l64 256v352a32 32 0 0 1-32 32H416a32 32 0 0 1-32-32V320z"
|
|
44
|
+
})
|
|
45
|
+
]));
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
var box_default = _sfc_main24;
|
|
49
|
+
var _sfc_main60 = /* @__PURE__ */ defineComponent({
|
|
50
|
+
name: "Coin",
|
|
51
|
+
__name: "coin",
|
|
52
|
+
setup(__props) {
|
|
53
|
+
return (_ctx, _cache) => (openBlock(), createElementBlock("svg", {
|
|
54
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
55
|
+
viewBox: "0 0 1024 1024"
|
|
56
|
+
}, [
|
|
57
|
+
createElementVNode("path", {
|
|
58
|
+
fill: "currentColor",
|
|
59
|
+
d: "m161.92 580.736 29.888 58.88C171.328 659.776 160 681.728 160 704c0 82.304 155.328 160 352 160s352-77.696 352-160c0-22.272-11.392-44.16-31.808-64.32l30.464-58.432C903.936 615.808 928 657.664 928 704c0 129.728-188.544 224-416 224S96 833.728 96 704c0-46.592 24.32-88.576 65.92-123.264"
|
|
60
|
+
}),
|
|
61
|
+
createElementVNode("path", {
|
|
62
|
+
fill: "currentColor",
|
|
63
|
+
d: "m161.92 388.736 29.888 58.88C171.328 467.84 160 489.792 160 512c0 82.304 155.328 160 352 160s352-77.696 352-160c0-22.272-11.392-44.16-31.808-64.32l30.464-58.432C903.936 423.808 928 465.664 928 512c0 129.728-188.544 224-416 224S96 641.728 96 512c0-46.592 24.32-88.576 65.92-123.264"
|
|
64
|
+
}),
|
|
65
|
+
createElementVNode("path", {
|
|
66
|
+
fill: "currentColor",
|
|
67
|
+
d: "M512 544c-227.456 0-416-94.272-416-224S284.544 96 512 96s416 94.272 416 224-188.544 224-416 224m0-64c196.672 0 352-77.696 352-160S708.672 160 512 160s-352 77.696-352 160 155.328 160 352 160"
|
|
68
|
+
})
|
|
69
|
+
]));
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
var coin_default = _sfc_main60;
|
|
73
|
+
var _sfc_main69 = /* @__PURE__ */ defineComponent({
|
|
74
|
+
name: "Cpu",
|
|
75
|
+
__name: "cpu",
|
|
76
|
+
setup(__props) {
|
|
77
|
+
return (_ctx, _cache) => (openBlock(), createElementBlock("svg", {
|
|
78
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
79
|
+
viewBox: "0 0 1024 1024"
|
|
80
|
+
}, [
|
|
81
|
+
createElementVNode("path", {
|
|
82
|
+
fill: "currentColor",
|
|
83
|
+
d: "M320 256a64 64 0 0 0-64 64v384a64 64 0 0 0 64 64h384a64 64 0 0 0 64-64V320a64 64 0 0 0-64-64zm0-64h384a128 128 0 0 1 128 128v384a128 128 0 0 1-128 128H320a128 128 0 0 1-128-128V320a128 128 0 0 1 128-128"
|
|
84
|
+
}),
|
|
85
|
+
createElementVNode("path", {
|
|
86
|
+
fill: "currentColor",
|
|
87
|
+
d: "M512 64a32 32 0 0 1 32 32v128h-64V96a32 32 0 0 1 32-32m160 0a32 32 0 0 1 32 32v128h-64V96a32 32 0 0 1 32-32m-320 0a32 32 0 0 1 32 32v128h-64V96a32 32 0 0 1 32-32m160 896a32 32 0 0 1-32-32V800h64v128a32 32 0 0 1-32 32m160 0a32 32 0 0 1-32-32V800h64v128a32 32 0 0 1-32 32m-320 0a32 32 0 0 1-32-32V800h64v128a32 32 0 0 1-32 32M64 512a32 32 0 0 1 32-32h128v64H96a32 32 0 0 1-32-32m0-160a32 32 0 0 1 32-32h128v64H96a32 32 0 0 1-32-32m0 320a32 32 0 0 1 32-32h128v64H96a32 32 0 0 1-32-32m896-160a32 32 0 0 1-32 32H800v-64h128a32 32 0 0 1 32 32m0-160a32 32 0 0 1-32 32H800v-64h128a32 32 0 0 1 32 32m0 320a32 32 0 0 1-32 32H800v-64h128a32 32 0 0 1 32 32"
|
|
88
|
+
})
|
|
89
|
+
]));
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
var cpu_default = _sfc_main69;
|
|
93
|
+
var _sfc_main150 = /* @__PURE__ */ defineComponent({
|
|
94
|
+
name: "Loading",
|
|
95
|
+
__name: "loading",
|
|
96
|
+
setup(__props) {
|
|
97
|
+
return (_ctx, _cache) => (openBlock(), createElementBlock("svg", {
|
|
98
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
99
|
+
viewBox: "0 0 1024 1024"
|
|
100
|
+
}, [
|
|
101
|
+
createElementVNode("path", {
|
|
102
|
+
fill: "currentColor",
|
|
103
|
+
d: "M512 64a32 32 0 0 1 32 32v192a32 32 0 0 1-64 0V96a32 32 0 0 1 32-32m0 640a32 32 0 0 1 32 32v192a32 32 0 1 1-64 0V736a32 32 0 0 1 32-32m448-192a32 32 0 0 1-32 32H736a32 32 0 1 1 0-64h192a32 32 0 0 1 32 32m-640 0a32 32 0 0 1-32 32H96a32 32 0 0 1 0-64h192a32 32 0 0 1 32 32M195.2 195.2a32 32 0 0 1 45.248 0L376.32 331.008a32 32 0 0 1-45.248 45.248L195.2 240.448a32 32 0 0 1 0-45.248m452.544 452.544a32 32 0 0 1 45.248 0L828.8 783.552a32 32 0 0 1-45.248 45.248L647.744 692.992a32 32 0 0 1 0-45.248M828.8 195.264a32 32 0 0 1 0 45.184L692.992 376.32a32 32 0 0 1-45.248-45.248l135.808-135.808a32 32 0 0 1 45.248 0m-452.544 452.48a32 32 0 0 1 0 45.248L240.448 828.8a32 32 0 0 1-45.248-45.248l135.808-135.808a32 32 0 0 1 45.248 0"
|
|
104
|
+
})
|
|
105
|
+
]));
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
var loading_default = _sfc_main150;
|
|
109
|
+
var _sfc_main171 = /* @__PURE__ */ defineComponent({
|
|
110
|
+
name: "Monitor",
|
|
111
|
+
__name: "monitor",
|
|
112
|
+
setup(__props) {
|
|
113
|
+
return (_ctx, _cache) => (openBlock(), createElementBlock("svg", {
|
|
114
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
115
|
+
viewBox: "0 0 1024 1024"
|
|
116
|
+
}, [
|
|
117
|
+
createElementVNode("path", {
|
|
118
|
+
fill: "currentColor",
|
|
119
|
+
d: "M544 768v128h192a32 32 0 1 1 0 64H288a32 32 0 1 1 0-64h192V768H192A128 128 0 0 1 64 640V256a128 128 0 0 1 128-128h640a128 128 0 0 1 128 128v384a128 128 0 0 1-128 128zM192 192a64 64 0 0 0-64 64v384a64 64 0 0 0 64 64h640a64 64 0 0 0 64-64V256a64 64 0 0 0-64-64z"
|
|
120
|
+
})
|
|
121
|
+
]));
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
var monitor_default = _sfc_main171;
|
|
125
|
+
var _sfc_main217 = /* @__PURE__ */ defineComponent({
|
|
126
|
+
name: "Refresh",
|
|
127
|
+
__name: "refresh",
|
|
128
|
+
setup(__props) {
|
|
129
|
+
return (_ctx, _cache) => (openBlock(), createElementBlock("svg", {
|
|
130
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
131
|
+
viewBox: "0 0 1024 1024"
|
|
132
|
+
}, [
|
|
133
|
+
createElementVNode("path", {
|
|
134
|
+
fill: "currentColor",
|
|
135
|
+
d: "M771.776 794.88A384 384 0 0 1 128 512h64a320 320 0 0 0 555.712 216.448H654.72a32 32 0 1 1 0-64h149.056a32 32 0 0 1 32 32v148.928a32 32 0 1 1-64 0v-50.56zM276.288 295.616h92.992a32 32 0 0 1 0 64H220.16a32 32 0 0 1-32-32V178.56a32 32 0 0 1 64 0v50.56A384 384 0 0 1 896.128 512h-64a320 320 0 0 0-555.776-216.384z"
|
|
136
|
+
})
|
|
137
|
+
]));
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
var refresh_default = _sfc_main217;
|
|
141
|
+
var _sfc_main261 = /* @__PURE__ */ defineComponent({
|
|
142
|
+
name: "Tickets",
|
|
143
|
+
__name: "tickets",
|
|
144
|
+
setup(__props) {
|
|
145
|
+
return (_ctx, _cache) => (openBlock(), createElementBlock("svg", {
|
|
146
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
147
|
+
viewBox: "0 0 1024 1024"
|
|
148
|
+
}, [
|
|
149
|
+
createElementVNode("path", {
|
|
150
|
+
fill: "currentColor",
|
|
151
|
+
d: "M192 128v768h640V128zm-32-64h704a32 32 0 0 1 32 32v832a32 32 0 0 1-32 32H160a32 32 0 0 1-32-32V96a32 32 0 0 1 32-32m160 448h384v64H320zm0-192h192v64H320zm0 384h384v64H320z"
|
|
152
|
+
})
|
|
153
|
+
]));
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
var tickets_default = _sfc_main261;
|
|
157
|
+
var _sfc_main287 = /* @__PURE__ */ defineComponent({
|
|
158
|
+
name: "WarningFilled",
|
|
159
|
+
__name: "warning-filled",
|
|
160
|
+
setup(__props) {
|
|
161
|
+
return (_ctx, _cache) => (openBlock(), createElementBlock("svg", {
|
|
162
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
163
|
+
viewBox: "0 0 1024 1024"
|
|
164
|
+
}, [
|
|
165
|
+
createElementVNode("path", {
|
|
166
|
+
fill: "currentColor",
|
|
167
|
+
d: "M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896m0 192a58.43 58.43 0 0 0-58.24 63.744l23.36 256.384a35.072 35.072 0 0 0 69.76 0l23.296-256.384A58.43 58.43 0 0 0 512 256m0 512a51.2 51.2 0 1 0 0-102.4 51.2 51.2 0 0 0 0 102.4"
|
|
168
|
+
})
|
|
169
|
+
]));
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
var warning_filled_default = _sfc_main287;
|
|
173
|
+
|
|
174
|
+
// src/style.ts
|
|
175
|
+
var STYLE_ID = "fci-style";
|
|
176
|
+
var CSS = `
|
|
177
|
+
.fci-container {
|
|
178
|
+
height: 100vh;
|
|
179
|
+
display: flex;
|
|
180
|
+
align-items: flex-start;
|
|
181
|
+
justify-content: center;
|
|
182
|
+
padding-top: 40px;
|
|
183
|
+
overflow-y: auto;
|
|
184
|
+
}
|
|
185
|
+
.fci-card {
|
|
186
|
+
width: 800px;
|
|
187
|
+
background: var(--app-panel);
|
|
188
|
+
border: 1px solid var(--app-border);
|
|
189
|
+
border-radius: 12px;
|
|
190
|
+
box-shadow: var(--app-glow), var(--app-shadow);
|
|
191
|
+
backdrop-filter: var(--app-blur);
|
|
192
|
+
margin-bottom: 40px;
|
|
193
|
+
}
|
|
194
|
+
.fci-title {
|
|
195
|
+
margin: 0;
|
|
196
|
+
font-size: 20px;
|
|
197
|
+
color: var(--app-text-bright);
|
|
198
|
+
}
|
|
199
|
+
.fci-header {
|
|
200
|
+
display: flex;
|
|
201
|
+
align-items: center;
|
|
202
|
+
justify-content: space-between;
|
|
203
|
+
margin-bottom: 24px;
|
|
204
|
+
}
|
|
205
|
+
.fci-back {
|
|
206
|
+
color: var(--app-text-dim);
|
|
207
|
+
padding: 4px 8px;
|
|
208
|
+
}
|
|
209
|
+
.fci-grid {
|
|
210
|
+
display: grid;
|
|
211
|
+
grid-template-columns: repeat(2, 1fr);
|
|
212
|
+
gap: 16px;
|
|
213
|
+
}
|
|
214
|
+
.fci-info-card {
|
|
215
|
+
background: var(--app-accent-bg) !important;
|
|
216
|
+
border: 1px solid var(--app-accent-border) !important;
|
|
217
|
+
}
|
|
218
|
+
.fci-info-card .el-card__header {
|
|
219
|
+
padding: 12px 16px !important;
|
|
220
|
+
border-bottom: 1px solid var(--app-accent-border) !important;
|
|
221
|
+
}
|
|
222
|
+
.fci-info-card .el-card__body {
|
|
223
|
+
padding: 16px !important;
|
|
224
|
+
}
|
|
225
|
+
.fci-card-header {
|
|
226
|
+
display: flex;
|
|
227
|
+
align-items: center;
|
|
228
|
+
gap: 8px;
|
|
229
|
+
font-weight: 600;
|
|
230
|
+
color: var(--app-accent);
|
|
231
|
+
}
|
|
232
|
+
.fci-disk-select {
|
|
233
|
+
margin-left: auto;
|
|
234
|
+
width: 200px;
|
|
235
|
+
}
|
|
236
|
+
.fci-disk-select .el-input__wrapper {
|
|
237
|
+
background: var(--app-accent-bg) !important;
|
|
238
|
+
border: 1px solid var(--app-accent-border) !important;
|
|
239
|
+
box-shadow: none !important;
|
|
240
|
+
}
|
|
241
|
+
.fci-disk-select .el-input__inner {
|
|
242
|
+
color: var(--app-text-bright) !important;
|
|
243
|
+
}
|
|
244
|
+
.fci-list {
|
|
245
|
+
display: flex;
|
|
246
|
+
flex-direction: column;
|
|
247
|
+
gap: 12px;
|
|
248
|
+
}
|
|
249
|
+
.fci-item {
|
|
250
|
+
display: flex;
|
|
251
|
+
justify-content: space-between;
|
|
252
|
+
align-items: center;
|
|
253
|
+
}
|
|
254
|
+
.fci-label {
|
|
255
|
+
color: var(--app-text-dim);
|
|
256
|
+
font-size: 13px;
|
|
257
|
+
}
|
|
258
|
+
.fci-value {
|
|
259
|
+
color: var(--app-text-bright);
|
|
260
|
+
font-size: 13px;
|
|
261
|
+
text-align: right;
|
|
262
|
+
word-break: break-all;
|
|
263
|
+
}
|
|
264
|
+
.fci-version {
|
|
265
|
+
max-width: 250px;
|
|
266
|
+
overflow: hidden;
|
|
267
|
+
text-overflow: ellipsis;
|
|
268
|
+
}
|
|
269
|
+
.fci-disk-progress {
|
|
270
|
+
flex-direction: column;
|
|
271
|
+
align-items: stretch;
|
|
272
|
+
gap: 4px;
|
|
273
|
+
}
|
|
274
|
+
.fci-state {
|
|
275
|
+
display: flex;
|
|
276
|
+
flex-direction: column;
|
|
277
|
+
align-items: center;
|
|
278
|
+
justify-content: center;
|
|
279
|
+
padding: 60px 20px;
|
|
280
|
+
color: var(--app-text-dim);
|
|
281
|
+
gap: 16px;
|
|
282
|
+
}
|
|
283
|
+
.fci-state p { margin: 0; }
|
|
284
|
+
`;
|
|
285
|
+
function injectStyles() {
|
|
286
|
+
if (document.getElementById(STYLE_ID)) return;
|
|
287
|
+
const style = document.createElement("style");
|
|
288
|
+
style.id = STYLE_ID;
|
|
289
|
+
style.textContent = CSS;
|
|
290
|
+
document.head.appendChild(style);
|
|
291
|
+
}
|
|
292
|
+
function removeStyles() {
|
|
293
|
+
document.getElementById(STYLE_ID)?.remove();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// src/page.ts
|
|
297
|
+
function spaNavigate(ctx, url) {
|
|
298
|
+
void ctx.router.push(url);
|
|
299
|
+
}
|
|
300
|
+
function createSystemInfoPage(ctx) {
|
|
301
|
+
const { defineComponent: defineComponent2, h, ref, computed, onMounted } = ctx.Vue;
|
|
302
|
+
const {
|
|
303
|
+
ElButton,
|
|
304
|
+
ElIcon,
|
|
305
|
+
ElCard,
|
|
306
|
+
ElSelect,
|
|
307
|
+
ElOption,
|
|
308
|
+
ElProgress,
|
|
309
|
+
ElTooltip,
|
|
310
|
+
ElMessage
|
|
311
|
+
} = ctx.ElementPlus;
|
|
312
|
+
const api = ctx.api.instance;
|
|
313
|
+
return defineComponent2({
|
|
314
|
+
name: "SystemInfoPage",
|
|
315
|
+
setup() {
|
|
316
|
+
const systemInfo = ref(null);
|
|
317
|
+
const loading = ref(false);
|
|
318
|
+
const error = ref(null);
|
|
319
|
+
const selectedDiskIndex = ref(0);
|
|
320
|
+
const selectedDisk = computed(() => {
|
|
321
|
+
if (!systemInfo.value) return null;
|
|
322
|
+
return systemInfo.value.disks[selectedDiskIndex.value] || systemInfo.value.disk;
|
|
323
|
+
});
|
|
324
|
+
const diskUsageColor = computed(() => {
|
|
325
|
+
const disk = selectedDisk.value;
|
|
326
|
+
if (!disk?.total) return "";
|
|
327
|
+
const percent = Math.round(disk.used / disk.total * 100);
|
|
328
|
+
return percent > 85 ? "#F56C6C" : percent > 65 ? "#E6A23C" : "#409EFF";
|
|
329
|
+
});
|
|
330
|
+
const fetchSystemInfo = async () => {
|
|
331
|
+
loading.value = true;
|
|
332
|
+
error.value = null;
|
|
333
|
+
try {
|
|
334
|
+
const resp = await api.get("/plugin/system-info/info");
|
|
335
|
+
systemInfo.value = resp.data;
|
|
336
|
+
} catch (e) {
|
|
337
|
+
error.value = e?.response?.data?.message || "\u83B7\u53D6\u7CFB\u7EDF\u4FE1\u606F\u5931\u8D25";
|
|
338
|
+
ElMessage.error("\u83B7\u53D6\u7CFB\u7EDF\u4FE1\u606F\u5931\u8D25");
|
|
339
|
+
} finally {
|
|
340
|
+
loading.value = false;
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
onMounted(() => {
|
|
344
|
+
void fetchSystemInfo();
|
|
345
|
+
});
|
|
346
|
+
return () => {
|
|
347
|
+
const infoItem = (label, value, version = false) => h("div", { class: "fci-item" }, [
|
|
348
|
+
h("span", { class: "fci-label" }, label),
|
|
349
|
+
h("span", { class: version ? "fci-value fci-version" : "fci-value" }, value)
|
|
350
|
+
]);
|
|
351
|
+
const diskSelect = systemInfo.value && systemInfo.value.disks.length > 1 ? h(
|
|
352
|
+
ElSelect,
|
|
353
|
+
{
|
|
354
|
+
modelValue: selectedDiskIndex.value,
|
|
355
|
+
size: "small",
|
|
356
|
+
class: "fci-disk-select",
|
|
357
|
+
"onUpdate:modelValue": (v) => {
|
|
358
|
+
selectedDiskIndex.value = v;
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
default: () => systemInfo.value.disks.map(
|
|
363
|
+
(d, i) => h(
|
|
364
|
+
ElOption,
|
|
365
|
+
{
|
|
366
|
+
key: i,
|
|
367
|
+
label: `${d.device} (${d.mountpoints[0] || "\u672A\u6302\u8F7D"})`,
|
|
368
|
+
value: i
|
|
369
|
+
}
|
|
370
|
+
)
|
|
371
|
+
)
|
|
372
|
+
}
|
|
373
|
+
) : null;
|
|
374
|
+
const infoCard = (icon, title, children, headerExtra) => h(
|
|
375
|
+
ElCard,
|
|
376
|
+
{ class: "fci-info-card", shadow: "never" },
|
|
377
|
+
{
|
|
378
|
+
header: () => h("div", { class: "fci-card-header" }, [
|
|
379
|
+
h(ElIcon, { size: 16 }, { default: () => h(icon) }),
|
|
380
|
+
h("span", title),
|
|
381
|
+
headerExtra || null
|
|
382
|
+
]),
|
|
383
|
+
default: () => h("div", { class: "fci-list" }, children)
|
|
384
|
+
}
|
|
385
|
+
);
|
|
386
|
+
let body;
|
|
387
|
+
if (systemInfo.value) {
|
|
388
|
+
const info = systemInfo.value;
|
|
389
|
+
const disk = selectedDisk.value;
|
|
390
|
+
const diskPercent = disk?.total ? Math.round(disk.used / disk.total * 100) : 0;
|
|
391
|
+
body = h("div", { class: "fci-grid" }, [
|
|
392
|
+
// 操作系统
|
|
393
|
+
infoCard(monitor_default, "\u64CD\u4F5C\u7CFB\u7EDF", [
|
|
394
|
+
infoItem("\u7C7B\u578B", info.os.type),
|
|
395
|
+
infoItem("\u5E73\u53F0", info.os.platform),
|
|
396
|
+
infoItem("\u67B6\u6784", info.os.arch),
|
|
397
|
+
infoItem("\u7248\u672C", info.os.release, true),
|
|
398
|
+
infoItem("\u4E3B\u673A\u540D", info.os.hostname),
|
|
399
|
+
infoItem("\u8FD0\u884C\u65F6\u95F4", info.os.uptimeFormatted)
|
|
400
|
+
]),
|
|
401
|
+
// CPU
|
|
402
|
+
infoCard(cpu_default, "CPU", [
|
|
403
|
+
infoItem("\u578B\u53F7", info.cpu.model, true),
|
|
404
|
+
infoItem("\u6838\u5FC3\u6570", String(info.cpu.cores)),
|
|
405
|
+
infoItem("\u9891\u7387", `${info.cpu.speed} MHz`)
|
|
406
|
+
]),
|
|
407
|
+
// 内存
|
|
408
|
+
infoCard(coin_default, "\u5185\u5B58", [
|
|
409
|
+
infoItem("\u603B\u91CF", info.memory.totalFormatted),
|
|
410
|
+
infoItem("\u5DF2\u7528", info.memory.usedFormatted),
|
|
411
|
+
infoItem("\u53EF\u7528", info.memory.freeFormatted)
|
|
412
|
+
]),
|
|
413
|
+
// 硬盘
|
|
414
|
+
infoCard(
|
|
415
|
+
box_default,
|
|
416
|
+
"\u786C\u76D8",
|
|
417
|
+
[
|
|
418
|
+
infoItem("\u8BBE\u5907", disk?.device || "--"),
|
|
419
|
+
...disk?.vendor || disk?.model ? [infoItem("\u5236\u9020\u5546", disk?.vendor || "\u672A\u77E5")] : [],
|
|
420
|
+
...disk?.model ? [infoItem("\u578B\u53F7", disk?.model)] : [],
|
|
421
|
+
infoItem(
|
|
422
|
+
"\u6302\u8F7D\u70B9",
|
|
423
|
+
disk?.mountpoints && disk.mountpoints.length > 1 ? disk.mountpoints.join(", ") : disk?.mountpoint || "--",
|
|
424
|
+
true
|
|
425
|
+
),
|
|
426
|
+
infoItem("\u6587\u4EF6\u7CFB\u7EDF", disk?.fstype || "--"),
|
|
427
|
+
h("div", { class: "fci-item fci-disk-progress" }, [
|
|
428
|
+
h(
|
|
429
|
+
ElTooltip,
|
|
430
|
+
{ placement: "top" },
|
|
431
|
+
{
|
|
432
|
+
content: () => (disk?.partitions || []).map(
|
|
433
|
+
(p) => h(
|
|
434
|
+
"div",
|
|
435
|
+
{ key: p.mountpoint, style: { whiteSpace: "nowrap" } },
|
|
436
|
+
`${p.mountpoint}: ${p.percent}% | ${p.usedFormatted} / ${p.totalFormatted}`
|
|
437
|
+
)
|
|
438
|
+
),
|
|
439
|
+
default: () => h(ElProgress, {
|
|
440
|
+
percentage: diskPercent,
|
|
441
|
+
format: (p) => `${p}% | ${disk?.totalFormatted || "0 B"}`,
|
|
442
|
+
color: diskUsageColor.value
|
|
443
|
+
})
|
|
444
|
+
}
|
|
445
|
+
)
|
|
446
|
+
])
|
|
447
|
+
],
|
|
448
|
+
diskSelect
|
|
449
|
+
),
|
|
450
|
+
// Node.js
|
|
451
|
+
infoCard(tickets_default, "Node.js", [
|
|
452
|
+
infoItem("\u7248\u672C", info.node.version),
|
|
453
|
+
infoItem("\u8FDB\u7A0B ID", String(info.node.pid))
|
|
454
|
+
])
|
|
455
|
+
]);
|
|
456
|
+
} else if (loading.value) {
|
|
457
|
+
body = h("div", { class: "fci-state" }, [
|
|
458
|
+
h(ElIcon, { class: "is-loading", size: 32 }, { default: () => h(loading_default) }),
|
|
459
|
+
h("p", "\u52A0\u8F7D\u7CFB\u7EDF\u4FE1\u606F\u4E2D...")
|
|
460
|
+
]);
|
|
461
|
+
} else {
|
|
462
|
+
body = h("div", { class: "fci-state" }, [
|
|
463
|
+
h(ElIcon, { size: 32, color: "#f56c6c" }, { default: () => h(warning_filled_default) }),
|
|
464
|
+
h("p", error.value || "\u83B7\u53D6\u7CFB\u7EDF\u4FE1\u606F\u5931\u8D25"),
|
|
465
|
+
h(ElButton, { type: "primary", onClick: () => void fetchSystemInfo() }, () => "\u91CD\u8BD5")
|
|
466
|
+
]);
|
|
467
|
+
}
|
|
468
|
+
return h("div", { class: "fci-container" }, [
|
|
469
|
+
h("div", { class: "fci-card" }, [
|
|
470
|
+
h("div", { style: { paddingTop: "10px", paddingLeft: "10px" } }, [
|
|
471
|
+
h(
|
|
472
|
+
ElButton,
|
|
473
|
+
{ text: true, class: "fci-back", onClick: () => spaNavigate(ctx, "/") },
|
|
474
|
+
{
|
|
475
|
+
default: () => [
|
|
476
|
+
h(ElIcon, null, { default: () => h(arrow_left_default) }),
|
|
477
|
+
h("span", "\u8FD4\u56DE")
|
|
478
|
+
]
|
|
479
|
+
}
|
|
480
|
+
)
|
|
481
|
+
]),
|
|
482
|
+
h("div", { style: { padding: "20px 36px" } }, [
|
|
483
|
+
h("div", { class: "fci-header" }, [
|
|
484
|
+
h("h3", { class: "fci-title" }, "\u7CFB\u7EDF\u4FE1\u606F"),
|
|
485
|
+
h(
|
|
486
|
+
ElButton,
|
|
487
|
+
{ size: "small", loading: loading.value, onClick: () => void fetchSystemInfo() },
|
|
488
|
+
{
|
|
489
|
+
default: () => [
|
|
490
|
+
h(ElIcon, null, { default: () => h(refresh_default) }),
|
|
491
|
+
h("span", "\u5237\u65B0")
|
|
492
|
+
]
|
|
493
|
+
}
|
|
494
|
+
)
|
|
495
|
+
]),
|
|
496
|
+
// 硬盘下拉已内嵌于硬盘卡片 header
|
|
497
|
+
body
|
|
498
|
+
])
|
|
499
|
+
])
|
|
500
|
+
]);
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// src/frontend.ts
|
|
507
|
+
function registerNavAction(action) {
|
|
508
|
+
const api = window.__fm_nav_actions;
|
|
509
|
+
if (!api || typeof api.register !== "function") {
|
|
510
|
+
console.warn("[system-info] \u4E3B\u5E94\u7528\u672A\u66B4\u9732\u5BFC\u822A\u6CE8\u518C\u8868\uFF08__fm_nav_actions\uFF09\uFF0C\u5165\u53E3\u6309\u94AE\u4E0D\u53EF\u7528");
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
api.register(action);
|
|
514
|
+
}
|
|
515
|
+
function unregisterNavAction(id) {
|
|
516
|
+
const api = window.__fm_nav_actions;
|
|
517
|
+
if (api && typeof api.unregister === "function") {
|
|
518
|
+
api.unregister(id);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
var install = (ctx) => {
|
|
522
|
+
injectStyles();
|
|
523
|
+
ctx.router.addRoute({
|
|
524
|
+
path: "/plugin/system-info",
|
|
525
|
+
component: createSystemInfoPage(ctx),
|
|
526
|
+
meta: { requiresAuth: true }
|
|
527
|
+
});
|
|
528
|
+
registerNavAction({
|
|
529
|
+
id: "system-info",
|
|
530
|
+
label: "\u7CFB\u7EDF\u4FE1\u606F",
|
|
531
|
+
path: "/plugin/system-info",
|
|
532
|
+
icon: monitor_default
|
|
533
|
+
});
|
|
534
|
+
return () => {
|
|
535
|
+
unregisterNavAction("system-info");
|
|
536
|
+
removeStyles();
|
|
537
|
+
};
|
|
538
|
+
};
|
|
539
|
+
export {
|
|
540
|
+
install
|
|
541
|
+
};
|
|
542
|
+
/*! Bundled license information:
|
|
543
|
+
|
|
544
|
+
@element-plus/icons-vue/dist/index.js:
|
|
545
|
+
(*! Element Plus Icons Vue v2.3.2 *)
|
|
546
|
+
*/
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @element-plus/icons-vue 的类型门面(仅供 tsc 类型解析)
|
|
3
|
+
*
|
|
4
|
+
* 为什么存在:icons 2.3.2 的类型入口(dist/types 下 .vue.d.ts + export * 结构)
|
|
5
|
+
* 在插件 tsconfig(NodeNext + CJS 模式)下无法解析出具名导出,且其内部类型
|
|
6
|
+
* 引用与插件无关的嵌套 vue 版本。
|
|
7
|
+
*
|
|
8
|
+
* 方案:tsconfig `paths` 把 `@element-plus/icons-vue` 映射到本文件——
|
|
9
|
+
* tsc 用这里的类型(经主项目 frontend-types 的 Vue 类型链,与主应用 vue 版本一致);
|
|
10
|
+
* esbuild 打包时不读 tsconfig paths,仍解析真实包并 tree-shake,
|
|
11
|
+
* 仅打包本插件 import 到的图标组件(配合 build.mjs 的 vue → vue-shim 桥)。
|
|
12
|
+
*/
|
|
13
|
+
import type { NavAction } from '@mqn00/file-manager/plugin/frontend';
|
|
14
|
+
/**
|
|
15
|
+
* 图标组件类型:直接取发布入口 NavAction['icon'](Component,可选)的非空形态,
|
|
16
|
+
* 与注册表契约保持同一类型链。不用 Parameters<h>[0](含 string 标签分支,
|
|
17
|
+
* 与组件图标语义不符,且无法赋给 Component)。
|
|
18
|
+
*/
|
|
19
|
+
type Icon = NonNullable<NavAction['icon']>;
|
|
20
|
+
export declare const ArrowLeft: Icon;
|
|
21
|
+
export declare const Refresh: Icon;
|
|
22
|
+
export declare const Monitor: Icon;
|
|
23
|
+
export declare const Cpu: Icon;
|
|
24
|
+
export declare const Coin: Icon;
|
|
25
|
+
export declare const Box: Icon;
|
|
26
|
+
export declare const Tickets: Icon;
|
|
27
|
+
export declare const Loading: Icon;
|
|
28
|
+
export declare const WarningFilled: Icon;
|
|
29
|
+
export {};
|
package/dist/page.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 系统信息页面(render 函数实现,插件无法 import 主应用 .vue 组件)
|
|
3
|
+
*
|
|
4
|
+
* 自主应用 SystemInfoView.vue 转写,UI 结构与样式保持一致;
|
|
5
|
+
* Element Plus 组件取自 ctx.ElementPlus,图标取自 @element-plus/icons-vue
|
|
6
|
+
* (构建时仅打包用到的图标,vue 运行时经 vue-shim 转发 window.Vue)。
|
|
7
|
+
*/
|
|
8
|
+
import type { FrontendPluginContext } from '@mqn00/file-manager/plugin/frontend';
|
|
9
|
+
/** 系统信息数据结构(与后端 /api/plugin/system-info/info 返回一致) */
|
|
10
|
+
export interface SystemInfo {
|
|
11
|
+
os: {
|
|
12
|
+
type: string;
|
|
13
|
+
platform: string;
|
|
14
|
+
arch: string;
|
|
15
|
+
release: string;
|
|
16
|
+
hostname: string;
|
|
17
|
+
uptime: number;
|
|
18
|
+
uptimeFormatted: string;
|
|
19
|
+
};
|
|
20
|
+
cpu: {
|
|
21
|
+
model: string;
|
|
22
|
+
cores: number;
|
|
23
|
+
physicalCores: number;
|
|
24
|
+
speed: number;
|
|
25
|
+
usage: number;
|
|
26
|
+
};
|
|
27
|
+
memory: {
|
|
28
|
+
total: number;
|
|
29
|
+
free: number;
|
|
30
|
+
used: number;
|
|
31
|
+
usagePercent: number;
|
|
32
|
+
totalFormatted: string;
|
|
33
|
+
freeFormatted: string;
|
|
34
|
+
usedFormatted: string;
|
|
35
|
+
};
|
|
36
|
+
disk: DiskInfo;
|
|
37
|
+
disks: DiskInfo[];
|
|
38
|
+
node: {
|
|
39
|
+
version: string;
|
|
40
|
+
pid: number;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export interface DiskInfo {
|
|
44
|
+
device: string;
|
|
45
|
+
vendor: string;
|
|
46
|
+
model: string;
|
|
47
|
+
mountpoint: string;
|
|
48
|
+
mountpoints: string[];
|
|
49
|
+
fstype: string;
|
|
50
|
+
total: number;
|
|
51
|
+
free: number;
|
|
52
|
+
used: number;
|
|
53
|
+
totalFormatted: string;
|
|
54
|
+
freeFormatted: string;
|
|
55
|
+
usedFormatted: string;
|
|
56
|
+
partitions: Array<{
|
|
57
|
+
mountpoint: string;
|
|
58
|
+
totalFormatted: string;
|
|
59
|
+
usedFormatted: string;
|
|
60
|
+
percent: number;
|
|
61
|
+
}>;
|
|
62
|
+
}
|
|
63
|
+
export declare function createSystemInfoPage(ctx: FrontendPluginContext): import("vue", { with: { "resolution-mode": "import" } }).DefineComponent<{}, () => import("vue", { with: { "resolution-mode": "import" } }).VNode<import("vue", { with: { "resolution-mode": "import" } }).RendererNode, import("vue", { with: { "resolution-mode": "import" } }).RendererElement, {
|
|
64
|
+
[key: string]: any;
|
|
65
|
+
}>, {}, {}, {}, import("vue", { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, import("vue", { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, {}, string, import("vue", { with: { "resolution-mode": "import" } }).PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue", { with: { "resolution-mode": "import" } }).ComponentProvideOptions, true, {}, any>;
|
package/dist/page.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 系统信息页面(render 函数实现,插件无法 import 主应用 .vue 组件)
|
|
4
|
+
*
|
|
5
|
+
* 自主应用 SystemInfoView.vue 转写,UI 结构与样式保持一致;
|
|
6
|
+
* Element Plus 组件取自 ctx.ElementPlus,图标取自 @element-plus/icons-vue
|
|
7
|
+
* (构建时仅打包用到的图标,vue 运行时经 vue-shim 转发 window.Vue)。
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.createSystemInfoPage = createSystemInfoPage;
|
|
11
|
+
const icons_vue_1 = require("@element-plus/icons-vue");
|
|
12
|
+
/** SPA 导航:平台路由 API 经 ctx.router.push 完成(适配 history/hash 双模式) */
|
|
13
|
+
function spaNavigate(ctx, url) {
|
|
14
|
+
void ctx.router.push(url);
|
|
15
|
+
}
|
|
16
|
+
function createSystemInfoPage(ctx) {
|
|
17
|
+
const { defineComponent, h, ref, computed, onMounted } = ctx.Vue;
|
|
18
|
+
const { ElButton, ElIcon, ElCard, ElSelect, ElOption, ElProgress, ElTooltip, ElMessage, } = ctx.ElementPlus;
|
|
19
|
+
const api = ctx.api.instance;
|
|
20
|
+
return defineComponent({
|
|
21
|
+
name: 'SystemInfoPage',
|
|
22
|
+
setup() {
|
|
23
|
+
const systemInfo = ref(null);
|
|
24
|
+
const loading = ref(false);
|
|
25
|
+
const error = ref(null);
|
|
26
|
+
const selectedDiskIndex = ref(0);
|
|
27
|
+
const selectedDisk = computed(() => {
|
|
28
|
+
if (!systemInfo.value)
|
|
29
|
+
return null;
|
|
30
|
+
return systemInfo.value.disks[selectedDiskIndex.value] || systemInfo.value.disk;
|
|
31
|
+
});
|
|
32
|
+
const diskUsageColor = computed(() => {
|
|
33
|
+
const disk = selectedDisk.value;
|
|
34
|
+
if (!disk?.total)
|
|
35
|
+
return '';
|
|
36
|
+
const percent = Math.round((disk.used / disk.total) * 100);
|
|
37
|
+
return percent > 85 ? '#F56C6C' : percent > 65 ? '#E6A23C' : '#409EFF';
|
|
38
|
+
});
|
|
39
|
+
const fetchSystemInfo = async () => {
|
|
40
|
+
loading.value = true;
|
|
41
|
+
error.value = null;
|
|
42
|
+
try {
|
|
43
|
+
// ctx.api.instance 的 baseURL 为 '/api',路径不带 /api 前缀
|
|
44
|
+
const resp = await api.get('/plugin/system-info/info');
|
|
45
|
+
systemInfo.value = resp.data;
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
error.value = e?.response?.data?.message || '获取系统信息失败';
|
|
49
|
+
ElMessage.error('获取系统信息失败');
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
loading.value = false;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
onMounted(() => {
|
|
56
|
+
void fetchSystemInfo();
|
|
57
|
+
});
|
|
58
|
+
return () => {
|
|
59
|
+
const infoItem = (label, value, version = false) => h('div', { class: 'fci-item' }, [
|
|
60
|
+
h('span', { class: 'fci-label' }, label),
|
|
61
|
+
h('span', { class: version ? 'fci-value fci-version' : 'fci-value' }, value),
|
|
62
|
+
]);
|
|
63
|
+
const diskSelect = systemInfo.value && systemInfo.value.disks.length > 1
|
|
64
|
+
? h(ElSelect, {
|
|
65
|
+
modelValue: selectedDiskIndex.value,
|
|
66
|
+
size: 'small',
|
|
67
|
+
class: 'fci-disk-select',
|
|
68
|
+
'onUpdate:modelValue': (v) => {
|
|
69
|
+
selectedDiskIndex.value = v;
|
|
70
|
+
},
|
|
71
|
+
}, {
|
|
72
|
+
default: () => systemInfo.value.disks.map((d, i) => h(ElOption, {
|
|
73
|
+
key: i,
|
|
74
|
+
label: `${d.device} (${d.mountpoints[0] || '未挂载'})`,
|
|
75
|
+
value: i,
|
|
76
|
+
})),
|
|
77
|
+
})
|
|
78
|
+
: null;
|
|
79
|
+
const infoCard = (icon, title, children, headerExtra) => h(ElCard, { class: 'fci-info-card', shadow: 'never' }, {
|
|
80
|
+
header: () => h('div', { class: 'fci-card-header' }, [
|
|
81
|
+
h(ElIcon, { size: 16 }, { default: () => h(icon) }),
|
|
82
|
+
h('span', title),
|
|
83
|
+
headerExtra || null,
|
|
84
|
+
]),
|
|
85
|
+
default: () => h('div', { class: 'fci-list' }, children),
|
|
86
|
+
});
|
|
87
|
+
// 已加载:信息网格
|
|
88
|
+
let body;
|
|
89
|
+
if (systemInfo.value) {
|
|
90
|
+
const info = systemInfo.value;
|
|
91
|
+
const disk = selectedDisk.value;
|
|
92
|
+
const diskPercent = disk?.total
|
|
93
|
+
? Math.round((disk.used / disk.total) * 100)
|
|
94
|
+
: 0;
|
|
95
|
+
body = h('div', { class: 'fci-grid' }, [
|
|
96
|
+
// 操作系统
|
|
97
|
+
infoCard(icons_vue_1.Monitor, '操作系统', [
|
|
98
|
+
infoItem('类型', info.os.type),
|
|
99
|
+
infoItem('平台', info.os.platform),
|
|
100
|
+
infoItem('架构', info.os.arch),
|
|
101
|
+
infoItem('版本', info.os.release, true),
|
|
102
|
+
infoItem('主机名', info.os.hostname),
|
|
103
|
+
infoItem('运行时间', info.os.uptimeFormatted),
|
|
104
|
+
]),
|
|
105
|
+
// CPU
|
|
106
|
+
infoCard(icons_vue_1.Cpu, 'CPU', [
|
|
107
|
+
infoItem('型号', info.cpu.model, true),
|
|
108
|
+
infoItem('核心数', String(info.cpu.cores)),
|
|
109
|
+
infoItem('频率', `${info.cpu.speed} MHz`),
|
|
110
|
+
]),
|
|
111
|
+
// 内存
|
|
112
|
+
infoCard(icons_vue_1.Coin, '内存', [
|
|
113
|
+
infoItem('总量', info.memory.totalFormatted),
|
|
114
|
+
infoItem('已用', info.memory.usedFormatted),
|
|
115
|
+
infoItem('可用', info.memory.freeFormatted),
|
|
116
|
+
]),
|
|
117
|
+
// 硬盘
|
|
118
|
+
infoCard(icons_vue_1.Box, '硬盘', [
|
|
119
|
+
infoItem('设备', disk?.device || '--'),
|
|
120
|
+
...(disk?.vendor || disk?.model
|
|
121
|
+
? [infoItem('制造商', disk?.vendor || '未知')]
|
|
122
|
+
: []),
|
|
123
|
+
...(disk?.model ? [infoItem('型号', disk?.model)] : []),
|
|
124
|
+
infoItem('挂载点', disk?.mountpoints && disk.mountpoints.length > 1
|
|
125
|
+
? disk.mountpoints.join(', ')
|
|
126
|
+
: disk?.mountpoint || '--', true),
|
|
127
|
+
infoItem('文件系统', disk?.fstype || '--'),
|
|
128
|
+
h('div', { class: 'fci-item fci-disk-progress' }, [
|
|
129
|
+
h(ElTooltip, { placement: 'top' }, {
|
|
130
|
+
content: () => (disk?.partitions || []).map((p) => h('div', { key: p.mountpoint, style: { whiteSpace: 'nowrap' } }, `${p.mountpoint}: ${p.percent}% | ${p.usedFormatted} / ${p.totalFormatted}`)),
|
|
131
|
+
default: () => h(ElProgress, {
|
|
132
|
+
percentage: diskPercent,
|
|
133
|
+
format: (p) => `${p}% | ${disk?.totalFormatted || '0 B'}`,
|
|
134
|
+
color: diskUsageColor.value,
|
|
135
|
+
}),
|
|
136
|
+
}),
|
|
137
|
+
]),
|
|
138
|
+
], diskSelect),
|
|
139
|
+
// Node.js
|
|
140
|
+
infoCard(icons_vue_1.Tickets, 'Node.js', [
|
|
141
|
+
infoItem('版本', info.node.version),
|
|
142
|
+
infoItem('进程 ID', String(info.node.pid)),
|
|
143
|
+
]),
|
|
144
|
+
]);
|
|
145
|
+
}
|
|
146
|
+
else if (loading.value) {
|
|
147
|
+
body = h('div', { class: 'fci-state' }, [
|
|
148
|
+
h(ElIcon, { class: 'is-loading', size: 32 }, { default: () => h(icons_vue_1.Loading) }),
|
|
149
|
+
h('p', '加载系统信息中...'),
|
|
150
|
+
]);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
body = h('div', { class: 'fci-state' }, [
|
|
154
|
+
h(ElIcon, { size: 32, color: '#f56c6c' }, { default: () => h(icons_vue_1.WarningFilled) }),
|
|
155
|
+
h('p', error.value || '获取系统信息失败'),
|
|
156
|
+
h(ElButton, { type: 'primary', onClick: () => void fetchSystemInfo() }, () => '重试'),
|
|
157
|
+
]);
|
|
158
|
+
}
|
|
159
|
+
return h('div', { class: 'fci-container' }, [
|
|
160
|
+
h('div', { class: 'fci-card' }, [
|
|
161
|
+
h('div', { style: { paddingTop: '10px', paddingLeft: '10px' } }, [
|
|
162
|
+
h(ElButton, { text: true, class: 'fci-back', onClick: () => spaNavigate(ctx, '/') }, {
|
|
163
|
+
default: () => [
|
|
164
|
+
h(ElIcon, null, { default: () => h(icons_vue_1.ArrowLeft) }),
|
|
165
|
+
h('span', '返回'),
|
|
166
|
+
],
|
|
167
|
+
}),
|
|
168
|
+
]),
|
|
169
|
+
h('div', { style: { padding: '20px 36px' } }, [
|
|
170
|
+
h('div', { class: 'fci-header' }, [
|
|
171
|
+
h('h3', { class: 'fci-title' }, '系统信息'),
|
|
172
|
+
h(ElButton, { size: 'small', loading: loading.value, onClick: () => void fetchSystemInfo() }, {
|
|
173
|
+
default: () => [
|
|
174
|
+
h(ElIcon, null, { default: () => h(icons_vue_1.Refresh) }),
|
|
175
|
+
h('span', '刷新'),
|
|
176
|
+
],
|
|
177
|
+
}),
|
|
178
|
+
]),
|
|
179
|
+
// 硬盘下拉已内嵌于硬盘卡片 header
|
|
180
|
+
body,
|
|
181
|
+
]),
|
|
182
|
+
]),
|
|
183
|
+
]);
|
|
184
|
+
};
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
}
|
package/dist/style.d.ts
ADDED
package/dist/style.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* system-info 插件样式注入(fci- 前缀,颜色只用主题令牌带 fallback)
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.injectStyles = injectStyles;
|
|
7
|
+
exports.removeStyles = removeStyles;
|
|
8
|
+
const STYLE_ID = 'fci-style';
|
|
9
|
+
const CSS = `
|
|
10
|
+
.fci-container {
|
|
11
|
+
height: 100vh;
|
|
12
|
+
display: flex;
|
|
13
|
+
align-items: flex-start;
|
|
14
|
+
justify-content: center;
|
|
15
|
+
padding-top: 40px;
|
|
16
|
+
overflow-y: auto;
|
|
17
|
+
}
|
|
18
|
+
.fci-card {
|
|
19
|
+
width: 800px;
|
|
20
|
+
background: var(--app-panel);
|
|
21
|
+
border: 1px solid var(--app-border);
|
|
22
|
+
border-radius: 12px;
|
|
23
|
+
box-shadow: var(--app-glow), var(--app-shadow);
|
|
24
|
+
backdrop-filter: var(--app-blur);
|
|
25
|
+
margin-bottom: 40px;
|
|
26
|
+
}
|
|
27
|
+
.fci-title {
|
|
28
|
+
margin: 0;
|
|
29
|
+
font-size: 20px;
|
|
30
|
+
color: var(--app-text-bright);
|
|
31
|
+
}
|
|
32
|
+
.fci-header {
|
|
33
|
+
display: flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
justify-content: space-between;
|
|
36
|
+
margin-bottom: 24px;
|
|
37
|
+
}
|
|
38
|
+
.fci-back {
|
|
39
|
+
color: var(--app-text-dim);
|
|
40
|
+
padding: 4px 8px;
|
|
41
|
+
}
|
|
42
|
+
.fci-grid {
|
|
43
|
+
display: grid;
|
|
44
|
+
grid-template-columns: repeat(2, 1fr);
|
|
45
|
+
gap: 16px;
|
|
46
|
+
}
|
|
47
|
+
.fci-info-card {
|
|
48
|
+
background: var(--app-accent-bg) !important;
|
|
49
|
+
border: 1px solid var(--app-accent-border) !important;
|
|
50
|
+
}
|
|
51
|
+
.fci-info-card .el-card__header {
|
|
52
|
+
padding: 12px 16px !important;
|
|
53
|
+
border-bottom: 1px solid var(--app-accent-border) !important;
|
|
54
|
+
}
|
|
55
|
+
.fci-info-card .el-card__body {
|
|
56
|
+
padding: 16px !important;
|
|
57
|
+
}
|
|
58
|
+
.fci-card-header {
|
|
59
|
+
display: flex;
|
|
60
|
+
align-items: center;
|
|
61
|
+
gap: 8px;
|
|
62
|
+
font-weight: 600;
|
|
63
|
+
color: var(--app-accent);
|
|
64
|
+
}
|
|
65
|
+
.fci-disk-select {
|
|
66
|
+
margin-left: auto;
|
|
67
|
+
width: 200px;
|
|
68
|
+
}
|
|
69
|
+
.fci-disk-select .el-input__wrapper {
|
|
70
|
+
background: var(--app-accent-bg) !important;
|
|
71
|
+
border: 1px solid var(--app-accent-border) !important;
|
|
72
|
+
box-shadow: none !important;
|
|
73
|
+
}
|
|
74
|
+
.fci-disk-select .el-input__inner {
|
|
75
|
+
color: var(--app-text-bright) !important;
|
|
76
|
+
}
|
|
77
|
+
.fci-list {
|
|
78
|
+
display: flex;
|
|
79
|
+
flex-direction: column;
|
|
80
|
+
gap: 12px;
|
|
81
|
+
}
|
|
82
|
+
.fci-item {
|
|
83
|
+
display: flex;
|
|
84
|
+
justify-content: space-between;
|
|
85
|
+
align-items: center;
|
|
86
|
+
}
|
|
87
|
+
.fci-label {
|
|
88
|
+
color: var(--app-text-dim);
|
|
89
|
+
font-size: 13px;
|
|
90
|
+
}
|
|
91
|
+
.fci-value {
|
|
92
|
+
color: var(--app-text-bright);
|
|
93
|
+
font-size: 13px;
|
|
94
|
+
text-align: right;
|
|
95
|
+
word-break: break-all;
|
|
96
|
+
}
|
|
97
|
+
.fci-version {
|
|
98
|
+
max-width: 250px;
|
|
99
|
+
overflow: hidden;
|
|
100
|
+
text-overflow: ellipsis;
|
|
101
|
+
}
|
|
102
|
+
.fci-disk-progress {
|
|
103
|
+
flex-direction: column;
|
|
104
|
+
align-items: stretch;
|
|
105
|
+
gap: 4px;
|
|
106
|
+
}
|
|
107
|
+
.fci-state {
|
|
108
|
+
display: flex;
|
|
109
|
+
flex-direction: column;
|
|
110
|
+
align-items: center;
|
|
111
|
+
justify-content: center;
|
|
112
|
+
padding: 60px 20px;
|
|
113
|
+
color: var(--app-text-dim);
|
|
114
|
+
gap: 16px;
|
|
115
|
+
}
|
|
116
|
+
.fci-state p { margin: 0; }
|
|
117
|
+
`;
|
|
118
|
+
function injectStyles() {
|
|
119
|
+
if (document.getElementById(STYLE_ID))
|
|
120
|
+
return;
|
|
121
|
+
const style = document.createElement('style');
|
|
122
|
+
style.id = STYLE_ID;
|
|
123
|
+
style.textContent = CSS;
|
|
124
|
+
document.head.appendChild(style);
|
|
125
|
+
}
|
|
126
|
+
/** 移除注入样式(插件 teardown 调用) */
|
|
127
|
+
function removeStyles() {
|
|
128
|
+
document.getElementById(STYLE_ID)?.remove();
|
|
129
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vue 运行时桥:插件 bundle 不打包 vue(避免与主应用双实例),
|
|
3
|
+
* 编译期把 `vue` 模块别名到此文件,浏览器运行时从主应用暴露的 window.Vue 转发。
|
|
4
|
+
*
|
|
5
|
+
* 类型经主项目 frontend-types(FrontendPluginContext.Vue = typeof import('vue'))
|
|
6
|
+
* 传递解析,与 @element-plus/icons-vue 组件的 vue 类型完全一致,
|
|
7
|
+
* 不做手写近似类型(同插件「类型从 @mqn00/file-manager/plugin/frontend 导入」的惯例)。
|
|
8
|
+
*
|
|
9
|
+
* 仅转发 @element-plus/icons-vue 编译产物实际使用的成员
|
|
10
|
+
* (defineComponent / openBlock / createElementBlock / createElementVNode)。
|
|
11
|
+
*/
|
|
12
|
+
import type { FrontendPluginContext } from '@mqn00/file-manager/plugin/frontend';
|
|
13
|
+
type VueRuntime = FrontendPluginContext['Vue'];
|
|
14
|
+
export declare const defineComponent: VueRuntime['defineComponent'];
|
|
15
|
+
export declare const openBlock: VueRuntime['openBlock'];
|
|
16
|
+
export declare const createElementBlock: VueRuntime['createElementBlock'];
|
|
17
|
+
export declare const createElementVNode: VueRuntime['createElementVNode'];
|
|
18
|
+
export {};
|
package/dist/vue-shim.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createElementVNode = exports.createElementBlock = exports.openBlock = exports.defineComponent = void 0;
|
|
4
|
+
const V = window.Vue;
|
|
5
|
+
exports.defineComponent = V.defineComponent;
|
|
6
|
+
exports.openBlock = V.openBlock;
|
|
7
|
+
exports.createElementBlock = V.createElementBlock;
|
|
8
|
+
exports.createElementVNode = V.createElementVNode;
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mqn00/file-manager-plugin-system-info",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "File Manager 系统信息插件:将内置系统信息页面抽取为独立插件,展示 OS/CPU/内存/磁盘分区/Node 信息,入口注册到文件浏览器顶栏导航",
|
|
5
|
+
"main": "dist/backend.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./dist/backend.js",
|
|
8
|
+
"./frontend": "./dist/frontend.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist/",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc && node build.mjs",
|
|
16
|
+
"dev": "tsc --watch",
|
|
17
|
+
"build:frontend": "node build.mjs",
|
|
18
|
+
"test": "vitest run"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"@mqn00/file-manager": "^3.0.0"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"systeminformation": "^5.31.17"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@element-plus/icons-vue": "^2.3.2",
|
|
28
|
+
"@mqn00/file-manager": "file:../../backend",
|
|
29
|
+
"@types/node": "^22.0.0",
|
|
30
|
+
"@types/systeminformation": "^3.23.1",
|
|
31
|
+
"esbuild": "^0.28.1",
|
|
32
|
+
"typescript": "^5.4.0",
|
|
33
|
+
"vitest": "^3.0.0"
|
|
34
|
+
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "https://github.com/mqnu00/file-manager.git",
|
|
38
|
+
"directory": "plugins/system-info"
|
|
39
|
+
},
|
|
40
|
+
"keywords": [
|
|
41
|
+
"file-manager",
|
|
42
|
+
"file-manager-plugin",
|
|
43
|
+
"system",
|
|
44
|
+
"system-info"
|
|
45
|
+
],
|
|
46
|
+
"author": "mqn00",
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
}
|
|
51
|
+
}
|