refactor: move /admin into vue component (#3466)

This commit is contained in:
dni ⚡
2025-11-07 18:10:59 +01:00
committed by GitHub
parent 2fecec2623
commit a40306f5cd
39 changed files with 1444 additions and 1235 deletions
@@ -0,0 +1,66 @@
window.app.component('lnbits-admin-audit', {
props: ['form-data'],
template: '#lnbits-admin-audit',
mixins: [window.windowMixin],
data() {
return {
formAddIncludePath: '',
formAddExcludePath: '',
formAddIncludeResponseCode: ''
}
},
methods: {
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)
}
}
})
@@ -0,0 +1,159 @@
window.app.component('lnbits-admin-exchange-providers', {
props: ['form-data'],
template: '#lnbits-admin-exchange-providers',
mixins: [window.windowMixin],
data() {
return {
exchangeData: {
selectedProvider: null,
showTickerConversion: false,
convertFromTicker: null,
convertToTicker: null
},
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
}
}
},
mounted() {
this.getExchangeRateHistory()
},
created() {
const hash = window.location.hash.replace('#', '')
if (hash === 'exchange_providers') {
this.showExchangeProvidersTab(hash)
}
},
methods: {
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)
})
},
showExchangeProvidersTab(tabName) {
if (tabName === 'exchange_providers') {
this.getExchangeRateHistory()
}
},
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.formData.touch = null
},
addExchangeTickerConversion() {
if (!this.exchangeData.selectedProvider) {
return
}
this.exchangeData.selectedProvider.ticker_conversion.push(
`${this.exchangeData.convertFromTicker}:${this.exchangeData.convertToTicker}`
)
this.formData.touch = null
this.exchangeData.showTickerConversion = false
},
showTickerConversionDialog(provider) {
this.exchangeData.convertFromTicker = null
this.exchangeData.convertToTicker = null
this.exchangeData.selectedProvider = provider
this.exchangeData.showTickerConversion = true
},
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
}
}
)
}
}
})
@@ -0,0 +1,30 @@
window.app.component('lnbits-admin-extensions', {
props: ['form-data'],
template: '#lnbits-admin-extensions',
mixins: [window.windowMixin],
data() {
return {
formAddExtensionsManifest: ''
}
},
methods: {
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
)
}
}
})
@@ -0,0 +1,44 @@
window.app.component('lnbits-admin-fiat-providers', {
props: ['form-data'],
template: '#lnbits-admin-fiat-providers',
mixins: [window.windowMixin],
data() {
return {
formAddStripeUser: '',
hideInputToggle: true
}
},
methods: {
addStripeAllowedUser() {
const addUser = this.formAddStripeUser || ''
if (
addUser.length &&
!this.formData.stripe_limits.allowed_users.includes(addUser)
) {
this.formData.stripe_limits.allowed_users = [
...this.formData.stripe_limits.allowed_users,
addUser
]
this.formAddStripeUser = ''
}
},
removeStripeAllowedUser(user) {
this.formData.stripe_limits.allowed_users =
this.formData.stripe_limits.allowed_users.filter(u => u !== user)
},
checkFiatProvider(providerName) {
LNbits.api
.request('PUT', `/api/v1/fiat/check/${providerName}`)
.then(response => {
response
const data = response.data
Quasar.Notify.create({
type: data.success ? 'positive' : 'warning',
message: data.message,
icon: null
})
})
.catch(LNbits.utils.notifyApiError)
}
}
})
@@ -0,0 +1,254 @@
window.app.component('lnbits-admin-funding-sources', {
template: '#lnbits-admin-funding-sources',
mixins: [window.windowMixin],
props: ['form-data', 'allowed-funding-sources'],
methods: {
getFundingSourceLabel(item) {
const fundingSource = this.rawFundingSources.find(
fundingSource => fundingSource[0] === item
)
return fundingSource ? fundingSource[1] : item
},
showQRValue(value) {
this.qrValue = value
this.showQRDialog = true
}
},
computed: {
fundingSources() {
let tmp = []
for (const [key, _, obj] of this.rawFundingSources) {
const tmpObj = {}
if (obj !== null) {
for (let [k, v] of Object.entries(obj)) {
tmpObj[k] =
typeof v === 'string' ? {label: v, value: null} : v || {}
}
}
tmp.push([key, tmpObj])
}
return new Map(tmp)
},
sortedAllowedFundingSources() {
return this.allowedFundingSources.sort()
}
},
data() {
return {
hideInput: true,
showQRDialog: false,
qrValue: '',
rawFundingSources: [
['VoidWallet', 'Void Wallet', null],
[
'FakeWallet',
'Fake Wallet',
{
fake_wallet_secret: 'Secret',
lnbits_denomination: '"sats" or 3 Letter Custom Denomination'
}
],
[
'CLNRestWallet',
'Core Lightning Rest (plugin)',
{
clnrest_url: 'Endpoint',
clnrest_ca: 'ca.pem',
clnrest_cert: 'server.pem',
clnrest_readonly_rune: 'Rune used for readonly requests',
clnrest_invoice_rune: 'Rune used for creating invoices',
clnrest_pay_rune: 'Rune used for paying invoices using pay',
clnrest_renepay_rune: 'Rune used for paying invoices using renepay',
clnrest_last_pay_index:
'Ignores any invoices paid prior to or including this index. 0 is equivalent to not specifying and negative value is invalid.',
clnrest_nodeid: 'Node id'
}
],
[
'CoreLightningWallet',
'Core Lightning',
{
corelightning_rpc: 'Endpoint',
corelightning_pay_command: 'Custom Pay Command'
}
],
[
'CoreLightningRestWallet',
'Core Lightning Rest (legacy)',
{
corelightning_rest_url: 'Endpoint',
corelightning_rest_cert: 'Certificate',
corelightning_rest_macaroon: 'Macaroon'
}
],
[
'LndRestWallet',
'Lightning Network Daemon (LND Rest)',
{
lnd_rest_endpoint: 'Endpoint',
lnd_rest_cert: 'Certificate',
lnd_rest_macaroon: 'Macaroon',
lnd_rest_macaroon_encrypted: 'Encrypted Macaroon',
lnd_rest_route_hints: 'Enable Route Hints',
lnd_rest_allow_self_payment: 'Allow Self Payment'
}
],
[
'LndWallet',
'Lightning Network Daemon (LND)',
{
lnd_grpc_endpoint: 'Endpoint',
lnd_grpc_cert: 'Certificate',
lnd_grpc_port: 'Port',
lnd_grpc_macaroon: 'GRPC Macaroon',
lnd_grpc_invoice_macaroon: 'GRPC Invoice Macaroon',
lnd_grpc_admin_macaroon: 'GRPC Admin Macaroon',
lnd_grpc_macaroon_encrypted: 'Encrypted Macaroon'
}
],
[
'LnTipsWallet',
'LN.Tips',
{
lntips_api_endpoint: 'Endpoint',
lntips_api_key: 'API Key'
}
],
[
'LNPayWallet',
'LN Pay',
{
lnpay_api_endpoint: 'Endpoint',
lnpay_api_key: 'API Key',
lnpay_wallet_key: 'Wallet Key'
}
],
[
'EclairWallet',
'Eclair (ACINQ)',
{
eclair_url: 'URL',
eclair_pass: 'Password'
}
],
[
'LNbitsWallet',
'LNbits',
{
lnbits_endpoint: 'Endpoint',
lnbits_key: 'Admin Key'
}
],
[
'BlinkWallet',
'Blink',
{
blink_api_endpoint: 'Endpoint',
blink_ws_endpoint: 'WebSocket',
blink_token: 'Key'
}
],
[
'AlbyWallet',
'Alby',
{
alby_api_endpoint: 'Endpoint',
alby_access_token: 'Key'
}
],
[
'BoltzWallet',
'Boltz',
{
boltz_client_endpoint: 'Endpoint',
boltz_client_macaroon: 'Admin Macaroon path or hex',
boltz_client_cert: 'Certificate path or hex',
boltz_client_wallet: 'Wallet Name',
boltz_client_password: 'Wallet Password (can be empty)',
boltz_mnemonic: {
label: 'Liquid mnemonic (copy into greenwallet)',
readonly: true,
copy: true,
qrcode: true
}
}
],
[
'ZBDWallet',
'ZBD',
{
zbd_api_endpoint: 'Endpoint',
zbd_api_key: 'Key'
}
],
[
'PhoenixdWallet',
'Phoenixd',
{
phoenixd_api_endpoint: 'Endpoint',
phoenixd_api_password: 'Key'
}
],
[
'OpenNodeWallet',
'OpenNode',
{
opennode_api_endpoint: 'Endpoint',
opennode_key: 'Key'
}
],
[
'ClicheWallet',
'Cliche (NBD)',
{
cliche_endpoint: 'Endpoint'
}
],
[
'SparkWallet',
'Spark',
{
spark_url: 'Endpoint',
spark_token: 'Token'
}
],
[
'NWCWallet',
'Nostr Wallet Connect',
{
nwc_pairing_url: 'Pairing URL'
}
],
[
'BreezSdkWallet',
'Breez SDK',
{
breez_api_key: 'Breez API Key',
breez_greenlight_seed: 'Greenlight Seed',
breez_greenlight_device_key: 'Greenlight Device Key',
breez_greenlight_device_cert: 'Greenlight Device Cert',
breez_greenlight_invite_code: 'Greenlight Invite Code'
}
],
[
'StrikeWallet',
'Strike (alpha)',
{
strike_api_endpoint: 'API Endpoint',
strike_api_key: 'API Key'
}
],
[
'BreezLiquidSdkWallet',
'Breez Liquid SDK',
{
breez_liquid_api_key: 'Breez API Key (can be empty)',
breez_liquid_seed: 'Liquid seed phrase',
breez_liquid_fee_offset_sat:
'Offset amount in sats to increase fee limit'
}
]
]
}
}
})
@@ -0,0 +1,24 @@
window.app.component('lnbits-admin-funding', {
props: ['is-super-user', 'form-data', 'settings'],
template: '#lnbits-admin-funding',
mixins: [window.windowMixin],
data() {
return {
auditData: []
}
},
created() {
this.getAudit()
},
methods: {
getAudit() {
LNbits.api
// TODO: should not use admin key here
.request('GET', '/admin/api/v1/audit', this.g.user.wallets[0].adminkey)
.then(response => {
this.auditData = response.data
})
.catch(LNbits.utils.notifyApiError)
}
}
})
@@ -0,0 +1,74 @@
window.app.component('lnbits-admin-library', {
props: ['form-data'],
template: '#lnbits-admin-library',
mixins: [window.windowMixin],
data() {
return {
library_images: []
}
},
async created() {
await this.getUploadedImages()
},
methods: {
onImageInput(e) {
const file = e.target.files[0]
if (file) {
this.uploadImage(file)
}
},
uploadImage(file) {
const formData = new FormData()
formData.append('file', file)
LNbits.api
.request(
'POST',
'/admin/api/v1/images',
this.g.user.wallets[0].adminkey,
formData,
{headers: {'Content-Type': 'multipart/form-data'}}
)
.then(() => {
this.$q.notify({
type: 'positive',
message: 'Image uploaded!',
icon: null
})
this.getUploadedImages()
})
.catch(LNbits.utils.notifyApiError)
},
getUploadedImages() {
LNbits.api
.request('GET', '/admin/api/v1/images', this.g.user.wallets[0].inkey)
.then(response => {
this.library_images = response.data.map(image => ({
...image,
url: `${window.origin}/${image.directory}/${image.filename}`
}))
})
.catch(LNbits.utils.notifyApiError)
},
deleteImage(filename) {
LNbits.utils
.confirmDialog('Are you sure you want to delete this image?')
.onOk(() => {
LNbits.api
.request(
'DELETE',
`/admin/api/v1/images/${filename}`,
this.g.user.wallets[0].adminkey
)
.then(() => {
this.$q.notify({
type: 'positive',
message: 'Image deleted!',
icon: null
})
this.getUploadedImages()
})
.catch(LNbits.utils.notifyApiError)
})
}
}
})
@@ -0,0 +1,67 @@
window.app.component('lnbits-admin-notifications', {
props: ['form-data'],
template: '#lnbits-admin-notifications',
mixins: [window.windowMixin],
data() {
return {
nostrNotificationIdentifier: '',
emailNotificationAddress: ''
}
},
methods: {
sendTestEmail() {
LNbits.api
.request(
'GET',
'/admin/api/v1/testemail',
this.g.user.wallets[0].adminkey
)
.then(response => {
if (response.data.status === 'error') {
throw new Error(response.data.message)
}
this.$q.notify({
message: 'Test email sent!',
color: 'positive'
})
})
.catch(error => {
this.$q.notify({
message: error.message,
color: 'negative'
})
})
},
addNostrNotificationIdentifier() {
const identifer = this.nostrNotificationIdentifier.trim()
const identifiers = this.formData.lnbits_nostr_notifications_identifiers
if (identifer && identifer.length && !identifiers.includes(identifer)) {
this.formData.lnbits_nostr_notifications_identifiers = [
...identifiers,
identifer
]
this.nostrNotificationIdentifier = ''
}
},
removeNostrNotificationIdentifier(identifer) {
const identifiers = this.formData.lnbits_nostr_notifications_identifiers
this.formData.lnbits_nostr_notifications_identifiers = identifiers.filter(
m => m !== identifer
)
},
addEmailNotificationAddress() {
const email = this.emailNotificationAddress.trim()
const emails = this.formData.lnbits_email_notifications_to_emails
if (email && email.length && !emails.includes(email)) {
this.formData.lnbits_email_notifications_to_emails = [...emails, email]
this.emailNotificationAddress = ''
}
},
removeEmailNotificationAddress(email) {
const emails = this.formData.lnbits_email_notifications_to_emails
this.formData.lnbits_email_notifications_to_emails = emails.filter(
m => m !== email
)
}
}
})
@@ -0,0 +1,111 @@
window.app.component('lnbits-admin-security', {
props: ['form-data'],
template: '#lnbits-admin-security',
mixins: [window.windowMixin],
data() {
return {
logs: [],
formBlockedIPs: '',
serverlogEnabled: false,
nostrAcceptedUrl: '',
formAllowedIPs: '',
formCallbackUrlRule: ''
}
},
created() {},
methods: {
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
)
},
addCallbackUrlRule() {
const allowedCallback = this.formCallbackUrlRule.trim()
const allowedCallbacks = this.formData.lnbits_callback_url_rules
if (
allowedCallback &&
allowedCallback.length &&
!allowedCallbacks.includes(allowedCallback)
) {
this.formData.lnbits_callback_url_rules = [
...allowedCallbacks,
allowedCallback
]
this.formCallbackUrlRule = ''
}
},
removeCallbackUrlRule(allowedCallback) {
const allowedCallbacks = this.formData.lnbits_callback_url_rules
this.formData.lnbits_callback_url_rules = allowedCallbacks.filter(
a => a !== allowedCallback
)
},
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)
},
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()
}
}
}
})
@@ -0,0 +1,13 @@
window.app.component('lnbits-admin-server', {
props: ['form-data'],
template: '#lnbits-admin-server',
mixins: [window.windowMixin],
data() {
return {
currencies: []
}
},
async created() {
this.currencies = await LNbits.api.getCurrencies()
}
})
@@ -0,0 +1,46 @@
window.app.component('lnbits-admin-site-customisation', {
props: ['form-data'],
template: '#lnbits-admin-site-customisation',
mixins: [window.windowMixin],
data() {
return {
lnbits_theme_options: [
'classic',
'bitcoin',
'flamingo',
'cyber',
'freedom',
'mint',
'autumn',
'monochrome',
'salvador'
],
colors: [
'primary',
'secondary',
'accent',
'positive',
'negative',
'info',
'warning',
'red',
'yellow',
'orange'
],
reactionOptions: [
'none',
'confettiBothSides',
'confettiFireworks',
'confettiStars',
'confettiTop'
],
globalBorderOptions: [
'retro-border',
'hard-border',
'neon-border',
'no-border'
]
}
},
methods: {}
})
@@ -0,0 +1,37 @@
window.app.component('lnbits-admin-users', {
props: ['form-data'],
template: '#lnbits-admin-users',
mixins: [window.windowMixin],
data() {
return {
formAddUser: '',
formAddAdmin: ''
}
},
methods: {
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)
},
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)
}
}
})