|
|
@ -100,6 +100,7 @@ |
|
|
|
<router-link |
|
|
|
:to="{ name: 'new-edit-account' }" |
|
|
|
class="block text-center text-lg font-bold uppercase bg-slate-500 text-white px-2 py-3 rounded-md mb-2" |
|
|
|
v-if="activeDid" |
|
|
|
> |
|
|
|
Edit Identity |
|
|
|
</router-link> |
|
|
@ -138,7 +139,7 @@ |
|
|
|
</router-link> |
|
|
|
</div> |
|
|
|
|
|
|
|
<h3 class="text-sm uppercase font-semibold mb-3">Data</h3> |
|
|
|
<h3 class="text-sm uppercase font-semibold mb-3">Data Export</h3> |
|
|
|
|
|
|
|
<router-link |
|
|
|
:to="{ name: 'seed-backup' }" |
|
|
@ -147,7 +148,9 @@ |
|
|
|
> |
|
|
|
Backup Identifier Seed |
|
|
|
</router-link> |
|
|
|
|
|
|
|
<button |
|
|
|
v-bind:class="computedStartDownloadLinkClassNames()" |
|
|
|
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-6" |
|
|
|
@click="exportDatabase()" |
|
|
|
> |
|
|
@ -155,21 +158,27 @@ |
|
|
|
<br /> |
|
|
|
(excluding Identifier Data) |
|
|
|
</button> |
|
|
|
<a ref="downloadLink" /> |
|
|
|
<a |
|
|
|
ref="downloadLink" |
|
|
|
v-bind:class="computedDownloadLinkClassNames()" |
|
|
|
class="block w-full text-center text-md uppercase bg-green-600 text-white px-1.5 py-2 rounded-md mb-6" |
|
|
|
> |
|
|
|
If no download happened yet, click again here to download now. |
|
|
|
</a> |
|
|
|
|
|
|
|
<div v-if="activeDid" class="my-8"> |
|
|
|
<h3 class="text-sm uppercase font-semibold mb-3">Rate Limits</h3> |
|
|
|
<div v-if="activeDid" class="flex mt-8 py-2"> |
|
|
|
<h3 class="text-sm uppercase font-semibold">Rate Limits</h3> |
|
|
|
<button |
|
|
|
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2" |
|
|
|
class="block w-fit text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md ml-2 mr-2 mb-2" |
|
|
|
@click="checkLimits()" |
|
|
|
> |
|
|
|
Check Limits |
|
|
|
</button> |
|
|
|
<!-- show spinner if loading limits --> |
|
|
|
<div v-if="loadingLimits" class="text-center mb-4"> |
|
|
|
<div v-if="loadingLimits" class="text-center"> |
|
|
|
Checking… <fa icon="spinner" class="fa-spin"></fa> |
|
|
|
</div> |
|
|
|
<div class="mb-4"> |
|
|
|
<div> |
|
|
|
{{ limitsMessage }} |
|
|
|
</div> |
|
|
|
<div v-if="!!limits?.nextWeekBeginDateTime"> |
|
|
@ -293,18 +302,37 @@ |
|
|
|
</div> |
|
|
|
</label> |
|
|
|
|
|
|
|
<router-link |
|
|
|
:to="{ name: 'statistics' }" |
|
|
|
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2" |
|
|
|
> |
|
|
|
See Animated Global History of Giving |
|
|
|
</router-link> |
|
|
|
<div class="grid-cols-2 mb-4"> |
|
|
|
<span class="text-slate-500 text-sm font-bold mb-2">Data Import</span> |
|
|
|
<input type="file" @change="uploadFile" class="ml-2" /> |
|
|
|
<div v-if="showContactImport()"> |
|
|
|
<button |
|
|
|
class="block text-center text-md uppercase bg-green-600 text-white px-1.5 py-2 rounded-md mb-6" |
|
|
|
@click="submitFile()" |
|
|
|
> |
|
|
|
Import Settings & Contacts |
|
|
|
<br /> |
|
|
|
(excluding Identifier Data) |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="flex py-2"> |
|
|
|
<button> |
|
|
|
<router-link |
|
|
|
:to="{ name: 'statistics' }" |
|
|
|
class="block w-fit text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2" |
|
|
|
> |
|
|
|
See Global Animated History of Giving |
|
|
|
</router-link> |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- id used by puppeteer test script --> |
|
|
|
<router-link |
|
|
|
id="switch-identity-link" |
|
|
|
:to="{ name: 'identity-switcher' }" |
|
|
|
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2" |
|
|
|
class="block w-fit text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2" |
|
|
|
> |
|
|
|
Switch Identity |
|
|
|
</router-link> |
|
|
@ -429,7 +457,9 @@ |
|
|
|
|
|
|
|
<script lang="ts"> |
|
|
|
import { AxiosError, AxiosRequestConfig } from "axios"; |
|
|
|
import Dexie from "dexie"; |
|
|
|
import "dexie-export-import"; |
|
|
|
import { ref } from "vue"; |
|
|
|
import { Component, Vue } from "vue-facing-decorator"; |
|
|
|
import { useClipboard } from "@vueuse/core"; |
|
|
|
|
|
|
@ -441,6 +471,7 @@ import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings"; |
|
|
|
import { accessToken } from "@/libs/crypto"; |
|
|
|
import { IIdentifier } from "@veramo/core"; |
|
|
|
import { ErrorResponse, RateLimits } from "@/libs/endorserServer"; |
|
|
|
import { ImportProgress } from "dexie-export-import/dist/import"; |
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-var-requires |
|
|
|
const Buffer = require("buffer/").Buffer; |
|
|
@ -459,6 +490,8 @@ interface IAccount { |
|
|
|
derivationPath: string; |
|
|
|
} |
|
|
|
|
|
|
|
const inputFileNameRef = ref<Blob>(); |
|
|
|
|
|
|
|
@Component({ components: { QuickNav, TopMessage } }) |
|
|
|
export default class AccountViewView extends Vue { |
|
|
|
$notify!: (notification: Notification, timeout?: number) => void; |
|
|
@ -469,6 +502,7 @@ export default class AccountViewView extends Vue { |
|
|
|
apiServer = ""; |
|
|
|
apiServerInput = ""; |
|
|
|
derivationPath = ""; |
|
|
|
downloadUrl = ""; // because DuckDuckGo doesn't download on automated call to "click" on the anchor |
|
|
|
givenName = ""; |
|
|
|
isRegistered = false; |
|
|
|
isSubscribed = false; |
|
|
@ -494,6 +528,11 @@ export default class AccountViewView extends Vue { |
|
|
|
warnIfProdServer = false; |
|
|
|
warnIfTestServer = false; |
|
|
|
|
|
|
|
async beforeCreate() { |
|
|
|
await accountsDB.open(); |
|
|
|
this.numAccounts = await accountsDB.accounts.count(); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Async function executed when the component is created. |
|
|
|
* Initializes the component's state with values from the database, |
|
|
@ -530,6 +569,12 @@ export default class AccountViewView extends Vue { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
beforeUnmount() { |
|
|
|
if (this.downloadUrl) { |
|
|
|
URL.revokeObjectURL(this.downloadUrl); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Initializes component state with values from the database or defaults. |
|
|
|
* @param {SettingsType} settings - Object containing settings from the database. |
|
|
@ -553,26 +598,17 @@ export default class AccountViewView extends Vue { |
|
|
|
try { |
|
|
|
// Open the accounts database |
|
|
|
await accountsDB.open(); |
|
|
|
} catch (error) { |
|
|
|
console.error("Failed to open accounts database:", error); |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
let account: { identity?: string } | undefined; |
|
|
|
|
|
|
|
try { |
|
|
|
// Search for the account with the matching DID (decentralized identifier) |
|
|
|
account = await accountsDB.accounts |
|
|
|
.where("did") |
|
|
|
.equals(activeDid) |
|
|
|
.first(); |
|
|
|
const account: { identity?: string } | undefined = |
|
|
|
await accountsDB.accounts.where("did").equals(activeDid).first(); |
|
|
|
|
|
|
|
// Return parsed identity or null if not found |
|
|
|
return JSON.parse((account?.identity as string) || "null"); |
|
|
|
} catch (error) { |
|
|
|
console.error("Failed to find account:", error); |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
// Return parsed identity or null if not found |
|
|
|
return JSON.parse((account?.identity as string) || "null"); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
@ -628,11 +664,6 @@ export default class AccountViewView extends Vue { |
|
|
|
return timeStr.substring(0, timeStr.indexOf("T")); |
|
|
|
} |
|
|
|
|
|
|
|
async beforeCreate() { |
|
|
|
await accountsDB.open(); |
|
|
|
this.numAccounts = await accountsDB.accounts.count(); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Processes the identity and updates the component's state. |
|
|
|
* @param {IdentityType} identity - Object containing identity information. |
|
|
@ -788,13 +819,13 @@ export default class AccountViewView extends Vue { |
|
|
|
const blob = await this.generateDatabaseBlob(); |
|
|
|
|
|
|
|
// Create a temporary URL for the blob |
|
|
|
const url = this.createBlobURL(blob); |
|
|
|
this.downloadUrl = this.createBlobURL(blob); |
|
|
|
|
|
|
|
// Trigger the download |
|
|
|
this.downloadDatabaseBackup(url); |
|
|
|
this.downloadDatabaseBackup(this.downloadUrl); |
|
|
|
|
|
|
|
// Revoke the temporary URL |
|
|
|
URL.revokeObjectURL(url); |
|
|
|
// Revoke the temporary URL -- not yet because of DuckDuckGo download failure |
|
|
|
//URL.revokeObjectURL(this.downloadUrl); |
|
|
|
|
|
|
|
// Notify the user that the download has started |
|
|
|
this.notifyDownloadStarted(); |
|
|
@ -831,7 +862,19 @@ export default class AccountViewView extends Vue { |
|
|
|
const downloadAnchor = this.$refs.downloadLink as HTMLAnchorElement; |
|
|
|
downloadAnchor.href = url; |
|
|
|
downloadAnchor.download = `${db.name}-backup.json`; |
|
|
|
downloadAnchor.click(); |
|
|
|
downloadAnchor.click(); // doesn't work for some browsers, eg. DuckDuckGo |
|
|
|
} |
|
|
|
|
|
|
|
public computedStartDownloadLinkClassNames() { |
|
|
|
return { |
|
|
|
invisible: this.downloadUrl, |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
public computedDownloadLinkClassNames() { |
|
|
|
return { |
|
|
|
invisible: !this.downloadUrl, |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
@ -867,6 +910,43 @@ export default class AccountViewView extends Vue { |
|
|
|
console.error("Export Error:", error); |
|
|
|
} |
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any |
|
|
|
async uploadFile(event: any) { |
|
|
|
inputFileNameRef.value = event.target.files[0]; |
|
|
|
} |
|
|
|
|
|
|
|
showContactImport() { |
|
|
|
return !!inputFileNameRef.value; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Asynchronously imports the database from a downloadable JSON file. |
|
|
|
* |
|
|
|
* @throws Will notify the user if there is an export error. |
|
|
|
*/ |
|
|
|
async submitFile() { |
|
|
|
if (inputFileNameRef.value != null) { |
|
|
|
if ( |
|
|
|
confirm( |
|
|
|
"This will replace all settings and contacts, so we recommend you first do the backup step above." + |
|
|
|
" Are you sure you want to import and replace all contacts and settings?", |
|
|
|
) |
|
|
|
) { |
|
|
|
await db.delete(); |
|
|
|
await Dexie.import(inputFileNameRef.value, { |
|
|
|
progressCallback: this.progressCallback, |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private progressCallback(progress: ImportProgress) { |
|
|
|
console.log( |
|
|
|
`Import progress: ${progress.completedRows} of ${progress.totalRows} rows completed.`, |
|
|
|
); |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
async checkLimits() { |
|
|
|
const identity = await this.getIdentity(this.activeDid); |
|
|
|
if (identity) { |
|
|
@ -996,6 +1076,7 @@ export default class AccountViewView extends Vue { |
|
|
|
const accounts = await accountsDB.accounts.toArray(); |
|
|
|
const account = accounts[accountNum - 1]; |
|
|
|
|
|
|
|
await db.open(); |
|
|
|
await db.settings.update(MASTER_SETTINGS_KEY, { activeDid: account.did }); |
|
|
|
|
|
|
|
this.updateActiveAccountProperties(account); |
|
|
|