Skip to content

Commit 5e3d072

Browse files
authored
Merge pull request #3 from DoktorShift/update_v1
Update v1
2 parents 5d7984b + 374b889 commit 5e3d072

File tree

5 files changed

+417
-98
lines changed

5 files changed

+417
-98
lines changed

crud.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,8 @@ async def create_device(data: CreateLnurldevice, req: Request) -> Lnurldevice:
3434
url = req.url_for("devicetimer.lnurl_v2_params", device_id=device_id)
3535
for _switch in data.switches:
3636
_switch.id = shortuuid.uuid()[:8]
37-
_switch.lnurl = lnurl_encode(
38-
str(url) + "?switch_id=" + str(_switch.id)
39-
)
37+
lnurl_obj = lnurl_encode(str(url) + "?switch_id=" + str(_switch.id))
38+
_switch.lnurl = str(lnurl_obj).upper()
4039

4140
switches_json = json.dumps(
4241
[s.dict() for s in data.switches] if data.switches else []
@@ -81,9 +80,12 @@ async def update_device(
8180
for _switch in data.switches:
8281
if _switch.id is None:
8382
_switch.id = shortuuid.uuid()[:8]
84-
_switch.lnurl = lnurl_encode(
85-
str(url) + "?switch_id=" + str(_switch.id)
86-
)
83+
lnurl_obj = lnurl_encode(str(url) + "?switch_id=" + str(_switch.id))
84+
_switch.lnurl = str(lnurl_obj).upper()
85+
elif not _switch.lnurl or not _switch.lnurl.upper().startswith("LNURL"):
86+
# Re-encode if lnurl is missing or invalid
87+
lnurl_obj = lnurl_encode(str(url) + "?switch_id=" + str(_switch.id))
88+
_switch.lnurl = str(lnurl_obj).upper()
8789

8890
switches_json = json.dumps(
8991
[s.dict() for s in data.switches] if data.switches else []

manifest.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
{
44
"id": "devicetimer",
55
"organisation": "DoktorShift",
6-
"repository": "DeviceTimer"
6+
"repository": "DeviceTimer",
7+
"branch": "main"
78
}
89
]
910
}

static/js/index.js

Lines changed: 90 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,16 @@ window.app = Vue.createApp({
1010
data() {
1111
return {
1212
loading: false,
13-
selectedWallet: null,
13+
selectedWallet: 'all',
1414
devices: [],
1515
filter: '',
1616
currencies: ['sat', 'EUR', 'USD', 'GBP', 'CHF', 'CAD', 'JPY', 'INR', 'ZAR', 'CZK'],
1717
timezones: ['Europe/Amsterdam'],
1818
lnurlValue: '',
1919
qrcodeUrl: '',
2020
websocketMessage: '',
21+
activeWebsocketDeviceId: null,
22+
activeWebsocket: null,
2123
protocol: window.location.protocol,
2224
wsLocation: '',
2325

@@ -28,6 +30,7 @@ window.app = Vue.createApp({
2830
},
2931

3032
deviceColumns: [
33+
{name: 'id', label: 'ID', field: 'id', align: 'left'},
3134
{name: 'title', label: 'Device', field: 'title', align: 'left', sortable: true},
3235
{name: 'currency', label: 'Currency', field: 'currency', align: 'left'},
3336
{name: 'timezone', label: 'Timezone', field: 'timezone', align: 'left'},
@@ -61,6 +64,19 @@ window.app = Vue.createApp({
6164
selectedSwitch: null
6265
},
6366

67+
websocketDialog: {
68+
show: false,
69+
url: '',
70+
deviceTitle: ''
71+
},
72+
73+
deleteDialog: {
74+
show: false,
75+
deviceId: null,
76+
deviceTitle: '',
77+
switchCount: 0
78+
},
79+
6480
drawerRight: false
6581
}
6682
},
@@ -70,30 +86,48 @@ window.app = Vue.createApp({
7086
return this.websocketMessage
7187
},
7288
filteredDevices() {
73-
if (!this.filter) return this.devices
74-
const search = this.filter.toLowerCase()
75-
return this.devices.filter(d =>
76-
d.title.toLowerCase().includes(search) ||
77-
d.currency.toLowerCase().includes(search)
78-
)
89+
let devices = this.devices
90+
// Filter by wallet if not "all"
91+
if (this.selectedWallet && this.selectedWallet !== 'all') {
92+
devices = devices.filter(d => d.wallet === this.selectedWallet)
93+
}
94+
// Filter by search term
95+
if (this.filter) {
96+
const search = this.filter.toLowerCase()
97+
devices = devices.filter(d =>
98+
d.title.toLowerCase().includes(search) ||
99+
d.currency.toLowerCase().includes(search)
100+
)
101+
}
102+
return devices
79103
},
80104
walletsWithCount() {
81-
return this.g.user.wallets.map(w => ({
105+
const wallets = this.g.user.wallets.map(w => ({
82106
...w,
83107
deviceCount: this.devices.filter(d => d.wallet === w.id).length
84108
}))
109+
return [
110+
{id: 'all', name: 'All Wallets', deviceCount: this.devices.length},
111+
...wallets
112+
]
85113
}
86114
},
87115

88116
methods: {
89117
onWalletChange() {
90-
this.getDevices()
118+
// Filtering is handled by filteredDevices computed property
119+
this.calculateStats()
91120
},
92121

93122
calculateStats() {
94-
this.stats.totalDevices = this.devices.length
95-
this.stats.activeDevices = this.devices.length
96-
this.stats.totalSwitches = this.devices.reduce((sum, d) => sum + (d.switches?.length || 0), 0)
123+
// Use filtered devices for stats when wallet is selected
124+
let devices = this.devices
125+
if (this.selectedWallet && this.selectedWallet !== 'all') {
126+
devices = this.devices.filter(d => d.wallet === this.selectedWallet)
127+
}
128+
this.stats.totalDevices = devices.length
129+
this.stats.activeDevices = devices.length
130+
this.stats.totalSwitches = devices.reduce((sum, d) => sum + (d.switches?.length || 0), 0)
97131
},
98132

99133
formatHours(device) {
@@ -103,11 +137,11 @@ window.app = Vue.createApp({
103137
async getDevices() {
104138
this.loading = true
105139
try {
106-
const wallet = _.findWhere(this.g.user.wallets, {id: this.selectedWallet})
140+
// Always fetch all devices using the first wallet's adminkey
107141
const response = await LNbits.api.request(
108142
'GET',
109143
'/devicetimer/api/v1/device',
110-
wallet?.adminkey || this.g.user.wallets[0].adminkey
144+
this.g.user.wallets[0].adminkey
111145
)
112146
if (response.data) {
113147
this.devices = response.data.map(mapDevice)
@@ -187,23 +221,30 @@ window.app = Vue.createApp({
187221

188222
deleteDevice(deviceId) {
189223
const device = this.devices.find(d => d.id === deviceId)
190-
LNbits.utils
191-
.confirmDialog(`Delete "${device?.title}"?`)
192-
.onOk(async () => {
193-
try {
194-
const wallet = _.findWhere(this.g.user.wallets, {id: device.wallet})
195-
await LNbits.api.request(
196-
'DELETE',
197-
'/devicetimer/api/v1/device/' + deviceId,
198-
wallet?.adminkey || this.g.user.wallets[0].adminkey
199-
)
200-
this.devices = this.devices.filter(d => d.id !== deviceId)
201-
this.calculateStats()
202-
this.$q.notify({type: 'positive', message: 'Device deleted'})
203-
} catch (error) {
204-
LNbits.utils.notifyApiError(error)
205-
}
206-
})
224+
if (!device) return
225+
this.deleteDialog.deviceId = deviceId
226+
this.deleteDialog.deviceTitle = device.title
227+
this.deleteDialog.switchCount = device.switches?.length || 0
228+
this.deleteDialog.show = true
229+
},
230+
231+
async confirmDeleteDevice() {
232+
const deviceId = this.deleteDialog.deviceId
233+
const device = this.devices.find(d => d.id === deviceId)
234+
try {
235+
const wallet = _.findWhere(this.g.user.wallets, {id: device?.wallet})
236+
await LNbits.api.request(
237+
'DELETE',
238+
'/devicetimer/api/v1/device/' + deviceId,
239+
wallet?.adminkey || this.g.user.wallets[0].adminkey
240+
)
241+
this.devices = this.devices.filter(d => d.id !== deviceId)
242+
this.calculateStats()
243+
this.deleteDialog.show = false
244+
this.$q.notify({type: 'positive', message: 'Device deleted'})
245+
} catch (error) {
246+
LNbits.utils.notifyApiError(error)
247+
}
207248
},
208249

209250
openDeviceDialog(device = null) {
@@ -251,7 +292,7 @@ window.app = Vue.createApp({
251292
this.qrCodeDialog.selectedSwitch = device.switches[0]
252293
this.lnurlValue = device.switches[0].lnurl
253294
this.qrcodeUrl = '/devicetimer/device/' + device.id + '/' + device.switches[0].id + '/qrcode?' + Date.now()
254-
this.websocketConnector(this.wsLocation + '/api/v1/ws/' + device.id)
295+
this.websocketConnector(this.wsLocation + '/api/v1/ws/' + device.id, device.id)
255296
this.qrCodeDialog.show = true
256297
},
257298

@@ -264,7 +305,7 @@ window.app = Vue.createApp({
264305
this.qrCodeDialog.selectedSwitch = sw
265306
this.lnurlValue = sw.lnurl
266307
this.qrcodeUrl = '/devicetimer/device/' + device.id + '/' + sw.id + '/qrcode?' + Date.now()
267-
this.websocketConnector(this.wsLocation + '/api/v1/ws/' + device.id)
308+
this.websocketConnector(this.wsLocation + '/api/v1/ws/' + device.id, device.id)
268309
this.qrCodeDialog.show = true
269310
},
270311

@@ -290,14 +331,19 @@ window.app = Vue.createApp({
290331
this.deviceDialog.data.switches.splice(index, 1)
291332
},
292333

293-
websocketConnector(websocketUrl) {
334+
websocketConnector(websocketUrl, deviceId) {
335+
if (this.activeWebsocket) {
336+
this.activeWebsocket.close()
337+
}
294338
if (!('WebSocket' in window)) {
295339
this.websocketMessage = 'WebSocket not supported'
296340
return
297341
}
298342
try {
299343
this.websocketMessage = 'Connecting...'
344+
this.activeWebsocketDeviceId = deviceId
300345
const ws = new WebSocket(websocketUrl)
346+
this.activeWebsocket = ws
301347
ws.onopen = () => {
302348
this.websocketMessage = 'connected'
303349
}
@@ -306,12 +352,17 @@ window.app = Vue.createApp({
306352
}
307353
ws.onclose = () => {
308354
this.websocketMessage = 'Disconnected'
355+
this.activeWebsocketDeviceId = null
356+
this.activeWebsocket = null
309357
}
310358
ws.onerror = () => {
311359
this.websocketMessage = 'Connection error'
360+
this.activeWebsocketDeviceId = null
361+
this.activeWebsocket = null
312362
}
313363
} catch (e) {
314364
this.websocketMessage = 'WebSocket error'
365+
this.activeWebsocketDeviceId = null
315366
}
316367
},
317368

@@ -321,9 +372,10 @@ window.app = Vue.createApp({
321372
})
322373
},
323374

324-
copyWebsocketUrl(deviceId) {
325-
const url = this.wsLocation + '/api/v1/ws/' + deviceId
326-
this.copyText(url, 'WebSocket URL copied')
375+
openWebsocketDialog(device) {
376+
this.websocketDialog.url = this.wsLocation + '/api/v1/ws/' + device.id
377+
this.websocketDialog.deviceTitle = device.title
378+
this.websocketDialog.show = true
327379
},
328380

329381
exportCSV() {
@@ -336,7 +388,7 @@ window.app = Vue.createApp({
336388
},
337389

338390
async created() {
339-
this.selectedWallet = this.g.user.wallets[0]?.id
391+
this.selectedWallet = 'all'
340392
this.wsLocation = (window.location.protocol === 'https:' ? 'wss://' : 'ws://') + window.location.host
341393

342394
await this.getDevices()

static/routes.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]

0 commit comments

Comments
 (0)