-
-
+
+
+
YouTube 配置编辑 (youtube.ini)
+
+
youtube.ini
+
+
+
+
+
+
就绪
+
+
+
+
+
+
URL 配置编辑 (URL_config.ini)
+
+
URL_config.ini
+
+
+
+
+
+
就绪
-
就绪
@@ -75,12 +93,21 @@ const processSelect = document.getElementById('processSelect');
let currentProcess = 'tiktok';
// 各专属区域
-const configSection = document.getElementById('configSection');
+const youtubeOnlySection = document.getElementById('youtubeOnlySection');
+const urlConfigSection = document.getElementById('urlConfigSection');
const obsSection = document.getElementById('obsSection');
+
+// youtube.ini 编辑(youtube 专属)
const youtubeConfig = document.getElementById('youtubeConfig');
-const loadConfigBtn = document.getElementById('loadConfigBtn');
-const saveConfigBtn = document.getElementById('saveConfigBtn');
-const configStatus = document.getElementById('configStatus');
+const loadYoutubeBtn = document.getElementById('loadYoutubeBtn');
+const saveYoutubeBtn = document.getElementById('saveYoutubeBtn');
+const youtubeStatus = document.getElementById('youtubeStatus');
+
+// URL_config.ini 编辑(youtube 和 tiktok 共享)
+const urlConfig = document.getElementById('urlConfig');
+const loadUrlBtn = document.getElementById('loadUrlBtn');
+const saveUrlBtn = document.getElementById('saveUrlBtn');
+const urlStatus = document.getElementById('urlStatus');
processSelect.addEventListener('change', () => {
currentProcess = processSelect.value;
@@ -88,16 +115,81 @@ processSelect.addEventListener('change', () => {
updateSectionsVisibility();
});
-// 根据当前进程显示/隐藏对应区域
function updateSectionsVisibility() {
- configSection.style.display = currentProcess === 'youtube' ? 'block' : 'none';
+ youtubeOnlySection.style.display = currentProcess === 'youtube' ? 'block' : 'none';
+ urlConfigSection.style.display = (currentProcess === 'youtube' || currentProcess === 'tiktok') ? 'block' : 'none';
obsSection.style.display = currentProcess === 'obs' ? 'block' : 'none';
if (currentProcess === 'youtube') {
- loadConfig();
+ loadYoutubeConfig();
+ }
+ if (currentProcess === 'youtube' || currentProcess === 'tiktok') {
+ loadUrlConfig();
}
}
+// textarea 高度自适应
+function autoResize(textarea) {
+ textarea.style.height = 'auto';
+ textarea.style.height = (textarea.scrollHeight) + 'px';
+}
+[youtubeConfig, urlConfig].forEach(ta => {
+ ta.addEventListener('input', () => autoResize(ta));
+ autoResize(ta);
+});
+
+// 通用加载/保存
+async function loadConfig(endpoint, textarea, statusEl) {
+ statusEl.textContent = '加载中...';
+ try {
+ const res = await fetch(`${endpoint}?process=${currentProcess}`);
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
+ const data = await res.json();
+ textarea.value = data.content || '';
+ autoResize(textarea);
+ statusEl.textContent = '加载成功';
+ } catch (err) {
+ statusEl.textContent = `加载失败: ${err.message}`;
+ }
+}
+
+async function saveConfig(endpoint, textarea, statusEl, saveBtn) {
+ statusEl.textContent = '保存中...';
+ const content = textarea.value;
+ try {
+ const res = await fetch(`${endpoint}?process=${currentProcess}`, {
+ method: 'POST',
+ headers: {'Content-Type': 'application/json'},
+ body: JSON.stringify({content})
+ });
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
+ const data = await res.json();
+ statusEl.textContent = data.message || '保存成功';
+ } catch (err) {
+ statusEl.textContent = `保存失败: ${err.message}`;
+ } finally {
+ if (saveBtn) saveBtn.classList.remove('loading');
+ }
+}
+
+// youtube.ini(仅 youtube)
+function loadYoutubeConfig() { loadConfig('/get_config', youtubeConfig, youtubeStatus); }
+function saveYoutubeConfig() {
+ saveYoutubeBtn.classList.add('loading');
+ saveConfig('/save_config', youtubeConfig, youtubeStatus, saveYoutubeBtn);
+}
+loadYoutubeBtn.onclick = loadYoutubeConfig;
+saveYoutubeBtn.onclick = saveYoutubeConfig;
+
+// URL_config.ini(youtube 和 tiktok 共享)
+function loadUrlConfig() { loadConfig('/get_url_config', urlConfig, urlStatus); }
+function saveUrlConfig() {
+ saveUrlBtn.classList.add('loading');
+ saveConfig('/save_url_config', urlConfig, urlStatus, saveUrlBtn);
+}
+loadUrlBtn.onclick = loadUrlConfig;
+saveUrlBtn.onclick = saveUrlConfig;
+
function scrollLog(){logEl.scrollTop = logEl.scrollHeight}
function updateStatus(process_status){
@@ -156,44 +248,6 @@ async function pm2Action(action){
async function fetchStatus(){ pm2Action('status'); }
-// 配置加载
-async function loadConfig(){
- configStatus.textContent = '加载中...';
- try{
- const res = await fetch(`/get_config?process=${currentProcess}`);
- if(!res.ok) throw new Error(`HTTP ${res.status}`);
- const data = await res.json();
- youtubeConfig.value = data.content || '';
- configStatus.textContent = '加载成功';
- }catch(err){
- configStatus.textContent = `加载失败: ${err.message}`;
- }
-}
-
-// 配置保存
-async function saveConfig(){
- configStatus.textContent = '保存中...';
- const content = youtubeConfig.value;
- try{
- const res = await fetch(`/save_config?process=${currentProcess}`, {
- method: 'POST',
- headers: {'Content-Type': 'application/json'},
- body: JSON.stringify({content})
- });
- if(!res.ok) throw new Error(`HTTP ${res.status}`);
- const data = await res.json();
- configStatus.textContent = data.message || '保存成功';
- }catch(err){
- configStatus.textContent = `保存失败: ${err.message}`;
- }
-}
-
-loadConfigBtn.onclick = loadConfig;
-saveConfigBtn.onclick = () => {
- saveConfigBtn.classList.add('loading');
- saveConfig().finally(() => saveConfigBtn.classList.remove('loading'));
-};
-
// 按钮事件委托
document.getElementById('pm2Buttons').addEventListener('click', e=>{
const btn = e.target.closest('button');
@@ -202,10 +256,8 @@ document.getElementById('pm2Buttons').addEventListener('click', e=>{
if(action && action !== 'status') pm2Action(action);
});
-// 自动刷新状态与日志
+// 自动刷新 + 初始
setInterval(fetchStatus, 1000);
-
-// 初始加载
fetchStatus();
updateSectionsVisibility();
diff --git a/web.py b/web.py
index 4a9b478..000514a 100644
--- a/web.py
+++ b/web.py
@@ -7,17 +7,17 @@ from fastapi.middleware.cors import CORSMiddleware
app = FastAPI(title="多进程录制控制台")
-# ---------------- 配置(修正为根目录结构) ----------------
+# ---------------- 配置(根目录结构) ----------------
BASE_DIR = Path(__file__).parent
-CONFIG_DIR = BASE_DIR / "config" # 直接在根目录下
-DEFAULT_LOG_DIR = BASE_DIR / ".pm2" / "logs" # 直接在根目录下
+CONFIG_DIR = BASE_DIR / "config" # config 文件夹在项目根目录
+DEFAULT_LOG_DIR = BASE_DIR / ".pm2" / "logs" # .pm2/logs 在项目根目录
MAX_LOG_LINES = 300
# 支持的进程列表
VALID_PROCESSES = {"tiktok", "youtube", "obs"}
-# CORS(支持 POST)
+# CORS(支持 GET 和 POST)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
@@ -86,7 +86,7 @@ async def get_process_info(process: str) -> dict:
out_path = value
elif key == "err_log_path":
err_path = value
- # 备用默认路径(修正为根目录)
+ # 备用默认路径
if not out_path:
out_path = str(DEFAULT_LOG_DIR / f"{process}-out.log")
if not err_path:
@@ -97,11 +97,11 @@ async def get_process_info(process: str) -> dict:
"err_path": err_path,
}
-# ---------------- 配置路由 ----------------
+# ---------------- 配置路由(youtube.ini,仅 youtube) ----------------
@app.get("/get_config")
async def get_config(process: str = Query(..., description="进程名")):
if process != "youtube":
- return JSONResponse({"error": "仅 youtube 支持配置编辑"}, status_code=400)
+ return JSONResponse({"error": "仅 youtube 支持此配置编辑"}, status_code=400)
config_file = CONFIG_DIR / "youtube.ini"
if not config_file.exists():
@@ -115,7 +115,7 @@ async def get_config(process: str = Query(..., description="进程名")):
@app.post("/save_config")
async def save_config(request: Request, process: str = Query(..., description="进程名")):
if process != "youtube":
- return JSONResponse({"error": "仅 youtube 支持配置编辑"}, status_code=400)
+ return JSONResponse({"error": "仅 youtube 支持此配置编辑"}, status_code=400)
try:
data = await request.json()
@@ -131,7 +131,41 @@ async def save_config(request: Request, process: str = Query(..., description="
except Exception as e:
return JSONResponse({"error": f"保存失败: {str(e)}"}, status_code=500)
-# ---------------- 原有路由 ----------------
+# ---------------- 配置路由(URL_config.ini,youtube 和 tiktok 共享) ----------------
+@app.get("/get_url_config")
+async def get_url_config(process: str = Query(..., description="进程名")):
+ if process not in ["youtube", "tiktok"]:
+ return JSONResponse({"error": "仅 youtube 和 tiktok 支持此配置编辑"}, status_code=400)
+
+ config_file = CONFIG_DIR / "URL_config.ini"
+ if not config_file.exists():
+ return {"content": "# URL_config.ini 文件不存在,将创建空文件\n"}
+ try:
+ content = config_file.read_text(encoding="utf-8")
+ return {"content": content}
+ except Exception as e:
+ return JSONResponse({"error": f"读取失败: {str(e)}"}, status_code=500)
+
+@app.post("/save_url_config")
+async def save_url_config(request: Request, process: str = Query(..., description="进程名")):
+ if process not in ["youtube", "tiktok"]:
+ return JSONResponse({"error": "仅 youtube 和 tiktok 支持此配置编辑"}, status_code=400)
+
+ try:
+ data = await request.json()
+ content = data.get("content", "")
+ except:
+ return JSONResponse({"error": "无效的 JSON 数据"}, status_code=400)
+
+ config_file = CONFIG_DIR / "URL_config.ini"
+ try:
+ CONFIG_DIR.mkdir(parents=True, exist_ok=True)
+ config_file.write_text(content, encoding="utf-8")
+ return {"message": "配置保存成功"}
+ except Exception as e:
+ return JSONResponse({"error": f"保存失败: {str(e)}"}, status_code=500)
+
+# ---------------- PM2 控制路由 ----------------
@app.get("/start")
async def start(process: str = Query(..., description="进程名")):
if process not in VALID_PROCESSES:
@@ -177,6 +211,7 @@ async def status(process: str = Query(..., description="进程名")):
"recent_error": recent_error,
})
+# ---------------- 首页 ----------------
@app.get("/")
async def index():
return FileResponse(BASE_DIR / "index.html")