better-staridc-MNBT
1<?php
2/**
3 * 后台管理:菜单、连接配置页、API 日志页与 AJAX 处理。
4 *
5 * @package MnbtWp
6 */
7
8namespace MnbtWp;
9
10use MnbtWp\Mnbt\Client;
11use MnbtWp\Mnbt\Exception as MnbtException;
12use MnbtWp\Services\Provision;
13
14if ( ! defined( 'ABSPATH' ) ) {
15 exit;
16}
17
18/**
19 * 后台管理类。
20 */
21class Admin {
22
23 /**
24 * 构造:挂载钩子。
25 */
26 public function __construct() {
27 add_action( 'admin_menu', array( $this, 'menu' ) );
28 add_action( 'wp_ajax_mnbtwp_save_provider', array( $this, 'ajax_save_provider' ) );
29 add_action( 'wp_ajax_mnbtwp_get_provider', array( $this, 'ajax_get_provider' ) );
30 add_action( 'wp_ajax_mnbtwp_delete_provider', array( $this, 'ajax_delete_provider' ) );
31 add_action( 'wp_ajax_mnbtwp_test_provider', array( $this, 'ajax_test_provider' ) );
32 add_action( 'wp_ajax_mnbtwp_admin_host_action', array( $this, 'ajax_admin_host_action' ) );
33 add_action( 'wp_ajax_mnbtwp_admin_retry_provision', array( $this, 'ajax_admin_retry_provision' ) );
34 }
35
36 /**
37 * 注册菜单。
38 */
39 public function menu() {
40 add_menu_page(
41 __( 'MNBT 主机销售', 'wp-seller-plugin' ),
42 __( 'MNBT 主机销售', 'wp-seller-plugin' ),
43 'manage_options',
44 'mnbtwp',
45 array( $this, 'page_settings' ),
46 'dashicons-cloud',
47 56
48 );
49 add_submenu_page(
50 'mnbtwp',
51 __( '主机管理', 'wp-seller-plugin' ),
52 __( '主机管理', 'wp-seller-plugin' ),
53 'manage_options',
54 'mnbtwp-hosts',
55 array( $this, 'page_hosts' )
56 );
57 add_submenu_page(
58 'mnbtwp',
59 __( '连接配置', 'wp-seller-plugin' ),
60 __( '连接配置', 'wp-seller-plugin' ),
61 'manage_options',
62 'mnbtwp',
63 array( $this, 'page_settings' )
64 );
65 add_submenu_page(
66 'mnbtwp',
67 __( 'API 日志', 'wp-seller-plugin' ),
68 __( 'API 日志', 'wp-seller-plugin' ),
69 'manage_options',
70 'mnbtwp-logs',
71 array( $this, 'page_logs' )
72 );
73 }
74
75 /**
76 * 主机总览页。
77 */
78 public function page_hosts() {
79 global $wpdb;
80 $hosts = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}mnbt_hosts ORDER BY id DESC LIMIT 500", ARRAY_A );
81 $provider_map = array();
82 foreach ( $hosts as $h ) {
83 if ( ! isset( $provider_map[ $h['provider_id'] ] ) ) {
84 $provider_map[ $h['provider_id'] ] = Helpers::get_provider( $h['provider_id'] );
85 }
86 }
87 $failed_orders = array();
88 if ( class_exists( 'WooCommerce' ) ) {
89 $failed_orders = wc_get_orders(
90 array(
91 'limit' => 50,
92 'status' => array( 'pending', 'processing', 'on-hold', 'completed' ),
93 'meta_key' => '_mnbtwp_provision_error',
94 'meta_compare' => 'EXISTS',
95 )
96 );
97 }
98 include MNBTWP_DIR . 'includes/admin/views/hosts.php';
99 }
100
101 /**
102 * 连接配置页。
103 */
104 public function page_settings() {
105 global $wpdb;
106 $providers = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}mnbt_providers ORDER BY id ASC", ARRAY_A );
107 include MNBTWP_DIR . 'includes/admin/views/settings.php';
108 }
109
110 /**
111 * API 日志页。
112 */
113 public function page_logs() {
114 global $wpdb;
115 $logs = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}mnbt_api_log ORDER BY id DESC LIMIT 200", ARRAY_A );
116 include MNBTWP_DIR . 'includes/admin/views/logs.php';
117 }
118
119 /**
120 * 保存/更新节点配置。
121 */
122 public function ajax_save_provider() {
123 check_ajax_referer( 'mnbtwp_admin', 'nonce' );
124 if ( ! current_user_can( 'manage_options' ) ) {
125 wp_send_json_error( __( '权限不足', 'wp-seller-plugin' ) );
126 }
127
128 global $wpdb;
129 $id = (int) ( $_POST['id'] ?? 0 );
130 $name = sanitize_text_field( $_POST['name'] ?? '' );
131 $api_url = esc_url_raw( trim( (string) ( $_POST['api_url'] ?? '' ) ) );
132 $btdh = sanitize_text_field( $_POST['btdh'] ?? '' );
133 $mn_key = sanitize_text_field( $_POST['mn_key'] ?? '' );
134 $mn_keye = sanitize_text_field( $_POST['mn_keye'] ?? '' );
135 $mn_vs = max( 15, (int) ( $_POST['mn_vs'] ?? 15 ) );
136 $qk = ( ! empty( $_POST['qk'] ) && '1' === (string) $_POST['qk'] ) ? 'true' : 'false';
137
138 if ( '' === $name || '' === $api_url || '' === $btdh || '' === $mn_key || '' === $mn_keye ) {
139 wp_send_json_error( __( '参数不能为空', 'wp-seller-plugin' ) );
140 }
141 if ( ! preg_match( '#^https?://#i', $api_url ) ) {
142 wp_send_json_error( __( 'API 地址需以 http(s):// 开头', 'wp-seller-plugin' ) );
143 }
144
145 $table = $wpdb->prefix . 'mnbt_providers';
146
147 if ( $id > 0 ) {
148 $current = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE id=%d", $id ), ARRAY_A );
149 if ( ! $current ) {
150 wp_send_json_error( __( '节点不存在', 'wp-seller-plugin' ) );
151 }
152 // 密钥未修改则保留原密文,避免每次保存变更密文。
153 $mn_key_enc = ( Crypto::decrypt( $current['mn_key_enc'] ) === $mn_key ) ? $current['mn_key_enc'] : Crypto::encrypt( $mn_key );
154 $mn_keye_enc = ( Crypto::decrypt( $current['mn_keye_enc'] ) === $mn_keye ) ? $current['mn_keye_enc'] : Crypto::encrypt( $mn_keye );
155
156 $wpdb->update(
157 $table,
158 array(
159 'name' => $name,
160 'api_url' => $api_url,
161 'btdh' => $btdh,
162 'mn_key_enc' => $mn_key_enc,
163 'mn_keye_enc' => $mn_keye_enc,
164 'mn_vs' => $mn_vs,
165 'qk' => $qk,
166 'updated_at' => current_time( 'mysql' ),
167 ),
168 array( 'id' => $id )
169 );
170 } else {
171 $wpdb->insert(
172 $table,
173 array(
174 'name' => $name,
175 'api_url' => $api_url,
176 'btdh' => $btdh,
177 'mn_key_enc' => Crypto::encrypt( $mn_key ),
178 'mn_keye_enc' => Crypto::encrypt( $mn_keye ),
179 'mn_vs' => $mn_vs,
180 'qk' => $qk,
181 'created_at' => current_time( 'mysql' ),
182 'updated_at' => current_time( 'mysql' ),
183 )
184 );
185 }
186
187 if ( $wpdb->last_error ) {
188 wp_send_json_error( __( '保存失败', 'wp-seller-plugin' ) . ':' . $wpdb->last_error );
189 }
190 wp_send_json_success( __( '保存成功', 'wp-seller-plugin' ) );
191 }
192
193 /**
194 * 获取节点配置(编辑回显,解密密钥)。
195 */
196 public function ajax_get_provider() {
197 check_ajax_referer( 'mnbtwp_admin', 'nonce' );
198 if ( ! current_user_can( 'manage_options' ) ) {
199 wp_send_json_error( __( '权限不足', 'wp-seller-plugin' ) );
200 }
201 global $wpdb;
202 $id = (int) ( $_POST['id'] ?? 0 );
203 $row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}mnbt_providers WHERE id=%d", $id ), ARRAY_A );
204 if ( ! $row ) {
205 wp_send_json_error( __( '节点不存在', 'wp-seller-plugin' ) );
206 }
207 wp_send_json_success(
208 array(
209 'id' => (int) $row['id'],
210 'name' => $row['name'],
211 'api_url' => $row['api_url'],
212 'btdh' => $row['btdh'],
213 'mn_key' => Crypto::decrypt( $row['mn_key_enc'] ),
214 'mn_keye' => Crypto::decrypt( $row['mn_keye_enc'] ),
215 'mn_vs' => (int) $row['mn_vs'],
216 'qk' => $row['qk'],
217 )
218 );
219 }
220
221 /**
222 * 删除节点配置。
223 */
224 public function ajax_delete_provider() {
225 check_ajax_referer( 'mnbtwp_admin', 'nonce' );
226 if ( ! current_user_can( 'manage_options' ) ) {
227 wp_send_json_error( __( '权限不足', 'wp-seller-plugin' ) );
228 }
229 global $wpdb;
230 $id = (int) ( $_POST['id'] ?? 0 );
231 $wpdb->delete( $wpdb->prefix . 'mnbt_providers', array( 'id' => $id ), array( '%d' ) );
232 if ( $wpdb->last_error ) {
233 wp_send_json_error( __( '删除失败', 'wp-seller-plugin' ) . ':' . $wpdb->last_error );
234 }
235 wp_send_json_success( __( '删除成功', 'wp-seller-plugin' ) );
236 }
237
238 /**
239 * 连接测试(cfif)。
240 */
241 public function ajax_test_provider() {
242 check_ajax_referer( 'mnbtwp_admin', 'nonce' );
243 if ( ! current_user_can( 'manage_options' ) ) {
244 wp_send_json_error( __( '权限不足', 'wp-seller-plugin' ) );
245 }
246 global $wpdb;
247 $id = (int) ( $_POST['id'] ?? 0 );
248 $row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}mnbt_providers WHERE id=%d", $id ), ARRAY_A );
249 if ( ! $row ) {
250 wp_send_json_error( __( '节点不存在', 'wp-seller-plugin' ) );
251 }
252 try {
253 $client = new Client(
254 array(
255 'api_url' => $row['api_url'],
256 'btdh' => $row['btdh'],
257 'mn_key' => Crypto::decrypt( $row['mn_key_enc'] ),
258 'mn_keye' => Crypto::decrypt( $row['mn_keye_enc'] ),
259 'mn_vs' => (int) $row['mn_vs'],
260 'provider_id' => (int) $row['id'],
261 )
262 );
263 $result = $client->testConnection();
264 /* translators: %s: MNBT 返回消息 */
265 wp_send_json_success( sprintf( __( '连接成功:%s', 'wp-seller-plugin' ), $result['msg'] ) );
266 } catch ( MnbtException $e ) {
267 wp_send_json_error( __( '连接失败', 'wp-seller-plugin' ) . ':' . $e->getMessage() );
268 }
269 }
270
271 /**
272 * 管理端主机操作:start/stop/suspend/resume/delete/change_pass/renew/change_package。
273 */
274 public function ajax_admin_host_action() {
275 check_ajax_referer( 'mnbtwp_admin', 'nonce' );
276 if ( ! current_user_can( 'manage_options' ) ) {
277 wp_send_json_error( __( '权限不足', 'wp-seller-plugin' ) );
278 }
279 $action = sanitize_key( $_POST['host_action'] ?? '' );
280 $host_id = (int) ( $_POST['host_id'] ?? 0 );
281 $host = Helpers::get_host( $host_id );
282 if ( ! $host ) {
283 wp_send_json_error( __( '主机不存在', 'wp-seller-plugin' ) );
284 }
285 $provider = Helpers::get_provider( $host['provider_id'] );
286 if ( ! $provider ) {
287 wp_send_json_error( __( '节点不可用', 'wp-seller-plugin' ) );
288 }
289
290 global $wpdb;
291 $table = $wpdb->prefix . 'mnbt_hosts';
292
293 try {
294 $client = Helpers::client_for_provider( $provider );
295 switch ( $action ) {
296 case 'start':
297 $client->startSite( $host['username'] );
298 break;
299 case 'stop':
300 $client->stopSite( $host['username'] );
301 break;
302 case 'suspend':
303 $client->suspendHost( $host['username'] );
304 $wpdb->update( $table, array( 'status' => 'suspended', 'updated_at' => current_time( 'mysql' ) ), array( 'id' => $host_id ) );
305 break;
306 case 'resume':
307 $client->resumeHost( $host['username'] );
308 $wpdb->update( $table, array( 'status' => 'active', 'updated_at' => current_time( 'mysql' ) ), array( 'id' => $host_id ) );
309 break;
310 case 'delete':
311 $client->deleteHost( $host['username'] );
312 $wpdb->delete( $table, array( 'id' => $host_id ), array( '%d' ) );
313 break;
314 case 'change_pass':
315 $password = sanitize_text_field( $_POST['password'] ?? '' );
316 if ( mb_strlen( $password ) < 6 ) {
317 wp_send_json_error( __( '密码不能少于 6 位', 'wp-seller-plugin' ) );
318 }
319 $client->changePassword( $host['username'], $password );
320 $wpdb->update( $table, array( 'password_enc' => Crypto::encrypt( $password ), 'updated_at' => current_time( 'mysql' ) ), array( 'id' => $host_id ) );
321 break;
322 case 'renew':
323 $setdate = sanitize_text_field( $_POST['setdate'] ?? '' );
324 if ( ! preg_match( '/^\d{4}-\d{2}-\d{2}$/', $setdate ) ) {
325 wp_send_json_error( __( '到期日期格式错误', 'wp-seller-plugin' ) );
326 }
327 $client->renewHost( $host['username'], $setdate );
328 $wpdb->update( $table, array( 'expire_date' => $setdate, 'updated_at' => current_time( 'mysql' ) ), array( 'id' => $host_id ) );
329 break;
330 case 'change_package':
331 $web = max( 0, (int) ( $_POST['websize'] ?? 0 ) );
332 $sql = max( 0, (int) ( $_POST['sqlsize'] ?? 0 ) );
333 $flow = max( 0, (int) ( $_POST['ll'] ?? 0 ) );
334 $client->changePackage( $host['username'], array( 'websize' => $web, 'sqlsize' => $sql, 'll' => $flow ) );
335 $quota = Helpers::parse_quota( $host['quota_json'] );
336 $quota['web']['max'] = $web;
337 $quota['sql']['max'] = $sql;
338 $quota['flow']['max'] = $flow;
339 $wpdb->update( $table, array( 'quota_json' => wp_json_encode( $quota ), 'updated_at' => current_time( 'mysql' ) ), array( 'id' => $host_id ) );
340 break;
341 default:
342 wp_send_json_error( __( '未知操作', 'wp-seller-plugin' ) );
343 }
344 wp_send_json_success( __( '操作成功', 'wp-seller-plugin' ) );
345 } catch ( \Exception $e ) {
346 wp_send_json_error( $e->getMessage() );
347 }
348 }
349
350 /**
351 * 重试开通失败的订单。
352 */
353 public function ajax_admin_retry_provision() {
354 check_ajax_referer( 'mnbtwp_admin', 'nonce' );
355 if ( ! current_user_can( 'manage_options' ) ) {
356 wp_send_json_error( __( '权限不足', 'wp-seller-plugin' ) );
357 }
358 $order_id = (int) ( $_POST['order_id'] ?? 0 );
359 if ( ! $order_id ) {
360 wp_send_json_error( __( '参数错误', 'wp-seller-plugin' ) );
361 }
362 try {
363 $result = Provision::provision_order( $order_id );
364 wp_send_json_success( $result['msg'] );
365 } catch ( \Exception $e ) {
366 wp_send_json_error( $e->getMessage() );
367 }
368 }
369}