Removes the test transaction button from the admin UI. The test transaction endpoint is still available in the API for development and debugging purposes.
842 lines
32 KiB
HTML
842 lines
32 KiB
HTML
<!--/////////////////////////////////////////////////-->
|
|
<!--//PAGE FOR THE DCA ADMIN EXTENSION IN LNBITS//////-->
|
|
<!--/////////////////////////////////////////////////-->
|
|
|
|
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
|
%} {% block scripts %} {{ window_vars(user) }}
|
|
<script src="{{ static_url_for('satmachineadmin/static', path='js/index.js') }}"></script>
|
|
{% endblock %} {% block page %}
|
|
<div class="row q-col-gutter-md" id="dcaAdmin">
|
|
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
|
|
<!-- Deposit Management Section -->
|
|
<q-card>
|
|
<q-card-section>
|
|
<div class="row items-center no-wrap q-mb-md">
|
|
<div class="col">
|
|
<h5 class="text-subtitle1 q-my-none">DCA Deposit Management</h5>
|
|
<p class="text-caption q-my-none">Manage fiat deposits for existing DCA clients</p>
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<!-- DCA Clients Table -->
|
|
<q-card>
|
|
<q-card-section>
|
|
<div class="row items-center no-wrap q-mb-md">
|
|
<div class="col">
|
|
<h6 class="text-subtitle2 q-my-none">Registered DCA Clients</h6>
|
|
<p class="text-caption q-my-none">Clients registered via the DCA client extension</p>
|
|
</div>
|
|
<div class="col-auto">
|
|
<q-btn flat color="grey" @click="exportClientsCSV">Export to CSV</q-btn>
|
|
</div>
|
|
</div>
|
|
<q-table
|
|
dense
|
|
flat
|
|
:rows="dcaClients"
|
|
row-key="id"
|
|
:columns="clientsTable.columns"
|
|
v-model:pagination="clientsTable.pagination"
|
|
>
|
|
<template v-slot:body="props">
|
|
<q-tr :props="props">
|
|
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
|
<div v-if="col.field == 'username'">${ col.value || 'No username' }</div>
|
|
<div v-else-if="col.field == 'user_id'">${ col.value.substring(0, 8) }...</div>
|
|
<div v-else-if="col.field == 'wallet_id'">${ col.value.substring(0, 8) }...</div>
|
|
<div v-else-if="col.field == 'status'">
|
|
<q-badge :color="col.value === 'active' ? 'green' : 'red'">
|
|
${ col.value }
|
|
</q-badge>
|
|
</div>
|
|
<div v-else-if="col.field == 'remaining_balance'">
|
|
<span :class="col.value > 0 ? 'text-green-8 text-weight-bold' : 'text-grey-6'">
|
|
${ formatCurrency(col.value || 0) }
|
|
</span>
|
|
</div>
|
|
<div v-else-if="col.field == 'fixed_mode_daily_limit' && col.value">
|
|
${ formatCurrency(col.value) }
|
|
</div>
|
|
<div v-else>${ col.value || '-' }</div>
|
|
</q-td>
|
|
<q-td auto-width>
|
|
<q-btn
|
|
flat dense size="sm" icon="account_balance_wallet"
|
|
color="primary" class="q-mr-sm"
|
|
@click="addDepositDialog(props.row)"
|
|
>
|
|
<q-tooltip>Add Deposit</q-tooltip>
|
|
</q-btn>
|
|
<q-btn
|
|
flat dense size="sm" icon="visibility"
|
|
color="blue"
|
|
@click="viewClientDetails(props.row)"
|
|
>
|
|
<q-tooltip>View Balance & Details</q-tooltip>
|
|
</q-btn>
|
|
</q-td>
|
|
</q-tr>
|
|
</template>
|
|
</q-table>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<!-- Quick Add Deposit Section -->
|
|
<q-card>
|
|
<q-card-section>
|
|
<h6 class="text-subtitle2 q-my-none">Quick Add Deposit</h6>
|
|
<p class="text-caption q-my-none">Add a new deposit for an existing client</p>
|
|
|
|
<div v-if="dcaClients.length === 0" class="q-mt-md">
|
|
<q-banner class="bg-orange-1 text-orange-9">
|
|
<template v-slot:avatar>
|
|
<q-icon name="info" color="orange" />
|
|
</template>
|
|
No DCA clients registered yet. Clients must first install and configure the DCA client extension.
|
|
</q-banner>
|
|
</div>
|
|
|
|
<q-form v-else @submit="sendQuickDeposit" class="q-gutter-md q-mt-md">
|
|
<div class="row q-gutter-md">
|
|
<div class="col">
|
|
<q-select
|
|
filled
|
|
dense
|
|
v-model="quickDepositForm.selectedClient"
|
|
:options="clientOptions"
|
|
label="Select Client *"
|
|
option-label="label"
|
|
option-value="value"
|
|
></q-select>
|
|
</div>
|
|
<div class="col">
|
|
<q-input
|
|
filled
|
|
dense
|
|
type="number"
|
|
step="0.01"
|
|
v-model.number="quickDepositForm.amount"
|
|
label="Amount (GTQ) *"
|
|
placeholder="150.00"
|
|
hint="Enter amount in GTQ (e.g., 150.00)"
|
|
></q-input>
|
|
</div>
|
|
<div class="col-auto">
|
|
<q-btn
|
|
unelevated
|
|
color="primary"
|
|
type="submit"
|
|
:disable="!quickDepositForm.selectedClient || !quickDepositForm.amount"
|
|
>Add Deposit</q-btn
|
|
>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col">
|
|
<q-input
|
|
filled
|
|
dense
|
|
type="textarea"
|
|
v-model.trim="quickDepositForm.notes"
|
|
label="Notes (Optional)"
|
|
placeholder="Optional notes about this deposit"
|
|
rows="2"
|
|
></q-input>
|
|
</div>
|
|
</div>
|
|
</q-form>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<!-- Deposits Management Section -->
|
|
<q-card>
|
|
<q-card-section>
|
|
<div class="row items-center no-wrap q-mb-md">
|
|
<div class="col">
|
|
<h6 class="text-subtitle2 q-my-none">Recent Deposits</h6>
|
|
</div>
|
|
<div class="col-auto">
|
|
<q-btn flat color="grey" @click="exportDepositsCSV">Export to CSV</q-btn>
|
|
</div>
|
|
</div>
|
|
<q-table
|
|
dense
|
|
flat
|
|
:rows="deposits"
|
|
row-key="id"
|
|
:columns="depositsTable.columns"
|
|
v-model:pagination="depositsTable.pagination"
|
|
>
|
|
<template v-slot:body="props">
|
|
<q-tr :props="props">
|
|
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
|
<div v-if="col.field == 'client_id'">${ getClientUsername(col.value) }</div>
|
|
<div v-else-if="col.field == 'amount'">${ formatCurrency(col.value) }</div>
|
|
<div v-else-if="col.field == 'status'">
|
|
<q-badge :color="col.value === 'confirmed' ? 'green' : 'orange'">
|
|
${ col.value }
|
|
</q-badge>
|
|
</div>
|
|
<div v-else-if="col.field == 'created_at'">${ formatDate(col.value) }</div>
|
|
<div v-else>${ col.value }</div>
|
|
</q-td>
|
|
<q-td auto-width>
|
|
<q-btn
|
|
v-if="props.row.status === 'pending'"
|
|
flat dense size="sm" icon="check_circle"
|
|
color="green" class="q-mr-sm"
|
|
@click="confirmDeposit(props.row)"
|
|
>
|
|
<q-tooltip>Confirm Deposit</q-tooltip>
|
|
</q-btn>
|
|
<q-btn
|
|
flat dense size="sm" icon="edit"
|
|
color="orange"
|
|
@click="editDeposit(props.row)"
|
|
>
|
|
<q-tooltip>Edit Deposit</q-tooltip>
|
|
</q-btn>
|
|
</q-td>
|
|
</q-tr>
|
|
</template>
|
|
</q-table>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<!-- Lamassu Transactions Section -->
|
|
<q-card>
|
|
<q-card-section>
|
|
<div class="row items-center no-wrap q-mb-md">
|
|
<div class="col">
|
|
<h6 class="text-subtitle2 q-my-none">Processed Lamassu Transactions</h6>
|
|
<p class="text-caption q-my-none">ATM transactions processed through DCA distribution</p>
|
|
</div>
|
|
<div class="col-auto">
|
|
<q-btn flat color="grey" @click="exportLamassuTransactionsCSV">Export to CSV</q-btn>
|
|
</div>
|
|
</div>
|
|
<q-table
|
|
dense
|
|
flat
|
|
:rows="lamassuTransactions"
|
|
row-key="id"
|
|
:columns="lamassuTransactionsTable.columns"
|
|
v-model:pagination="lamassuTransactionsTable.pagination"
|
|
>
|
|
<template v-slot:body="props">
|
|
<q-tr :props="props" class="cursor-pointer" @click="viewTransactionDistributions(props.row)">
|
|
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
|
<div v-if="col.field == 'lamassu_transaction_id'">${ col.value }</div>
|
|
<div v-else-if="col.field == 'fiat_amount'">${ formatCurrency(col.value) }</div>
|
|
<div v-else-if="col.field == 'crypto_amount'">${ formatSats(col.value) }</div>
|
|
<div v-else-if="col.field == 'commission_amount_sats'">${ formatSats(col.value) }</div>
|
|
<div v-else-if="col.field == 'base_amount_sats'">${ formatSats(col.value) }</div>
|
|
<div v-else-if="col.field == 'distributions_total_sats'">${ formatSats(col.value) }</div>
|
|
<div v-else-if="col.field == 'commission_percentage'">${ (col.value * 100).toFixed(1) }%</div>
|
|
<div v-else-if="col.field == 'effective_commission'">${ (col.value * 100).toFixed(1) }%</div>
|
|
<div v-else-if="col.field == 'discount'">${ col.value }%</div>
|
|
<div v-else-if="col.field == 'exchange_rate'">${ col.value.toLocaleString() }</div>
|
|
<div v-else-if="col.field == 'transaction_time'">${ formatDateTime(col.value) }</div>
|
|
<div v-else-if="col.field == 'processed_at'">${ formatDateTime(col.value) }</div>
|
|
<div v-else>${ col.value || '-' }</div>
|
|
</q-td>
|
|
<q-td auto-width>
|
|
<q-btn
|
|
flat dense size="sm" icon="visibility"
|
|
color="primary"
|
|
@click.stop="viewTransactionDistributions(props.row)"
|
|
>
|
|
<q-tooltip>View Distribution Details</q-tooltip>
|
|
</q-btn>
|
|
</q-td>
|
|
</q-tr>
|
|
</template>
|
|
</q-table>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
</div>
|
|
|
|
<div class="col-12 col-md-4 col-lg-5 q-gutter-y-md">
|
|
<q-card>
|
|
<q-card-section>
|
|
<h6 class="text-subtitle1 q-my-none">
|
|
{{SITE_TITLE}} DCA Admin Extension
|
|
</h6>
|
|
<p>
|
|
Dollar Cost Averaging administration for Lamassu ATM integration. <br />
|
|
Manage client deposits and DCA distribution settings.
|
|
</p>
|
|
</q-card-section>
|
|
<q-card-section class="q-pa-none">
|
|
<q-separator></q-separator>
|
|
<q-list>
|
|
<q-expansion-item
|
|
group="api"
|
|
icon="info"
|
|
label="DCA System Status"
|
|
:content-inset-level="0.5"
|
|
>
|
|
<q-card-section class="text-caption">
|
|
<div class="row">
|
|
<div class="col-6">Active Clients:</div>
|
|
<div class="col-6">${ dcaClients.filter(c => c.status === 'active').length }</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-6">Pending Deposits:</div>
|
|
<div class="col-6">${ deposits.filter(d => d.status === 'pending').length }</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-6">Total DCA Balance:</div>
|
|
<div class="col-6">${ formatCurrency(totalDcaBalance) }</div>
|
|
</div>
|
|
</q-card-section>
|
|
</q-expansion-item>
|
|
<q-separator></q-separator>
|
|
<q-expansion-item
|
|
group="api"
|
|
icon="settings"
|
|
label="Lamassu Database Config"
|
|
:content-inset-level="0.5"
|
|
>
|
|
<q-card-section class="text-caption">
|
|
<div v-if="lamassuConfig">
|
|
<p><strong>Database:</strong> ${ lamassuConfig.host }:${ lamassuConfig.port }/${ lamassuConfig.database_name }</p>
|
|
<p><strong>Status:</strong>
|
|
<q-badge v-if="lamassuConfig.test_connection_success === true" color="green">Connected</q-badge>
|
|
<q-badge v-else-if="lamassuConfig.test_connection_success === false" color="red">Failed</q-badge>
|
|
<q-badge v-else color="grey">Not tested</q-badge>
|
|
</p>
|
|
<p><strong>Last Poll:</strong> ${ lamassuConfig.last_poll_time ? formatDateTime(lamassuConfig.last_poll_time) : 'Not yet run' }</p>
|
|
<p><strong>Last Success:</strong> ${ lamassuConfig.last_successful_poll ? formatDateTime(lamassuConfig.last_successful_poll) : 'Never' }</p>
|
|
</div>
|
|
<div v-else>
|
|
<p><strong>Status:</strong> <q-badge color="orange">Not configured</q-badge></p>
|
|
</div>
|
|
|
|
<div class="q-mt-md">
|
|
<q-btn
|
|
size="sm"
|
|
color="primary"
|
|
@click="configDialog.show = true"
|
|
icon="settings"
|
|
>
|
|
Configure Database
|
|
</q-btn>
|
|
<q-btn
|
|
v-if="lamassuConfig"
|
|
size="sm"
|
|
color="accent"
|
|
@click="testDatabaseConnection"
|
|
:loading="testingConnection"
|
|
class="q-ml-sm"
|
|
>
|
|
Test Connection
|
|
</q-btn>
|
|
<q-btn
|
|
v-if="lamassuConfig"
|
|
size="sm"
|
|
color="secondary"
|
|
@click="manualPoll"
|
|
:loading="runningManualPoll"
|
|
class="q-ml-sm"
|
|
>
|
|
Manual Poll
|
|
</q-btn>
|
|
<q-btn
|
|
v-if="lamassuConfig"
|
|
size="sm"
|
|
color="deep-orange"
|
|
@click="openManualTransactionDialog"
|
|
class="q-ml-sm"
|
|
icon="build"
|
|
>
|
|
<q-tooltip>Process specific transaction by ID (bypasses dispense checks)</q-tooltip>
|
|
Manual TX
|
|
</q-btn>
|
|
</div>
|
|
</q-card-section>
|
|
</q-expansion-item>
|
|
<q-separator></q-separator>
|
|
{% include "satmachineadmin/_api_docs.html" %}
|
|
</q-list>
|
|
</q-card-section>
|
|
</q-card>
|
|
</div>
|
|
|
|
|
|
<!--/////////////////////////////////////////////////-->
|
|
<!--//////////////DEPOSIT FORM DIALOG////////////////-->
|
|
<!--/////////////////////////////////////////////////-->
|
|
|
|
<q-dialog v-model="depositFormDialog.show" position="top" @hide="closeDepositFormDialog">
|
|
<q-card class="q-pa-lg q-pt-xl" style="width: 500px">
|
|
<q-form @submit="sendDepositData" class="q-gutter-md">
|
|
<div v-if="depositFormDialog.data.client_name" class="text-h6 q-mb-md">
|
|
Deposit for: ${ depositFormDialog.data.client_name }
|
|
</div>
|
|
<q-input
|
|
filled
|
|
dense
|
|
type="number"
|
|
step="0.01"
|
|
v-model.number="depositFormDialog.data.amount"
|
|
label="Deposit Amount (GTQ) *"
|
|
placeholder="150.00"
|
|
hint="Enter amount in GTQ (e.g., 150.00)"
|
|
></q-input>
|
|
<q-select
|
|
filled
|
|
dense
|
|
emit-value
|
|
v-model="depositFormDialog.data.currency"
|
|
:options="currencyOptions"
|
|
label="Currency"
|
|
></q-select>
|
|
<q-input
|
|
filled
|
|
dense
|
|
type="textarea"
|
|
v-model.trim="depositFormDialog.data.notes"
|
|
label="Notes"
|
|
placeholder="Optional notes about this deposit"
|
|
rows="3"
|
|
></q-input>
|
|
<div class="row q-mt-lg">
|
|
<q-btn
|
|
v-if="depositFormDialog.data.id"
|
|
unelevated
|
|
color="primary"
|
|
type="submit"
|
|
>Update Deposit</q-btn
|
|
>
|
|
<q-btn
|
|
v-else
|
|
unelevated
|
|
color="primary"
|
|
:disable="!depositFormDialog.data.amount"
|
|
type="submit"
|
|
>Create Deposit</q-btn
|
|
>
|
|
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
|
|
>Cancel</q-btn
|
|
>
|
|
</div>
|
|
</q-form>
|
|
</q-card>
|
|
</q-dialog>
|
|
|
|
<!--/////////////////////////////////////////////////-->
|
|
<!--//////////////CLIENT DETAILS DIALOG//////////////-->
|
|
<!--/////////////////////////////////////////////////-->
|
|
|
|
<q-dialog v-model="clientDetailsDialog.show" position="top">
|
|
<q-card class="q-pa-lg" style="width: 600px; max-width: 90vw">
|
|
<div class="text-h6 q-mb-md">Client Details</div>
|
|
<div v-if="clientDetailsDialog.data">
|
|
<q-list>
|
|
<q-item v-if="clientDetailsDialog.data.username">
|
|
<q-item-section>
|
|
<q-item-label caption>Username</q-item-label>
|
|
<q-item-label>${ clientDetailsDialog.data.username }</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
<q-item>
|
|
<q-item-section>
|
|
<q-item-label caption>User ID</q-item-label>
|
|
<q-item-label>${ clientDetailsDialog.data.user_id }</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
<q-item>
|
|
<q-item-section>
|
|
<q-item-label caption>Wallet ID</q-item-label>
|
|
<q-item-label>${ clientDetailsDialog.data.wallet_id }</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
<q-item>
|
|
<q-item-section>
|
|
<q-item-label caption>DCA Mode</q-item-label>
|
|
<q-item-label>${ clientDetailsDialog.data.dca_mode }</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
<q-item v-if="clientDetailsDialog.data.fixed_mode_daily_limit">
|
|
<q-item-section>
|
|
<q-item-label caption>Daily Limit</q-item-label>
|
|
<q-item-label>${ formatCurrency(clientDetailsDialog.data.fixed_mode_daily_limit) }</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
<q-item>
|
|
<q-item-section>
|
|
<q-item-label caption>Balance Summary</q-item-label>
|
|
<q-item-label v-if="clientDetailsDialog.balance">
|
|
Deposits: ${ formatCurrency(clientDetailsDialog.balance.total_deposits) } |
|
|
Payments: ${ formatCurrency(clientDetailsDialog.balance.total_payments) } |
|
|
Remaining: ${ formatCurrency(clientDetailsDialog.balance.remaining_balance) }
|
|
</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
</div>
|
|
<div class="row q-mt-lg">
|
|
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
|
|
</div>
|
|
</q-card>
|
|
</q-dialog>
|
|
|
|
<!--/////////////////////////////////////////////////-->
|
|
<!--//////////////LAMASSU CONFIG DIALOG//////////////-->
|
|
<!--/////////////////////////////////////////////////-->
|
|
|
|
<q-dialog v-model="configDialog.show" position="top" @hide="closeConfigDialog">
|
|
<q-card class="q-pa-lg q-pt-xl" style="width: 600px; max-width: 90vw">
|
|
<div class="text-h6 q-mb-md">Lamassu Database Configuration</div>
|
|
<q-form @submit="saveConfiguration" class="q-gutter-md">
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="configDialog.data.host"
|
|
label="Database Host *"
|
|
placeholder="e.g., localhost or 192.168.1.100"
|
|
hint="Hostname or IP address of the Lamassu Postgres server"
|
|
></q-input>
|
|
|
|
<q-input
|
|
filled
|
|
dense
|
|
type="number"
|
|
v-model.number="configDialog.data.port"
|
|
label="Database Port *"
|
|
placeholder="5432"
|
|
hint="Postgres port (usually 5432)"
|
|
></q-input>
|
|
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="configDialog.data.database_name"
|
|
label="Database Name *"
|
|
placeholder="lamassu"
|
|
hint="Name of the Lamassu database"
|
|
></q-input>
|
|
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="configDialog.data.username"
|
|
label="Username *"
|
|
placeholder="postgres"
|
|
hint="Database username with read access"
|
|
></q-input>
|
|
|
|
<q-input
|
|
filled
|
|
dense
|
|
type="password"
|
|
v-model.trim="configDialog.data.password"
|
|
label="Password *"
|
|
placeholder="Enter database password"
|
|
hint="Database password"
|
|
></q-input>
|
|
|
|
<q-separator class="q-my-md"></q-separator>
|
|
|
|
<div class="text-h6 q-mb-md">DCA Source Wallet</div>
|
|
|
|
<q-select
|
|
filled
|
|
dense
|
|
:options="g.user.wallets"
|
|
v-model="configDialog.data.selectedWallet"
|
|
label="Source Wallet for DCA Distributions *"
|
|
option-label="name"
|
|
hint="Wallet that holds Bitcoin for distribution to DCA clients"
|
|
></q-select>
|
|
|
|
<q-select
|
|
filled
|
|
dense
|
|
:options="g.user.wallets"
|
|
v-model="configDialog.data.selectedCommissionWallet"
|
|
label="Commission Wallet (Optional)"
|
|
option-label="name"
|
|
hint="Wallet where commission earnings will be sent (leave empty to keep in source wallet)"
|
|
></q-select>
|
|
|
|
<q-separator class="q-my-md"></q-separator>
|
|
|
|
<div class="text-h6 q-mb-md">DCA Client Limits</div>
|
|
|
|
<q-input
|
|
filled
|
|
dense
|
|
type="number"
|
|
v-model.number="configDialog.data.max_daily_limit_gtq"
|
|
label="Maximum Daily Limit (GTQ) *"
|
|
placeholder="2000"
|
|
hint="Maximum daily purchase limit that Fixed Mode clients can set"
|
|
:rules="[
|
|
val => val && val > 0 || 'Maximum daily limit is required',
|
|
val => val <= 10000 || 'Maximum daily limit cannot exceed 10,000 GTQ'
|
|
]"
|
|
></q-input>
|
|
|
|
<q-separator class="q-my-md"></q-separator>
|
|
|
|
<div class="text-h6 q-mb-md">SSH Tunnel (Recommended)</div>
|
|
|
|
<div class="row items-center q-mb-md">
|
|
<q-toggle
|
|
v-model="configDialog.data.use_ssh_tunnel"
|
|
color="primary"
|
|
@click.stop
|
|
/>
|
|
<span class="q-ml-sm">Use SSH Tunnel</span>
|
|
</div>
|
|
|
|
<div v-if="configDialog.data.use_ssh_tunnel" class="q-mt-md" @click.stop>
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="configDialog.data.ssh_host"
|
|
label="SSH Host *"
|
|
placeholder="e.g., your-server.com or 192.168.1.100"
|
|
hint="SSH server hostname or IP address"
|
|
@click.stop
|
|
></q-input>
|
|
|
|
<q-input
|
|
filled
|
|
dense
|
|
type="number"
|
|
v-model.number="configDialog.data.ssh_port"
|
|
label="SSH Port *"
|
|
placeholder="22"
|
|
hint="SSH port (usually 22)"
|
|
@click.stop
|
|
></q-input>
|
|
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="configDialog.data.ssh_username"
|
|
label="SSH Username *"
|
|
placeholder="ubuntu"
|
|
hint="SSH username"
|
|
@click.stop
|
|
></q-input>
|
|
|
|
<q-input
|
|
filled
|
|
dense
|
|
type="password"
|
|
v-model.trim="configDialog.data.ssh_password"
|
|
label="SSH Password"
|
|
placeholder="SSH password (if not using key)"
|
|
hint="SSH password or leave empty to use private key"
|
|
@click.stop
|
|
></q-input>
|
|
|
|
<q-input
|
|
filled
|
|
dense
|
|
type="textarea"
|
|
v-model.trim="configDialog.data.ssh_private_key"
|
|
label="SSH Private Key"
|
|
placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"
|
|
hint="SSH private key content (alternative to password)"
|
|
rows="4"
|
|
@click.stop
|
|
></q-input>
|
|
|
|
<q-banner class="bg-green-1 text-green-9 q-mt-md">
|
|
<template v-slot:avatar>
|
|
<q-icon name="security" color="green" />
|
|
</template>
|
|
SSH tunneling keeps your database secure by avoiding direct internet exposure.
|
|
The database connection will be routed through the SSH server.
|
|
</q-banner>
|
|
</div>
|
|
|
|
<q-banner v-if="!configDialog.data.id" class="bg-blue-1 text-blue-9">
|
|
<template v-slot:avatar>
|
|
<q-icon name="info" color="blue" />
|
|
</template>
|
|
This configuration will be securely stored and used for hourly polling.
|
|
Only read access to the Lamassu database is required.
|
|
</q-banner>
|
|
|
|
<div class="row q-mt-lg">
|
|
<q-btn
|
|
unelevated
|
|
color="primary"
|
|
type="submit"
|
|
:disable="!isConfigFormValid"
|
|
@click.stop
|
|
>Save Configuration</q-btn
|
|
>
|
|
<q-btn v-close-popup flat color="grey" class="q-ml-auto" @click.stop
|
|
>Cancel</q-btn
|
|
>
|
|
</div>
|
|
</q-form>
|
|
</q-card>
|
|
</q-dialog>
|
|
|
|
<!--/////////////////////////////////////////////////-->
|
|
<!--//////////////TRANSACTION DISTRIBUTIONS DIALOG////-->
|
|
<!--/////////////////////////////////////////////////-->
|
|
|
|
<q-dialog v-model="distributionDialog.show" position="top" maximized>
|
|
<q-card class="q-pa-lg">
|
|
<div class="text-h6 q-mb-md">Transaction Distribution Details</div>
|
|
|
|
<div v-if="distributionDialog.transaction" class="q-mb-lg">
|
|
<q-list>
|
|
<q-item>
|
|
<q-item-section>
|
|
<q-item-label caption>Lamassu Transaction ID</q-item-label>
|
|
<q-item-label>${ distributionDialog.transaction.lamassu_transaction_id }</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
<q-item>
|
|
<q-item-section>
|
|
<q-item-label caption>Transaction Time</q-item-label>
|
|
<q-item-label>${ formatDateTime(distributionDialog.transaction.transaction_time) }</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
<q-item>
|
|
<q-item-section>
|
|
<q-item-label caption>Total Amount</q-item-label>
|
|
<q-item-label>
|
|
${ formatCurrency(distributionDialog.transaction.fiat_amount) }
|
|
(${ formatSats(distributionDialog.transaction.crypto_amount) })
|
|
</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
<q-item>
|
|
<q-item-section>
|
|
<q-item-label caption>Commission</q-item-label>
|
|
<q-item-label>
|
|
${ (distributionDialog.transaction.commission_percentage * 100).toFixed(1) }%
|
|
<span v-if="distributionDialog.transaction.discount > 0">
|
|
(with ${ distributionDialog.transaction.discount }% discount = ${ (distributionDialog.transaction.effective_commission * 100).toFixed(1) }% effective)
|
|
</span>
|
|
= ${ formatSats(distributionDialog.transaction.commission_amount_sats) }
|
|
</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
<q-item>
|
|
<q-item-section>
|
|
<q-item-label caption>Available for Distribution</q-item-label>
|
|
<q-item-label>${ formatSats(distributionDialog.transaction.base_amount_sats) }</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
<q-item>
|
|
<q-item-section>
|
|
<q-item-label caption>Total Distributed</q-item-label>
|
|
<q-item-label>${ formatSats(distributionDialog.transaction.distributions_total_sats) } to ${ distributionDialog.transaction.clients_count } clients</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
</div>
|
|
|
|
<q-separator class="q-my-md"></q-separator>
|
|
|
|
<div class="text-h6 q-mb-md">Client Distributions</div>
|
|
|
|
<q-table
|
|
dense
|
|
flat
|
|
:rows="distributionDialog.distributions"
|
|
row-key="payment_id"
|
|
:columns="distributionDetailsTable.columns"
|
|
>
|
|
<template v-slot:body="props">
|
|
<q-tr :props="props">
|
|
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
|
<div v-if="col.field == 'client_username'">${ col.value || 'No username' }</div>
|
|
<div v-else-if="col.field == 'client_user_id'">${ col.value.substring(0, 8) }...</div>
|
|
<div v-else-if="col.field == 'amount_sats'">${ formatSats(col.value) }</div>
|
|
<div v-else-if="col.field == 'amount_fiat'">${ formatCurrency(col.value) }</div>
|
|
<div v-else-if="col.field == 'exchange_rate'">${ col.value.toLocaleString() }</div>
|
|
<div v-else-if="col.field == 'status'">
|
|
<q-badge :color="col.value === 'confirmed' ? 'green' : col.value === 'failed' ? 'red' : 'orange'">
|
|
${ col.value }
|
|
</q-badge>
|
|
</div>
|
|
<div v-else-if="col.field == 'created_at'">${ formatDateTime(col.value) }</div>
|
|
<div v-else>${ col.value || '-' }</div>
|
|
</q-td>
|
|
</q-tr>
|
|
</template>
|
|
</q-table>
|
|
|
|
<div class="row q-mt-lg">
|
|
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
|
|
</div>
|
|
</q-card>
|
|
</q-dialog>
|
|
|
|
<!--/////////////////////////////////////////////////-->
|
|
<!--//////////////MANUAL TRANSACTION DIALOG///////////-->
|
|
<!--/////////////////////////////////////////////////-->
|
|
|
|
<q-dialog v-model="manualTransactionDialog.show" position="top">
|
|
<q-card class="q-pa-lg q-pt-xl" style="width: 500px; max-width: 90vw">
|
|
<div class="text-h6 q-mb-md">Process Specific Transaction</div>
|
|
|
|
<q-banner class="bg-orange-1 text-orange-9 q-mb-md">
|
|
<template v-slot:avatar>
|
|
<q-icon name="warning" color="orange" />
|
|
</template>
|
|
<div class="text-caption">
|
|
<strong>Use with caution:</strong> This bypasses all dispense status checks and will process the transaction even if dispense_confirmed is false. Only use this for manually settled transactions.
|
|
</div>
|
|
</q-banner>
|
|
|
|
<q-form @submit="processSpecificTransaction" class="q-gutter-md">
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="manualTransactionDialog.transactionId"
|
|
label="Lamassu Transaction ID *"
|
|
placeholder="e.g., 82746dfb-674d-4d7e-b008-7507caa02773"
|
|
hint="Enter the transaction/session ID from Lamassu database"
|
|
>
|
|
<template v-slot:prepend>
|
|
<q-icon name="receipt" />
|
|
</template>
|
|
</q-input>
|
|
|
|
<div class="text-caption text-grey-7">
|
|
This will:
|
|
<ul class="q-my-sm">
|
|
<li>Fetch the transaction from Lamassu regardless of dispense status</li>
|
|
<li>Process it through the normal DCA distribution flow</li>
|
|
<li>Credit the source wallet and distribute to clients</li>
|
|
<li>Send commission to the commission wallet (if configured)</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="row q-mt-lg">
|
|
<q-btn
|
|
unelevated
|
|
color="deep-orange"
|
|
type="submit"
|
|
:loading="processingSpecificTransaction"
|
|
:disable="!manualTransactionDialog.transactionId"
|
|
>
|
|
Process Transaction
|
|
</q-btn>
|
|
<q-btn v-close-popup flat color="grey" class="q-ml-auto">
|
|
Cancel
|
|
</q-btn>
|
|
</div>
|
|
</q-form>
|
|
</q-card>
|
|
</q-dialog>
|
|
|
|
</div>
|
|
{% endblock %}
|