diff --git a/openpilot/starpilot/galaxy/tests/test_layout_backup.mjs b/openpilot/starpilot/galaxy/tests/test_layout_backup.mjs index 3127735210..adf835b2a0 100644 --- a/openpilot/starpilot/galaxy/tests/test_layout_backup.mjs +++ b/openpilot/starpilot/galaxy/tests/test_layout_backup.mjs @@ -42,3 +42,58 @@ await ToggleBackup.methods.restoreBackup.call(moving) assert.equal(writes,1) assert.match(moving.error,/Park/) console.log("Portable layout files and legacy restore through Toggle backup passed") + +assert.match(ToggleBackup.template, /Reset toggles to default/) +assert.ok(ToggleBackup.template.indexOf('Restore toggles') < ToggleBackup.template.indexOf('Reset toggles to default')) +const resetCalls = [] +const reset = { ...ToggleBackup.data(), request: async (path, body) => { + resetCalls.push([path, body]) + if (path.endsWith('non-defaults')) return {view:'fresh-view', count:3, rows:[ + {id:0,resetAvailable:true}, {id:1,resetAvailable:false}, {id:2,resetAvailable:true}]} + if (path.endsWith('preview')) return {intent:'fresh-intent', question:'Reset 2 settings?'} + return {saved:true,applied:['First','Second']} +} } +await ToggleBackup.methods.reviewResetDefaults.call(reset) +assert.deepEqual(resetCalls, [['./api/settings/non-defaults',undefined], + ['./api/settings/recovery/preview',{view:'fresh-view',selected:[0,2]}]]) +assert.equal(reset.resetDraft.intent,'fresh-intent') +assert.equal(resetCalls.some(([path]) => path.endsWith('confirm')),false,'Review must not change settings') +await Promise.all([ToggleBackup.methods.resetDefaults.call(reset),ToggleBackup.methods.resetDefaults.call(reset)]) +assert.deepEqual(resetCalls.at(-1),['./api/settings/recovery/confirm',{intent:'fresh-intent',confirmed:true}]) +assert.equal(resetCalls.filter(([path]) => path.endsWith('confirm')).length,1) +assert.equal(reset.resetDraft,null) +assert.equal(reset.notice,'2 settings reset to default.') + +const alreadyDefault = {...ToggleBackup.data(),request:async path => { + assert.equal(path,'./api/settings/non-defaults'); return {count:0,rows:[]} +} } +await ToggleBackup.methods.reviewResetDefaults.call(alreadyDefault) +assert.match(alreadyDefault.notice,/already use their defaults/) +assert.equal(alreadyDefault.resetDraft,null) + +const cannotReset = {...ToggleBackup.data(),request:async path => { + assert.equal(path,'./api/settings/non-defaults');return {count:1,rows:[{id:0,resetAvailable:false}]} +} } +await ToggleBackup.methods.reviewResetDefaults.call(cannotReset) +assert.match(cannotReset.error,/Park/) +assert.equal(cannotReset.resetDraft,null) + +const interrupted = {...ToggleBackup.data(),resetDraft:{intent:'approved'},request:async () => + ({saved:false,applied:['First'],reason:'Vehicle state changed'})} +await ToggleBackup.methods.resetDefaults.call(interrupted) +assert.match(interrupted.error,/1 settings reset.*Vehicle state changed/) +assert.equal(interrupted.notice,'') +assert.equal(interrupted.resetDraft,null) + +const failedReview = {...ToggleBackup.data(),request:async () => {throw new Error('Sign in to Galaxy')}} +await ToggleBackup.methods.reviewResetDefaults.call(failedReview) +assert.equal(failedReview.error,'Sign in to Galaxy') +assert.equal(failedReview.resetDraft,null) +assert.equal(failedReview.busy,false) + +let busyRequests=0 +const busyReset = {...ToggleBackup.data(),busy:true,resetDraft:{intent:'old'},request:async () => {busyRequests++}} +await ToggleBackup.methods.reviewResetDefaults.call(busyReset) +await ToggleBackup.methods.resetDefaults.call(busyReset) +assert.equal(busyRequests,0) +console.log('Software-screen default reset uses guarded preview and explicit confirmation') diff --git a/openpilot/starpilot/galaxy/web/js/toggle-backup.js b/openpilot/starpilot/galaxy/web/js/toggle-backup.js index ec949c6796..15859c930a 100644 --- a/openpilot/starpilot/galaxy/web/js/toggle-backup.js +++ b/openpilot/starpilot/galaxy/web/js/toggle-backup.js @@ -8,7 +8,7 @@ export const ToggleBackup = { name: "ToggleBackup", components: { GxDialog, GxNotice }, props: { unauthorized: { type: Function, required: true } }, - data: () => ({ busy: false, draft: null, legacy: false, error: "", notice: "" }), + data: () => ({ busy: false, draft: null, resetDraft: null, legacy: false, error: "", notice: "" }), methods: { async request(path, payload) { try { @@ -51,6 +51,32 @@ export const ToggleBackup = { } } catch (error) { this.draft = null; this.error = error.message || "Could not read toggle backup." } }, + async reviewResetDefaults() { + if (this.busy) return + this.busy = true; this.resetDraft = null; this.error = this.notice = "" + try { + const snapshot = await this.request("./api/settings/non-defaults") + const selected = snapshot.rows.filter(row => row.resetAvailable === true).map(row => row.id) + if (!selected.length) { + if (snapshot.count) throw new Error("Park the vehicle before resetting available toggles.") + this.notice = "All available toggles already use their defaults." + return + } + this.resetDraft = await this.request("./api/settings/recovery/preview", { view: snapshot.view, selected }) + } catch (error) { this.error = error.message || "Could not prepare toggle reset." } + finally { this.busy = false } + }, + async resetDefaults() { + if (this.busy || !this.resetDraft) return + const pending = this.resetDraft + this.resetDraft = null; this.busy = true; this.error = this.notice = "" + try { + const result = await this.request("./api/settings/recovery/confirm", { intent: pending.intent, confirmed: true }) + if (!result.saved) throw new Error(`${result.applied.length} settings reset. ${result.reason || "Review Toggles before retrying."}`) + this.notice = `${result.applied.length} ${result.applied.length === 1 ? "setting" : "settings"} reset to default.` + } catch (error) { this.error = error.message || "Could not reset toggles. Review Toggles before retrying." } + finally { this.busy = false } + }, async restoreBackup() { if (this.busy || !this.draft) return const draft = this.draft, legacy = this.legacy @@ -75,6 +101,7 @@ export const ToggleBackup = {
Save your editable toggles, onroad layout and colors. Restore compatible settings while parked.
Working…
{{ legacy ? 'Replace your saved layout and colors?' : 'Apply the compatible saved toggles, layout and colors from this backup? Unavailable settings will be skipped.' }} Keep the vehicle parked.
+{{ resetDraft.question }}
+Keep the vehicle parked. Settings unavailable for this vehicle are left unchanged. Calibration, downloaded models, accounts and network credentials are kept.
+