better-staridc-MNBT
1<?php
2/**
3 * resource_pool 插件 - 主入口
4 *
5 * 管理员后台一级菜单「资源池管理」,二级菜单:
6 * - 添加资源池
7 * - 资源池列表
8 *
9 * 数据表:MN_plugin_respool(插件自有)
10 * 主机表:MN_zj 新增 pool_id 字段(默认 NULL,资源池开通的主机记录归属)
11 */
12
13if (!defined('IN_CRONLITE')) {
14 exit;
15}
16
17require_once __DIR__ . '/lib/pool.php';
18
19mnbt_plugin_register('resource_pool', [
20 'name' => '资源池管理',
21 'description' => '资源池账号、可用节点、配额与到期管理',
22]);
23
24/* ============================================================
25 * 菜单与页面
26 * ============================================================ */
27
28mnbt_register_page('admin', 'add', 'admin/add.php', '添加资源池');
29mnbt_register_page('admin', 'list', 'admin/list.php', '资源池列表');
30mnbt_register_page('admin', 'hosts', 'admin/hosts.php', '资源主机管理');
31
32mnbt_register_menu('admin', [
33 'title' => '资源池管理',
34 'icon' => 'mdi-database',
35 'order' => 60,
36 'children' => [
37 ['title' => '添加资源池', 'page' => 'add', 'icon' => 'mdi-plus-box', 'order' => 10, 'multitabs' => true],
38 ['title' => '资源池列表', 'page' => 'list', 'icon' => 'mdi-format-list-bulleted', 'order' => 20, 'multitabs' => true],
39 ['title' => '资源主机管理', 'page' => 'hosts', 'icon' => 'mdi-server', 'order' => 30, 'multitabs' => true],
40 ],
41]);
42
43mnbt_register_settings_tab([
44 'title' => '资源池管理',
45 'page' => 'list',
46 'order' => 60,
47]);
48
49/* ============================================================
50 * 主机列表注入「资源池」列
51 * ============================================================
52 * 不改核心文件:在管理端 head 之后注入一段脚本,包装
53 * $.fn.bootstrapTable,在核心 list.php 初始化主机表时插入一列。
54 * 仅在 admin/list.php?gn=zj 生效;异常时静默降级,不影响原页面。
55 */
56mnbt_register_partial_override('admin', 'head', function ($vars) {
57 if (($_GET['gn'] ?? '') !== 'zj') {
58 return null;
59 }
60 $script = (string)($_SERVER['SCRIPT_NAME'] ?? '');
61 if (basename($script) !== 'list.php') {
62 return null;
63 }
64 // 主机账号 => 资源池名(归属存在资源池表,不依赖主机表字段)
65 // JSON_FORCE_OBJECT:防止纯数字主机账号让 json_encode 输出数组而非对象
66 $map = rp_host_user_name_map();
67 $json = json_encode($map, JSON_UNESCAPED_UNICODE | JSON_FORCE_OBJECT) ?: '{}';
68
69 $js = <<<HTML
70<script type="text/javascript">
71/* resource_pool 插件:向主机列表注入「资源池」列(按主机账号匹配) */
72(function () {
73 var POOL_BY_USER = {$json};
74 function formatter(value, row) {
75 var u = row ? row.user : null;
76 if (u === null || u === undefined || u === '') {
77 return '<span class="text-muted">-</span>';
78 }
79 var name = POOL_BY_USER[String(u)];
80 if (name) {
81 return '<span class="badge badge-info">' + \$('<div>').text(name).html() + '</span>';
82 }
83 return '<span class="text-muted">-</span>';
84 }
85 function inject(columns) {
86 if (!columns || !columns.length) return false;
87 for (var i = 0; i < columns.length; i++) {
88 if (columns[i] && columns[i].field === 'rp_pool') return false;
89 }
90 var at = columns.length;
91 for (var j = 0; j < columns.length; j++) {
92 if (columns[j] && columns[j].field === 'operate') { at = j; break; }
93 }
94 columns.splice(at, 0, { field: 'rp_pool', title: '资源池', formatter: formatter });
95 return true;
96 }
97 function wrap(real) {
98 var wrapped = function (option) {
99 try {
100 if (option && typeof option === 'object' && option.columns) {
101 if (Array.isArray(option.columns[0])) {
102 inject(option.columns[0]);
103 } else {
104 inject(option.columns);
105 }
106 }
107 } catch (e) {}
108 return real.apply(this, arguments);
109 };
110 return wrapped;
111 }
112 function install() {
113 if (!window.jQuery) return false;
114 var \$ = window.jQuery;
115 if (\$.fn.bootstrapTable) {
116 if (!\$.fn.bootstrapTable.__rpWrapped) {
117 var real = \$.fn.bootstrapTable;
118 var w = wrap(real);
119 for (var k in real) { if (Object.prototype.hasOwnProperty.call(real, k)) w[k] = real[k]; }
120 w.__rpWrapped = true;
121 \$.fn.bootstrapTable = w;
122 }
123 return true;
124 }
125 // bootstrapTable 尚未加载:拦截其赋值
126 try {
127 var stored;
128 Object.defineProperty(\$.fn, 'bootstrapTable', {
129 configurable: true,
130 enumerable: true,
131 get: function () { return stored; },
132 set: function (v) {
133 if (typeof v === 'function' && !v.__rpWrapped) {
134 var w2 = wrap(v);
135 for (var k2 in v) { if (Object.prototype.hasOwnProperty.call(v, k2)) w2[k2] = v[k2]; }
136 w2.__rpWrapped = true;
137 stored = w2;
138 } else {
139 stored = v;
140 }
141 }
142 });
143 return true;
144 } catch (e) { return false; }
145 }
146 try { install(); } catch (e) {}
147})();
148</script>
149HTML;
150 return ['after' => $js];
151}, 10);
152
153/* ============================================================
154 * AJAX(管理端,gn 前缀 p_respool_)
155 * ============================================================ */
156
157/** 新增 / 编辑资源池 */
158mnbt_register_ajax('admin', 'p_respool_save', function () {
159 global $DB, $user;
160 mnbt_plugin_require_admin();
161 rp_ensure_schema();
162
163 $id = (int)($_POST['id'] ?? 0);
164 $in = [
165 'name' => $_POST['name'] ?? '',
166 'username' => $_POST['username'] ?? '',
167 'password' => $_POST['password'] ?? '',
168 'nodes' => isset($_POST['nodes']) && is_array($_POST['nodes']) ? $_POST['nodes'] : [],
169 'web_space' => $_POST['web_space'] ?? 0,
170 'sql_space' => $_POST['sql_space'] ?? 0,
171 'flow' => $_POST['flow'] ?? 0,
172 'expire_date' => $_POST['expire_date'] ?? '',
173 'status' => $_POST['status'] ?? 'enabled',
174 'remark' => $_POST['remark'] ?? '',
175 ];
176
177 if ($id > 0) {
178 $r = rp_update($id, $in);
179 if (!$r['ok']) {
180 json_exit_error($r['msg']);
181 }
182 mnbt_log($user ?? '系统', '插件-资源池', '编辑资源池 ID' . $id, '成功', $DB);
183 json_exit_success('保存成功', ['id' => $id]);
184 }
185
186 $r = rp_create($in);
187 if (!$r['ok']) {
188 json_exit_error($r['msg']);
189 }
190 mnbt_log($user ?? '系统', '插件-资源池', '添加资源池 ' . $in['name'], '成功', $DB);
191 json_exit_success('添加成功', ['id' => $r['id']]);
192});
193
194/** 删除资源池 */
195mnbt_register_ajax('admin', 'p_respool_delete', function () {
196 global $DB, $user;
197 mnbt_plugin_require_admin();
198 $id = (int)($_POST['id'] ?? 0);
199 $r = rp_delete($id);
200 if (!$r['ok']) {
201 json_exit_error($r['msg']);
202 }
203 mnbt_log($user ?? '系统', '插件-资源池', '删除资源池 ID' . $id, '成功', $DB);
204 json_exit_success('删除成功');
205});
206
207/** 启用 / 禁用资源池 */
208mnbt_register_ajax('admin', 'p_respool_status', function () {
209 global $DB, $user;
210 mnbt_plugin_require_admin();
211 $id = (int)($_POST['id'] ?? 0);
212 $status = (string)($_POST['status'] ?? '');
213 $r = rp_set_status($id, $status);
214 if (!$r['ok']) {
215 json_exit_error($r['msg']);
216 }
217 mnbt_log($user ?? '系统', '插件-资源池', '资源池 ID' . $id . ' 状态改为 ' . $status, '成功', $DB);
218 json_exit_success('操作成功');
219});
220
221/** 从资源池开通主机 */
222mnbt_register_ajax('admin', 'p_respool_open_host', function () {
223 global $DB, $user;
224 mnbt_plugin_require_admin();
225 $pool_id = (int)($_POST['pool_id'] ?? 0);
226 $r = rp_open_host($pool_id, [
227 'node' => $_POST['node'] ?? '',
228 'user' => $_POST['host_user'] ?? '',
229 'pass' => $_POST['host_pass'] ?? '',
230 'web_space' => $_POST['web_space'] ?? 0,
231 'sql_space' => $_POST['sql_space'] ?? 0,
232 'flow' => $_POST['flow'] ?? 0,
233 'domain_count' => $_POST['domain_count'] ?? 0,
234 'expire_date' => $_POST['expire_date'] ?? '',
235 'status' => ($_POST['host_status'] ?? 'true') === 'true' ? 'true' : 'false',
236 ]);
237 if (!$r['ok']) {
238 mnbt_log($user ?? '系统', '插件-资源池', '资源池 ID' . $pool_id . ' 开通主机失败:' . $r['msg'], '失败', $DB);
239 json_exit_error($r['msg']);
240 }
241 mnbt_log($user ?? '系统', '插件-资源池', '资源池 ID' . $pool_id . ' 开通主机 ID' . $r['host_id'], '成功', $DB);
242 json_exit_success('开通成功', ['host_id' => $r['host_id']]);
243});
244
245/** 把已有主机绑定到资源池(按主机账号写入 host_users) */
246mnbt_register_ajax('admin', 'p_respool_bind_host', function () {
247 global $DB, $user;
248 mnbt_plugin_require_admin();
249 $pool_id = (int)($_POST['pool_id'] ?? 0);
250 $host_user = trim((string)($_POST['host_user'] ?? ''));
251 $r = rp_bind_host_user($pool_id, $host_user);
252 if (!$r['ok']) {
253 json_exit_error($r['msg']);
254 }
255 mnbt_log($user ?? '系统', '插件-资源池', '主机 ' . $host_user . ' 绑定到资源池 ID' . $pool_id, '成功', $DB);
256 json_exit_success($r['msg']);
257});
258
259/** 解除主机的资源池归属(从 host_users 移除,不删主机) */
260mnbt_register_ajax('admin', 'p_respool_unbind_host', function () {
261 global $DB, $user;
262 mnbt_plugin_require_admin();
263 $host_user = trim((string)($_POST['host_user'] ?? ''));
264 $pool_id = isset($_POST['pool_id']) && $_POST['pool_id'] !== '' ? (int)$_POST['pool_id'] : null;
265 if ($host_user === '') {
266 json_exit_error('参数错误');
267 }
268 $r = rp_unbind_host_user($host_user, $pool_id);
269 if (!$r['ok']) {
270 json_exit_error($r['msg']);
271 }
272 mnbt_log($user ?? '系统', '插件-资源池', '解除主机 ' . $host_user . ' 的资源池归属', '成功', $DB);
273 json_exit_success($r['msg']);
274});
275
276/** 清理失效归属(主机已被删除但账号仍留在 host_users 里) */
277mnbt_register_ajax('admin', 'p_respool_prune', function () {
278 global $DB, $user;
279 mnbt_plugin_require_admin();
280 $pool_id = isset($_POST['pool_id']) && (int)$_POST['pool_id'] > 0 ? (int)$_POST['pool_id'] : null;
281 $r = rp_prune_host_users($pool_id);
282 if (!$r['ok']) {
283 json_exit_error($r['msg']);
284 }
285 if ((int)$r['removed'] > 0) {
286 mnbt_log($user ?? '系统', '插件-资源池', '清理失效归属 ' . (int)$r['removed'] . ' 条', '成功', $DB);
287 }
288 json_exit_success($r['msg'], ['removed' => (int)$r['removed']]);
289});
290
291/** 修复数据表(补建 MN_plugin_respool 与 host_users 列) */
292mnbt_register_ajax('admin', 'p_respool_repair', function () {
293 global $DB;
294 mnbt_plugin_require_admin();
295 rp_ensure_schema(true);
296 if (@$DB->get_row_prepare("SELECT `host_users` FROM " . RP_TABLE . " WHERE 1 LIMIT 1") === false) {
297 json_exit_error('资源池表的 host_users 列仍不可用,请检查数据库账号权限');
298 }
299 json_exit_success('数据表已就绪');
300});