FIX: reset tất cả về mặc định khi tạo new project
This commit is contained in:
@@ -259,3 +259,32 @@ async def get_profile(current_user: dict = Depends(get_current_user)):
|
|||||||
"max_tracks": row["max_tracks"] or 16
|
"max_tracks": row["max_tracks"] or 16
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/first-time")
|
||||||
|
async def auth_first_time():
|
||||||
|
# "Lần đăng nhập đầu" = tài khoản admin vẫn dùng mật khẩu MẶC ĐỊNH
|
||||||
|
# (chưa từng đổi). Sau khi đổi lần đầu → first_time = false → UI xóa
|
||||||
|
# gợi ý username/mật khẩu (Tùy chọn - Admin có thể bỏ trống, lần đầu:
|
||||||
|
# admin123, nút Điền nhanh).
|
||||||
|
try:
|
||||||
|
conn = get_db_connection()
|
||||||
|
try:
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute(
|
||||||
|
"SELECT id, hashed_password, must_change_password FROM users "
|
||||||
|
"WHERE LOWER(role) = 'admin' ORDER BY created_at ASC LIMIT 1"
|
||||||
|
)
|
||||||
|
row = cur.fetchone()
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
if not row:
|
||||||
|
return {"first_time": True}
|
||||||
|
still_default = False
|
||||||
|
try:
|
||||||
|
still_default = verify_password("admin123", row["hashed_password"])
|
||||||
|
except Exception:
|
||||||
|
still_default = False
|
||||||
|
return {"first_time": bool(row["must_change_password"]) and still_default}
|
||||||
|
except Exception:
|
||||||
|
return {"first_time": True}
|
||||||
|
|||||||
+43
-3
@@ -4850,12 +4850,27 @@ const AuthModal = ({
|
|||||||
const [newPassword, setNewPassword] = useState('');
|
const [newPassword, setNewPassword] = useState('');
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [firstTime, setFirstTime] = useState(true);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (mode) setActiveTab(mode);
|
if (mode) setActiveTab(mode);
|
||||||
if (mode === 'force_change' && !oldPassword) {
|
if (mode === 'force_change' && !oldPassword) {
|
||||||
setOldPassword('admin123');
|
setOldPassword('admin123');
|
||||||
}
|
}
|
||||||
}, [mode]);
|
}, [mode]);
|
||||||
|
// Lần đăng nhập ĐẦU (admin còn mật khẩu mặc định) → hiện gợi ý
|
||||||
|
// username/mật khẩu; sau khi đã đổi → ẩn vĩnh viễn.
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) return;
|
||||||
|
if (window.SonicAPI && window.SonicAPI.apiRequest) {
|
||||||
|
window.SonicAPI.apiRequest('/api/v1/auth/first-time', { method: 'GET' })
|
||||||
|
.then(function (d) { setFirstTime(!!(d && d.first_time)); })
|
||||||
|
// Fail-closed: endpoint lỗi/404 (backend chưa restart) → ẨN gợi ý
|
||||||
|
// (không hiện — user đã yêu cầu bỏ gợi ý sau lần đầu).
|
||||||
|
.catch(function () { setFirstTime(false); });
|
||||||
|
} else {
|
||||||
|
setFirstTime(false);
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
const handleSubmit = async e => {
|
const handleSubmit = async e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setError('');
|
setError('');
|
||||||
@@ -4930,7 +4945,7 @@ const AuthModal = ({
|
|||||||
className: "w-full bg-[#1e1e1e] border border-[#383838] rounded px-3 py-2 text-sm focus:outline-none focus:border-teal-500"
|
className: "w-full bg-[#1e1e1e] border border-[#383838] rounded px-3 py-2 text-sm focus:outline-none focus:border-teal-500"
|
||||||
}))) : /*#__PURE__*/React.createElement(React.Fragment, null, activeTab === 'login' ? /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("label", {
|
}))) : /*#__PURE__*/React.createElement(React.Fragment, null, activeTab === 'login' ? /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("label", {
|
||||||
className: "block text-xs font-semibold mb-1 text-slate-400"
|
className: "block text-xs font-semibold mb-1 text-slate-400"
|
||||||
}, "Tên đăng nhập ", /*#__PURE__*/React.createElement("span", {
|
}, "Tên đăng nhập ", firstTime && /*#__PURE__*/React.createElement("span", {
|
||||||
className: "text-teal-400 font-normal"
|
className: "text-teal-400 font-normal"
|
||||||
}, "(Tùy chọn - Admin có thể bỏ trống)")), /*#__PURE__*/React.createElement("input", {
|
}, "(Tùy chọn - Admin có thể bỏ trống)")), /*#__PURE__*/React.createElement("input", {
|
||||||
type: "text",
|
type: "text",
|
||||||
@@ -4956,7 +4971,7 @@ const AuthModal = ({
|
|||||||
className: "w-full bg-[#1e1e1e] border border-[#383838] rounded px-3 py-2 text-sm focus:outline-none focus:border-teal-500"
|
className: "w-full bg-[#1e1e1e] border border-[#383838] rounded px-3 py-2 text-sm focus:outline-none focus:border-teal-500"
|
||||||
})), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("label", {
|
})), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("label", {
|
||||||
className: "block text-xs font-semibold mb-1 text-slate-400"
|
className: "block text-xs font-semibold mb-1 text-slate-400"
|
||||||
}, "Mật khẩu ", activeTab === 'login' && /*#__PURE__*/React.createElement("span", {
|
}, "Mật khẩu ", activeTab === 'login' && firstTime && /*#__PURE__*/React.createElement("span", {
|
||||||
className: "text-amber-400 font-normal"
|
className: "text-amber-400 font-normal"
|
||||||
}, "(Lần đầu: admin123)")), /*#__PURE__*/React.createElement("input", {
|
}, "(Lần đầu: admin123)")), /*#__PURE__*/React.createElement("input", {
|
||||||
type: "password",
|
type: "password",
|
||||||
@@ -4969,7 +4984,7 @@ const AuthModal = ({
|
|||||||
type: "submit",
|
type: "submit",
|
||||||
disabled: loading,
|
disabled: loading,
|
||||||
className: "w-full py-2 bg-teal-600 hover:bg-teal-500 text-white font-semibold rounded-lg shadow transition duration-150"
|
className: "w-full py-2 bg-teal-600 hover:bg-teal-500 text-white font-semibold rounded-lg shadow transition duration-150"
|
||||||
}, loading ? 'Đang xác thực...' : isForceMode ? 'Đổi Mật Khẩu Ngay' : activeTab === 'login' ? 'Đăng Nhập System' : 'Tạo Tài Khoản Mới'), activeTab === 'login' && !isForceMode && /*#__PURE__*/React.createElement("button", {
|
}, loading ? 'Đang xác thực...' : isForceMode ? 'Đổi Mật Khẩu Ngay' : activeTab === 'login' ? 'Đăng Nhập System' : 'Tạo Tài Khoản Mới'), activeTab === 'login' && !isForceMode && firstTime && /*#__PURE__*/React.createElement("button", {
|
||||||
type: "button",
|
type: "button",
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setUsername('admin');
|
setUsername('admin');
|
||||||
@@ -15832,6 +15847,16 @@ const App = () => {
|
|||||||
}
|
}
|
||||||
if (ctrl && !alt && e.key === 'n') {
|
if (ctrl && !alt && e.key === 'n') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
// New project: đóng hết tab + reset MỌI trạng thái về mặc định
|
||||||
|
try { stopAllPlayback(); } catch (e) {}
|
||||||
|
setSubTabs([]);
|
||||||
|
setSessionTabs([]);
|
||||||
|
setActiveTab('main');
|
||||||
|
trackMidiChannelsRef.current = {};
|
||||||
|
Object.keys(trackMidiBypassMap).forEach(function (k) { delete trackMidiBypassMap[k]; });
|
||||||
|
Object.keys(trackAudioBypassMap).forEach(function (k) { delete trackAudioBypassMap[k]; });
|
||||||
|
Object.keys(trackMasteringBypassMap).forEach(function (k) { delete trackMasteringBypassMap[k]; });
|
||||||
|
setSelectedItemIds(new Set());
|
||||||
setTracks([{
|
setTracks([{
|
||||||
id: '1',
|
id: '1',
|
||||||
name: 'Track 01',
|
name: 'Track 01',
|
||||||
@@ -15858,6 +15883,10 @@ const App = () => {
|
|||||||
serverFileId: null
|
serverFileId: null
|
||||||
}]);
|
}]);
|
||||||
setSelectedTrackId('1');
|
setSelectedTrackId('1');
|
||||||
|
setProjectName('');
|
||||||
|
setCurrentProjectId(null);
|
||||||
|
localStorage.removeItem('sonic_project_name');
|
||||||
|
localStorage.removeItem('sonic_project_id');
|
||||||
showToast('New project created', 'info');
|
showToast('New project created', 'info');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -24388,6 +24417,17 @@ STRICT CONSTRAINTS:
|
|||||||
icon: 'file-plus',
|
icon: 'file-plus',
|
||||||
shortcut: 'Ctrl+N',
|
shortcut: 'Ctrl+N',
|
||||||
action: () => {
|
action: () => {
|
||||||
|
// New project: đóng hết tab + reset MỌI trạng thái về mặc định
|
||||||
|
try { stopAllPlayback(); } catch (e) {}
|
||||||
|
setSubTabs([]);
|
||||||
|
setSessionTabs([]);
|
||||||
|
setActiveTab('main');
|
||||||
|
// Reset instrument/channel/route context đã load
|
||||||
|
trackMidiChannelsRef.current = {};
|
||||||
|
Object.keys(trackMidiBypassMap).forEach(function (k) { delete trackMidiBypassMap[k]; });
|
||||||
|
Object.keys(trackAudioBypassMap).forEach(function (k) { delete trackAudioBypassMap[k]; });
|
||||||
|
Object.keys(trackMasteringBypassMap).forEach(function (k) { delete trackMasteringBypassMap[k]; });
|
||||||
|
setSelectedItemIds(new Set());
|
||||||
setTracks([{
|
setTracks([{
|
||||||
id: '1',
|
id: '1',
|
||||||
name: 'Track 01',
|
name: 'Track 01',
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -24,7 +24,7 @@
|
|||||||
<script src="/static/js/services/midiExtractor.js?v=202607281052"></script>
|
<script src="/static/js/services/midiExtractor.js?v=202607281052"></script>
|
||||||
<script src="/static/js/services/promptTemplateManager.js?v=202607281039"></script>
|
<script src="/static/js/services/promptTemplateManager.js?v=202607281039"></script>
|
||||||
<script src="/static/js/services/undoRedoEngine.js?v=202607290941"></script>
|
<script src="/static/js/services/undoRedoEngine.js?v=202607290941"></script>
|
||||||
<script src="/static/js/app.precompiled.js?v=202608061030" defer></script>
|
<script src="/static/js/app.precompiled.js?v=202608061200" defer></script>
|
||||||
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
|
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
|
|||||||
@@ -2041,3 +2041,29 @@
|
|||||||
- **FIX (app.jsx):** nút Đóng chuyển từ cuối toolbar → SAU nút MIDI ghost notes (hàng 1) + `ml-auto` (đẩy phải); nhóm Lưu/Export giữ cuối hàng 2 + `ml-auto`; Export → **Export MIDI**. Emoji 🎵 Chuyển giọng bị patch tool double-escape → sửa bằng emoji thật (verify: 0 double-escape còn lại).
|
- **FIX (app.jsx):** nút Đóng chuyển từ cuối toolbar → SAU nút MIDI ghost notes (hàng 1) + `ml-auto` (đẩy phải); nhóm Lưu/Export giữ cuối hàng 2 + `ml-auto`; Export → **Export MIDI**. Emoji 🎵 Chuyển giọng bị patch tool double-escape → sửa bằng emoji thật (verify: 0 double-escape còn lại).
|
||||||
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608061030), `wiki.md`. Rebuild precompiled.
|
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608061030), `wiki.md`. Rebuild precompiled.
|
||||||
- **Ghi chú/Test:** `npm run build` → hard refresh → hàng 1 phải có [Đóng]; hàng 2 phải: [Lưu] [Export MIDI] ở cuối bên phải.
|
- **Ghi chú/Test:** `npm run build` → hard refresh → hàng 1 phải có [Đóng]; hàng 2 phải: [Lưu] [Export MIDI] ở cuối bên phải.
|
||||||
|
|
||||||
|
### [2026-08-06 11:00] Task: Ẩn gợi ý đăng nhập sau lần đăng nhập đầu (username/password hints + nút Điền nhanh)
|
||||||
|
- **Yêu cầu user:** sau lần đăng nhập hệ thống đầu tiên → xóa gợi ý: "(Tùy chọn - Admin có thể bỏ trống)", "(Lần đầu: admin123)", nút "🔑 Điền Nhanh Tài Khoản Admin".
|
||||||
|
- **FIX:**
|
||||||
|
(1) Backend `auth.py`: endpoint **`GET /api/v1/auth/first-time`** → `{first_time: bool}` — true khi admin vẫn dùng mật khẩu mặc định (verify_password admin123 + must_change_password); sau khi đổi → false.
|
||||||
|
(2) Frontend AuthModal: state `firstTime` + fetch khi modal mở; 3 gợi ý render có điều kiện `firstTime &&` — hết lần đầu → ẩn vĩnh viễn.
|
||||||
|
- **Các file ảnh hưởng:** `app/api/v1/auth.py`, `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608061100), `wiki.md`. Rebuild precompiled + restart backend.
|
||||||
|
- **Ghi chú/Test:** restart backend → login admin (mật khẩu còn mặc định) → vẫn thấy gợi ý; đổi mật khẩu → logout → login lại → KHÔNG còn gợi ý/nút Điền nhanh.
|
||||||
|
|
||||||
|
### [2026-08-06 11:30] Task: Gợi ý đăng nhập vẫn hiện sau khi đổi mật khẩu — fail-closed (ẩn khi endpoint lỗi)
|
||||||
|
- **Báo cáo user:** đã đổi mật khẩu nhưng gợi ý vẫn xuất hiện.
|
||||||
|
- **Nguyên nhân nghi:** backend chưa restart → `/api/v1/auth/first-time` 404 → catch cũ `setFirstTime(true)` → gợi ý hiện (fail-open sai hướng).
|
||||||
|
- **FIX (app.jsx AuthModal):** catch → `setFirstTime(false)` + else branch → fail-closed: endpoint lỗi/404 → **ẨN gợi ý**. Endpoint OK: `verify_password("admin123", hash mới)` = false → first_time=false → ẩn (đã đúng logic backend).
|
||||||
|
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608061130), `wiki.md`. Rebuild precompiled.
|
||||||
|
- **Ghi chú/Test:** `npm run build` → hard refresh → login → gợi ý ẩn (kể cả khi backend chưa restart). Khuyến nghị restart backend để endpoint hoạt động đúng.
|
||||||
|
|
||||||
|
### [2026-08-06 12:00] Task: New project — reset toàn bộ (đóng tab, clear selection, reset instrument context)
|
||||||
|
- **Yêu cầu user:** tạo new project → đóng hết tab đang mở, clear selection, reset instrument đã load về mặc định.
|
||||||
|
- **FIX (app.jsx — 2 nơi: menu File → New Project + Ctrl+N):**
|
||||||
|
- `stopAllPlayback()` (dừng mọi play)
|
||||||
|
- `setSubTabs([])` + `setSessionTabs([])` + `setActiveTab('main')` (đóng hết tab)
|
||||||
|
- `setSelectedItemIds(new Set())` (clear multi-selection)
|
||||||
|
- Reset context instrument: `trackMidiChannelsRef.current = {}` + clear `trackMidiBypassMap`/`trackAudioBypassMap`/`trackMasteringBypassMap` (delete từng key — const map)
|
||||||
|
- Reset project: 2 track mặc định, selectedTrackId '1', projectName '', currentProjectId null, xóa localStorage id/name
|
||||||
|
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608061200), `wiki.md`. Rebuild precompiled.
|
||||||
|
- **Ghi chú/Test:** `npm run build` → hard refresh → mở vài tab + chọn track → File → New Project (hoặc Ctrl+N) → tab đóng hết, selection trống, instrument track về mặc định.
|
||||||
|
|||||||
Reference in New Issue
Block a user