feat(v2)(ui): per-direction fee inputs in super-config + machine modals (#38 5/5)
Some checks failed
ci.yml / feat(v2)(ui): per-direction fee inputs in super-config + machine modals (#38 5/5) (pull_request) Failing after 0s

Surfaces the new directional fee fields in the admin dashboard so
operators + the LNbits super can configure cash-in and cash-out fees
independently:

Templates (`templates/satmachineadmin/index.html`):
- Platform fee banner now shows both directional super fractions
  side-by-side ("cash-in X% · cash-out Y% of each transaction's
  principal"). Wording updated to "principal" not "commission" since
  the math is now principal-based.
- Super-fee edit dialog: replaces the single q-input with two
  (super_cash_in_fee_fraction + super_cash_out_fee_fraction); each
  capped at 0.15 via max attr (visual hint; server enforces).
- Add-machine + edit-machine dialogs both gain operator_cash_in_fee_
  fraction + operator_cash_out_fee_fraction inputs with the same 0.15
  cap hint. Hint text mentions the "sits on top of platform fee, total
  capped at 15% per direction" semantics so operators understand the
  layering.

JS (`static/js/index.js`):
- superFeeDialog.data shape switches to the new directional fields.
- openSuperFeeDialog / submitSuperFee load + POST the new shape.
- _emptyMachineForm / _cleanMachineForm pass through operator
  directional fields (Number-coerced, default 0).
- openEditMachineDialog / submitEditMachine include the operator fee
  fields in the form data + PUT body.
- New computed `superAnyFee` drives the banner styling (sum of both
  directional fractions — non-zero → blue active banner; zero → muted
  grey "free instance" banner).

All Quasar UMD components use explicit close tags per the UMD-mode
parsing rule.

Migration carry-over verified in dev container: pre-m009
super_fee_fraction=0.33 backfilled to super_cash_in=0.33 +
super_cash_out=0.33 on migrate-up. Note this puts existing dev
instances above the new 15% cap; operators will see the cap
validation error on their next super-config save and must adjust to
≤0.15 per direction. Production aiolabs/server-deploy will land at
0.03 on both directions (well under cap).

164/164 tests green. Layer 1 (#38) complete; Layer 2 (#39) wire-
format publisher is the next milestone.

Refs: aiolabs/satmachineadmin#37 (parent), #38 (closes Layer 1).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-06-01 14:46:27 +02:00
commit 10f4b50ca5
2 changed files with 81 additions and 19 deletions

View file

@ -100,7 +100,11 @@ window.app = Vue.createApp({
superFeeDialog: {
show: false,
saving: false,
data: {super_fee_fraction: 0, super_fee_wallet_id: ''}
data: {
super_cash_in_fee_fraction: 0,
super_cash_out_fee_fraction: 0,
super_fee_wallet_id: ''
}
},
// UI configuration -----------------------------------------------
@ -266,6 +270,17 @@ window.app = Vue.createApp({
},
computed: {
superAnyFee() {
// Banner styling key — true when either directional super fee is
// non-zero, so the banner reads as "active platform fee" instead
// of the muted grey "free instance" state.
const c = this.superConfig
if (!c) return 0
return (
Number(c.super_cash_in_fee_fraction || 0) +
Number(c.super_cash_out_fee_fraction || 0)
)
},
walletOptions() {
// g.user is sometimes null on initial mount in LNbits 1.4 — guard it.
const wallets = this.g?.user?.wallets || []
@ -549,7 +564,10 @@ window.app = Vue.createApp({
// -----------------------------------------------------------------
openSuperFeeDialog() {
this.superFeeDialog.data = {
super_fee_fraction: this.superConfig?.super_fee_fraction ?? 0,
super_cash_in_fee_fraction:
this.superConfig?.super_cash_in_fee_fraction ?? 0,
super_cash_out_fee_fraction:
this.superConfig?.super_cash_out_fee_fraction ?? 0,
super_fee_wallet_id: this.superConfig?.super_fee_wallet_id || ''
}
this.superFeeDialog.show = true
@ -562,7 +580,8 @@ window.app = Vue.createApp({
const {data} = await LNbits.api.request(
'PUT', SUPER_FEE_PATH, null,
{
super_fee_fraction: Number(d.super_fee_fraction),
super_cash_in_fee_fraction: Number(d.super_cash_in_fee_fraction),
super_cash_out_fee_fraction: Number(d.super_cash_out_fee_fraction),
super_fee_wallet_id: (d.super_fee_wallet_id || '').trim() || null
}
)
@ -705,7 +724,9 @@ window.app = Vue.createApp({
location: machine.location || '',
wallet_id: machine.wallet_id,
fiat_code: machine.fiat_code,
is_active: machine.is_active
is_active: machine.is_active,
operator_cash_in_fee_fraction: machine.operator_cash_in_fee_fraction ?? 0,
operator_cash_out_fee_fraction: machine.operator_cash_out_fee_fraction ?? 0
}
this.editMachineDialog.show = true
},
@ -723,7 +744,9 @@ window.app = Vue.createApp({
location: d.location,
wallet_id: d.wallet_id,
fiat_code: d.fiat_code,
is_active: d.is_active
is_active: d.is_active,
operator_cash_in_fee_fraction: Number(d.operator_cash_in_fee_fraction) || 0,
operator_cash_out_fee_fraction: Number(d.operator_cash_out_fee_fraction) || 0
}
)
const idx = this.machines.findIndex(m => m.id === data.id)
@ -1475,7 +1498,9 @@ window.app = Vue.createApp({
wallet_id: null,
name: '',
location: '',
fiat_code: 'GTQ'
fiat_code: 'GTQ',
operator_cash_in_fee_fraction: 0,
operator_cash_out_fee_fraction: 0
}
},
@ -1485,7 +1510,9 @@ window.app = Vue.createApp({
wallet_id: d.wallet_id,
name: (d.name || '').trim() || null,
location: (d.location || '').trim() || null,
fiat_code: (d.fiat_code || 'GTQ').trim()
fiat_code: (d.fiat_code || 'GTQ').trim(),
operator_cash_in_fee_fraction: Number(d.operator_cash_in_fee_fraction) || 0,
operator_cash_out_fee_fraction: Number(d.operator_cash_out_fee_fraction) || 0
}
},