Files
lnbits/lnbits/static/js/pages/account.js
T

718 lines
18 KiB
JavaScript

window.PageAccount = {
template: '#page-account',
data() {
return {
untouchedUser: null,
hasUsername: false,
showUserId: false,
themeOptions: [
{
name: 'bitcoin',
color: 'deep-orange'
},
{
name: 'mint',
color: 'green'
},
{
name: 'autumn',
color: 'brown'
},
{
name: 'monochrome',
color: 'grey'
},
{
name: 'salvador',
color: 'blue-10'
},
{
name: 'freedom',
color: 'pink-13'
},
{
name: 'cyber',
color: 'light-green-9'
},
{
name: 'flamingo',
color: 'pink-3'
}
],
defaultSiteCustomisation: {
locale: 'en',
themeChoice: 'salvador',
bgimageChoice: '',
gradientChoice: true,
darkChoice: true,
borderChoice: 'hard-border',
reactionChoice: 'confettiBothSides'
},
reactionOptions: [
'None',
'confettiBothSides',
'confettiFireworks',
'confettiStars',
'confettiTop'
],
borderOptions: [
'retro-border',
'hard-border',
'neon-border',
'no-border'
],
tab: 'user',
credentialsData: {
show: false,
oldPassword: null,
newPassword: null,
newPasswordRepeat: null,
username: null,
pubkey: null
},
apiAcl: {
showNewAclDialog: false,
showPasswordDialog: false,
showNewTokenDialog: false,
data: [],
passwordGuardedFunction: null,
newAclName: '',
newTokenName: '',
password: '',
apiToken: null,
selectedTokenId: null,
columns: [
{
name: 'Name',
align: 'left',
label: this.$t('Name'),
field: 'Name',
sortable: false
},
{
name: 'path',
align: 'left',
label: this.$t('path'),
field: 'path',
sortable: false
},
{
name: 'read',
align: 'left',
label: this.$t('read'),
field: 'read',
sortable: false
},
{
name: 'write',
align: 'left',
label: this.$t('write'),
field: 'write',
sortable: false
}
],
pagination: {
rowsPerPage: 100,
page: 1
}
},
selectedApiAcl: {
id: null,
name: null,
endpoints: [],
token_id_list: [],
allRead: false,
allWrite: false
},
assets: [],
assetsTable: {
loading: false,
columns: [
{
name: 'name',
align: 'left',
label: this.$t('Name'),
field: 'name',
sortable: true
},
{
name: 'created_at',
align: 'left',
label: this.$t('created_at'),
field: 'created_at',
sortable: true
}
],
pagination: {
rowsPerPage: 6,
page: 1
}
},
assetsUploadToPublic: false,
notifications: {
nostr: {
identifier: ''
}
},
labels: [],
labelsDialog: {
show: false,
data: {
name: '',
description: '',
color: '#000000'
}
},
labelsTable: {
loading: false,
columns: [
{
name: 'actions',
align: 'left'
},
{
name: 'name',
align: 'left',
label: this.$t('Name'),
field: 'name',
sortable: true
},
{
name: 'description',
align: 'left',
label: this.$t('description'),
field: 'description'
},
{
name: 'color',
align: 'left',
label: this.$t('color'),
field: 'color'
}
],
pagination: {
rowsPerPage: 6,
page: 1
}
}
}
},
watch: {
tab(tab) {
this.$router.push(`/account#${tab}`)
},
$route(to) {
if (to.hash.length > 1) {
this.tab = to.hash.replace('#', '')
}
},
'assetsTable.search': {
handler() {
const props = {}
if (this.assetsTable.search) {
props['search'] = this.assetsTable.search
}
this.getUserAssets()
}
}
},
computed: {
isUserTouched() {
return !_.isEqual(this.g.user, this.untouchedUser)
}
},
methods: {
changeLanguage(newValue) {
window.i18n.global.locale = newValue
this.$q.localStorage.set('lnbits.lang', newValue)
},
async updateAccount() {
try {
const {data} = await LNbits.api.request('PATCH', '/api/v1/auth', null, {
user_id: this.g.user.id,
username: this.g.user.username,
email: this.g.user.email,
extra: this.g.user.extra
})
this.untouchedUser = JSON.parse(JSON.stringify(this.g.user))
this.hasUsername = !!data.username
Quasar.Notify.create({
type: 'positive',
message: 'Account updated.'
})
} catch (e) {
LNbits.utils.notifyApiError(e)
}
},
disableUpdatePassword() {
return (
!this.credentialsData.newPassword ||
!this.credentialsData.newPasswordRepeat ||
this.credentialsData.newPassword !==
this.credentialsData.newPasswordRepeat
)
},
async updatePassword() {
if (!this.credentialsData.username) {
Quasar.Notify.create({
type: 'warning',
message: 'Please set a username.'
})
return
}
try {
const {data} = await LNbits.api.request(
'PUT',
'/api/v1/auth/password',
null,
{
user_id: this.g.user.id,
username: this.credentialsData.username,
password_old: this.credentialsData.oldPassword,
password: this.credentialsData.newPassword,
password_repeat: this.credentialsData.newPasswordRepeat
}
)
this.untouchedUser = JSON.parse(JSON.stringify(data))
this.hasUsername = !!data.username
this.credentialsData.show = false
Quasar.Notify.create({
type: 'positive',
message: 'Password updated.'
})
} catch (e) {
LNbits.utils.notifyApiError(e)
}
},
async updatePubkey() {
try {
const {data} = await LNbits.api.request(
'PUT',
'/api/v1/auth/pubkey',
null,
{
user_id: this.g.user.id,
pubkey: this.credentialsData.pubkey
}
)
this.untouchedUser = JSON.parse(JSON.stringify(data))
this.hasUsername = !!data.username
this.credentialsData.show = false
this.$q.notify({
type: 'positive',
message: 'Public key updated.'
})
} catch (e) {
LNbits.utils.notifyApiError(e)
}
},
showUpdateCredentials() {
this.credentialsData = {
show: true,
oldPassword: null,
username: this.g.user.username,
pubkey: this.g.user.pubkey,
newPassword: null,
newPasswordRepeat: null
}
},
newApiAclDialog() {
this.apiAcl.newAclName = null
this.apiAcl.showNewAclDialog = true
},
newTokenAclDialog() {
this.apiAcl.newTokenName = null
this.apiAcl.newTokenExpiry = null
this.apiAcl.showNewTokenDialog = true
},
handleApiACLSelected(aclId) {
this.selectedApiAcl = {
id: null,
name: null,
endpoints: [],
token_id_list: []
}
this.apiAcl.selectedTokenId = null
if (!aclId) {
return
}
setTimeout(() => {
const selectedApiAcl = this.apiAcl.data.find(t => t.id === aclId)
if (!this.selectedApiAcl) {
return
}
this.selectedApiAcl = {...selectedApiAcl}
this.selectedApiAcl.allRead = this.selectedApiAcl.endpoints.every(
e => e.read
)
this.selectedApiAcl.allWrite = this.selectedApiAcl.endpoints.every(
e => e.write
)
})
},
handleAllEndpointsReadAccess() {
this.selectedApiAcl.endpoints.forEach(
e => (e.read = this.selectedApiAcl.allRead)
)
},
handleAllEndpointsWriteAccess() {
this.selectedApiAcl.endpoints.forEach(
e => (e.write = this.selectedApiAcl.allWrite)
)
},
async getApiACLs() {
try {
const {data} = await LNbits.api.request('GET', '/api/v1/auth/acl', null)
this.apiAcl.data = data.access_control_list
} catch (e) {
LNbits.utils.notifyApiError(e)
}
},
askPasswordAndRunFunction(func) {
this.apiAcl.passwordGuardedFunction = func
this.apiAcl.showPasswordDialog = true
},
runPasswordGuardedFunction() {
this.apiAcl.showPasswordDialog = false
const func = this.apiAcl.passwordGuardedFunction
if (func) {
this[func]()
}
},
async addApiACL() {
if (!this.apiAcl.newAclName) {
this.$q.notify({
type: 'warning',
message: 'Name is required.'
})
return
}
try {
const {data} = await LNbits.api.request(
'PUT',
'/api/v1/auth/acl',
null,
{
id: this.apiAcl.newAclName,
name: this.apiAcl.newAclName,
password: this.apiAcl.password
}
)
this.apiAcl.data = data.access_control_list
const acl = this.apiAcl.data.find(
t => t.name === this.apiAcl.newAclName
)
this.handleApiACLSelected(acl.id)
this.apiAcl.showNewAclDialog = false
this.$q.notify({
type: 'positive',
message: 'Access Control List created.'
})
} catch (e) {
LNbits.utils.notifyApiError(e)
} finally {
this.apiAcl.name = ''
this.apiAcl.password = ''
}
this.apiAcl.showNewAclDialog = false
},
async updateApiACLs() {
try {
const {data} = await LNbits.api.request(
'PUT',
'/api/v1/auth/acl',
null,
{
id: this.g.user.id,
password: this.apiAcl.password,
...this.selectedApiAcl
}
)
this.apiAcl.data = data.access_control_list
} catch (e) {
LNbits.utils.notifyApiError(e)
} finally {
this.apiAcl.password = ''
}
},
async deleteApiACL() {
if (!this.selectedApiAcl.id) {
return
}
try {
await LNbits.api.request('DELETE', '/api/v1/auth/acl', null, {
id: this.selectedApiAcl.id,
password: this.apiAcl.password
})
this.$q.notify({
type: 'positive',
message: 'Access Control List deleted.'
})
} catch (e) {
LNbits.utils.notifyApiError(e)
} finally {
this.apiAcl.password = ''
}
this.apiAcl.data = this.apiAcl.data.filter(
t => t.id !== this.selectedApiAcl.id
)
this.handleApiACLSelected(this.apiAcl.data[0]?.id)
},
async generateApiToken() {
if (!this.selectedApiAcl.id) {
return
}
const expirationTimeMilliseconds =
new Date(this.apiAcl.newTokenExpiry) - new Date()
try {
const {data} = await LNbits.api.request(
'POST',
'/api/v1/auth/acl/token',
null,
{
acl_id: this.selectedApiAcl.id,
token_name: this.apiAcl.newTokenName,
password: this.apiAcl.password,
expiration_time_minutes: Math.trunc(
expirationTimeMilliseconds / 60000
)
}
)
this.apiAcl.apiToken = data.api_token
this.apiAcl.selectedTokenId = data.id
Quasar.Notify.create({
type: 'positive',
message: 'Token Generated.'
})
await this.getApiACLs()
this.handleApiACLSelected(this.selectedApiAcl.id)
this.apiAcl.showNewTokenDialog = false
} catch (e) {
LNbits.utils.notifyApiError(e)
} finally {
this.apiAcl.password = ''
}
},
async deleteToken() {
if (!this.apiAcl.selectedTokenId) {
return
}
try {
await LNbits.api.request('DELETE', '/api/v1/auth/acl/token', null, {
id: this.apiAcl.selectedTokenId,
acl_id: this.selectedApiAcl.id,
password: this.apiAcl.password
})
this.$q.notify({
type: 'positive',
message: 'Token deleted.'
})
this.selectedApiAcl.token_id_list =
this.selectedApiAcl.token_id_list.filter(
t => t.id !== this.apiAcl.selectedTokenId
)
this.apiAcl.selectedTokenId = null
} catch (e) {
LNbits.utils.notifyApiError(e)
} finally {
this.apiAcl.password = ''
}
},
async getUserAssets(props) {
try {
this.assetsTable.loading = true
const params = LNbits.utils.prepareFilterQuery(this.assetsTable, props)
const {data} = await LNbits.api.request(
'GET',
`/api/v1/assets/paginated?${params}`,
null
)
this.assets = data.data
this.assetsTable.pagination.rowsNumber = data.total
} catch (e) {
LNbits.utils.notifyApiError(e)
} finally {
this.assetsTable.loading = false
}
},
onImageInput(e) {
const file = e.target.files[0]
if (file) {
this.uploadAsset(file)
}
},
async uploadAsset(file) {
const formData = new FormData()
formData.append('file', file)
try {
await LNbits.api.request(
'POST',
`/api/v1/assets?public_asset=${this.assetsUploadToPublic}`,
null,
formData,
{
headers: {'Content-Type': 'multipart/form-data'}
}
)
this.$q.notify({
type: 'positive',
message: 'Upload successful!',
icon: null
})
await this.getUserAssets()
} catch (e) {
console.warn(e)
LNbits.utils.notifyApiError(e)
}
},
async deleteAsset(asset) {
LNbits.utils
.confirmDialog('Are you sure you want to delete this asset?')
.onOk(async () => {
try {
await LNbits.api.request(
'DELETE',
`/api/v1/assets/${asset.id}`,
null
)
this.$q.notify({
type: 'positive',
message: 'Asset deleted.'
})
await this.getUserAssets()
} catch (e) {
console.warn(e)
LNbits.utils.notifyApiError(e)
}
})
},
async toggleAssetPublicAccess(asset) {
try {
await LNbits.api.request('PUT', `/api/v1/assets/${asset.id}`, null, {
is_public: !asset.is_public
})
this.$q.notify({
type: 'positive',
message: 'Update successful!',
icon: null
})
await this.getUserAssets()
} catch (e) {
console.warn(e)
LNbits.utils.notifyApiError(e)
}
},
copyAssetLinkToClipboard(asset) {
const assetUrl = `${window.location.origin}/api/v1/assets/${asset.id}/data`
this.utils.copyText(assetUrl)
},
addUserLabel() {
if (!this.labelsDialog.data.name) {
this.$q.notify({
type: 'warning',
message: 'Name is required.'
})
return
}
if (!this.labelsDialog.data.color) {
this.$q.notify({
type: 'warning',
message: 'Color is required.'
})
return
}
this.g.user.extra.labels = this.g.user.extra.labels || []
const duplicate = this.g.user.extra.labels.find(
label => label.name === this.labelsDialog.data.name
)
if (duplicate) {
this.$q.notify({
type: 'warning',
message: 'A label with this name already exists.'
})
return
}
this.g.user.extra.labels.unshift({...this.labelsDialog.data})
this.labelsDialog.show = false
return true
},
openAddLabelDialog() {
this.labelsDialog.data = {
name: '',
description: '',
color: '#000000'
}
this.labelsDialog.show = true
},
openEditLabelDialog(label) {
this.labelsDialog.data = {
name: label.name,
description: label.description,
color: label.color
}
this.labelsDialog.show = true
},
updateUserLabel() {
const label = this.labelsDialog.data
const existingLabels = JSON.parse(
JSON.stringify(this.g.user.extra.labels)
)
this.g.user.extra.labels = this.g.user.extra.labels.filter(
l => l.name !== label.name
)
const labelUpdated = this.addUserLabel()
if (!labelUpdated) {
this.g.user.extra.labels = existingLabels
}
this.labelsDialog.show = false
},
deleteUserLabel(label) {
LNbits.utils
.confirmDialog('Are you sure you want to delete this label?')
.onOk(() => {
this.g.user.extra.labels = this.g.user.extra.labels.filter(
l => l.name !== label.name
)
})
},
async siteCustomisationChanged(options = {}) {
try {
Object.entries(options || {}).forEach(([key, value]) => {
if (key in this.g) {
this.g[key] = value
}
})
await LNbits.api.updateUiCustomization(options)
this.$q.notify({
type: 'positive',
message: 'UI Customization updated.'
})
} catch (e) {
LNbits.utils.notifyApiError(e)
}
}
},
async created() {
this.untouchedUser = JSON.parse(JSON.stringify(this.g.user))
this.hasUsername = !!this.g.user.username
if (this.$route.hash.length > 1) {
this.tab = this.$route.hash.replace('#', '')
}
await this.getApiACLs()
await this.getUserAssets()
// filter out themes that are not allowed
this.themeOptions = this.themeOptions.filter(theme =>
this.LNBITS_THEME_OPTIONS.includes(theme.name)
)
}
}