window.app = Vue.createApp({ el: '#vue', mixins: [windowMixin], data() { return { settings: {}, logs: [], serverlogEnabled: false, lnbits_theme_options: [ 'classic', 'bitcoin', 'flamingo', 'cyber', 'freedom', 'mint', 'autumn', 'monochrome', 'salvador' ], auditData: {}, statusData: {}, statusDataTable: { columns: [ { name: 'date', align: 'left', label: this.$t('date'), field: 'date' }, { name: 'message', align: 'left', label: this.$t('memo'), field: 'message' } ] }, formData: {}, formAddAdmin: '', formAddUser: '', formAddExtensionsManifest: '', formAllowedIPs: '', formBlockedIPs: '', nostrAcceptedUrl: '', formAddIncludePath: '', formAddExcludePath: '', formAddIncludeResponseCode: '', isSuperUser: false, wallet: {}, cancel: {}, colors: [ 'primary', 'secondary', 'accent', 'positive', 'negative', 'info', 'warning', 'red', 'yellow', 'orange' ], tab: 'funding', needsRestart: false, exchangesTable: { columns: [ { name: 'name', align: 'left', label: 'Exchange Name', field: 'name', sortable: true }, { name: 'api_url', align: 'left', label: 'URL', field: 'api_url', sortable: false }, { name: 'path', align: 'left', label: 'JSON Path', field: 'path', sortable: false }, { name: 'exclude_to', align: 'left', label: 'Exclude Currencies', field: 'exclude_to', sortable: false }, { name: 'ticker_conversion', align: 'left', label: 'Ticker Conversion', field: 'ticker_conversion', sortable: false } ], pagination: { sortBy: 'name', rowsPerPage: 100, page: 1, rowsNumber: 100 }, search: null, hideEmpty: true }, exchangeData: { selectedProvider: null, showTickerConversion: false, convertFromTicker: null, convertToTicker: null } } }, created() { this.getSettings() this.getAudit() this.balance = +'{{ balance|safe }}' }, computed: { lnbitsVersion() { return LNBITS_VERSION }, checkChanges() { return !_.isEqual(this.settings, this.formData) }, updateAvailable() { return LNBITS_VERSION !== this.statusData.version } }, methods: { addAdminUser() { let addUser = this.formAddAdmin let admin_users = this.formData.lnbits_admin_users if (addUser && addUser.length && !admin_users.includes(addUser)) { this.formData.lnbits_admin_users = [...admin_users, addUser] this.formAddAdmin = '' } }, removeAdminUser(user) { let admin_users = this.formData.lnbits_admin_users this.formData.lnbits_admin_users = admin_users.filter(u => u !== user) }, addAllowedUser() { let addUser = this.formAddUser let allowed_users = this.formData.lnbits_allowed_users if (addUser && addUser.length && !allowed_users.includes(addUser)) { this.formData.lnbits_allowed_users = [...allowed_users, addUser] this.formAddUser = '' } }, removeAllowedUser(user) { let allowed_users = this.formData.lnbits_allowed_users this.formData.lnbits_allowed_users = allowed_users.filter(u => u !== user) }, addIncludePath() { if (!this.formAddIncludePath) { return } const paths = this.formData.lnbits_audit_include_paths if (!paths.includes(this.formAddIncludePath)) { this.formData.lnbits_audit_include_paths = [ ...paths, this.formAddIncludePath ] } this.formAddIncludePath = '' }, removeIncludePath(path) { this.formData.lnbits_audit_include_paths = this.formData.lnbits_audit_include_paths.filter(p => p !== path) }, addExcludePath() { if (!this.formAddExcludePath) { return } const paths = this.formData.lnbits_audit_exclude_paths if (!paths.includes(this.formAddExcludePath)) { this.formData.lnbits_audit_exclude_paths = [ ...paths, this.formAddExcludePath ] } this.formAddExcludePath = '' }, removeExcludePath(path) { this.formData.lnbits_audit_exclude_paths = this.formData.lnbits_audit_exclude_paths.filter(p => p !== path) }, addIncludeResponseCode() { if (!this.formAddIncludeResponseCode) { return } const codes = this.formData.lnbits_audit_http_response_codes if (!codes.includes(this.formAddIncludeResponseCode)) { this.formData.lnbits_audit_http_response_codes = [ ...codes, this.formAddIncludeResponseCode ] } this.formAddIncludeResponseCode = '' }, removeIncludeResponseCode(code) { this.formData.lnbits_audit_http_response_codes = this.formData.lnbits_audit_http_response_codes.filter(c => c !== code) }, addExtensionsManifest() { const addManifest = this.formAddExtensionsManifest.trim() const manifests = this.formData.lnbits_extensions_manifests if ( addManifest && addManifest.length && !manifests.includes(addManifest) ) { this.formData.lnbits_extensions_manifests = [...manifests, addManifest] this.formAddExtensionsManifest = '' } }, removeExtensionsManifest(manifest) { const manifests = this.formData.lnbits_extensions_manifests this.formData.lnbits_extensions_manifests = manifests.filter( m => m !== manifest ) }, async toggleServerLog() { this.serverlogEnabled = !this.serverlogEnabled if (this.serverlogEnabled) { const wsProto = location.protocol !== 'http:' ? 'wss://' : 'ws://' const digestHex = await LNbits.utils.digestMessage(this.g.user.id) const localUrl = wsProto + document.domain + ':' + location.port + '/api/v1/ws/' + digestHex this.ws = new WebSocket(localUrl) this.ws.addEventListener('message', async ({data}) => { this.logs.push(data.toString()) const scrollArea = this.$refs.logScroll if (scrollArea) { const scrollTarget = scrollArea.getScrollTarget() const duration = 0 scrollArea.setScrollPosition(scrollTarget.scrollHeight, duration) } }) } else { this.ws.close() } }, addAllowedIPs() { const allowedIPs = this.formAllowedIPs.trim() const allowed_ips = this.formData.lnbits_allowed_ips if ( allowedIPs && allowedIPs.length && !allowed_ips.includes(allowedIPs) ) { this.formData.lnbits_allowed_ips = [...allowed_ips, allowedIPs] this.formAllowedIPs = '' } }, removeAllowedIPs(allowed_ip) { const allowed_ips = this.formData.lnbits_allowed_ips this.formData.lnbits_allowed_ips = allowed_ips.filter( a => a !== allowed_ip ) }, addBlockedIPs() { const blockedIPs = this.formBlockedIPs.trim() const blocked_ips = this.formData.lnbits_blocked_ips if ( blockedIPs && blockedIPs.length && !blocked_ips.includes(blockedIPs) ) { this.formData.lnbits_blocked_ips = [...blocked_ips, blockedIPs] this.formBlockedIPs = '' } }, removeBlockedIPs(blocked_ip) { const blocked_ips = this.formData.lnbits_blocked_ips this.formData.lnbits_blocked_ips = blocked_ips.filter( b => b !== blocked_ip ) }, addNostrUrl() { const url = this.nostrAcceptedUrl.trim() this.removeNostrUrl(url) this.formData.nostr_absolute_request_urls.push(url) this.nostrAcceptedUrl = '' }, removeNostrUrl(url) { this.formData.nostr_absolute_request_urls = this.formData.nostr_absolute_request_urls.filter(b => b !== url) }, addExchangeProvider() { this.formData.lnbits_exchange_rate_providers = [ { name: '', api_url: '', path: '', exclude_to: [] }, ...this.formData.lnbits_exchange_rate_providers ] }, removeExchangeProvider(provider) { this.formData.lnbits_exchange_rate_providers = this.formData.lnbits_exchange_rate_providers.filter(p => p !== provider) }, removeExchangeTickerConversion(provider, ticker) { provider.ticker_conversion = provider.ticker_conversion.filter( t => t !== ticker ) this.touchSettings() }, addExchangeTickerConversion() { if (!this.exchangeData.selectedProvider) { return } this.exchangeData.selectedProvider.ticker_conversion.push( `${this.exchangeData.convertFromTicker}:${this.exchangeData.convertToTicker}` ) this.touchSettings() this.exchangeData.showTickerConversion = false }, showTickerConversionDialog(provider) { this.exchangeData.convertFromTicker = null this.exchangeData.convertToTicker = null this.exchangeData.selectedProvider = provider this.exchangeData.showTickerConversion = true }, getDefaultSetting(fieldName) { LNbits.api .request( 'GET', `/admin/api/v1/settings/default?field_name=${fieldName}` ) .then(response => { this.formData[fieldName] = response.data.default_value }) .catch(function (error) { LNbits.utils.notifyApiError(error) }) }, restartServer() { LNbits.api .request('GET', '/admin/api/v1/restart/') .then(response => { Quasar.Notify.create({ type: 'positive', message: 'Success! Restarted Server', icon: null }) this.needsRestart = false }) .catch(LNbits.utils.notifyApiError) }, formatDate(date) { return moment(date * 1000).fromNow() }, getNotifications() { if (this.settings.lnbits_notifications) { axios .get(this.settings.lnbits_status_manifest) .then(response => { this.statusData = response.data }) .catch(error => { this.formData.lnbits_notifications = false error.response.data = {} error.response.data.message = 'Could not fetch status manifest.' LNbits.utils.notifyApiError(error) }) } }, getAudit() { LNbits.api .request('GET', '/admin/api/v1/audit', this.g.user.wallets[0].adminkey) .then(response => { this.auditData = response.data }) .catch(LNbits.utils.notifyApiError) }, getExchangeRateHistory() { LNbits.api .request('GET', '/api/v1/rate/history', this.g.user.wallets[0].inkey) .then(response => { this.initExchangeChart(response.data) }) .catch(function (error) { LNbits.utils.notifyApiError(error) }) }, getSettings() { LNbits.api .request( 'GET', '/admin/api/v1/settings', this.g.user.wallets[0].adminkey ) .then(response => { this.isSuperUser = response.data.is_super_user || false this.settings = response.data this.formData = {...this.settings} this.getNotifications() }) .catch(LNbits.utils.notifyApiError) }, updateSettings() { const data = _.omit(this.formData, [ 'is_super_user', 'lnbits_allowed_funding_sources', 'touch' ]) LNbits.api .request( 'PUT', '/admin/api/v1/settings', this.g.user.wallets[0].adminkey, data ) .then(response => { this.needsRestart = this.settings.lnbits_backend_wallet_class !== this.formData.lnbits_backend_wallet_class || this.settings.lnbits_killswitch !== this.formData.lnbits_killswitch this.settings = this.formData this.formData = _.clone(this.settings) Quasar.Notify.create({ type: 'positive', message: `Success! Settings changed! ${ this.needsRestart ? 'Restart required!' : '' }`, icon: null }) }) .catch(LNbits.utils.notifyApiError) }, deleteSettings() { LNbits.utils .confirmDialog('Are you sure you want to restore settings to default?') .onOk(() => { LNbits.api .request('DELETE', '/admin/api/v1/settings') .then(response => { Quasar.Notify.create({ type: 'positive', message: 'Success! Restored settings to defaults, restart required!', icon: null }) this.needsRestart = true }) .catch(LNbits.utils.notifyApiError) }) }, downloadBackup() { window.open('/admin/api/v1/backup', '_blank') }, showExchangeProvidersTab(tabName) { if (tabName === 'exchange_providers') { this.getExchangeRateHistory() } }, touchSettings() { this.formData.touch = null }, initExchangeChart(data) { const xValues = data.map(d => Quasar.date.formatDate(new Date(d.timestamp * 1000), 'HH:mm') ) const exchanges = [ ...this.formData.lnbits_exchange_rate_providers, {name: 'LNbits'} ] const datasets = exchanges.map(exchange => ({ label: exchange.name, data: data.map(d => d.rates[exchange.name]), pointStyle: true, borderWidth: exchange.name === 'LNbits' ? 4 : 1, tension: 0.4 })) this.exchangeRatesChart = new Chart( this.$refs.exchangeRatesChart.getContext('2d'), { type: 'line', options: { plugins: { legend: { display: false } } }, data: { labels: xValues, datasets } } ) } } })