fix:修复页面bug
This commit is contained in:
@@ -1,4 +1,7 @@
|
|||||||
PORT=3000
|
PORT=3000
|
||||||
|
# Node 代理 /api、/auth 时转发的后端地址(单容器部署可不设,默认 127.0.0.1:8000)
|
||||||
|
# BACKEND_PORT=8000
|
||||||
|
# BACKEND_HOST=127.0.0.1
|
||||||
WECHAT_UPSTREAM_BASE_URL=http://113.44.162.180:7006
|
WECHAT_UPSTREAM_BASE_URL=http://113.44.162.180:7006
|
||||||
CHECK_STATUS_BASE_URL=http://113.44.162.180:7006
|
CHECK_STATUS_BASE_URL=http://113.44.162.180:7006
|
||||||
# 第三方滑块(7765):iframe 加载自带预填表单页,提交到下方地址
|
# 第三方滑块(7765):iframe 加载自带预填表单页,提交到下方地址
|
||||||
|
|||||||
@@ -488,7 +488,11 @@ async def slider_asset_proxy(path: str):
|
|||||||
if resp.status_code >= 400:
|
if resp.status_code >= 400:
|
||||||
raise HTTPException(status_code=resp.status_code, detail=resp.text[:200])
|
raise HTTPException(status_code=resp.status_code, detail=resp.text[:200])
|
||||||
media_type = "application/javascript" if path.endswith(".js") else "application/octet-stream"
|
media_type = "application/javascript" if path.endswith(".js") else "application/octet-stream"
|
||||||
return Response(content=resp.content, media_type=media_type)
|
return Response(
|
||||||
|
content=resp.content,
|
||||||
|
media_type=media_type,
|
||||||
|
headers={"Cache-Control": "no-store, no-cache, must-revalidate", "Pragma": "no-cache"},
|
||||||
|
)
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -596,36 +596,53 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<aside>
|
<aside>
|
||||||
<div class="qr-card">
|
<div id="qr-area">
|
||||||
<div class="qr-header">
|
<div class="qr-card">
|
||||||
<span>扫码登录二维码</span>
|
<div class="qr-header">
|
||||||
<span class="badge" id="login-state-badge">未创建</span>
|
<span>扫码登录二维码</span>
|
||||||
</div>
|
<span class="badge" id="login-state-badge">未创建</span>
|
||||||
<div class="qr-box">
|
</div>
|
||||||
<div class="qr-img" id="qr-img-box">
|
<div class="qr-box">
|
||||||
<div class="qr-placeholder" id="qr-placeholder">
|
<div class="qr-img" id="qr-img-box">
|
||||||
点击左侧「获取登录二维码」后,将在这里显示二维码图片或登录链接信息。
|
<div class="qr-placeholder" id="qr-placeholder">
|
||||||
|
点击左侧「获取登录二维码」后,将在这里显示二维码图片或登录链接信息。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="hint" id="qr-hint">
|
||||||
|
请使用微信客户端扫描二维码完成登录。
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hint" id="qr-hint">
|
</div>
|
||||||
请使用微信客户端扫描二维码完成登录。
|
<div style="height: 12px"></div>
|
||||||
</div>
|
<div class="card">
|
||||||
|
<div class="small-label">当前账号在线状态</div>
|
||||||
|
<div class="login-state unknown" id="login-state-text">未知</div>
|
||||||
|
<div class="small-label" id="login-extra">尚未查询</div>
|
||||||
|
</div>
|
||||||
|
<div id="slider-reopen-wrap" style="display: none; margin-top: 12px;">
|
||||||
|
<button type="button" class="secondary" id="btn-show-slider">打开滑块验证</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="height: 12px"></div>
|
<div id="slider-area" style="display: none;">
|
||||||
|
<div class="card" style="max-width: 480px;">
|
||||||
<div class="card">
|
<div class="card-title">滑块验证</div>
|
||||||
<div class="small-label">当前账号在线状态</div>
|
<div id="slider-app" data-v-app="">
|
||||||
<div class="login-state unknown" id="login-state-text">未知</div>
|
<div class="params-section" style="margin-bottom: 12px;">
|
||||||
<div class="small-label" id="login-extra">尚未查询</div>
|
<label class="form-label" for="keyInput">Key:</label>
|
||||||
|
<input type="text" class="form-control" id="keyInput" placeholder="请输入key" style="width:100%;box-sizing:border-box;padding:8px;margin-bottom:8px;border:1px solid var(--border);border-radius:8px;background:rgba(15,23,42,0.6);color:var(--text);">
|
||||||
|
<label class="form-label" for="data62Input">Data62:</label>
|
||||||
|
<input type="text" class="form-control" id="data62Input" placeholder="请输入data62" style="width:100%;box-sizing:border-box;padding:8px;margin-bottom:8px;border:1px solid var(--border);border-radius:8px;background:rgba(15,23,42,0.6);color:var(--text);">
|
||||||
|
<label class="form-label" for="originalTicketInput">Original Ticket:</label>
|
||||||
|
<input type="text" class="form-control" id="originalTicketInput" placeholder="请输入original_ticket" style="width:100%;box-sizing:border-box;padding:8px;margin-bottom:12px;border:1px solid var(--border);border-radius:8px;background:rgba(15,23,42,0.6);color:var(--text);">
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<button type="button" class="btn btn-verify btn-lg" id="slider-btn-verify" disabled style="padding:10px 20px;background:var(--accent);color:#fff;border:none;border-radius:8px;cursor:pointer;">开始验证</button>
|
||||||
|
<div class="text-muted mt-2" style="font-size:12px;color:var(--muted);margin-top:8px;">请先填写完整的参数信息</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="slider-card" id="slider-card" style="display: none">
|
|
||||||
<div class="small-label">需完成滑块验证,已在弹窗打开验证页(与 7765 同结构,本地实现)</div>
|
|
||||||
<a id="slider-open-link" href="#" target="_blank" rel="noopener" class="btn secondary">重新打开滑块验证</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -634,15 +651,78 @@
|
|||||||
<script>
|
<script>
|
||||||
const $ = (id) => document.getElementById(id);
|
const $ = (id) => document.getElementById(id);
|
||||||
|
|
||||||
// 相对路径,由 Node 代理到后端,适配 -p/-b 任意端口
|
// 相对路径由 Node 代理到后端;若从 file:// 打开则请求会失败,改用同源或默认地址
|
||||||
const API_BASE = '';
|
const API_BASE = (typeof location !== 'undefined' && location.origin && (location.protocol === 'http:' || location.protocol === 'https:'))
|
||||||
|
? '' : (typeof location !== 'undefined' ? 'http://127.0.0.1:3000' : '');
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
pollingScan: null,
|
pollingScan: null,
|
||||||
pollingOnline: null,
|
pollingOnline: null,
|
||||||
sliderOpened: false,
|
sliderOpened: false,
|
||||||
|
sliderParams: null,
|
||||||
|
sliderScriptLoaded: false,
|
||||||
|
sliderListenersBound: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function parseSliderUrlParams(sliderUrl) {
|
||||||
|
if (!sliderUrl || typeof sliderUrl !== 'string') return null;
|
||||||
|
const q = sliderUrl.indexOf('?');
|
||||||
|
if (q === -1) return null;
|
||||||
|
const params = {};
|
||||||
|
sliderUrl.slice(q + 1).split('&').forEach(function(pair) {
|
||||||
|
const i = pair.indexOf('=');
|
||||||
|
if (i > 0) params[decodeURIComponent(pair.slice(0, i).replace(/\+/g, ' '))] = decodeURIComponent((pair.slice(i + 1) || '').replace(/\+/g, ' '));
|
||||||
|
});
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSliderAreaAndFill(params) {
|
||||||
|
var qrArea = $('qr-area');
|
||||||
|
var sliderArea = $('slider-area');
|
||||||
|
if (!qrArea || !sliderArea) return;
|
||||||
|
qrArea.style.display = 'none';
|
||||||
|
sliderArea.style.display = 'block';
|
||||||
|
var keyInput = $('keyInput');
|
||||||
|
var data62Input = $('data62Input');
|
||||||
|
var ticketInput = $('originalTicketInput');
|
||||||
|
var btnVerify = $('slider-btn-verify');
|
||||||
|
if (params) {
|
||||||
|
if (keyInput) keyInput.value = params.key || params.Key || '';
|
||||||
|
if (data62Input) data62Input.value = params.data62 || '';
|
||||||
|
if (ticketInput) ticketInput.value = params.ticket || params.original_ticket || '';
|
||||||
|
}
|
||||||
|
function toggleVerifyBtn() {
|
||||||
|
if (!btnVerify) return;
|
||||||
|
var hasAll = keyInput && data62Input && ticketInput &&
|
||||||
|
(keyInput.value || '').trim() && (data62Input.value || '').trim() && (ticketInput.value || '').trim();
|
||||||
|
btnVerify.disabled = !hasAll;
|
||||||
|
}
|
||||||
|
if (!state.sliderListenersBound) {
|
||||||
|
state.sliderListenersBound = true;
|
||||||
|
if (keyInput) keyInput.addEventListener('input', toggleVerifyBtn);
|
||||||
|
if (data62Input) data62Input.addEventListener('input', toggleVerifyBtn);
|
||||||
|
if (ticketInput) ticketInput.addEventListener('input', toggleVerifyBtn);
|
||||||
|
}
|
||||||
|
toggleVerifyBtn();
|
||||||
|
if (!state.sliderScriptLoaded) {
|
||||||
|
state.sliderScriptLoaded = true;
|
||||||
|
var script = document.createElement('script');
|
||||||
|
script.type = 'module';
|
||||||
|
script.crossOrigin = 'anonymous';
|
||||||
|
script.src = (API_BASE || '') + '/auth/slider-assets/N_jYM_2V.js';
|
||||||
|
document.body.appendChild(script);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showQrArea() {
|
||||||
|
var qrArea = $('qr-area');
|
||||||
|
var sliderArea = $('slider-area');
|
||||||
|
var reopenWrap = $('slider-reopen-wrap');
|
||||||
|
if (qrArea) qrArea.style.display = 'block';
|
||||||
|
if (sliderArea) sliderArea.style.display = 'none';
|
||||||
|
if (reopenWrap) reopenWrap.style.display = state.sliderParams ? 'block' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
function log(message, level = 'info') {
|
function log(message, level = 'info') {
|
||||||
const logBody = $('log-body');
|
const logBody = $('log-body');
|
||||||
const line = document.createElement('div');
|
const line = document.createElement('div');
|
||||||
@@ -883,14 +963,22 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function callApi(path, options = {}) {
|
async function callApi(path, options = {}) {
|
||||||
const url = API_BASE + path;
|
const url = (API_BASE || '') + path;
|
||||||
const res = await fetch(url, {
|
let res;
|
||||||
...options,
|
try {
|
||||||
headers: {
|
res = await fetch(url, {
|
||||||
'Content-Type': 'application/json',
|
...options,
|
||||||
...(options.headers || {}),
|
headers: {
|
||||||
},
|
'Content-Type': 'application/json',
|
||||||
});
|
...(options.headers || {}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
const msg = (e && e.message) ? String(e.message) : String(e);
|
||||||
|
const isRefused = /failed to fetch|networkerror|connection refused|load failed/i.test(msg);
|
||||||
|
log(isRefused ? '无法连接服务(ERR_CONNECTION_REFUSED):请通过前端地址访问(如 http://localhost:3000)并确保已启动 run-dev.sh 或 Docker。' : '请求失败: ' + msg, 'error');
|
||||||
|
throw new Error(isRefused ? '无法连接服务,请确认已通过 http://localhost:3000 打开页面且前后端已启动。' : msg);
|
||||||
|
}
|
||||||
let body = null;
|
let body = null;
|
||||||
try {
|
try {
|
||||||
body = await res.json();
|
body = await res.json();
|
||||||
@@ -922,10 +1010,11 @@
|
|||||||
log('获取二维码成功');
|
log('获取二维码成功');
|
||||||
renderQrFromResponse(data);
|
renderQrFromResponse(data);
|
||||||
updateLoginState('等待扫码 / 确认中', 'pending', '请在 60 秒内使用微信扫码。');
|
updateLoginState('等待扫码 / 确认中', 'pending', '请在 60 秒内使用微信扫码。');
|
||||||
// 启动自动轮询,每 5 秒检测一次扫码状态
|
// 扫完码后自动检测:先 2 秒做一次检测,再每 5 秒轮询,避免误判为「二维码失效」
|
||||||
if (state.pollingScan) {
|
if (state.pollingScan) {
|
||||||
clearInterval(state.pollingScan);
|
clearInterval(state.pollingScan);
|
||||||
}
|
}
|
||||||
|
setTimeout(() => onCheckScanStatus(true), 2000);
|
||||||
state.pollingScan = setInterval(() => {
|
state.pollingScan = setInterval(() => {
|
||||||
onCheckScanStatus(true);
|
onCheckScanStatus(true);
|
||||||
}, 5000);
|
}, 5000);
|
||||||
@@ -954,41 +1043,50 @@
|
|||||||
const d = obj.Data && typeof obj.Data === 'object' ? obj.Data : obj;
|
const d = obj.Data && typeof obj.Data === 'object' ? obj.Data : obj;
|
||||||
const stateVal = d.state ?? d.State;
|
const stateVal = d.state ?? d.State;
|
||||||
|
|
||||||
// 后端返回滑块页 path,在新窗口打开(本地页,与 7765 同 DOM 结构 + 加载 7765 的 module 脚本),不用 iframe
|
// 后端返回滑块 path,在当前页右侧替换为滑块区域并自动填充参数
|
||||||
const sliderUrl = data.slider_url;
|
const sliderUrl = data.slider_url;
|
||||||
if (sliderUrl && typeof sliderUrl === 'string') {
|
if (sliderUrl && typeof sliderUrl === 'string') {
|
||||||
state.sliderOpened = true;
|
state.sliderOpened = true;
|
||||||
log('需完成滑块验证,已在新窗口打开验证页。', 'warn');
|
log('需完成滑块验证,已切换到滑块验证区域。', 'warn');
|
||||||
const sliderCard = $('slider-card');
|
const params = parseSliderUrlParams(sliderUrl);
|
||||||
const openLink = $('slider-open-link');
|
if (params) state.sliderParams = params;
|
||||||
const fullUrl = sliderUrl.startsWith('/') ? (API_BASE + sliderUrl) : sliderUrl;
|
showSliderAreaAndFill(params);
|
||||||
if (sliderCard) sliderCard.style.display = 'flex';
|
|
||||||
if (openLink) {
|
|
||||||
openLink.href = fullUrl;
|
|
||||||
openLink.target = '_blank';
|
|
||||||
}
|
|
||||||
try { window.open(fullUrl, 'slider-verify', 'width=520,height=520,scrollbars=yes'); } catch (e) { log('弹窗被拦截,请点击上方链接打开验证页', 'warn'); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// state == 2 → 登录成功,跳转后端管理页
|
// state == 2 → 登录成功,跳转后端管理页
|
||||||
if (stateVal === 2 || lower.includes('\"state\":2') || /\"state\"\\s*:\\s*2/.test(lower)) {
|
if (stateVal === 2 || lower.includes('\"state\":2') || /\"state\"\\s*:\\s*2/.test(lower)) {
|
||||||
updateLoginState('登录成功', 'ok', text);
|
updateLoginState('登录成功', 'ok', '');
|
||||||
if (state.pollingScan) {
|
if (state.pollingScan) {
|
||||||
clearInterval(state.pollingScan);
|
clearInterval(state.pollingScan);
|
||||||
state.pollingScan = null;
|
state.pollingScan = null;
|
||||||
}
|
}
|
||||||
showQrExpired('登录成功,正在跳转…');
|
showQrExpired('登录成功,正在跳转…');
|
||||||
window.location.href = 'manage.html';
|
window.location.href = 'manage.html';
|
||||||
// state == 1 → 二维码失效 / 不可用(按你的规则)
|
// state == 1 → 先判定是否明确「过期」,否则一律视为可能需验证,继续轮询并优先根据 slider_url 切到滑块
|
||||||
} else if (stateVal === 1 || lower.includes('\"state\":1') || /\"state\"\\s*:\\s*1/.test(lower)) {
|
} else if (stateVal === 1 || lower.includes('\"state\":1') || /\"state\"\\s*:\\s*1/.test(lower)) {
|
||||||
updateLoginState('二维码已过期,请重新获取', 'offline', text);
|
var needVerify = /请提交验证码|提交验证码后登录|验证码后登录|安全验证|完成验证/.test(text) ||
|
||||||
if (state.pollingScan) {
|
(d && (d.msg && /请提交验证码|验证码后登录|安全验证|完成验证/.test(d.msg)) || (obj.Text && /请提交验证码|验证码后登录|安全验证|完成验证/.test(obj.Text)));
|
||||||
clearInterval(state.pollingScan);
|
var explicitExpired = /二维码已过期|已过期|已失效|失效|请重新获取|重新获取二维码/.test(text) ||
|
||||||
state.pollingScan = null;
|
(d && (d.msg && /已过期|已失效|失效|请重新获取/.test(d.msg)) || (obj.Text && /已过期|已失效|失效|请重新获取/.test(obj.Text)));
|
||||||
|
if (explicitExpired && !sliderUrl) {
|
||||||
|
updateLoginState('二维码已过期,请重新获取', 'offline', '');
|
||||||
|
if (state.pollingScan) {
|
||||||
|
clearInterval(state.pollingScan);
|
||||||
|
state.pollingScan = null;
|
||||||
|
}
|
||||||
|
showQrExpired('二维码已过期,请重新获取');
|
||||||
|
} else {
|
||||||
|
updateLoginState(needVerify || sliderUrl ? '请完成滑块验证' : '正在确认登录状态…', 'pending',
|
||||||
|
needVerify || sliderUrl ? '扫码完成,请完成验证后登录' : '');
|
||||||
|
// 判定到需验证时直接切换验证模块,不等用户点「检测扫码状态」;有 slider_url 已在上方填参,无则先切过去,后续轮询会带参
|
||||||
|
if (needVerify || sliderUrl) {
|
||||||
|
state.sliderOpened = true;
|
||||||
|
if (!sliderUrl) log('需完成滑块验证,已切换到滑块验证区域,等待参数…', 'warn');
|
||||||
|
showSliderAreaAndFill(state.sliderParams || {});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
showQrExpired('二维码已过期,请重新获取');
|
|
||||||
} else {
|
} else {
|
||||||
updateLoginState('扫码状态更新', 'pending', text);
|
updateLoginState('扫码状态更新', 'pending', '');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!silent) log('检测扫码状态失败: ' + e.message, 'error');
|
if (!silent) log('检测扫码状态失败: ' + e.message, 'error');
|
||||||
@@ -1008,9 +1106,9 @@
|
|||||||
log('在线状态: ' + text);
|
log('在线状态: ' + text);
|
||||||
const lower = text.toLowerCase();
|
const lower = text.toLowerCase();
|
||||||
if (lower.includes('online') || lower.includes('true') || lower.includes('登录')) {
|
if (lower.includes('online') || lower.includes('true') || lower.includes('登录')) {
|
||||||
updateLoginState('当前账号在线', 'ok', text);
|
updateLoginState('当前账号在线', 'ok', '');
|
||||||
} else {
|
} else {
|
||||||
updateLoginState('当前账号离线或未登录', 'offline', text);
|
updateLoginState('当前账号离线或未登录', 'offline', '');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('获取在线状态失败: ' + e.message, 'error');
|
log('获取在线状态失败: ' + e.message, 'error');
|
||||||
@@ -1047,8 +1145,14 @@
|
|||||||
function bindEvents() {
|
function bindEvents() {
|
||||||
$('btn-qrcode').addEventListener('click', (e) => {
|
$('btn-qrcode').addEventListener('click', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if ($('slider-area') && $('slider-area').style.display !== 'none') {
|
||||||
|
showQrArea();
|
||||||
|
}
|
||||||
onGetQrCode();
|
onGetQrCode();
|
||||||
});
|
});
|
||||||
|
$('btn-show-slider') && $('btn-show-slider').addEventListener('click', function() {
|
||||||
|
if (state.sliderParams) showSliderAreaAndFill(state.sliderParams);
|
||||||
|
});
|
||||||
$('btn-check-scan').addEventListener('click', (e) => {
|
$('btn-check-scan').addEventListener('click', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onCheckScanStatus(false);
|
onCheckScanStatus(false);
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ WECHAT_UPSTREAM_BASE_URL="${UPSTREAM_URL}" uvicorn backend.main:app --host 0.0.0
|
|||||||
BACKEND_PID=$!
|
BACKEND_PID=$!
|
||||||
|
|
||||||
echo "Starting Node frontend dev server on :${FRONTEND_PORT}..."
|
echo "Starting Node frontend dev server on :${FRONTEND_PORT}..."
|
||||||
PORT="${FRONTEND_PORT}" npm run dev &
|
PORT="${FRONTEND_PORT}" BACKEND_PORT="${BACKEND_PORT}" npm run dev &
|
||||||
FRONTEND_PID=$!
|
FRONTEND_PID=$!
|
||||||
|
|
||||||
trap 'echo "Stopping dev servers..."; kill ${BACKEND_PID} ${FRONTEND_PID} 2>/dev/null || true' INT TERM
|
trap 'echo "Stopping dev servers..."; kill ${BACKEND_PID} ${FRONTEND_PID} 2>/dev/null || true' INT TERM
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ import path from 'path';
|
|||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
const backendHost = process.env.BACKEND_HOST || '127.0.0.1';
|
||||||
const backendPort = Number(process.env.BACKEND_PORT) || 8000;
|
const backendPort = Number(process.env.BACKEND_PORT) || 8000;
|
||||||
const backendBase = `http://127.0.0.1:${backendPort}`;
|
const backendBase = `http://${backendHost}:${backendPort}`;
|
||||||
|
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
@@ -41,6 +42,11 @@ app.get('/openapi.json', (req, res) => proxyToBackend(req, res));
|
|||||||
const publicDir = path.join(__dirname, '../public');
|
const publicDir = path.join(__dirname, '../public');
|
||||||
app.use(express.static(publicDir));
|
app.use(express.static(publicDir));
|
||||||
|
|
||||||
|
// 避免 Chrome DevTools 请求该路径时产生 404
|
||||||
|
app.get('/.well-known/appspecific/com.chrome.devtools.json', (_req, res) => {
|
||||||
|
res.status(204).end();
|
||||||
|
});
|
||||||
|
|
||||||
app.get('/health', (_req, res) => {
|
app.get('/health', (_req, res) => {
|
||||||
res.json({ status: 'ok', server: 'node-static' });
|
res.json({ status: 'ok', server: 'node-static' });
|
||||||
});
|
});
|
||||||
|
|||||||
2
start.sh
2
start.sh
@@ -10,7 +10,7 @@ uvicorn backend.main:app --host 0.0.0.0 --port "${BACKEND_PORT}" &
|
|||||||
BACKEND_PID=$!
|
BACKEND_PID=$!
|
||||||
|
|
||||||
echo "Starting Node static frontend on :${PORT}..."
|
echo "Starting Node static frontend on :${PORT}..."
|
||||||
node dist/server.js &
|
PORT="${PORT}" BACKEND_PORT="${BACKEND_PORT}" node dist/server.js &
|
||||||
FRONTEND_PID=$!
|
FRONTEND_PID=$!
|
||||||
|
|
||||||
trap 'echo "Stopping services..."; kill ${BACKEND_PID} ${FRONTEND_PID} 2>/dev/null || true' INT TERM
|
trap 'echo "Stopping services..."; kill ${BACKEND_PID} ${FRONTEND_PID} 2>/dev/null || true' INT TERM
|
||||||
|
|||||||
Reference in New Issue
Block a user