仰望星辰工作室

better-staridc-MNBT

better-staridc-MNBT/ templates/default/docker/console.php 10.1 KB · 205 行 原始文件
Z zfhsh first commit 2 天前
1<?php
2/** 我的容器(单容器模型) */
3$active = 'console';
4include __DIR__ . '/head.php';
5?>
6<div id="dkConsoleView">
7 <div class="dk-spinner-overlay"><span class="dk-spin"></span> 加载中…</div>
8</div>
9
10<script>
11(function(){
12 var pollTimer = null;
13
14 function renderEmpty(){
15 return '<div class="dk-card"><div class="dk-card-body"><div class="dk-empty">' +
16 '<div class="dk-empty-ico">▢</div>' +
17 '<h4>您还没有容器</h4>' +
18 '<p>每个账户可创建一个容器,前往应用商店选择镜像或应用即可开通。</p>' +
19 '<a class="dk-btn" href="appstore.php">前往应用商店</a>' +
20 '</div></div></div>';
21 }
22
23 function statusTag(s){
24 var map = { running: ['运行中','running'], stopped: ['已停止','stopped'], creating: ['创建中','creating'], none: ['未创建','none'], failed: ['失败','failed'] };
25 var m = map[s] || ['未知','none'];
26 return '<span class="dk-tag dk-tag-'+ m[1] +'">'+ m[0] +'</span>';
27 }
28
29 function fmtBytes(n){
30 n = parseInt(n, 10) || 0;
31 if (n < 1024) return n + ' B';
32 if (n < 1048576) return (n/1024).toFixed(1) + ' KB';
33 if (n < 1073741824) return (n/1048576).toFixed(1) + ' MB';
34 return (n/1073741824).toFixed(2) + ' GB';
35 }
36
37 function appIconChar(name){
38 if (!name) return '◆';
39 name = String(name).trim();
40 if (/^[\u4e00-\u9fa5]/.test(name)) {
41 return name.charAt(0);
42 }
43 return name.charAt(0).toUpperCase();
44 }
45
46 function renderContainer(c, me, node, plan){
47 // get_installed_apps 返回字段:service_name/appname/apptitle/status/port[]/host_ip/server_ip/container_id/appinfo[]
48 var name = c.service_name || me.service_name || '';
49 var appTitle = c.apptitle || c.appname || me.app_name || '';
50 var appDesc = c.appdesc || '';
51 var status = c.status || '';
52 var portsArr = c.port || [];
53 var home = c.home || '';
54 var appinfo = c.appinfo || [];
55 var cs = me.container_status || 'running';
56 var nodeIp = (node && node.btip) ? node.btip : (c.server_ip || c.host_ip || '');
57 // 磁盘用量
58 var diskUsage = parseInt(me.disk_usage, 10) || 0;
59 var diskMax = plan && plan.disk_max ? parseInt(plan.disk_max, 10) : 0;
60 var diskHtml = diskUsage > 0 ? fmtBytes(diskUsage) : '暂无数据';
61 var diskOverQuota = false;
62 if (diskMax > 0 && diskUsage > 0) {
63 diskHtml += ' / ' + (diskMax >= 1024 ? (diskMax/1024).toFixed(1) + ' GB' : diskMax + ' MB');
64 var pct = diskUsage / (diskMax * 1048576) * 100;
65 if (pct >= 100) { diskHtml += ' <span class="dk-tag dk-tag-expired" style="font-size:11px">已超限</span>'; diskOverQuota = true; }
66 else if (pct > 90) diskHtml += ' <span class="dk-tag dk-tag-expired" style="font-size:11px">' + pct.toFixed(0) + '% 即将超限</span>';
67 else if (pct > 70) diskHtml += ' <span class="dk-tag dk-tag-paused" style="font-size:11px">' + pct.toFixed(0) + '%</span>';
68 else diskHtml += ' <span class="dk-tag dk-tag-none" style="font-size:11px">' + pct.toFixed(0) + '%</span>';
69 } else if (diskMax > 0) {
70 diskHtml += ' / ' + (diskMax >= 1024 ? (diskMax/1024).toFixed(1) + ' GB' : diskMax + ' MB');
71 }
72 var actions = '';
73 if (cs === 'running') {
74 actions = '<button class="dk-btn dk-btn-warning dk-btn-sm" onclick="dkContainerOp(\'container_stop\')">停止</button>' +
75 '<button class="dk-btn dk-btn-ghost dk-btn-sm" onclick="dkContainerOp(\'container_restart\')">重启</button>';
76 } else if (cs === 'stopped') {
77 actions = '<button class="dk-btn dk-btn-success dk-btn-sm" onclick="dkContainerOp(\'container_start\')">启动</button>' +
78 '<button class="dk-btn dk-btn-ghost dk-btn-sm" onclick="dkContainerOp(\'container_restart\')">重启</button>';
79 }
80 actions += '<button class="dk-btn dk-btn-danger dk-btn-sm" onclick="dkContainerRemove()">删除容器</button>';
81 return '<div class="dk-card">' +
82 '<div class="dk-card-head">' +
83 '<div class="dk-app-title">' +
84 '<div class="dk-app-icon">'+ esc(appIconChar(c.appname || me.app_name)) +'</div>' +
85 '<div><h3>'+ esc(appTitle || '容器详情') +'</h3>'+ (appDesc ? '<div class="dk-muted" style="font-size:12.5px;margin-top:2px">'+ esc(appDesc) +'</div>' : '') +'</div>' +
86 '</div>' +
87 statusTag(cs) +
88 '</div>' +
89 (diskOverQuota && cs === 'stopped' ? '<div class="dk-alert dk-alert-danger" style="margin:0 20px 0 20px;border-radius:0 0 var(--dk-radius) var(--dk-radius)">磁盘用量已超出配额('+ (diskMax >= 1024 ? (diskMax/1024).toFixed(1) + ' GB' : diskMax + ' MB') +'),容器已被自动停机。请清理数据后重新启动。</div>' : '') +
90 '<div class="dk-card-body">' +
91 '<div class="dk-metrics" style="margin-bottom:20px">' +
92 '<div class="dk-metric"><div class="dk-m-label">服务名</div><div class="dk-m-value dk-mono" style="font-size:14px">'+ esc(name || '-') +'</div></div>' +
93 '<div class="dk-metric"><div class="dk-m-label">节点 IP</div><div class="dk-m-value dk-mono" style="font-size:14px">'+ esc(nodeIp || '-') +'</div></div>' +
94 '<div class="dk-metric"><div class="dk-m-label">配额</div><div class="dk-m-value dk-mono" style="font-size:14px">'+ esc(specCpu(me)) +'核 / '+ esc(specMem(me)) +'MB</div></div>' +
95 '<div class="dk-metric"><div class="dk-m-label">版本</div><div class="dk-m-value dk-mono" style="font-size:14px">'+ esc(c.version || (c.m_version + (c.s_version ? '.'+c.s_version : '')) || '-') +'</div></div>' +
96 '</div>' +
97 '<div class="dk-card-body flush" style="border:1px solid var(--dk-border);border-radius:var(--dk-radius-sm)">' +
98 '<table class="dk-table"><tbody>' +
99 '<tr><th style="width:140px">状态</th><td>'+ esc(status || cs) +'</td></tr>' +
100 '<tr><th>节点 IP</th><td class="dk-mono">'+ esc(nodeIp || '-') +'</td></tr>' +
101 '<tr><th>端口映射</th><td>'+ renderPortsFromArray(portsArr, nodeIp, appinfo) +'</td></tr>' +
102 (home ? '<tr><th>应用主页</th><td><a href="'+ esc(home.trim()) +'" target="_blank" rel="noopener" class="dk-port-link">'+ esc(home.trim()) +'</a></td></tr>' : '') +
103 '<tr><th>服务名</th><td class="dk-mono">'+ esc(name || '-') +'</td></tr>' +
104 '<tr><th>磁盘用量</th><td>'+ diskHtml +'</td></tr>' +
105 '</tbody></table>' +
106 '</div>' +
107 '<div class="dk-row-actions" style="margin-top:20px">'+ actions +
108 '<button class="dk-btn dk-btn-ghost dk-btn-sm" onclick="load()">刷新状态</button>' +
109 '</div>' +
110 '</div></div>';
111 }
112
113 // 渲染端口列表:80/443 自动用 http/https 协议,其他端口用 host:port 链接
114 // 同时从 appinfo 中匹配端口的中文说明(如 frps_http_port → HTTP 监听端口)
115 function renderPortsFromArray(ports, nodeIp, appinfo){
116 if (!ports || !ports.length) return '<span class="dk-muted">无端口映射</span>';
117 // 从 appinfo 构造 port→title 映射(key 含 _port 且 value 是数字)
118 var portTitleMap = {};
119 (appinfo || []).forEach(function(info){
120 var key = info.fieldKey || '';
121 var val = String(info.fieldValue || '');
122 if (/_port$/i.test(key) && /^\d+$/.test(val)) {
123 portTitleMap[val] = info.fieldTitle || key;
124 }
125 });
126 var html = '';
127 ports.forEach(function(p){
128 var port = String(p);
129 var label = (nodeIp ? esc(nodeIp) : '节点') +':'+ esc(port);
130 var url = '';
131 if (nodeIp) {
132 if (port === '80') url = 'http://'+ nodeIp;
133 else if (port === '443') url = 'https://'+ nodeIp;
134 else url = 'http://'+ nodeIp +':'+ port;
135 }
136 var link = url ? '<a href="'+ esc(url) +'" target="_blank" rel="noopener" class="dk-port-link">'+ label +'</a>' : '<span class="dk-mono">'+ label +'</span>';
137 var title = portTitleMap[port];
138 var titleHtml = title ? ' <span class="dk-muted dk-port-meta">('+ esc(title) +')</span>' : '';
139 html += '<div class="dk-port-item">'+ link + titleHtml +'</div>';
140 });
141 return html;
142 }
143
144 function renderCreating(me){
145 var elapsed = me.container_spec ? '' : '';
146 return '<div class="dk-card">' +
147 '<div class="dk-card-head"><h3>容器创建中</h3>'+ statusTag('creating') +'</div>' +
148 '<div class="dk-card-body">' +
149 '<div class="dk-alert dk-alert-info">应用 <b>'+ esc(me.app_name || '') +'</b> 正在初始化,通常需要 1-5 分钟,请耐心等待,页面会自动刷新。</div>' +
150 '<div class="dk-step-list">' +
151 '<div class="dk-step" data-s="1"><span class="dk-step-ico">1</span><div class="dk-step-txt">提交创建请求</div></div>' +
152 '<div class="dk-step" data-s="2"><span class="dk-step-ico">2</span><div class="dk-step-txt">拉取镜像 / 启动容器</div></div>' +
153 '<div class="dk-step" data-s="3"><span class="dk-step-ico">3</span><div class="dk-step-txt">应用初始化完成</div></div>' +
154 '</div>' +
155 '<div class="dk-muted" style="margin-top:14px;font-size:13px">每 8 秒自动检查容器状态…</div>' +
156 '</div></div>';
157 }
158
159 function esc(s){ return $('<div>').text(s == null ? '' : String(s)).html(); }
160 function ltrim(s, ch){ return s.charAt(0) === ch ? s.slice(1) : s; }
161 function specCpu(me){ try { return JSON.parse(me.container_spec||'{}').cpus || '-'; } catch(e){ return '-'; } }
162 function specMem(me){ try { return JSON.parse(me.container_spec||'{}').memory_limit || '-'; } catch(e){ return '-'; } }
163
164 window.dkContainerOp = function(gn){
165 dkAjax(gn, {}).then(function(r){
166 dkToast(r.msg || '操作已提交', 'success');
167 setTimeout(load, 1500);
168 });
169 };
170
171 window.dkContainerRemove = function(){
172 if (!confirm('确定要删除容器吗?容器数据将被永久清除,此操作不可恢复。')) return;
173 dkAjax('container_remove', {}).then(function(r){
174 dkToast(r.msg || '操作已提交', 'success');
175 setTimeout(load, 2000);
176 });
177 };
178
179 function load(){
180 return dkAjax('my_container', {}, {silent:true}).then(function(r){
181 if (pollTimer) { clearInterval(pollTimer); pollTimer = null; }
182 var me = r.me || {};
183 var c = r.container;
184 var node = r.node || {};
185 var plan = r.plan || null;
186 var html;
187 if (!me.service_name && !me.container_id) {
188 html = renderEmpty();
189 } else if (me.container_status === 'creating' || (!c && me.service_name)) {
190 html = renderCreating(me);
191 // creating 状态:每 8 秒自动刷新检查容器是否就绪
192 pollTimer = setInterval(load, 8000);
193 } else if (c) {
194 html = renderContainer(c, me, node, plan);
195 } else {
196 html = renderEmpty();
197 }
198 $('#dkConsoleView').html(html);
199 });
200 }
201
202 load();
203})();
204</script>
205<?php include __DIR__ . '/foot.php'; ?>