@ -2,7 +2,7 @@
< div class = "min-h-screen bg-gray-50 py-8" >
< div class = "min-h-screen bg-gray-50 py-8" >
< div class = "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8" >
< div class = "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8" >
<!-- Header -- >
<!-- Header -- >
< div class = "mb-8" >
< div >
< h1 class = "text-3xl font-bold text-gray-900" > Database Migration < / h1 >
< h1 class = "text-3xl font-bold text-gray-900" > Database Migration < / h1 >
< p class = "mt-2 text-gray-600" >
< p class = "mt-2 text-gray-600" >
Compare and migrate data between Dexie ( IndexedDB ) and SQLite
Compare and migrate data between Dexie ( IndexedDB ) and SQLite
@ -10,33 +10,34 @@
< / p >
< / p >
< / div >
< / div >
<!-- Status Banner -- >
< div class = "mt-4" >
< div
<!-- Migration Options -- >
v - if = "!isDexieEnabled"
< div class = "bg-white shadow rounded-lg" >
class = "mb-6 bg-yellow-50 border border-yellow-200 rounded-lg p-4"
< div class = "px-4 py-5 sm:p-6" >
>
< h3 class = "text-lg leading-6 font-medium text-gray-900 mb-4" >
< div class = "flex" >
Migration Options
< div class = "flex-shrink-0" >
< IconRenderer
icon - name = "warning"
svg - class = "h-5 w-5 text-yellow-400"
fill = "currentColor"
/ >
< / div >
< div class = "ml-3" >
< h3 class = "text-sm font-medium text-yellow-800" >
Dexie Database Disabled
< / h3 >
< / h3 >
< div class = "mt-2 text-sm text-yellow-700" >
< p >
< div class = "space-y-4" >
To use migration features , enable Dexie database by setting
< div class = "flex items-center" >
< code class = "bg-yellow-100 px-1 rounded" >
< input
USE_DEXIE_DB = true
id = "overwrite-existing"
< / code >
v - model = "overwriteExisting"
in
type = "checkbox"
< code class = "bg-yellow-100 px-1 rounded" >
class = "h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
constants / app . ts
/ >
< / code >
< label
for = "overwrite-existing"
class = "ml-2 block text-sm text-gray-900"
>
Overwrite existing records in SQLite
< / label >
< / div >
< p class = "text-sm text-gray-600" >
When enabled , existing records in SQLite will be updated with
data from Dexie . When disabled , existing records will be skipped
during migration .
< / p >
< / p >
< / div >
< / div >
< / div >
< / div >
@ -44,9 +45,28 @@
< / div >
< / div >
<!-- Action Buttons -- >
<!-- Action Buttons -- >
< div class = "mb-8 flex flex-wrap gap-4" >
< div class = "mt-4 mb-8 flex flex-wrap gap-4" >
< button
: disabled = "isLoading"
class = "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
@ click = "displayDatabases"
>
< IconRenderer
v - if = "isLoading"
icon - name = "spinner"
svg - class = "animate-spin -ml-1 mr-3 h-5 w-5 text-white"
fill = "currentColor"
/ >
< IconRenderer
v - else
icon - name = "chart"
svg - class = "-ml-1 mr-3 h-5 w-5"
/ >
Show Existing Data
< / button >
< button
< button
: disabled = "isLoading || !isDexieEnabled"
: disabled = "isLoading"
class = "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
class = "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
@ click = "compareDatabases"
@ click = "compareDatabases"
>
>
@ -65,7 +85,7 @@
< / button >
< / button >
< button
< button
: disabled = "isLoading || !isDexieEnabled || ! comparison"
: disabled = "isLoading || !comparison"
class = "inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed"
class = "inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed"
@ click = "migrateAll"
@ click = "migrateAll"
>
>
@ -86,7 +106,7 @@
< div class = "w-full border-t border-gray-200 my-4" > < / div >
< div class = "w-full border-t border-gray-200 my-4" > < / div >
< button
< button
: disabled = "isLoading || !isDexieEnabled || ! comparison"
: disabled = "isLoading || !comparison"
class = "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 disabled:opacity-50 disabled:cursor-not-allowed"
class = "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 disabled:opacity-50 disabled:cursor-not-allowed"
@ click = "migrateContacts"
@ click = "migrateContacts"
>
>
@ -95,7 +115,7 @@
< / button >
< / button >
< button
< button
: disabled = "isLoading || !isDexieEnabled || ! comparison"
: disabled = "isLoading || !comparison"
class = "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-purple-600 hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 disabled:opacity-50 disabled:cursor-not-allowed"
class = "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-purple-600 hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 disabled:opacity-50 disabled:cursor-not-allowed"
@ click = "migrateSettings"
@ click = "migrateSettings"
>
>
@ -104,7 +124,7 @@
< / button >
< / button >
< button
< button
: disabled = "isLoading || !isDexieEnabled || ! comparison"
: disabled = "isLoading || !comparison"
class = "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-orange-600 hover:bg-orange-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-orange-500 disabled:opacity-50 disabled:cursor-not-allowed"
class = "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-orange-600 hover:bg-orange-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-orange-500 disabled:opacity-50 disabled:cursor-not-allowed"
@ click = "migrateAccounts"
@ click = "migrateAccounts"
>
>
@ -112,15 +132,6 @@
Migrate Accounts
Migrate Accounts
< / button >
< / button >
< button
: disabled = "isLoading || !isDexieEnabled"
class = "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-pink-600 hover:bg-pink-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500 disabled:opacity-50 disabled:cursor-not-allowed"
@ click = "testSpecificSettingsMigration"
>
< IconRenderer icon -name = " test " svg -class = " -ml -1 mr -3 h -5 w -5 " / >
Test Settings Migration
< / button >
< button
< button
: disabled = "!comparison"
: disabled = "!comparison"
class = "inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
class = "inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
@ -129,15 +140,6 @@
< IconRenderer icon -name = " download " svg -class = " -ml -1 mr -3 h -5 w -5 " / >
< IconRenderer icon -name = " download " svg -class = " -ml -1 mr -3 h -5 w -5 " / >
Export Comparison
Export Comparison
< / button >
< / button >
< button
: disabled = "isLoading"
class = "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-teal-600 hover:bg-teal-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-teal-500 disabled:opacity-50 disabled:cursor-not-allowed"
@ click = "verifyMigration"
>
< IconRenderer icon -name = " check " svg -class = " -ml -1 mr -3 h -5 w -5 " / >
Verify Migration
< / button >
< / div >
< / div >
<!-- Migration Information -- >
<!-- Migration Information -- >
@ -760,45 +762,27 @@
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
<!-- Migration Options -- >
< div class = "bg-white shadow rounded-lg" >
< div class = "px-4 py-5 sm:p-6" >
< h3 class = "text-lg leading-6 font-medium text-gray-900 mb-4" >
Migration Options
< / h3 >
< div class = "space-y-4" >
< div class = "flex items-center" >
< input
id = "overwrite-existing"
v - model = "overwriteExisting"
type = "checkbox"
class = "h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/ >
< label
for = "overwrite-existing"
class = "ml-2 block text-sm text-gray-900"
>
Overwrite existing records in SQLite
< / label >
< / div >
< p class = "text-sm text-gray-600" >
When enabled , existing records in SQLite will be updated with
data from Dexie . When disabled , existing records will be skipped
during migration .
< / p >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
<!-- Export Results -- >
< div v-if ="exportedData" class="mt-4 space-y-6" >
< h2 > Exported Data < / h2 >
< span
v - on : click = "copyToClipboard"
class = "text-blue-500 cursor-pointer hover:text-blue-700"
>
Copy to Clipboard
< / span >
< pre > { { JSON . stringify ( exportedData , null , 2 ) } } < / pre >
< / div >
< / div >
< / div >
< / template >
< / template >
< script lang = "ts" >
< script lang = "ts" >
import { Component , Vue } from "vue-facing-decorator" ;
import { Component , Vue } from "vue-facing-decorator" ;
import { useClipboard } from "@vueuse/core" ;
import IconRenderer from "../components/IconRenderer.vue" ;
import IconRenderer from "../components/IconRenderer.vue" ;
import {
import {
compareDatabases ,
compareDatabases ,
@ -807,11 +791,12 @@ import {
migrateAccounts ,
migrateAccounts ,
migrateAll ,
migrateAll ,
generateComparisonYaml ,
generateComparisonYaml ,
testSettingsMigration ,
type DataComparison ,
type DataComparison ,
type MigrationResult ,
type MigrationResult ,
getDexieAccounts ,
getDexieSettings ,
getDexieContacts ,
} from "../services/migrationService" ;
} from "../services/migrationService" ;
import { USE_DEXIE_DB } from "../constants/app" ;
import { logger } from "../utils/logger" ;
import { logger } from "../utils/logger" ;
/ * *
/ * *
@ -844,18 +829,12 @@ export default class DatabaseMigration extends Vue {
private isLoading = false ;
private isLoading = false ;
private loadingMessage = "" ;
private loadingMessage = "" ;
private error = "" ;
private error = "" ;
private exportedData : Record < string , any > | null = null ;
private successMessage = "" ;
private successMessage = "" ;
private comparison : DataComparison | null = null ;
private comparison : DataComparison | null = null ;
private overwriteExisting = fals e;
private overwriteExisting = tru e;
/ * *
useClipboard = useClipboard ;
* Computed property to check if Dexie database is enabled
*
* @ returns { boolean } True if Dexie database is enabled , false otherwise
* /
get isDexieEnabled ( ) : boolean {
return USE_DEXIE_DB ;
}
/ * *
/ * *
* Computed property to get the display name for a setting
* Computed property to get the display name for a setting
@ -927,6 +906,34 @@ export default class DatabaseMigration extends Vue {
return ! ! account . mnemonic ;
return ! ! account . mnemonic ;
}
}
/ * *
* Copies exported data to clipboard and shows success message
* /
async copyToClipboard ( ) : Promise < void > {
if ( ! this . exportedData ) return ;
try {
await this . useClipboard ( ) . copy ( JSON . stringify ( this . exportedData , null , 2 ) ) ;
/ / U s e g l o b a l w i n d o w o b j e c t p r o p e r l y
if ( typeof window !== 'undefined' ) {
window . alert ( 'Copied to clipboard!' ) ;
}
} catch ( error ) {
console . error ( 'Failed to copy to clipboard:' , error ) ;
if ( typeof window !== 'undefined' ) {
window . alert ( 'Failed to copy to clipboard' ) ;
}
}
}
async displayDatabases ( ) {
this . exportedData = {
accounts : await getDexieAccounts ( ) ,
settings : await getDexieSettings ( ) ,
contacts : await getDexieContacts ( )
} ;
}
/ * *
/ * *
* Migrates all data from Dexie to SQLite in the proper order
* Migrates all data from Dexie to SQLite in the proper order
*
*
@ -1147,47 +1154,6 @@ export default class DatabaseMigration extends Vue {
}
}
}
}
/ * *
* Verifies the migration by running a fresh comparison
*
* This method runs a new comparison after migration to verify
* that the data was transferred correctly .
*
* @ async
* @ returns { Promise < void > }
* /
async verifyMigration ( ) : Promise < void > {
this . setLoading ( "Verifying migration..." ) ;
this . clearMessages ( ) ;
try {
const newComparison = await compareDatabases ( ) ;
const totalRemaining =
newComparison . differences . contacts . added . length +
newComparison . differences . settings . added . length +
newComparison . differences . accounts . added . length ;
if ( totalRemaining === 0 ) {
this . successMessage = "Migration verification successful! All data has been migrated." ;
this . comparison = newComparison ;
} else {
this . error = ` Migration verification failed. ${ totalRemaining } items still need to be migrated. ` ;
this . comparison = newComparison ;
}
logger . info ( "[DatabaseMigration] Migration verification completed" , {
totalRemaining ,
success : totalRemaining === 0
} ) ;
} catch ( error ) {
this . error = ` Failed to verify migration: ${ error } ` ;
logger . error ( "[DatabaseMigration] Migration verification failed:" , error ) ;
} finally {
this . setLoading ( "" ) ;
}
}
/ * *
/ * *
* Exports the comparison data to a JSON file
* Exports the comparison data to a JSON file
*
*
@ -1224,34 +1190,6 @@ export default class DatabaseMigration extends Vue {
}
}
}
}
/ * *
* Tests the specific settings migration for the fields you mentioned
*
* This method tests the migration of firstName , isRegistered , profileImageUrl ,
* showShortcutBvc , and searchBoxes from Dexie to SQLite .
*
* @ async
* @ returns { Promise < void > }
* /
async testSpecificSettingsMigration ( ) : Promise < void > {
this . setLoading ( "Testing specific settings migration..." ) ;
this . clearMessages ( ) ;
try {
await testSettingsMigration ( ) ;
this . successMessage = "✅ Settings migration test completed successfully! Check the console for detailed logs." ;
logger . info ( "[DatabaseMigration] Settings migration test completed successfully" ) ;
/ / R e f r e s h c o m p a r i s o n d a t a a f t e r s u c c e s s f u l t e s t
this . comparison = await compareDatabases ( ) ;
} catch ( error ) {
this . error = ` Settings migration test failed: ${ error } ` ;
logger . error ( "[DatabaseMigration] Settings migration test failed:" , error ) ;
} finally {
this . setLoading ( "" ) ;
}
}
/ * *
/ * *
* Sets the loading state and message
* Sets the loading state and message
*
*