node-red-contrib-fox-admin 1.0.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 +66 -0
- package/index.js +492 -0
- package/lib/dashboard.html +324 -0
- package/lib/deploy.html +349 -0
- package/lib/firewall.html +584 -0
- package/lib/login.html +195 -0
- package/lib/network.html +563 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Fox Control 系统管理控制台
|
|
2
|
+
|
|
3
|
+
Node-RED 运行时插件,提供系统管理控制台功能。
|
|
4
|
+
|
|
5
|
+
## 功能特性
|
|
6
|
+
|
|
7
|
+
- **登录认证**:安全的密码登录系统
|
|
8
|
+
- **设备管理**:修改主机名、查看系统信息
|
|
9
|
+
- **网络管理**:查看网络接口、配置网络参数
|
|
10
|
+
- **防火墙管理**:启用/禁用防火墙、添加防火墙规则
|
|
11
|
+
- **站点部署**:上传 ZIP 包进行站点部署
|
|
12
|
+
|
|
13
|
+
## 支持的系统
|
|
14
|
+
|
|
15
|
+
- Debian 11/12
|
|
16
|
+
- Ubuntu 20.04/22.04
|
|
17
|
+
- Kylin V10
|
|
18
|
+
- openEuler 22.03
|
|
19
|
+
|
|
20
|
+
## 安装
|
|
21
|
+
|
|
22
|
+
插件已安装在 Node-RED 的 node_modules 目录中,无需额外安装。
|
|
23
|
+
|
|
24
|
+
## 使用方法
|
|
25
|
+
|
|
26
|
+
1. 启动 Node-RED
|
|
27
|
+
2. 访问 `http://your-node-red-ip:port/foxcontroladmin_login/`
|
|
28
|
+
3. 使用默认密码登录:`foxcontrol@123`
|
|
29
|
+
4. 修改密码:设置环境变量 `FOX_ADMIN_PASSWORD`
|
|
30
|
+
|
|
31
|
+
## 环境变量
|
|
32
|
+
|
|
33
|
+
- `FOX_ADMIN_PASSWORD`:管理员密码(默认:foxcontrol@123)
|
|
34
|
+
- `FOX_SESSION_SECRET`:会话密钥(默认:fox-admin-secret-key-2024)
|
|
35
|
+
|
|
36
|
+
## API 接口
|
|
37
|
+
|
|
38
|
+
### 认证相关
|
|
39
|
+
- `POST /api/foxcontrol_admin/login`:登录
|
|
40
|
+
- `POST /api/foxcontrol_admin/logout`:登出
|
|
41
|
+
- `GET /api/foxcontrol_admin/check-auth`:检查认证状态
|
|
42
|
+
|
|
43
|
+
### 系统管理
|
|
44
|
+
- `GET /api/foxcontrol_admin/system/info`:获取系统信息
|
|
45
|
+
- `PUT /api/foxcontrol_admin/hostname`:修改主机名
|
|
46
|
+
|
|
47
|
+
### 网络管理
|
|
48
|
+
- `GET /api/foxcontrol_admin/network/interfaces`:获取网络接口
|
|
49
|
+
- `GET /api/foxcontrol_admin/network/connections`:获取网络连接
|
|
50
|
+
- `PUT /api/foxcontrol_admin/network/config`:配置网络
|
|
51
|
+
|
|
52
|
+
### 防火墙管理
|
|
53
|
+
- `GET /api/foxcontrol_admin/firewall/status`:获取防火墙状态
|
|
54
|
+
- `PUT /api/foxcontrol_admin/firewall/enable`:启用防火墙
|
|
55
|
+
- `PUT /api/foxcontrol_admin/firewall/disable`:禁用防火墙
|
|
56
|
+
- `POST /api/foxcontrol_admin/firewall/rule`:添加防火墙规则
|
|
57
|
+
|
|
58
|
+
### 站点部署
|
|
59
|
+
- `POST /api/foxcontrol_admin/deploy/upload`:上传并部署站点
|
|
60
|
+
|
|
61
|
+
## 注意事项
|
|
62
|
+
|
|
63
|
+
- Node-RED 需要以 root 用户运行才能执行系统管理命令
|
|
64
|
+
- 网络配置修改后可能需要重启网络服务
|
|
65
|
+
- 防火墙规则修改立即生效
|
|
66
|
+
- 部署的站点会解压到 Node-RED 的 public/UI 目录
|
package/index.js
ADDED
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const si = require('systeminformation');
|
|
4
|
+
const multer = require('multer');
|
|
5
|
+
const AdmZip = require('adm-zip');
|
|
6
|
+
const { exec } = require('child_process');
|
|
7
|
+
const bodyParser = require('body-parser');
|
|
8
|
+
const express = require('express');
|
|
9
|
+
const session = require('express-session');
|
|
10
|
+
|
|
11
|
+
module.exports = function(RED) {
|
|
12
|
+
function FoxAdminPlugin() {
|
|
13
|
+
const app = RED.httpNode;
|
|
14
|
+
if (!app) return;
|
|
15
|
+
|
|
16
|
+
const htmlPath = path.join(__dirname, 'lib');
|
|
17
|
+
|
|
18
|
+
const ADMIN_PASSWORD = process.env.FOX_ADMIN_PASSWORD || 'foxcontrol@123';
|
|
19
|
+
const SESSION_SECRET = process.env.FOX_SESSION_SECRET || 'fox-admin-secret-key-2024';
|
|
20
|
+
|
|
21
|
+
const upload = multer({ storage: multer.memoryStorage() });
|
|
22
|
+
|
|
23
|
+
const publicDir = path.join(RED.settings.userDir, '..', 'public', 'UI');
|
|
24
|
+
|
|
25
|
+
const serverPort = RED.settings.uiPort || 1880;
|
|
26
|
+
const serverHost = RED.settings.uiHost || '0.0.0.0';
|
|
27
|
+
|
|
28
|
+
app.use(bodyParser.json({ limit: '10mb' }));
|
|
29
|
+
app.use(bodyParser.urlencoded({ extended: true, limit: '10mb' }));
|
|
30
|
+
app.use(session({
|
|
31
|
+
secret: SESSION_SECRET,
|
|
32
|
+
resave: false,
|
|
33
|
+
saveUninitialized: false,
|
|
34
|
+
cookie: { maxAge: 3600000 }
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
RED.log.info(">>> Fox Control 管理控制台插件已启动");
|
|
38
|
+
RED.log.info(">>> 访问管理后台: http://" + serverHost + ":" + serverPort + "/foxadmin/");
|
|
39
|
+
RED.log.info(">>> 默认密码: " + ADMIN_PASSWORD);
|
|
40
|
+
|
|
41
|
+
function execCommand(cmd) {
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
exec(cmd, { encoding: 'utf8' }, (error, stdout, stderr) => {
|
|
44
|
+
if (error) {
|
|
45
|
+
reject(error);
|
|
46
|
+
} else {
|
|
47
|
+
resolve(stdout.trim());
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getSystemType() {
|
|
54
|
+
try {
|
|
55
|
+
const osRelease = fs.readFileSync('/etc/os-release', 'utf8');
|
|
56
|
+
if (osRelease.includes('Debian')) return 'debian';
|
|
57
|
+
if (osRelease.includes('Kylin')) return 'kylin';
|
|
58
|
+
if (osRelease.includes('openEuler')) return 'openEuler';
|
|
59
|
+
if (osRelease.includes('Ubuntu')) return 'ubuntu';
|
|
60
|
+
return 'unknown';
|
|
61
|
+
} catch (err) {
|
|
62
|
+
return 'unknown';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function getDefaultGateway() {
|
|
67
|
+
try {
|
|
68
|
+
const osType = getSystemType();
|
|
69
|
+
const platform = process.platform;
|
|
70
|
+
let cmd = '';
|
|
71
|
+
|
|
72
|
+
if (osType === 'debian' || osType === 'kylin' || osType === 'openEuler' || osType === 'ubuntu') {
|
|
73
|
+
cmd = 'ip route | grep default | awk \'{print $3}\'';
|
|
74
|
+
} else if (osType === 'unknown') {
|
|
75
|
+
if (platform === 'win32') {
|
|
76
|
+
cmd = 'route print 0.0.0.0 | findstr "0.0.0.0"';
|
|
77
|
+
} else {
|
|
78
|
+
return '';
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
return '';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const gateway = await execCommand(cmd);
|
|
85
|
+
if (platform === 'win32') {
|
|
86
|
+
const match = gateway.match(/0\.0\.0\.0\s+(\d+\.\d+\.\d+\.\d+)/);
|
|
87
|
+
return match ? match[1] : '';
|
|
88
|
+
}
|
|
89
|
+
return gateway || '';
|
|
90
|
+
} catch (err) {
|
|
91
|
+
return '';
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
app.get('/foxadmin', (req, res) => {
|
|
96
|
+
const loginPath = path.join(htmlPath, 'login.html');
|
|
97
|
+
if (fs.existsSync(loginPath)) {
|
|
98
|
+
res.sendFile(loginPath);
|
|
99
|
+
} else {
|
|
100
|
+
res.status(404).send("Fox Admin Error: 登录页面不存在");
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
app.get('/foxcontrol_edit', (req, res) => {
|
|
105
|
+
res.redirect('/red');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
app.get('/foxcontroladmin_dashboard', (req, res) => {
|
|
109
|
+
const dashboardPath = path.join(htmlPath, 'dashboard.html');
|
|
110
|
+
if (fs.existsSync(dashboardPath)) {
|
|
111
|
+
res.sendFile(dashboardPath);
|
|
112
|
+
} else {
|
|
113
|
+
res.status(404).send("Fox Admin Error: 控制台页面不存在");
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
app.get('/foxcontroladmin_network', (req, res) => {
|
|
118
|
+
const networkPath = path.join(htmlPath, 'network.html');
|
|
119
|
+
if (fs.existsSync(networkPath)) {
|
|
120
|
+
res.sendFile(networkPath);
|
|
121
|
+
} else {
|
|
122
|
+
res.status(404).send("Fox Admin Error: 网络管理页面不存在");
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
app.get('/foxcontroladmin_firewall', (req, res) => {
|
|
127
|
+
const firewallPath = path.join(htmlPath, 'firewall.html');
|
|
128
|
+
if (fs.existsSync(firewallPath)) {
|
|
129
|
+
res.sendFile(firewallPath);
|
|
130
|
+
} else {
|
|
131
|
+
res.status(404).send("Fox Admin Error: 防火墙管理页面不存在");
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
app.get('/foxcontroladmin_deploy', (req, res) => {
|
|
136
|
+
const deployPath = path.join(htmlPath, 'deploy.html');
|
|
137
|
+
if (fs.existsSync(deployPath)) {
|
|
138
|
+
res.sendFile(deployPath);
|
|
139
|
+
} else {
|
|
140
|
+
res.status(404).send("Fox Admin Error: 部署页面不存在");
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
app.post('/foxcontrol_api/admin/login', (req, res) => {
|
|
145
|
+
try {
|
|
146
|
+
const { password } = req.body;
|
|
147
|
+
if (!password) {
|
|
148
|
+
return res.status(400).json({ status: 'error', message: '未接收到密码数据' });
|
|
149
|
+
}
|
|
150
|
+
if (password === ADMIN_PASSWORD) {
|
|
151
|
+
if (!req.session) {
|
|
152
|
+
throw new Error("Session 中间件未正确初始化");
|
|
153
|
+
}
|
|
154
|
+
req.session.admin = true;
|
|
155
|
+
res.json({ status: 'success', message: '登录成功' });
|
|
156
|
+
} else {
|
|
157
|
+
res.status(401).json({ status: 'error', message: '密码错误' });
|
|
158
|
+
}
|
|
159
|
+
} catch (err) {
|
|
160
|
+
RED.log.error(`[Fox Admin] Login Error: ${err.message}`);
|
|
161
|
+
res.status(500).json({ status: 'error', message: `内部错误: ${err.message}` });
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
app.post('/foxcontrol_api/admin/logout', (req, res) => {
|
|
166
|
+
req.session.destroy();
|
|
167
|
+
res.json({ status: 'success', message: '已退出登录' });
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
app.get('/foxcontrol_api/admin/check-auth', (req, res) => {
|
|
171
|
+
if (req.session.admin) {
|
|
172
|
+
res.json({ status: 'success', authenticated: true });
|
|
173
|
+
} else {
|
|
174
|
+
res.json({ status: 'error', authenticated: false });
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
app.get('/foxcontrol_api/admin/system/info', async (req, res) => {
|
|
179
|
+
try {
|
|
180
|
+
const osInfo = await si.osInfo();
|
|
181
|
+
const cpu = await si.cpu();
|
|
182
|
+
const mem = await si.mem();
|
|
183
|
+
const systemType = getSystemType();
|
|
184
|
+
|
|
185
|
+
let hostname = 'unknown';
|
|
186
|
+
try {
|
|
187
|
+
hostname = await execCommand('hostname');
|
|
188
|
+
} catch (err) {}
|
|
189
|
+
|
|
190
|
+
res.json({
|
|
191
|
+
status: 'success',
|
|
192
|
+
data: {
|
|
193
|
+
system: {
|
|
194
|
+
type: systemType,
|
|
195
|
+
hostname: hostname,
|
|
196
|
+
os: osInfo.distro,
|
|
197
|
+
version: osInfo.release,
|
|
198
|
+
arch: osInfo.arch,
|
|
199
|
+
kernel: osInfo.kernel
|
|
200
|
+
},
|
|
201
|
+
cpu: {
|
|
202
|
+
manufacturer: cpu.manufacturer,
|
|
203
|
+
brand: cpu.brand,
|
|
204
|
+
cores: cpu.cores,
|
|
205
|
+
speed: cpu.speed
|
|
206
|
+
},
|
|
207
|
+
memory: {
|
|
208
|
+
total: Math.round(mem.total / 1024 / 1024 / 1024),
|
|
209
|
+
used: Math.round(mem.used / 1024 / 1024 / 1024),
|
|
210
|
+
free: Math.round(mem.free / 1024 / 1024 / 1024),
|
|
211
|
+
usage: Math.round((mem.used / mem.total) * 100)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
} catch (err) {
|
|
216
|
+
RED.log.error(`[Fox Admin] 获取系统信息失败: ${err.message}`);
|
|
217
|
+
res.status(500).json({ status: 'error', message: err.message });
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
app.put('/foxcontrol_api/admin/hostname', async (req, res) => {
|
|
222
|
+
try {
|
|
223
|
+
const { hostname } = req.body;
|
|
224
|
+
if (!hostname || hostname.length < 2) {
|
|
225
|
+
return res.status(400).json({ status: 'error', message: '主机名无效' });
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
await execCommand(`hostnamectl set-hostname ${hostname}`);
|
|
229
|
+
RED.log.info(`[Fox Admin] 主机名已修改为: ${hostname}`);
|
|
230
|
+
res.json({ status: 'success', message: '主机名修改成功' });
|
|
231
|
+
} catch (err) {
|
|
232
|
+
RED.log.error(`[Fox Admin] 修改主机名失败: ${err.message}`);
|
|
233
|
+
res.status(500).json({ status: 'error', message: err.message });
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
app.get('/foxcontrol_api/admin/network/interfaces', async (req, res) => {
|
|
238
|
+
try {
|
|
239
|
+
const networkInterfaces = await si.networkInterfaces();
|
|
240
|
+
const networkStats = await si.networkStats();
|
|
241
|
+
const defaultGateway = await getDefaultGateway();
|
|
242
|
+
|
|
243
|
+
const interfaces = networkInterfaces.map(iface => {
|
|
244
|
+
const stats = networkStats.find(s => s.iface === iface.iface) || {};
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
iface: iface.iface,
|
|
248
|
+
type: iface.type,
|
|
249
|
+
ip4: iface.ip4,
|
|
250
|
+
ip4_subnet: iface.ip4_subnet || '',
|
|
251
|
+
ip6: iface.ip6,
|
|
252
|
+
mac: iface.mac,
|
|
253
|
+
internal: iface.internal,
|
|
254
|
+
virtual: iface.virtual,
|
|
255
|
+
operstate: iface.operstate,
|
|
256
|
+
speed: iface.speed,
|
|
257
|
+
duplex: iface.duplex,
|
|
258
|
+
mtu: iface.mtu,
|
|
259
|
+
gateway: iface.ip4 ? defaultGateway : '',
|
|
260
|
+
rx_bytes: stats.rx_bytes || 0,
|
|
261
|
+
tx_bytes: stats.tx_bytes || 0,
|
|
262
|
+
rx_sec: stats.rx_sec || 0,
|
|
263
|
+
tx_sec: stats.tx_sec || 0
|
|
264
|
+
};
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
res.json({ status: 'success', data: interfaces });
|
|
268
|
+
} catch (err) {
|
|
269
|
+
RED.log.error(`[Fox Admin] 获取网络接口失败: ${err.message}`);
|
|
270
|
+
res.status(500).json({ status: 'error', message: err.message });
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
app.get('/foxcontrol_api/admin/network/connections', async (req, res) => {
|
|
275
|
+
try {
|
|
276
|
+
const connections = await si.networkConnections();
|
|
277
|
+
res.json({ status: 'success', data: connections });
|
|
278
|
+
} catch (err) {
|
|
279
|
+
RED.log.error(`[Fox Admin] 获取网络连接失败: ${err.message}`);
|
|
280
|
+
res.status(500).json({ status: 'error', message: err.message });
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
app.put('/foxcontrol_api/admin/network/config', async (req, res) => {
|
|
285
|
+
try {
|
|
286
|
+
const { iface, ip4, netmask, gateway, dns } = req.body;
|
|
287
|
+
const systemType = getSystemType();
|
|
288
|
+
|
|
289
|
+
if (systemType === 'unknown') {
|
|
290
|
+
return res.status(400).json({ status: 'error', message: '不支持的系统' });
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (systemType === 'debian' || systemType === 'ubuntu') {
|
|
294
|
+
const config = `
|
|
295
|
+
auto ${iface}
|
|
296
|
+
iface ${iface} inet static
|
|
297
|
+
address ${ip4}
|
|
298
|
+
netmask ${netmask}
|
|
299
|
+
gateway ${gateway}
|
|
300
|
+
dns-nameservers ${dns}
|
|
301
|
+
`;
|
|
302
|
+
const configPath = `/etc/network/interfaces.d/${iface}`;
|
|
303
|
+
fs.writeFileSync(configPath, config);
|
|
304
|
+
await execCommand('systemctl restart networking');
|
|
305
|
+
} else if (systemType === 'kylin' || systemType === 'openEuler') {
|
|
306
|
+
const config = `
|
|
307
|
+
TYPE=Ethernet
|
|
308
|
+
BOOTPROTO=static
|
|
309
|
+
DEFROUTE=yes
|
|
310
|
+
IPV4_FAILURE_FATAL=no
|
|
311
|
+
IPV6INIT=yes
|
|
312
|
+
IPV6_AUTOCONF=yes
|
|
313
|
+
IPV6_DEFROUTE=yes
|
|
314
|
+
IPV6_FAILURE_FATAL=no
|
|
315
|
+
NAME=${iface}
|
|
316
|
+
DEVICE=${iface}
|
|
317
|
+
ONBOOT=yes
|
|
318
|
+
IPADDR=${ip4}
|
|
319
|
+
NETMASK=${netmask}
|
|
320
|
+
GATEWAY=${gateway}
|
|
321
|
+
DNS1=${dns}
|
|
322
|
+
`;
|
|
323
|
+
const configPath = `/etc/sysconfig/network-scripts/ifcfg-${iface}`;
|
|
324
|
+
fs.writeFileSync(configPath, config);
|
|
325
|
+
await execCommand('systemctl restart network');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
RED.log.info(`[Fox Admin] 网络配置已更新: ${iface}`);
|
|
329
|
+
res.json({ status: 'success', message: '网络配置已更新' });
|
|
330
|
+
} catch (err) {
|
|
331
|
+
RED.log.error(`[Fox Admin] 配置网络失败: ${err.message}`);
|
|
332
|
+
res.status(500).json({ status: 'error', message: err.message });
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
app.get('/foxcontrol_api/admin/firewall/status', async (req, res) => {
|
|
337
|
+
try {
|
|
338
|
+
const systemType = getSystemType();
|
|
339
|
+
let status = { enabled: false, rules: [] };
|
|
340
|
+
|
|
341
|
+
if (systemType === 'debian' || systemType === 'ubuntu') {
|
|
342
|
+
try {
|
|
343
|
+
const ufwStatus = await execCommand('ufw status verbose');
|
|
344
|
+
status.enabled = ufwStatus.includes('Status: active');
|
|
345
|
+
const rules = ufwStatus.split('\n').filter(line => line.includes('ALLOW') || line.includes('DENY'));
|
|
346
|
+
status.rules = rules;
|
|
347
|
+
} catch (err) {
|
|
348
|
+
status.enabled = false;
|
|
349
|
+
}
|
|
350
|
+
} else if (systemType === 'kylin' || systemType === 'openEuler') {
|
|
351
|
+
try {
|
|
352
|
+
const fwStatus = await execCommand('firewall-cmd --state');
|
|
353
|
+
status.enabled = fwStatus === 'running';
|
|
354
|
+
const rules = await execCommand('firewall-cmd --list-all');
|
|
355
|
+
status.rules = [rules];
|
|
356
|
+
} catch (err) {
|
|
357
|
+
status.enabled = false;
|
|
358
|
+
}
|
|
359
|
+
} else {
|
|
360
|
+
return res.status(400).json({ status: 'error', message: '不支持的系统' });
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
res.json({ status: 'success', data: status });
|
|
364
|
+
} catch (err) {
|
|
365
|
+
RED.log.error(`[Fox Admin] 获取防火墙状态失败: ${err.message}`);
|
|
366
|
+
res.status(500).json({ status: 'error', message: err.message });
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
app.put('/foxcontrol_api/admin/firewall/enable', async (req, res) => {
|
|
371
|
+
try {
|
|
372
|
+
const systemType = getSystemType();
|
|
373
|
+
|
|
374
|
+
if (systemType === 'debian' || systemType === 'ubuntu') {
|
|
375
|
+
await execCommand('ufw --force enable');
|
|
376
|
+
} else if (systemType === 'kylin' || systemType === 'openEuler') {
|
|
377
|
+
await execCommand('systemctl enable firewalld');
|
|
378
|
+
await execCommand('systemctl start firewalld');
|
|
379
|
+
} else {
|
|
380
|
+
return res.status(400).json({ status: 'error', message: '不支持的系统' });
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
RED.log.info('[Fox Admin] 防火墙已启用');
|
|
384
|
+
res.json({ status: 'success', message: '防火墙已启用' });
|
|
385
|
+
} catch (err) {
|
|
386
|
+
RED.log.error(`[Fox Admin] 启用防火墙失败: ${err.message}`);
|
|
387
|
+
res.status(500).json({ status: 'error', message: err.message });
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
app.put('/foxcontrol_api/admin/firewall/disable', async (req, res) => {
|
|
392
|
+
try {
|
|
393
|
+
const systemType = getSystemType();
|
|
394
|
+
|
|
395
|
+
if (systemType === 'debian' || systemType === 'ubuntu') {
|
|
396
|
+
await execCommand('ufw disable');
|
|
397
|
+
} else if (systemType === 'kylin' || systemType === 'openEuler') {
|
|
398
|
+
await execCommand('systemctl stop firewalld');
|
|
399
|
+
await execCommand('systemctl disable firewalld');
|
|
400
|
+
} else {
|
|
401
|
+
return res.status(400).json({ status: 'error', message: '不支持的系统' });
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
RED.log.info('[Fox Admin] 防火墙已禁用');
|
|
405
|
+
res.json({ status: 'success', message: '防火墙已禁用' });
|
|
406
|
+
} catch (err) {
|
|
407
|
+
RED.log.error(`[Fox Admin] 禁用防火墙失败: ${err.message}`);
|
|
408
|
+
res.status(500).json({ status: 'error', message: err.message });
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
app.post('/foxcontrol_api/admin/firewall/rule', async (req, res) => {
|
|
413
|
+
try {
|
|
414
|
+
const { action, port, protocol, ip } = req.body;
|
|
415
|
+
const systemType = getSystemType();
|
|
416
|
+
|
|
417
|
+
if (systemType === 'debian' || systemType === 'ubuntu') {
|
|
418
|
+
if (action === 'allow') {
|
|
419
|
+
if (ip) {
|
|
420
|
+
await execCommand(`ufw allow from ${ip} to any port ${port} proto ${protocol}`);
|
|
421
|
+
} else {
|
|
422
|
+
await execCommand(`ufw allow ${port}/${protocol}`);
|
|
423
|
+
}
|
|
424
|
+
} else if (action === 'deny') {
|
|
425
|
+
if (ip) {
|
|
426
|
+
await execCommand(`ufw deny from ${ip} to any port ${port} proto ${protocol}`);
|
|
427
|
+
} else {
|
|
428
|
+
await execCommand(`ufw deny ${port}/${protocol}`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
RED.log.info(`[Fox Admin] 防火墙规则已添加: ${action} ${port}/${protocol}`);
|
|
433
|
+
res.json({ status: 'success', message: '防火墙规则已添加' });
|
|
434
|
+
} else if (systemType === 'kylin' || systemType === 'openEuler') {
|
|
435
|
+
if (action === 'allow') {
|
|
436
|
+
if (ip) {
|
|
437
|
+
await execCommand(`firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="${ip}" port protocol="${protocol}" port="${port}" accept'`);
|
|
438
|
+
} else {
|
|
439
|
+
await execCommand(`firewall-cmd --permanent --add-port=${port}/${protocol}`);
|
|
440
|
+
}
|
|
441
|
+
} else if (action === 'deny') {
|
|
442
|
+
if (ip) {
|
|
443
|
+
await execCommand(`firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="${ip}" port protocol="${protocol}" port="${port}" reject'`);
|
|
444
|
+
} else {
|
|
445
|
+
await execCommand(`firewall-cmd --permanent --remove-port=${port}/${protocol}`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
await execCommand('firewall-cmd --reload');
|
|
449
|
+
RED.log.info(`[Fox Admin] 防火墙规则已添加: ${action} ${port}/${protocol}`);
|
|
450
|
+
res.json({ status: 'success', message: '防火墙规则已添加' });
|
|
451
|
+
} else {
|
|
452
|
+
res.status(400).json({ status: 'error', message: '不支持的系统' });
|
|
453
|
+
}
|
|
454
|
+
} catch (err) {
|
|
455
|
+
RED.log.error(`[Fox Admin] 添加防火墙规则失败: ${err.message}`);
|
|
456
|
+
res.status(500).json({ status: 'error', message: err.message });
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
app.post('/foxcontrol_api/admin/deploy/upload', upload.single('site_zip'), async (req, res) => {
|
|
461
|
+
try {
|
|
462
|
+
if (!req.file) throw new Error("未选择有效的 ZIP 文件");
|
|
463
|
+
|
|
464
|
+
fs.mkdirSync(publicDir, { recursive: true });
|
|
465
|
+
const files = fs.readdirSync(publicDir);
|
|
466
|
+
files.forEach(file => {
|
|
467
|
+
const filePath = path.join(publicDir, file);
|
|
468
|
+
const stat = fs.statSync(filePath);
|
|
469
|
+
if (stat.isDirectory()) {
|
|
470
|
+
fs.rmSync(filePath, { recursive: true, force: true });
|
|
471
|
+
} else {
|
|
472
|
+
fs.unlinkSync(filePath);
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
const zip = new AdmZip(req.file.buffer);
|
|
477
|
+
zip.extractAllTo(publicDir, true);
|
|
478
|
+
|
|
479
|
+
RED.log.info(`[Fox Control] 站点已成功部署至: ${publicDir}`);
|
|
480
|
+
res.json({ status: 'success', url: '/UI/index.html' });
|
|
481
|
+
} catch (err) {
|
|
482
|
+
RED.log.error(`[Fox Control] 部署失败: ${err.message}`);
|
|
483
|
+
res.status(500).json({ status: 'error', message: err.message });
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
RED.plugins.registerPlugin("fox-admin", {
|
|
489
|
+
type: "runtime",
|
|
490
|
+
onadd: FoxAdminPlugin
|
|
491
|
+
});
|
|
492
|
+
};
|