From 24051a2794b9dbdd59266688d625309737d76a7e Mon Sep 17 00:00:00 2001 From: 3dtours Date: Sat, 8 Aug 2026 10:51:58 +0700 Subject: [PATCH] =?UTF-8?q?FIX:=20l=E1=BB=97i=20m=C3=A0n=20h=C3=ACnh=20?= =?UTF-8?q?=C4=91en=20tr=C3=AAn=20docker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/static/js/app.jsx | 43 ++++++++++++++++++++++---------- app/static/js/app.precompiled.js | 8 ++++-- app/static/js/services/api.js | 2 ++ app/templates/index.html | 4 +-- docker-compose.prod.yml | 14 +++-------- main.py | 28 ++++++++++++++++++--- wiki.md | 32 ++++++++++++++++++++++++ 7 files changed, 101 insertions(+), 30 deletions(-) diff --git a/app/static/js/app.jsx b/app/static/js/app.jsx index 47de1bc..26477a8 100644 --- a/app/static/js/app.jsx +++ b/app/static/js/app.jsx @@ -15543,6 +15543,16 @@ const App = () => { // ── Auth / User State ── const [currentUser, setCurrentUser] = useState(null); const [authModalOpen, setAuthModalOpen] = useState(false); + // 401 bất kỳ → mở màn login (bug 09:45 — màn đen khi token hỏng/hết hạn) + React.useEffect(() => { + window.__onAuthFail = () => { + setCurrentUser(null); + setIsMandatoryLogin(true); + setAuthMode('login'); + setAuthModalOpen(true); + }; + return () => { delete window.__onAuthFail; }; + }, []); const [authMode, setAuthMode] = useState('login'); // 'login' | 'register' | 'force_change' const [isMandatoryLogin, setIsMandatoryLogin] = useState(false); const [profileModalOpen, setProfileModalOpen] = useState(false); @@ -15847,20 +15857,27 @@ const App = () => { restoreLastSessionProject(); } } catch (err) { - const cached = localStorage.getItem('sonic_user'); - if (cached) { - try { setCurrentUser(JSON.parse(cached)); } catch (_) { } - setIsMandatoryLogin(false); - setAuthModalOpen(false); - restoreLastSessionProject(); - } else { - localStorage.removeItem('sonic_token'); - localStorage.removeItem('sonic_user'); - setCurrentUser(null); - setIsMandatoryLogin(true); - setAuthMode('login'); - setAuthModalOpen(true); + // 401 (token hỏng/đổi SECRET_KEY — bug 09:45: dùng cached user → vào app + // nhưng mọi API 401 → màn đen không login) → BỎ qua cached → mở LOGIN. + // detail server: "Thiếu Token xác thực..." / "Token đã hết hạn..." (auth.py) + const errMsg = String((err && err.message) || err || ''); + const isAuthErr = errMsg.includes('Token') || errMsg.includes('401') || errMsg.includes('Unauthorized') || errMsg.includes('unauthorized') || errMsg.includes('Không xác thực'); + if (!isAuthErr) { + const cached = localStorage.getItem('sonic_user'); + if (cached) { + try { setCurrentUser(JSON.parse(cached)); } catch (_) { } + setIsMandatoryLogin(false); + setAuthModalOpen(false); + restoreLastSessionProject(); + return; + } } + localStorage.removeItem('sonic_token'); + localStorage.removeItem('sonic_user'); + setCurrentUser(null); + setIsMandatoryLogin(true); + setAuthMode('login'); + setAuthModalOpen(true); } }; checkAuthStatus(); diff --git a/app/static/js/app.precompiled.js b/app/static/js/app.precompiled.js index eb2d9c3..ca242f0 100644 --- a/app/static/js/app.precompiled.js +++ b/app/static/js/app.precompiled.js @@ -810,7 +810,8 @@ const prevIsPlayingRef=useRef(false);useEffect(()=>{const wasPlaying=prevIsPlayi try{const _tf=window.__trigFrame=(window.__trigFrame||0)+1;if(_tf%20===1){console.log('[VUTrig]',trackId,'vel=',velocity,'peak=',peak.toFixed(2),'play=',isPlaying,'tab=',activeTabRef.current);}}catch(e){}};window.triggerMidiVuActivity=triggerMidiVuActivity;// ── Temp Edit Tab (LOOP_EDITOR_2.md §1.2 Sub Tab) ── const[tempTabActive,setTempTabActive]=useState(false);const[tempTabBuffer,setTempTabBuffer]=useState(null);const[tempTabTrackId,setTempTabTrackId]=useState(null);const[tempTabOrigStart,setTempTabOrigStart]=useState(0);const[tempTabOrigEnd,setTempTabOrigEnd]=useState(0);const tempTabCanvasRef=useRef(null);// Effect parameters for temp tab const[tempTabEffects,setTempTabEffects]=useState({reverse:false,gainDb:0,fadeInMs:0,fadeOutMs:0});// ── Auth / User State ── -const[currentUser,setCurrentUser]=useState(null);const[authModalOpen,setAuthModalOpen]=useState(false);const[authMode,setAuthMode]=useState('login');// 'login' | 'register' | 'force_change' +const[currentUser,setCurrentUser]=useState(null);const[authModalOpen,setAuthModalOpen]=useState(false);// 401 bất kỳ → mở màn login (bug 09:45 — màn đen khi token hỏng/hết hạn) +React.useEffect(()=>{window.__onAuthFail=()=>{setCurrentUser(null);setIsMandatoryLogin(true);setAuthMode('login');setAuthModalOpen(true);};return()=>{delete window.__onAuthFail;};},[]);const[authMode,setAuthMode]=useState('login');// 'login' | 'register' | 'force_change' const[isMandatoryLogin,setIsMandatoryLogin]=useState(false);const[profileModalOpen,setProfileModalOpen]=useState(false);const[systemManagerModalOpen,setSystemManagerModalOpen]=useState(false);const[aiConfigModalOpen,setAiConfigModalOpen]=useState(false);const[aiPresetModalOpen,setAiPresetModalOpen]=useState(false);const[aiPresetVersion,setAiPresetVersion]=useState(0);const[showMasteringModal,setShowMasteringModal]=useState(false);// Unified FX Rack target context (unified_fx_rack_panel.md): { trackId, trackName } | null const[fxRackTarget,setFxRackTarget]=useState(null);window.__openFxRack=(trackId,trackName)=>setFxRackTarget({trackId,trackName});const[masteringSettings,setMasteringSettings]=useState({masterConnected:false,activeModule:'eq',eqActive:true,imagerActive:true,maximizerActive:true,eqLowGain:1.5,eqMid1Gain:-1.0,eqMid2Gain:2.0,eqHighGain:1.8,w1:0,w2:115,w3:135,w4:150,imagerScale:'v2',maxGain:5.4,maxUpward:2.0,maxSoftClip:15,maxTransient:25,ceiling:-0.1,isBypassed:false,chain:DEFAULT_MASTER_CHAIN.map(m=>({...m})),compActive:false,compThreshold:-16,compRatio:3,compMakeup:0,limActive:false,limThreshold:-1.0,excActive:false,excDrive:40,rebalActive:false,rebalMid:0,rebalSide:0});useEffect(()=>{window.currentMasteringSettings=masteringSettings;if(audioCtx&&masterBus){toggleMasteringOnMaster(masteringSettings.masterConnected,masteringSettings.isBypassed);applyMasteringSettings(masteringSettings);// Re-sync live track routes: mastering ON → mọi track qua chain (♪ bị // override bởi effMidiBypass/effAudioBypass) — nút PWR bật/tắt phải áp @@ -835,7 +836,10 @@ lastId=latest.id;lastName=latest.name||lastName;const proj=await window.SonicAPI if(result.zoom>1&&setZoom)setZoom(result.zoom);if(result.masteringSettings)setMasteringSettings(result.masteringSettings);}else{restoredTracks=(parsed.tracks||[]).map(function(t){var rest=Object.assign({},t);delete rest.height;return Object.assign({},rest,{buffer:null,channelInfo:t.channelInfo||null,clips:t.clips||[],serverFileId:t.serverFileId||null});});}setTracks(restoredTracks);loadAudioBuffersForTracks(restoredTracks).catch(function(err){console.warn('loadAudioBuffersForTracks error:',err);});trackMidiChannelsRef.current={};preloadTrackInstruments(restoredTracks).catch(function(err){console.warn('preloadTrackInstruments error:',err);});setBpm(restoredBpm.toString());setSelectedTrackId(restoredTracks[0]?.id||'1');setProjectName(lastName);setCurrentProjectId(lastId);localStorage.setItem('sonic_project_id',lastId);setSessionTabs(restoredSessionTabs);setSubTabs(restoredSubTabs);// DIAG section-tab block: log tracks + sections của section tab sau restore try{(restoredSessionTabs||[]).forEach(function(st){var secCount=0,midiCount=0,clipCount=0;(st.tracks||[]).forEach(function(tr){secCount+=(tr.sections||[]).length;midiCount+=(tr.midiItems||[]).length;clipCount+=(tr.clips||[]).length;});console.log('[Restore] sessionTab',st.id,'sectionId',st.sectionId,'tracks',(st.tracks||[]).length,'sections',secCount,'midi',midiCount,'clips',clipCount);});}catch(e){}var restoredItemCount=0;restoredTracks.forEach(function(rt){if(rt.clips)restoredItemCount+=rt.clips.length;if(rt.midiItems)restoredItemCount+=rt.midiItems.length;if(rt.sections)restoredItemCount+=rt.sections.length;});showToast('Đã khôi phục dự án "'+lastName+'" ('+restoredItemCount+' items).','info');}catch(e){console.warn('restoreLastSessionProject failed:',e);// Stale session id (project deleted / DB reset): clear it so the error // does not repeat on every page load. -localStorage.removeItem('sonic_project_id');localStorage.removeItem('sonic_project_name');}};useEffect(()=>{const checkAuthStatus=async()=>{const savedToken=localStorage.getItem('sonic_token');if(!savedToken){setIsMandatoryLogin(true);setAuthMode('login');setAuthModalOpen(true);return;}try{const profile=await window.SonicAPI.getProfile();setCurrentUser(profile);localStorage.setItem('sonic_user',JSON.stringify(profile));if(profile.must_change_password){setIsMandatoryLogin(true);setAuthMode('force_change');setAuthModalOpen(true);}else{setIsMandatoryLogin(false);setAuthModalOpen(false);restoreLastSessionProject();}}catch(err){const cached=localStorage.getItem('sonic_user');if(cached){try{setCurrentUser(JSON.parse(cached));}catch(_){}setIsMandatoryLogin(false);setAuthModalOpen(false);restoreLastSessionProject();}else{localStorage.removeItem('sonic_token');localStorage.removeItem('sonic_user');setCurrentUser(null);setIsMandatoryLogin(true);setAuthMode('login');setAuthModalOpen(true);}}};checkAuthStatus();},[]);const loadPendingSfsProject=()=>{const proj=window.__pendingSfsProject;if(!proj)return;try{let restoredTracks=[];let restoredBpm=bpm;let restoredSessionTabs=[];let restoredSubTabs=[];if(proj.main_session){const result=deserializeProjectFromSchema(proj);restoredTracks=result.tracks;restoredBpm=result.bpm;restoredSessionTabs=result.sessionTabs;restoredSubTabs=result.subTabs;if(result.masteringSettings){setMasteringSettings(result.masteringSettings);}else{setMasteringSettings({masterConnected:false,activeModule:'eq',eqActive:true,imagerActive:true,maximizerActive:true,eqLowGain:1.5,eqMid1Gain:-1.0,eqMid2Gain:2.0,eqHighGain:1.8,w1:0,w2:115,w3:135,w4:150,imagerScale:'v2',maxGain:5.4,maxUpward:2.0,maxSoftClip:15,maxTransient:25,ceiling:-0.1,isBypassed:false,chain:DEFAULT_MASTER_CHAIN.map(m=>({...m})),compActive:false,compThreshold:-16,compRatio:3,compMakeup:0,limActive:false,limThreshold:-1.0,excActive:false,excDrive:40,rebalActive:false,rebalMid:0,rebalSide:0});}}else{restoredTracks=(proj.tracks||[]).map(t=>{const{height:_h,...rest}=t;return{...rest,buffer:null,channelInfo:t.channelInfo||null,clips:t.clips||[],serverFileId:t.serverFileId||null};});}if(restoredTracks.length>0){setTracks(restoredTracks);setBpm(restoredBpm.toString());if(restoredSessionTabs.length>0){setSessionTabs(restoredSessionTabs);}if(restoredSubTabs.length>0){setSubTabs(restoredSubTabs);}showToast(`Đã tải dự án "${proj.metadata?.title||proj.name||'Dự án mới'}" từ liên kết .sfs thành công!`,"success");}}catch(e){showToast("Lỗi tải dự án từ .sfs","error");}finally{window.__pendingSfsProject=null;}};const handleAuthSuccess=user=>{setCurrentUser(user);if(user.must_change_password){setIsMandatoryLogin(true);setAuthMode('force_change');setAuthModalOpen(true);}else{setIsMandatoryLogin(false);setAuthModalOpen(false);restoreLastSessionProject();const loadPrefs=p=>{if(!p)return;if(p.showAIPanel!==undefined)setShowAIPanel(p.showAIPanel);if(p.showExportPanel!==undefined)setShowExportPanel(p.showExportPanel);if(p.showSelectionPanel!==undefined)setShowSelectionPanel(p.showSelectionPanel);if(p.showPythonToolsPanel!==undefined)setShowPythonToolsPanel(p.showPythonToolsPanel);if(p.showMediaExplorer!==undefined)setShowMediaExplorer(p.showMediaExplorer);if(p.showFxRack!==undefined)setShowFxRack(p.showFxRack);if(p.showMidiEvents!==undefined)setShowMidiEvents(p.showMidiEvents);if(p.panelPositions)setPanelPositions(p.panelPositions);if(p.rightSidebarWidth)setRightSidebarWidth(p.rightSidebarWidth);if(p.mediaExplorerHeight)setMediaExplorerHeight(p.mediaExplorerHeight);if(p.selectedProviderId)setSelectedProviderId(p.selectedProviderId);};(async()=>{try{const data=await window.SonicAPI.getPreferences();if(data&&data.preferences)loadPrefs(data.preferences);else{const cached=localStorage.getItem('sonic_preferences');if(cached)loadPrefs(JSON.parse(cached));}}catch(e){const cached=localStorage.getItem('sonic_preferences');if(cached)loadPrefs(JSON.parse(cached));}try{const data=await window.SonicAPI.getAIConfigs();if(data&&data.providers){setAiProviders(data.providers);const active=data.providers.find(p=>p.is_active)||data.providers[0];if(active)setSelectedProviderId(active.id);}}catch(e){}try{window.SonicAPI.getSoundfontCatalog().then(cat=>{window.__soundfontCatalog=cat;}).catch(()=>{});}catch(e){}// Re-fetch instrument data after auth (useEffect on mount runs before token is set) +localStorage.removeItem('sonic_project_id');localStorage.removeItem('sonic_project_name');}};useEffect(()=>{const checkAuthStatus=async()=>{const savedToken=localStorage.getItem('sonic_token');if(!savedToken){setIsMandatoryLogin(true);setAuthMode('login');setAuthModalOpen(true);return;}try{const profile=await window.SonicAPI.getProfile();setCurrentUser(profile);localStorage.setItem('sonic_user',JSON.stringify(profile));if(profile.must_change_password){setIsMandatoryLogin(true);setAuthMode('force_change');setAuthModalOpen(true);}else{setIsMandatoryLogin(false);setAuthModalOpen(false);restoreLastSessionProject();}}catch(err){// 401 (token hỏng/đổi SECRET_KEY — bug 09:45: dùng cached user → vào app +// nhưng mọi API 401 → màn đen không login) → BỎ qua cached → mở LOGIN. +// detail server: "Thiếu Token xác thực..." / "Token đã hết hạn..." (auth.py) +const errMsg=String(err&&err.message||err||'');const isAuthErr=errMsg.includes('Token')||errMsg.includes('401')||errMsg.includes('Unauthorized')||errMsg.includes('unauthorized')||errMsg.includes('Không xác thực');if(!isAuthErr){const cached=localStorage.getItem('sonic_user');if(cached){try{setCurrentUser(JSON.parse(cached));}catch(_){}setIsMandatoryLogin(false);setAuthModalOpen(false);restoreLastSessionProject();return;}}localStorage.removeItem('sonic_token');localStorage.removeItem('sonic_user');setCurrentUser(null);setIsMandatoryLogin(true);setAuthMode('login');setAuthModalOpen(true);}};checkAuthStatus();},[]);const loadPendingSfsProject=()=>{const proj=window.__pendingSfsProject;if(!proj)return;try{let restoredTracks=[];let restoredBpm=bpm;let restoredSessionTabs=[];let restoredSubTabs=[];if(proj.main_session){const result=deserializeProjectFromSchema(proj);restoredTracks=result.tracks;restoredBpm=result.bpm;restoredSessionTabs=result.sessionTabs;restoredSubTabs=result.subTabs;if(result.masteringSettings){setMasteringSettings(result.masteringSettings);}else{setMasteringSettings({masterConnected:false,activeModule:'eq',eqActive:true,imagerActive:true,maximizerActive:true,eqLowGain:1.5,eqMid1Gain:-1.0,eqMid2Gain:2.0,eqHighGain:1.8,w1:0,w2:115,w3:135,w4:150,imagerScale:'v2',maxGain:5.4,maxUpward:2.0,maxSoftClip:15,maxTransient:25,ceiling:-0.1,isBypassed:false,chain:DEFAULT_MASTER_CHAIN.map(m=>({...m})),compActive:false,compThreshold:-16,compRatio:3,compMakeup:0,limActive:false,limThreshold:-1.0,excActive:false,excDrive:40,rebalActive:false,rebalMid:0,rebalSide:0});}}else{restoredTracks=(proj.tracks||[]).map(t=>{const{height:_h,...rest}=t;return{...rest,buffer:null,channelInfo:t.channelInfo||null,clips:t.clips||[],serverFileId:t.serverFileId||null};});}if(restoredTracks.length>0){setTracks(restoredTracks);setBpm(restoredBpm.toString());if(restoredSessionTabs.length>0){setSessionTabs(restoredSessionTabs);}if(restoredSubTabs.length>0){setSubTabs(restoredSubTabs);}showToast(`Đã tải dự án "${proj.metadata?.title||proj.name||'Dự án mới'}" từ liên kết .sfs thành công!`,"success");}}catch(e){showToast("Lỗi tải dự án từ .sfs","error");}finally{window.__pendingSfsProject=null;}};const handleAuthSuccess=user=>{setCurrentUser(user);if(user.must_change_password){setIsMandatoryLogin(true);setAuthMode('force_change');setAuthModalOpen(true);}else{setIsMandatoryLogin(false);setAuthModalOpen(false);restoreLastSessionProject();const loadPrefs=p=>{if(!p)return;if(p.showAIPanel!==undefined)setShowAIPanel(p.showAIPanel);if(p.showExportPanel!==undefined)setShowExportPanel(p.showExportPanel);if(p.showSelectionPanel!==undefined)setShowSelectionPanel(p.showSelectionPanel);if(p.showPythonToolsPanel!==undefined)setShowPythonToolsPanel(p.showPythonToolsPanel);if(p.showMediaExplorer!==undefined)setShowMediaExplorer(p.showMediaExplorer);if(p.showFxRack!==undefined)setShowFxRack(p.showFxRack);if(p.showMidiEvents!==undefined)setShowMidiEvents(p.showMidiEvents);if(p.panelPositions)setPanelPositions(p.panelPositions);if(p.rightSidebarWidth)setRightSidebarWidth(p.rightSidebarWidth);if(p.mediaExplorerHeight)setMediaExplorerHeight(p.mediaExplorerHeight);if(p.selectedProviderId)setSelectedProviderId(p.selectedProviderId);};(async()=>{try{const data=await window.SonicAPI.getPreferences();if(data&&data.preferences)loadPrefs(data.preferences);else{const cached=localStorage.getItem('sonic_preferences');if(cached)loadPrefs(JSON.parse(cached));}}catch(e){const cached=localStorage.getItem('sonic_preferences');if(cached)loadPrefs(JSON.parse(cached));}try{const data=await window.SonicAPI.getAIConfigs();if(data&&data.providers){setAiProviders(data.providers);const active=data.providers.find(p=>p.is_active)||data.providers[0];if(active)setSelectedProviderId(active.id);}}catch(e){}try{window.SonicAPI.getSoundfontCatalog().then(cat=>{window.__soundfontCatalog=cat;}).catch(()=>{});}catch(e){}// Re-fetch instrument data after auth (useEffect on mount runs before token is set) try{window.SonicAPI.listPlugins().then(async data=>{try{const catResp=(await window.SonicAPI.getSoundfontCatalog?.())??(await fetch('/api/v1/plugins/soundfonts/catalog').then(r=>r.json()));const catalog=catResp.full_catalog||{};data.soundfonts=(data.soundfonts||[]).map(sf=>{const sfId=sf.id.replace('sf_','');const catEntry=catalog[sfId.toLowerCase()]||catalog[sfId];if(catEntry&&catEntry.instruments)return{...sf,presets:catEntry.instruments};return sf;});}catch(e){console.warn('Catalog fetch error:',e);}setInstrumentSelectorData(data);}).catch(()=>{});}catch(e){}})();}};const handleLogout=()=>{localStorage.removeItem('sonic_token');localStorage.removeItem('sonic_user');setCurrentUser(null);setIsMandatoryLogin(true);setAuthMode('login');setAuthModalOpen(true);};// ── Temp project auto-save (local + server) ── useEffect(()=>{const serializeSafe=arr=>(arr||[]).map(t=>({id:t.id,name:t.name,startTime:t.startTime,height:t.height,volumeDb:t.volumeDb,pan:t.pan,muted:t.muted,solo:t.solo,color:t.color,markers:t.markers||[],serverFileId:t.serverFileId||null,channelInfo:t.channelInfo?{channels:t.channelInfo.channels,isStereo:t.channelInfo.isStereo,label:t.channelInfo.label}:null}));window.SonicStorage.scheduleTempAutoSave(()=>{return serializeProjectToSchema(currentProjectId||'temp_project',projectName||'Dự án tạm chưa lưu',bpm,tracks,subTabs,sessionTabs,masteringSettings);});},[tracks,subTabs,sessionTabs,masteringSettings]);// ── Timer-based auto-save (5 min) + backup (30 min) ── useEffect(()=>{const BACKUP_MAX_KEY='sonic_backup_max_count';if(!localStorage.getItem(BACKUP_MAX_KEY)){localStorage.setItem(BACKUP_MAX_KEY,'10');}const interval5=setInterval(()=>{if(!currentProjectId)return;// Auto-save: giống handleSaveProject diff --git a/app/static/js/services/api.js b/app/static/js/services/api.js index 88b835b..b119f2b 100644 --- a/app/static/js/services/api.js +++ b/app/static/js/services/api.js @@ -22,6 +22,8 @@ window.API_BASE_URL = window.API_BASE_URL || window.location.origin; if (response.status === 401) { localStorage.removeItem('sonic_token'); localStorage.removeItem('sonic_user'); + // Mở màn hình đăng nhập ngay (bug 09:45 — màn đen khi session hết hạn) + if (window.__onAuthFail) window.__onAuthFail(); } const data = await response.json().catch(() => ({})); diff --git a/app/templates/index.html b/app/templates/index.html index 960090a..253390b 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -12,7 +12,7 @@ - + @@ -24,7 +24,7 @@ - +