693 lines
20 KiB
JavaScript
693 lines
20 KiB
JavaScript
window.app = Vue.createApp({
|
|
el: '#vue',
|
|
mixins: [windowMixin],
|
|
delimiters: ['${', '}'],
|
|
data: function () {
|
|
return {
|
|
// DCA Admin Data
|
|
dcaClients: [],
|
|
deposits: [],
|
|
|
|
// Table configurations
|
|
clientsTable: {
|
|
columns: [
|
|
{ name: 'user_id', align: 'left', label: 'User ID', field: 'user_id' },
|
|
{ name: 'wallet_id', align: 'left', label: 'Wallet ID', field: 'wallet_id' },
|
|
{ name: 'dca_mode', align: 'left', label: 'DCA Mode', field: 'dca_mode' },
|
|
{ name: 'fixed_mode_daily_limit', align: 'left', label: 'Daily Limit', field: 'fixed_mode_daily_limit' },
|
|
{ name: 'status', align: 'left', label: 'Status', field: 'status' }
|
|
],
|
|
pagination: {
|
|
rowsPerPage: 10
|
|
}
|
|
},
|
|
depositsTable: {
|
|
columns: [
|
|
{ name: 'client_id', align: 'left', label: 'Client', field: 'client_id' },
|
|
{ name: 'amount', align: 'left', label: 'Amount', field: 'amount' },
|
|
{ name: 'currency', align: 'left', label: 'Currency', field: 'currency' },
|
|
{ name: 'status', align: 'left', label: 'Status', field: 'status' },
|
|
{ name: 'created_at', align: 'left', label: 'Created', field: 'created_at' },
|
|
{ name: 'notes', align: 'left', label: 'Notes', field: 'notes' }
|
|
],
|
|
pagination: {
|
|
rowsPerPage: 10
|
|
}
|
|
},
|
|
|
|
// Dialog states
|
|
depositFormDialog: {
|
|
show: false,
|
|
data: {
|
|
currency: 'GTQ'
|
|
}
|
|
},
|
|
clientDetailsDialog: {
|
|
show: false,
|
|
data: null,
|
|
balance: null
|
|
},
|
|
|
|
// Quick deposit form
|
|
quickDepositForm: {
|
|
client_id: '',
|
|
amount: null,
|
|
notes: ''
|
|
},
|
|
|
|
// Polling status
|
|
lastPollTime: null,
|
|
testingConnection: false,
|
|
runningManualPoll: false,
|
|
lamassuConfig: null,
|
|
|
|
// Config dialog
|
|
configDialog: {
|
|
show: false,
|
|
data: {
|
|
host: '',
|
|
port: 5432,
|
|
database_name: '',
|
|
username: '',
|
|
password: '',
|
|
// SSH Tunnel settings
|
|
use_ssh_tunnel: false,
|
|
ssh_host: '',
|
|
ssh_port: 22,
|
|
ssh_username: '',
|
|
ssh_password: '',
|
|
ssh_private_key: ''
|
|
}
|
|
},
|
|
|
|
// Options
|
|
currencyOptions: [
|
|
{ label: 'GTQ', value: 'GTQ' },
|
|
{ label: 'USD', value: 'USD' }
|
|
],
|
|
|
|
// Legacy data (keep for backward compatibility)
|
|
invoiceAmount: 10,
|
|
qrValue: 'lnurlpay',
|
|
myex: [],
|
|
myexTable: {
|
|
columns: [
|
|
{ name: 'id', align: 'left', label: 'ID', field: 'id' },
|
|
{ name: 'name', align: 'left', label: 'Name', field: 'name' },
|
|
{ name: 'wallet', align: 'left', label: 'Wallet', field: 'wallet' },
|
|
{ name: 'total', align: 'left', label: 'Total sent/received', field: 'total' }
|
|
],
|
|
pagination: {
|
|
rowsPerPage: 10
|
|
}
|
|
},
|
|
formDialog: {
|
|
show: false,
|
|
data: {},
|
|
advanced: {}
|
|
},
|
|
urlDialog: {
|
|
show: false,
|
|
data: {}
|
|
}
|
|
}
|
|
},
|
|
|
|
///////////////////////////////////////////////////
|
|
////////////////METHODS FUNCTIONS//////////////////
|
|
///////////////////////////////////////////////////
|
|
|
|
methods: {
|
|
// Utility Methods
|
|
formatCurrency(amount) {
|
|
if (!amount) return 'Q 0.00';
|
|
|
|
return new Intl.NumberFormat('es-GT', {
|
|
style: 'currency',
|
|
currency: 'GTQ',
|
|
}).format(amount);
|
|
},
|
|
|
|
formatDate(dateString) {
|
|
if (!dateString) return ''
|
|
return new Date(dateString).toLocaleDateString()
|
|
},
|
|
|
|
// Configuration Methods
|
|
async getLamassuConfig() {
|
|
try {
|
|
const {data} = await LNbits.api.request(
|
|
'GET',
|
|
'/myextension/api/v1/dca/config',
|
|
this.g.user.wallets[0].inkey
|
|
)
|
|
this.lamassuConfig = data
|
|
} catch (error) {
|
|
// It's OK if no config exists yet
|
|
this.lamassuConfig = null
|
|
}
|
|
},
|
|
|
|
async saveConfiguration() {
|
|
try {
|
|
const data = {
|
|
host: this.configDialog.data.host,
|
|
port: this.configDialog.data.port,
|
|
database_name: this.configDialog.data.database_name,
|
|
username: this.configDialog.data.username,
|
|
password: this.configDialog.data.password,
|
|
// SSH Tunnel settings
|
|
use_ssh_tunnel: this.configDialog.data.use_ssh_tunnel,
|
|
ssh_host: this.configDialog.data.ssh_host,
|
|
ssh_port: this.configDialog.data.ssh_port,
|
|
ssh_username: this.configDialog.data.ssh_username,
|
|
ssh_password: this.configDialog.data.ssh_password,
|
|
ssh_private_key: this.configDialog.data.ssh_private_key
|
|
}
|
|
|
|
const {data: config} = await LNbits.api.request(
|
|
'POST',
|
|
'/myextension/api/v1/dca/config',
|
|
this.g.user.wallets[0].adminkey,
|
|
data
|
|
)
|
|
|
|
this.lamassuConfig = config
|
|
this.closeConfigDialog()
|
|
|
|
this.$q.notify({
|
|
type: 'positive',
|
|
message: 'Database configuration saved successfully',
|
|
timeout: 5000
|
|
})
|
|
} catch (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
}
|
|
},
|
|
|
|
closeConfigDialog() {
|
|
this.configDialog.show = false
|
|
this.configDialog.data = {
|
|
host: '',
|
|
port: 5432,
|
|
database_name: '',
|
|
username: '',
|
|
password: '',
|
|
// SSH Tunnel settings
|
|
use_ssh_tunnel: false,
|
|
ssh_host: '',
|
|
ssh_port: 22,
|
|
ssh_username: '',
|
|
ssh_password: '',
|
|
ssh_private_key: ''
|
|
}
|
|
},
|
|
|
|
// DCA Client Methods
|
|
async getDcaClients() {
|
|
try {
|
|
const { data } = await LNbits.api.request(
|
|
'GET',
|
|
'/myextension/api/v1/dca/clients',
|
|
this.g.user.wallets[0].inkey
|
|
)
|
|
this.dcaClients = data
|
|
} catch (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
}
|
|
},
|
|
|
|
// Test Client Creation (temporary for testing)
|
|
async createTestClient() {
|
|
try {
|
|
const testData = {
|
|
user_id: this.g.user.id,
|
|
wallet_id: this.g.user.wallets[0].id,
|
|
dca_mode: 'flow'
|
|
}
|
|
|
|
const { data: newClient } = await LNbits.api.request(
|
|
'POST',
|
|
'/myextension/api/v1/dca/clients',
|
|
this.g.user.wallets[0].adminkey,
|
|
testData
|
|
)
|
|
|
|
this.dcaClients.push(newClient)
|
|
|
|
this.$q.notify({
|
|
type: 'positive',
|
|
message: 'Test client created successfully!',
|
|
timeout: 5000
|
|
})
|
|
} catch (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
}
|
|
},
|
|
|
|
// Quick Deposit Methods
|
|
async sendQuickDeposit() {
|
|
try {
|
|
const data = {
|
|
client_id: this.quickDepositForm.client_id,
|
|
amount: this.quickDepositForm.amount,
|
|
currency: 'GTQ',
|
|
notes: this.quickDepositForm.notes
|
|
}
|
|
|
|
const { data: newDeposit } = await LNbits.api.request(
|
|
'POST',
|
|
'/myextension/api/v1/dca/deposits',
|
|
this.g.user.wallets[0].adminkey,
|
|
data
|
|
)
|
|
|
|
this.deposits.unshift(newDeposit)
|
|
|
|
// Reset form
|
|
this.quickDepositForm = {
|
|
client_id: '',
|
|
amount: null,
|
|
notes: ''
|
|
}
|
|
|
|
this.$q.notify({
|
|
type: 'positive',
|
|
message: 'Deposit created successfully',
|
|
timeout: 5000
|
|
})
|
|
} catch (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
}
|
|
},
|
|
|
|
async viewClientDetails(client) {
|
|
try {
|
|
const { data: balance } = await LNbits.api.request(
|
|
'GET',
|
|
`/myextension/api/v1/dca/clients/${client.id}/balance`,
|
|
this.g.user.wallets[0].inkey
|
|
)
|
|
this.clientDetailsDialog.data = client
|
|
this.clientDetailsDialog.balance = balance
|
|
this.clientDetailsDialog.show = true
|
|
} catch (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
}
|
|
},
|
|
|
|
// Deposit Methods
|
|
async getDeposits() {
|
|
try {
|
|
const { data } = await LNbits.api.request(
|
|
'GET',
|
|
'/myextension/api/v1/dca/deposits',
|
|
this.g.user.wallets[0].inkey
|
|
)
|
|
this.deposits = data
|
|
} catch (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
}
|
|
},
|
|
|
|
addDepositDialog(client) {
|
|
this.depositFormDialog.data = {
|
|
client_id: client.id,
|
|
client_name: `${client.user_id.substring(0, 8)}...`,
|
|
currency: 'GTQ'
|
|
}
|
|
this.depositFormDialog.show = true
|
|
},
|
|
|
|
async sendDepositData() {
|
|
try {
|
|
const data = {
|
|
client_id: this.depositFormDialog.data.client_id,
|
|
amount: this.depositFormDialog.data.amount,
|
|
currency: this.depositFormDialog.data.currency,
|
|
notes: this.depositFormDialog.data.notes
|
|
}
|
|
|
|
if (this.depositFormDialog.data.id) {
|
|
// Update existing deposit (mainly for notes/status)
|
|
const { data: updatedDeposit } = await LNbits.api.request(
|
|
'PUT',
|
|
`/myextension/api/v1/dca/deposits/${this.depositFormDialog.data.id}`,
|
|
this.g.user.wallets[0].adminkey,
|
|
{ status: this.depositFormDialog.data.status, notes: data.notes }
|
|
)
|
|
const index = this.deposits.findIndex(d => d.id === updatedDeposit.id)
|
|
if (index !== -1) {
|
|
this.deposits.splice(index, 1, updatedDeposit)
|
|
}
|
|
} else {
|
|
// Create new deposit
|
|
const { data: newDeposit } = await LNbits.api.request(
|
|
'POST',
|
|
'/myextension/api/v1/dca/deposits',
|
|
this.g.user.wallets[0].adminkey,
|
|
data
|
|
)
|
|
this.deposits.unshift(newDeposit)
|
|
}
|
|
|
|
this.closeDepositFormDialog()
|
|
this.$q.notify({
|
|
type: 'positive',
|
|
message: this.depositFormDialog.data.id ? 'Deposit updated successfully' : 'Deposit created successfully',
|
|
timeout: 5000
|
|
})
|
|
} catch (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
}
|
|
},
|
|
|
|
closeDepositFormDialog() {
|
|
this.depositFormDialog.show = false
|
|
this.depositFormDialog.data = {
|
|
currency: 'GTQ'
|
|
}
|
|
},
|
|
|
|
async confirmDeposit(deposit) {
|
|
try {
|
|
await LNbits.utils
|
|
.confirmDialog('Confirm that this deposit has been physically placed in the ATM machine?')
|
|
.onOk(async () => {
|
|
const { data: updatedDeposit } = await LNbits.api.request(
|
|
'PUT',
|
|
`/myextension/api/v1/dca/deposits/${deposit.id}/status`,
|
|
this.g.user.wallets[0].adminkey,
|
|
{ status: 'confirmed', notes: 'Confirmed by admin - money placed in machine' }
|
|
)
|
|
const index = this.deposits.findIndex(d => d.id === deposit.id)
|
|
if (index !== -1) {
|
|
this.deposits.splice(index, 1, updatedDeposit)
|
|
}
|
|
this.$q.notify({
|
|
type: 'positive',
|
|
message: 'Deposit confirmed! DCA is now active for this client.',
|
|
timeout: 5000
|
|
})
|
|
})
|
|
} catch (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
}
|
|
},
|
|
|
|
editDeposit(deposit) {
|
|
this.depositFormDialog.data = { ...deposit }
|
|
this.depositFormDialog.show = true
|
|
},
|
|
|
|
// Export Methods
|
|
async exportClientsCSV() {
|
|
await LNbits.utils.exportCSV(this.clientsTable.columns, this.dcaClients)
|
|
},
|
|
|
|
async exportDepositsCSV() {
|
|
await LNbits.utils.exportCSV(this.depositsTable.columns, this.deposits)
|
|
},
|
|
|
|
// Polling Methods
|
|
async testDatabaseConnection() {
|
|
this.testingConnection = true
|
|
try {
|
|
const {data} = await LNbits.api.request(
|
|
'POST',
|
|
'/myextension/api/v1/dca/test-connection',
|
|
this.g.user.wallets[0].adminkey
|
|
)
|
|
|
|
this.$q.notify({
|
|
type: data.success ? 'positive' : 'negative',
|
|
message: data.message,
|
|
timeout: 5000
|
|
})
|
|
} catch (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
} finally {
|
|
this.testingConnection = false
|
|
}
|
|
},
|
|
|
|
async manualPoll() {
|
|
this.runningManualPoll = true
|
|
try {
|
|
const {data} = await LNbits.api.request(
|
|
'POST',
|
|
'/myextension/api/v1/dca/manual-poll',
|
|
this.g.user.wallets[0].adminkey
|
|
)
|
|
|
|
this.lastPollTime = new Date().toLocaleString()
|
|
this.$q.notify({
|
|
type: 'positive',
|
|
message: `Manual poll completed. Found ${data.transactions_processed} new transactions.`,
|
|
timeout: 5000
|
|
})
|
|
|
|
// Refresh data
|
|
await this.getDeposits()
|
|
} catch (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
} finally {
|
|
this.runningManualPoll = false
|
|
}
|
|
},
|
|
|
|
// Legacy Methods (keep for backward compatibility)
|
|
async closeFormDialog() {
|
|
this.formDialog.show = false
|
|
this.formDialog.data = {}
|
|
},
|
|
async getMyExtensions() {
|
|
await LNbits.api
|
|
.request(
|
|
'GET',
|
|
'/myextension/api/v1/myex',
|
|
this.g.user.wallets[0].inkey
|
|
)
|
|
.then(response => {
|
|
this.myex = response.data
|
|
})
|
|
.catch(err => {
|
|
LNbits.utils.notifyApiError(err)
|
|
})
|
|
},
|
|
async sendMyExtensionData() {
|
|
const data = {
|
|
name: this.formDialog.data.name,
|
|
lnurlwithdrawamount: this.formDialog.data.lnurlwithdrawamount,
|
|
lnurlpayamount: this.formDialog.data.lnurlpayamount
|
|
}
|
|
const wallet = _.findWhere(this.g.user.wallets, {
|
|
id: this.formDialog.data.wallet
|
|
})
|
|
if (this.formDialog.data.id) {
|
|
data.id = this.formDialog.data.id
|
|
data.total = this.formDialog.data.total
|
|
await this.updateMyExtension(wallet, data)
|
|
} else {
|
|
await this.createMyExtension(wallet, data)
|
|
}
|
|
},
|
|
|
|
async updateMyExtensionForm(tempId) {
|
|
const myextension = _.findWhere(this.myex, { id: tempId })
|
|
this.formDialog.data = {
|
|
...myextension
|
|
}
|
|
if (this.formDialog.data.tip_wallet != '') {
|
|
this.formDialog.advanced.tips = true
|
|
}
|
|
if (this.formDialog.data.withdrawlimit >= 1) {
|
|
this.formDialog.advanced.otc = true
|
|
}
|
|
this.formDialog.show = true
|
|
},
|
|
async createMyExtension(wallet, data) {
|
|
data.wallet = wallet.id
|
|
await LNbits.api
|
|
.request('POST', '/myextension/api/v1/myex', wallet.adminkey, data)
|
|
.then(response => {
|
|
this.myex.push(response.data)
|
|
this.closeFormDialog()
|
|
})
|
|
.catch(error => {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
},
|
|
|
|
async updateMyExtension(wallet, data) {
|
|
data.wallet = wallet.id
|
|
await LNbits.api
|
|
.request(
|
|
'PUT',
|
|
`/myextension/api/v1/myex/${data.id}`,
|
|
wallet.adminkey,
|
|
data
|
|
)
|
|
.then(response => {
|
|
this.myex = _.reject(this.myex, obj => obj.id == data.id)
|
|
this.myex.push(response.data)
|
|
this.closeFormDialog()
|
|
})
|
|
.catch(error => {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
},
|
|
async deleteMyExtension(tempId) {
|
|
var myextension = _.findWhere(this.myex, { id: tempId })
|
|
const wallet = _.findWhere(this.g.user.wallets, {
|
|
id: myextension.wallet
|
|
})
|
|
await LNbits.utils
|
|
.confirmDialog('Are you sure you want to delete this MyExtension?')
|
|
.onOk(function () {
|
|
LNbits.api
|
|
.request(
|
|
'DELETE',
|
|
'/myextension/api/v1/myex/' + tempId,
|
|
wallet.adminkey
|
|
)
|
|
.then(() => {
|
|
this.myex = _.reject(this.myex, function (obj) {
|
|
return obj.id === myextension.id
|
|
})
|
|
})
|
|
.catch(error => {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
})
|
|
},
|
|
|
|
async exportCSV() {
|
|
await LNbits.utils.exportCSV(this.myexTable.columns, this.myex)
|
|
},
|
|
async itemsArray(tempId) {
|
|
const myextension = _.findWhere(this.myex, { id: tempId })
|
|
return [...myextension.itemsMap.values()]
|
|
},
|
|
async openformDialog(id) {
|
|
const [tempId, itemId] = id.split(':')
|
|
const myextension = _.findWhere(this.myex, { id: tempId })
|
|
if (itemId) {
|
|
const item = myextension.itemsMap.get(id)
|
|
this.formDialog.data = {
|
|
...item,
|
|
myextension: tempId
|
|
}
|
|
} else {
|
|
this.formDialog.data.myextension = tempId
|
|
}
|
|
this.formDialog.data.currency = myextension.currency
|
|
this.formDialog.show = true
|
|
},
|
|
async openUrlDialog(tempid) {
|
|
this.urlDialog.data = _.findWhere(this.myex, { id: tempid })
|
|
this.qrValue = this.urlDialog.data.lnurlpay
|
|
|
|
// Connecting to our websocket fired in tasks.py
|
|
this.connectWebocket(this.urlDialog.data.id)
|
|
|
|
this.urlDialog.show = true
|
|
},
|
|
async closeformDialog() {
|
|
this.formDialog.show = false
|
|
this.formDialog.data = {}
|
|
},
|
|
async createInvoice(tempid) {
|
|
///////////////////////////////////////////////////
|
|
///Simple call to the api to create an invoice/////
|
|
///////////////////////////////////////////////////
|
|
myex = _.findWhere(this.myex, { id: tempid })
|
|
const wallet = _.findWhere(this.g.user.wallets, { id: myex.wallet })
|
|
const data = {
|
|
myextension_id: tempid,
|
|
amount: this.invoiceAmount,
|
|
memo: 'MyExtension - ' + myex.name
|
|
}
|
|
await LNbits.api
|
|
.request('POST', `/myextension/api/v1/myex/payment`, wallet.inkey, data)
|
|
.then(response => {
|
|
this.qrValue = response.data.payment_request
|
|
this.connectWebocket(wallet.inkey)
|
|
})
|
|
.catch(error => {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
},
|
|
connectWebocket(myextension_id) {
|
|
//////////////////////////////////////////////////
|
|
///wait for pay action to happen and do a thing////
|
|
///////////////////////////////////////////////////
|
|
if (location.protocol !== 'http:') {
|
|
localUrl =
|
|
'wss://' +
|
|
document.domain +
|
|
':' +
|
|
location.port +
|
|
'/api/v1/ws/' +
|
|
myextension_id
|
|
} else {
|
|
localUrl =
|
|
'ws://' +
|
|
document.domain +
|
|
':' +
|
|
location.port +
|
|
'/api/v1/ws/' +
|
|
myextension_id
|
|
}
|
|
this.connection = new WebSocket(localUrl)
|
|
this.connection.onmessage = () => {
|
|
this.urlDialog.show = false
|
|
}
|
|
}
|
|
},
|
|
///////////////////////////////////////////////////
|
|
//////LIFECYCLE FUNCTIONS RUNNING ON PAGE LOAD/////
|
|
///////////////////////////////////////////////////
|
|
async created() {
|
|
// Load DCA admin data
|
|
await Promise.all([
|
|
this.getLamassuConfig(),
|
|
this.getDcaClients(),
|
|
this.getDeposits()
|
|
])
|
|
|
|
// Legacy data loading
|
|
await this.getMyExtensions()
|
|
},
|
|
|
|
computed: {
|
|
isConfigFormValid() {
|
|
const data = this.configDialog.data
|
|
|
|
// Basic database fields are required
|
|
const basicValid = data.host && data.database_name && data.username
|
|
|
|
// If SSH tunnel is enabled, validate SSH fields
|
|
if (data.use_ssh_tunnel) {
|
|
const sshValid = data.ssh_host && data.ssh_username &&
|
|
(data.ssh_password || data.ssh_private_key)
|
|
return basicValid && sshValid
|
|
}
|
|
|
|
return basicValid
|
|
},
|
|
|
|
clientOptions() {
|
|
return this.dcaClients.map(client => ({
|
|
label: `${client.user_id.substring(0, 8)}... (${client.dca_mode})`,
|
|
value: client.id
|
|
}))
|
|
},
|
|
|
|
totalDcaBalance() {
|
|
return this.deposits
|
|
.filter(d => d.status === 'confirmed')
|
|
.reduce((total, deposit) => total + deposit.amount, 0)
|
|
}
|
|
}
|
|
})
|