better-staridc-MNBT
1<?php
2if (!defined('IN_CRONLITE')) exit;
3mnbt_theme_include('head');
4global $yhc, $DB;
5$user = $yhc['user'] ?? '';
6
7// 服务端直接获取当前用户主机(系统设计:一个用户对应一个主机)
8$myHost = function_exists('auto_deploy_get_my_host') ? auto_deploy_get_my_host() : null;
9$myNodeId = 0;
10if ($myHost) {
11 $myNodeId = (int)($myHost['node_id'] ?? 0);
12}
13?>
14<style>
15.ad-loading { display: none; text-align: center; padding: 30px; }
16.ad-deploy-result { display: none; }
17.ad-pkg-table td { vertical-align: middle; }
18</style>
19
20<div class="container-fluid p-t-15">
21 <div class="card">
22 <header class="card-header">
23 <div class="card-title"><i class="mdi mdi-monitor-dashboard"></i> 软件商店 - 一键部署</div>
24 </header>
25 <div class="card-body">
26
27 <!-- 当前主机信息(服务端渲染) -->
28 <div class="mb-3">
29 <?php if (!$myHost): ?>
30 <div class="alert alert-warning mb-0">
31 <i class="mdi mdi-alert-circle-outline"></i> 您还没有可用的主机,请先开通主机服务后再使用一键部署。
32 </div>
33 <?php else: ?>
34 <div class="alert alert-info mb-0">
35 <i class="mdi mdi-server"></i> 当前主机:<strong><?= htmlspecialchars((string)$myHost['sqldz']) ?></strong>
36 <span class="text-muted">(节点:<?= htmlspecialchars((string)$myHost['btdh']) ?> · <?= htmlspecialchars((string)$myHost['btip']) ?>:<?= htmlspecialchars((string)$myHost['btdk']) ?>)</span>
37 <br><small class="text-muted">部署将覆盖该主机网站目录中的现有内容,请谨慎操作。</small>
38 </div>
39 <?php endif; ?>
40 </div>
41
42 <hr>
43
44 <!-- 软件包列表(表格 + 分页) -->
45 <div class="row mb-3">
46 <div class="col-md-4">
47 <button type="button" class="btn btn-primary btn-block" id="btn-load-pkgs" <?= $myHost ? '' : 'disabled' ?>>
48 <i class="mdi mdi-refresh"></i> 加载软件列表
49 </button>
50 </div>
51 <div class="col-md-5">
52 <input type="text" class="form-control" id="ad-pkg-search" placeholder="搜索软件包..." disabled>
53 </div>
54 </div>
55 <div id="ad-packages-tip" class="text-muted mb-2" <?= $myHost ? 'style="display:none;"' : '' ?>>点击「加载软件列表」按钮获取可用软件。</div>
56 <div id="ad-packages-loading" class="ad-loading"><div class="spinner-border text-primary"></div><p>加载软件包中...</p></div>
57 <div id="ad-pkg-area" style="display:none;">
58 <div class="table-responsive">
59 <table class="table table-bordered table-hover table-striped ad-pkg-table">
60 <thead class="thead-light">
61 <tr>
62 <th style="width:50px;">#</th>
63 <th>软件名称</th>
64 <th style="width:90px;">版本</th>
65 <th style="width:120px;">作者</th>
66 <th style="width:90px;">价格</th>
67 <th style="width:130px;">操作</th>
68 </tr>
69 </thead>
70 <tbody id="ad-pkg-tbody"></tbody>
71 </table>
72 </div>
73 <div class="d-flex justify-content-between align-items-center" id="ad-pkg-pager"></div>
74 </div>
75
76 <!-- 部署结果 -->
77 <div class="ad-deploy-result mt-3 card border-success" id="ad-result-box">
78 <div class="card-header bg-success text-white">部署结果</div>
79 <div class="card-body" id="ad-result-body"></div>
80 </div>
81
82 </div>
83 </div>
84</div>
85
86<script>
87function parseRes(res) {
88 try { return typeof res === 'string' ? JSON.parse(res) : res; } catch (e) { return {code: res}; }
89}
90function escHtml(s) {
91 return String(s).replace(/[&<>"']/g, function (c) {
92 return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c];
93 });
94}
95function msg(msg, type, time) {
96 if (typeof msalert === 'function') { msalert(type || 1, msg, time || 2500); }
97 else { alert(msg); }
98}
99
100var allPackages = []; // 全部软件包
101var filteredPackages = []; // 过滤后的软件包
102var pageSize = 10; // 每页条数
103var currentPage = 1;
104var myNodeId = <?= (int)$myNodeId ?>; // 当前用户主机所在节点 ID(服务端注入)
105var myHostReady = <?= $myHost ? 'true' : 'false' ?>;
106
107// ======================== 加载软件包列表 ========================
108function loadPackages() {
109 if (!myHostReady) { msg('您还没有可用的主机,请先开通主机服务', 4); return; }
110 if (!myNodeId) { msg('主机节点信息异常', 4); return; }
111
112 $('#ad-packages-tip').hide();
113 $('#ad-pkg-area').hide();
114 $('#ad-packages-loading').show();
115 $('#ad-pkg-search').prop('disabled', true);
116
117 $.ajax({
118 url: 'ajax.php',
119 type: 'POST',
120 data: {gn: 'p_auto_deploy_packages', node_id: myNodeId},
121 timeout: 35000,
122 success: function (res) {
123 $('#ad-packages-loading').hide();
124 var d = parseRes(res);
125 if (d.qk != 1 && !d.success) {
126 $('#ad-packages-tip').text(d.msg || '加载失败').show();
127 return;
128 }
129 allPackages = d.packages || [];
130 if (!allPackages.length) {
131 $('#ad-packages-tip').text('该节点暂无可用软件包').show();
132 return;
133 }
134 $('#ad-pkg-search').val('').prop('disabled', false);
135 applyFilter();
136 $('#ad-pkg-area').show();
137 },
138 error: function (xhr, status) {
139 $('#ad-packages-loading').hide();
140 $('#ad-packages-tip').text('软件列表加载失败(' + (status === 'timeout' ? '请求超时' : xhr.status) + '),请检查节点面板是否可访问。').show();
141 }
142 });
143}
144$('#btn-load-pkgs').on('click', loadPackages);
145
146// ======================== 过滤 + 分页渲染 ========================
147function applyFilter() {
148 var kw = $('#ad-pkg-search').val().trim().toLowerCase();
149 if (kw) {
150 filteredPackages = allPackages.filter(function (p) {
151 return (String(p.title || '') + ' ' + String(p.name || '')).toLowerCase().indexOf(kw) !== -1;
152 });
153 } else {
154 filteredPackages = allPackages.slice();
155 }
156 currentPage = 1;
157 renderPkgPage();
158}
159
160function renderPkgPage() {
161 var total = filteredPackages.length;
162 var pages = Math.max(1, Math.ceil(total / pageSize));
163 if (currentPage > pages) currentPage = pages;
164 var start = (currentPage - 1) * pageSize;
165 var slice = filteredPackages.slice(start, start + pageSize);
166
167 var html = '';
168 if (!slice.length) {
169 html = '<tr><td colspan="6" class="text-center text-muted">未找到匹配的软件包</td></tr>';
170 } else {
171 $.each(slice, function (i, p) {
172 var price = p.price > 0 ? '<span class="text-warning">¥' + escHtml(p.price) + '</span>' : '<span class="text-success">免费</span>';
173 html += '<tr>';
174 html += '<td>' + (start + i + 1) + '</td>';
175 html += '<td><strong>' + escHtml(p.title || p.name) + '</strong></td>';
176 html += '<td>v' + escHtml(p.version || '') + '</td>';
177 html += '<td>' + escHtml(p.author || '') + '</td>';
178 html += '<td>' + price + '</td>';
179 html += '<td><button type="button" class="btn btn-sm btn-primary ad-deploy-btn" data-name="' + escHtml(p.name || '') + '" data-title="' + escHtml(p.title || p.name) + '">';
180 html += '<i class="mdi mdi-open-in-new"></i> 部署</button></td>';
181 html += '</tr>';
182 });
183 }
184 $('#ad-pkg-tbody').html(html);
185
186 // 分页控件
187 var pager = '';
188 if (pages > 1) {
189 pager += '<nav><ul class="pagination pagination-sm mb-0">';
190 pager += '<li class="page-item' + (currentPage <= 1 ? ' disabled' : '') + '"><a class="page-link ad-page" href="#" data-page="' + (currentPage - 1) + '">上一页</a></li>';
191 var showStart = Math.max(1, currentPage - 2);
192 var showEnd = Math.min(pages, showStart + 4);
193 showStart = Math.max(1, showEnd - 4);
194 for (var i = showStart; i <= showEnd; i++) {
195 pager += '<li class="page-item' + (i === currentPage ? ' active' : '') + '"><a class="page-link ad-page" href="#" data-page="' + i + '">' + i + '</a></li>';
196 }
197 pager += '<li class="page-item' + (currentPage >= pages ? ' disabled' : '') + '"><a class="page-link ad-page" href="#" data-page="' + (currentPage + 1) + '">下一页</a></li>';
198 pager += '</ul></nav>';
199 }
200 pager += '<small class="text-muted">共 ' + total + ' 个软件,第 ' + currentPage + '/' + pages + ' 页</small>';
201 $('#ad-pkg-pager').html(pager);
202}
203
204// 分页点击
205$(document).on('click', '.ad-page', function (e) {
206 e.preventDefault();
207 if ($(this).parent().hasClass('disabled')) return;
208 currentPage = parseInt($(this).data('page')) || 1;
209 renderPkgPage();
210});
211
212// 搜索过滤
213$('#ad-pkg-search').on('keyup', function () {
214 applyFilter();
215});
216
217// ======================== 执行部署(每行按钮) ========================
218$(document).on('click', '.ad-deploy-btn', function () {
219 if (!myHostReady) { msg('主机信息尚未加载完成,请稍候', 4); return; }
220 var dname = $(this).data('name') || '';
221 var title = $(this).data('title') || dname;
222 if (!dname) { msg('软件包信息异常', 4); return; }
223 if (!confirm('将部署「' + title + '」到您的主机,\n该主机网站目录现有内容可能被覆盖,是否继续?')) return;
224
225 var btn = this;
226 $(btn).prop('disabled', true).html('<i class="mdi mdi-cached mdi-spin"></i> 部署中...');
227 if (typeof msloading === 'function') msloading('部署中...');
228
229 function restoreBtn() {
230 if (typeof msloadingde === 'function') msloadingde();
231 $(btn).prop('disabled', false).html('<i class="mdi mdi-open-in-new"></i> 部署');
232 }
233
234 $.ajax({
235 url: 'ajax.php',
236 type: 'POST',
237 data: {
238 gn: 'p_auto_deploy_setup',
239 dname: dname,
240 project_type: 'php'
241 },
242 timeout: 40000,
243 success: function (res) {
244 restoreBtn();
245 var d = parseRes(res);
246 if (d.qk == 1 || d.success) {
247 var html = '<p class="text-success"><strong>部署成功!</strong></p>';
248 if (d.admin_username) html += '<p>管理员账号:<code>' + escHtml(d.admin_username) + '</code></p>';
249 if (d.admin_password) html += '<p>管理员密码:<code>' + escHtml(d.admin_password) + '</code></p>';
250 if (d.success_url) {
251 html += '<p>访问入口:<a href="' + escHtml('http://' + d.success_url) + '" target="_blank">' + escHtml(d.success_url) + '</a></p>';
252 } else {
253 html += '<p class="text-warning small">该 CMS 软件需访问网站域名进入 Web 安装向导完成配置。</p>';
254 }
255 $('#ad-result-body').html(html);
256 $('#ad-result-box').removeClass('border-danger').addClass('border-success').show();
257 msg(d.msg || '部署成功', 1);
258 } else {
259 $('#ad-result-body').html('<p class="text-danger">' + escHtml(d.msg || '部署失败') + '</p>');
260 $('#ad-result-box').removeClass('border-success').addClass('border-danger').show();
261 msg(d.msg || '部署失败', 4);
262 }
263 },
264 error: function (xhr, status) {
265 restoreBtn();
266 var tip = (status === 'timeout' ? '部署请求超时' : ('部署请求失败(HTTP ' + xhr.status + ')')) + ',请到「部署记录」查看是否已提交。';
267 $('#ad-result-body').html('<p class="text-danger">' + escHtml(tip) + '</p>');
268 $('#ad-result-box').removeClass('border-success').addClass('border-danger').show();
269 msg(tip, 4, 4000);
270 }
271 });
272});
273</script>