lnlink-server 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +1 -1
- package/dist/build-info.json +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +2 -2
- package/dist/package.json +1 -1
- package/dist/public/js/init.js +296 -241
- package/package.json +2 -1
- package/prisma/migrations/20250918020814_/migration.sql +188 -0
- package/prisma/migrations/20251114105314_auto_update/migration.sql +2 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +181 -0
package/dist/public/js/init.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
|
+
/* eslint-disable unused-imports/no-unused-vars */
|
|
2
|
+
/* eslint-disable no-unused-vars */
|
|
1
3
|
/* global bootstrap */
|
|
2
4
|
|
|
3
5
|
// Global toggle state
|
|
4
|
-
let isAdvancedConfigVisible = false
|
|
6
|
+
let isAdvancedConfigVisible = false
|
|
5
7
|
|
|
6
8
|
// Function to open external URL in default browser
|
|
7
9
|
function openExternalUrl(url) {
|
|
8
10
|
if (window.electronAPI && window.electronAPI.openExternal) {
|
|
9
|
-
window.electronAPI.openExternal(url)
|
|
10
|
-
}
|
|
11
|
+
window.electronAPI.openExternal(url)
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
11
14
|
// Fallback for non-Electron environments
|
|
12
|
-
window.open(url, "_blank")
|
|
15
|
+
window.open(url, "_blank")
|
|
13
16
|
}
|
|
14
17
|
}
|
|
15
18
|
|
|
@@ -24,10 +27,10 @@ const defaultNetworkConfigs = {
|
|
|
24
27
|
mainnet: {
|
|
25
28
|
network: "mainnet",
|
|
26
29
|
},
|
|
27
|
-
}
|
|
30
|
+
}
|
|
28
31
|
|
|
29
32
|
// Initialize networkConfigs with default values
|
|
30
|
-
let networkConfigs = JSON.parse(JSON.stringify(defaultNetworkConfigs))
|
|
33
|
+
let networkConfigs = JSON.parse(JSON.stringify(defaultNetworkConfigs))
|
|
31
34
|
|
|
32
35
|
// Fetch network configurations from API
|
|
33
36
|
async function fetchNetworkConfigs() {
|
|
@@ -41,16 +44,17 @@ async function fetchNetworkConfigs() {
|
|
|
41
44
|
},
|
|
42
45
|
body: JSON.stringify({}),
|
|
43
46
|
},
|
|
44
|
-
)
|
|
47
|
+
)
|
|
45
48
|
|
|
46
|
-
const result = await response.json()
|
|
49
|
+
const result = await response.json()
|
|
47
50
|
|
|
48
51
|
if (result.code === "0" && result.data && Array.isArray(result.data)) {
|
|
49
|
-
const apiConfigs = {}
|
|
52
|
+
const apiConfigs = {}
|
|
50
53
|
|
|
51
54
|
result.data.forEach((item) => {
|
|
52
|
-
if (!item.network)
|
|
53
|
-
|
|
55
|
+
if (!item.network)
|
|
56
|
+
return
|
|
57
|
+
const networkKey = item.network.toLowerCase()
|
|
54
58
|
|
|
55
59
|
apiConfigs[networkKey] = {
|
|
56
60
|
bitcoindIndex: item.rgbIndex,
|
|
@@ -70,24 +74,26 @@ async function fetchNetworkConfigs() {
|
|
|
70
74
|
officialUniverseServer: item.lndUniverseServer,
|
|
71
75
|
priceOracle: item.lndPriceOracle,
|
|
72
76
|
rgbProxy: item.rgbProxy,
|
|
73
|
-
}
|
|
74
|
-
})
|
|
77
|
+
}
|
|
78
|
+
})
|
|
75
79
|
|
|
76
80
|
// Update global configs, strictly using API response if valid
|
|
77
81
|
if (Object.keys(apiConfigs).length > 0) {
|
|
78
|
-
networkConfigs = apiConfigs
|
|
79
|
-
console.log("Network configs updated from API")
|
|
82
|
+
networkConfigs = apiConfigs
|
|
83
|
+
console.log("Network configs updated from API")
|
|
80
84
|
}
|
|
81
|
-
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
82
87
|
console.warn(
|
|
83
88
|
"API returned success but no data or invalid format, using default configs.",
|
|
84
|
-
)
|
|
89
|
+
)
|
|
85
90
|
}
|
|
86
|
-
}
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
87
93
|
console.error(
|
|
88
94
|
"Error fetching network configs, using default configs:",
|
|
89
95
|
error,
|
|
90
|
-
)
|
|
96
|
+
)
|
|
91
97
|
}
|
|
92
98
|
}
|
|
93
99
|
|
|
@@ -115,149 +121,157 @@ const fieldLabels = {
|
|
|
115
121
|
nostrRelays: "Relays",
|
|
116
122
|
network: "Network Type",
|
|
117
123
|
owner: "Owner Address",
|
|
118
|
-
}
|
|
124
|
+
}
|
|
119
125
|
|
|
120
126
|
// Show error modal
|
|
121
127
|
function showErrorModal(message) {
|
|
122
|
-
const errorModalBody = document.getElementById("errorModalBody")
|
|
123
|
-
errorModalBody.textContent = message
|
|
128
|
+
const errorModalBody = document.getElementById("errorModalBody")
|
|
129
|
+
errorModalBody.textContent = message
|
|
124
130
|
if (typeof bootstrap !== "undefined") {
|
|
125
131
|
const errorModal = new bootstrap.Modal(
|
|
126
132
|
document.getElementById("errorModal"),
|
|
127
|
-
)
|
|
128
|
-
errorModal.show()
|
|
129
|
-
}
|
|
130
|
-
|
|
133
|
+
)
|
|
134
|
+
errorModal.show()
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
console.error("Error:", message)
|
|
131
138
|
}
|
|
132
139
|
}
|
|
133
140
|
|
|
134
141
|
// Show success modal
|
|
135
142
|
function showSuccessModal(message) {
|
|
136
|
-
const successModalBody = document.getElementById("successModalBody")
|
|
137
|
-
successModalBody.textContent = message
|
|
143
|
+
const successModalBody = document.getElementById("successModalBody")
|
|
144
|
+
successModalBody.textContent = message
|
|
138
145
|
if (typeof bootstrap !== "undefined") {
|
|
139
146
|
const successModal = new bootstrap.Modal(
|
|
140
147
|
document.getElementById("successModal"),
|
|
141
|
-
)
|
|
142
|
-
successModal.show()
|
|
143
|
-
}
|
|
144
|
-
|
|
148
|
+
)
|
|
149
|
+
successModal.show()
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
console.log("Success:", message)
|
|
145
153
|
}
|
|
146
154
|
}
|
|
147
155
|
|
|
148
156
|
// Status determination function
|
|
149
157
|
function getStatusClass(value) {
|
|
150
|
-
const strValue = String(value).toLowerCase()
|
|
158
|
+
const strValue = String(value).toLowerCase()
|
|
151
159
|
if (
|
|
152
|
-
strValue === "true"
|
|
153
|
-
strValue === "running"
|
|
154
|
-
strValue === "active"
|
|
155
|
-
strValue === "online"
|
|
160
|
+
strValue === "true"
|
|
161
|
+
|| strValue === "running"
|
|
162
|
+
|| strValue === "active"
|
|
163
|
+
|| strValue === "online"
|
|
156
164
|
) {
|
|
157
|
-
return { class: "status-success", icon: "fas fa-check-circle" }
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
strValue === "
|
|
161
|
-
strValue === "
|
|
162
|
-
strValue === "
|
|
165
|
+
return { class: "status-success", icon: "fas fa-check-circle" }
|
|
166
|
+
}
|
|
167
|
+
else if (
|
|
168
|
+
strValue === "false"
|
|
169
|
+
|| strValue === "stopped"
|
|
170
|
+
|| strValue === "inactive"
|
|
171
|
+
|| strValue === "offline"
|
|
163
172
|
) {
|
|
164
|
-
return { class: "status-error", icon: "fas fa-times-circle" }
|
|
165
|
-
}
|
|
166
|
-
|
|
173
|
+
return { class: "status-error", icon: "fas fa-times-circle" }
|
|
174
|
+
}
|
|
175
|
+
else if (strValue.includes("pending") || strValue.includes("waiting")) {
|
|
176
|
+
return { class: "status-warning", icon: "fas fa-clock" }
|
|
167
177
|
}
|
|
168
|
-
return { class: "", icon: "fas fa-info-circle" }
|
|
178
|
+
return { class: "", icon: "fas fa-info-circle" }
|
|
169
179
|
}
|
|
170
180
|
|
|
171
181
|
// Get system information
|
|
172
182
|
async function getInfo(showLoading = true) {
|
|
173
|
-
const mainContent = document.getElementById("main-content")
|
|
183
|
+
const mainContent = document.getElementById("main-content")
|
|
174
184
|
|
|
175
185
|
// Show loading state
|
|
176
186
|
if (showLoading) {
|
|
177
|
-
mainContent.innerHTML
|
|
178
|
-
|
|
187
|
+
mainContent.innerHTML
|
|
188
|
+
= "<div class=\"loading-container\"><div class=\"loading\"></div><div>Loading information...</div></div>"
|
|
179
189
|
}
|
|
180
190
|
|
|
181
191
|
try {
|
|
182
|
-
const res = await fetch("/api/lnd/info").then(
|
|
192
|
+
const res = await fetch("/api/lnd/info").then(res => res.json())
|
|
183
193
|
|
|
184
194
|
if (res.code === 200) {
|
|
185
195
|
if (res.data) {
|
|
186
|
-
const { owner, settings } = res.data
|
|
196
|
+
const { owner, settings } = res.data
|
|
187
197
|
|
|
188
198
|
// If both owner and settings exist, show configured view
|
|
189
199
|
if (owner && settings) {
|
|
190
|
-
renderConfiguredView(res.data, settings)
|
|
200
|
+
renderConfiguredView(res.data, settings)
|
|
191
201
|
}
|
|
192
202
|
// If missing owner or settings, show configuration form
|
|
193
203
|
else {
|
|
194
204
|
// Fetch network configs only when needed for configuration
|
|
195
|
-
await fetchNetworkConfigs()
|
|
205
|
+
await fetchNetworkConfigs()
|
|
196
206
|
|
|
197
207
|
fetch("https://api.example.com/login", {
|
|
198
208
|
method: "POST",
|
|
199
209
|
headers: {
|
|
200
210
|
"Content-Type": "application/json",
|
|
201
|
-
Authorization: "Bearer your-token-here", // 可选
|
|
211
|
+
"Authorization": "Bearer your-token-here", // 可选
|
|
202
212
|
},
|
|
203
213
|
body: JSON.stringify({
|
|
204
214
|
username: "test",
|
|
205
215
|
password: "123456",
|
|
206
216
|
}),
|
|
207
|
-
})
|
|
208
|
-
renderConfigurationForm(res.data)
|
|
217
|
+
})
|
|
218
|
+
renderConfigurationForm(res.data)
|
|
209
219
|
}
|
|
210
220
|
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
res.
|
|
221
|
+
}
|
|
222
|
+
else if (
|
|
223
|
+
res.code === 500
|
|
224
|
+
&& res.message === "Please init the wallet or init owner first"
|
|
214
225
|
) {
|
|
215
226
|
// Fetch network configs only when needed for configuration
|
|
216
|
-
await fetchNetworkConfigs()
|
|
227
|
+
await fetchNetworkConfigs()
|
|
217
228
|
|
|
218
229
|
// If wallet/owner not initialized, show configuration form
|
|
219
|
-
renderConfigurationForm({})
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
|
|
230
|
+
renderConfigurationForm({})
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
mainContent.innerHTML
|
|
234
|
+
= "<div class=\"alert alert-danger\"><i class=\"fas fa-exclamation-triangle\"></i> Failed to load information</div>"
|
|
223
235
|
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
console.error("Failed to get info:", error)
|
|
239
|
+
mainContent.innerHTML
|
|
240
|
+
= "<div class=\"alert alert-danger\"><i class=\"fas fa-exclamation-triangle\"></i> Network error occurred</div>"
|
|
228
241
|
}
|
|
229
242
|
}
|
|
230
243
|
|
|
231
244
|
// Render configured view
|
|
232
245
|
function renderConfiguredView(basicData, settings) {
|
|
233
|
-
const mainContent = document.getElementById("main-content")
|
|
246
|
+
const mainContent = document.getElementById("main-content")
|
|
234
247
|
|
|
235
248
|
// Extract Node ID (Nostr PubKey)
|
|
236
|
-
const nodeId
|
|
237
|
-
settings.officialNostrPubKey || basicData.officialNostrPubKey || "Unknown"
|
|
249
|
+
const nodeId
|
|
250
|
+
= settings.officialNostrPubKey || basicData.officialNostrPubKey || "Unknown"
|
|
238
251
|
|
|
239
252
|
// Determine Service Status
|
|
240
253
|
// We check basicData for status indicators or default to design mocks if not present
|
|
241
254
|
// Assuming keys might exist, otherwise using placeholders for the design requirement
|
|
242
|
-
const rgbStatus = basicData.rgb || "Stopped"
|
|
243
|
-
const litdStatus = basicData.litd || "Stopped"
|
|
255
|
+
const rgbStatus = basicData.rgb || "Stopped" // Mock/Default based on design
|
|
256
|
+
const litdStatus = basicData.litd || "Stopped" // Mock/Default based on design
|
|
244
257
|
|
|
245
|
-
const isRgbRunning
|
|
246
|
-
rgbStatus.toLowerCase() === "running" || rgbStatus === "true"
|
|
247
|
-
const isLitdRunning
|
|
248
|
-
litdStatus.toLowerCase() === "running" || litdStatus === "true"
|
|
258
|
+
const isRgbRunning
|
|
259
|
+
= rgbStatus.toLowerCase() === "running" || rgbStatus === "true"
|
|
260
|
+
const isLitdRunning
|
|
261
|
+
= litdStatus.toLowerCase() === "running" || litdStatus === "true"
|
|
249
262
|
|
|
250
|
-
const rgbClass = isRgbRunning ? "running" : "stopped"
|
|
251
|
-
const litdClass = isLitdRunning ? "running" : "stopped"
|
|
263
|
+
const rgbClass = isRgbRunning ? "running" : "stopped"
|
|
264
|
+
const litdClass = isLitdRunning ? "running" : "stopped"
|
|
252
265
|
|
|
253
|
-
const rgbText = isRgbRunning ? "Running" : "Stopped"
|
|
254
|
-
const litdText = isLitdRunning ? "Running" : "Stopped"
|
|
266
|
+
const rgbText = isRgbRunning ? "Running" : "Stopped"
|
|
267
|
+
const litdText = isLitdRunning ? "Running" : "Stopped"
|
|
255
268
|
|
|
256
269
|
// Helper to shorten address in the middle
|
|
257
270
|
const shortenAddress = (address, chars = 10) => {
|
|
258
|
-
if (!address || address.length <= chars * 2)
|
|
259
|
-
|
|
260
|
-
|
|
271
|
+
if (!address || address.length <= chars * 2)
|
|
272
|
+
return address || ""
|
|
273
|
+
return `${address.substring(0, chars)}...${address.substring(address.length - chars)}`
|
|
274
|
+
}
|
|
261
275
|
|
|
262
276
|
const html = `
|
|
263
277
|
<div class="header-logo text-center mb-5">
|
|
@@ -319,34 +333,59 @@ function renderConfiguredView(basicData, settings) {
|
|
|
319
333
|
</div>
|
|
320
334
|
</div>
|
|
321
335
|
|
|
322
|
-
<button class="manage-btn" onclick="
|
|
336
|
+
<button class="manage-btn" onclick="navigateToManage('${basicData.manageUrl || "#"}', this)">
|
|
323
337
|
Manage My Node <i class="fas fa-arrow-right"></i>
|
|
324
338
|
</button>
|
|
325
339
|
</div>
|
|
326
340
|
|
|
327
341
|
<!-- Settings Modal Placeholder -->
|
|
328
342
|
<div id="settings-modal-container"></div>
|
|
329
|
-
|
|
343
|
+
`
|
|
330
344
|
|
|
331
|
-
mainContent.innerHTML = html
|
|
345
|
+
mainContent.innerHTML = html
|
|
332
346
|
|
|
333
347
|
// Store data globally for modal use
|
|
334
|
-
window.currentSettings = settings
|
|
335
|
-
window.currentBasicData = basicData
|
|
348
|
+
window.currentSettings = settings
|
|
349
|
+
window.currentBasicData = basicData
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Navigate to manage URL with loading state
|
|
353
|
+
window.navigateToManage = function navigateToManage(url, button) {
|
|
354
|
+
if (!url || url === "#" || !button) {
|
|
355
|
+
return
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Prevent double-click
|
|
359
|
+
if (button.dataset.loading === "true") {
|
|
360
|
+
return
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Set loading state
|
|
364
|
+
button.dataset.loading = "true"
|
|
365
|
+
button.disabled = true
|
|
366
|
+
button.classList.add("loading-state")
|
|
367
|
+
button.innerHTML = `<span class="loading"></span> Loading...`
|
|
368
|
+
|
|
369
|
+
// Allow UI to render before navigation
|
|
370
|
+
requestAnimationFrame(() => {
|
|
371
|
+
setTimeout(() => {
|
|
372
|
+
window.location.href = url
|
|
373
|
+
}, 50)
|
|
374
|
+
})
|
|
336
375
|
}
|
|
337
376
|
|
|
338
377
|
// Open Settings Modal
|
|
339
378
|
function openSettingsModal() {
|
|
340
|
-
const container = document.getElementById("settings-modal-container")
|
|
341
|
-
const settings = window.currentSettings || {}
|
|
342
|
-
const basicData = window.currentBasicData || {}
|
|
379
|
+
const container = document.getElementById("settings-modal-container")
|
|
380
|
+
const settings = window.currentSettings || {}
|
|
381
|
+
const basicData = window.currentBasicData || {}
|
|
343
382
|
|
|
344
383
|
// Helper to create read-only field
|
|
345
384
|
const createField = (label, value, isPassword = false) => {
|
|
346
|
-
const type = isPassword ? "password" : "text"
|
|
385
|
+
const type = isPassword ? "password" : "text"
|
|
347
386
|
const toggleHtml = isPassword
|
|
348
387
|
? `<i class="far fa-eye password-toggle-icon" onclick="togglePasswordVisibility(this)"></i>`
|
|
349
|
-
: ""
|
|
388
|
+
: ""
|
|
350
389
|
|
|
351
390
|
return `
|
|
352
391
|
<div class="settings-field">
|
|
@@ -356,8 +395,8 @@ function openSettingsModal() {
|
|
|
356
395
|
${toggleHtml}
|
|
357
396
|
</div>
|
|
358
397
|
</div>
|
|
359
|
-
|
|
360
|
-
}
|
|
398
|
+
`
|
|
399
|
+
}
|
|
361
400
|
|
|
362
401
|
const modalHtml = `
|
|
363
402
|
<div class="settings-modal-overlay" onclick="closeSettingsModal(event)">
|
|
@@ -413,28 +452,29 @@ function openSettingsModal() {
|
|
|
413
452
|
</div>
|
|
414
453
|
</div>
|
|
415
454
|
</div>
|
|
416
|
-
|
|
455
|
+
`
|
|
417
456
|
|
|
418
|
-
container.innerHTML = modalHtml
|
|
457
|
+
container.innerHTML = modalHtml
|
|
419
458
|
}
|
|
420
459
|
|
|
421
460
|
// Close Settings Modal
|
|
422
461
|
function closeSettingsModal(event) {
|
|
423
|
-
const container = document.getElementById("settings-modal-container")
|
|
424
|
-
container.innerHTML = ""
|
|
462
|
+
const container = document.getElementById("settings-modal-container")
|
|
463
|
+
container.innerHTML = ""
|
|
425
464
|
}
|
|
426
465
|
|
|
427
466
|
// Toggle Password Visibility
|
|
428
467
|
function togglePasswordVisibility(icon) {
|
|
429
|
-
const input = icon.previousElementSibling
|
|
468
|
+
const input = icon.previousElementSibling
|
|
430
469
|
if (input.type === "password") {
|
|
431
|
-
input.type = "text"
|
|
432
|
-
icon.classList.remove("fa-eye")
|
|
433
|
-
icon.classList.add("fa-eye-slash")
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
icon.classList.
|
|
470
|
+
input.type = "text"
|
|
471
|
+
icon.classList.remove("fa-eye")
|
|
472
|
+
icon.classList.add("fa-eye-slash")
|
|
473
|
+
}
|
|
474
|
+
else {
|
|
475
|
+
input.type = "password"
|
|
476
|
+
icon.classList.remove("fa-eye-slash")
|
|
477
|
+
icon.classList.add("fa-eye")
|
|
438
478
|
}
|
|
439
479
|
}
|
|
440
480
|
|
|
@@ -442,48 +482,50 @@ function togglePasswordVisibility(icon) {
|
|
|
442
482
|
function copyToClipboard(text, triggerElement) {
|
|
443
483
|
const showFeedback = () => {
|
|
444
484
|
if (triggerElement) {
|
|
445
|
-
const icon = triggerElement.querySelector("i")
|
|
485
|
+
const icon = triggerElement.querySelector("i")
|
|
446
486
|
if (icon) {
|
|
447
|
-
const originalClass = icon.className
|
|
448
|
-
icon.className = "fas fa-check"
|
|
487
|
+
const originalClass = icon.className
|
|
488
|
+
icon.className = "fas fa-check"
|
|
449
489
|
|
|
450
490
|
setTimeout(() => {
|
|
451
|
-
icon.className = originalClass
|
|
452
|
-
}, 2000)
|
|
491
|
+
icon.className = originalClass
|
|
492
|
+
}, 2000)
|
|
453
493
|
}
|
|
454
494
|
}
|
|
455
|
-
}
|
|
495
|
+
}
|
|
456
496
|
|
|
457
497
|
if (navigator.clipboard && window.isSecureContext) {
|
|
458
498
|
navigator.clipboard.writeText(text).then(
|
|
459
499
|
() => {
|
|
460
|
-
showFeedback()
|
|
500
|
+
showFeedback()
|
|
461
501
|
},
|
|
462
502
|
(err) => {
|
|
463
|
-
console.error("Could not copy text: ", err)
|
|
503
|
+
console.error("Could not copy text: ", err)
|
|
464
504
|
},
|
|
465
|
-
)
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
textArea
|
|
469
|
-
textArea.
|
|
470
|
-
textArea.style.
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
textArea.
|
|
505
|
+
)
|
|
506
|
+
}
|
|
507
|
+
else {
|
|
508
|
+
const textArea = document.createElement("textarea")
|
|
509
|
+
textArea.value = text
|
|
510
|
+
textArea.style.position = "fixed"
|
|
511
|
+
textArea.style.left = "-9999px"
|
|
512
|
+
document.body.appendChild(textArea)
|
|
513
|
+
textArea.focus()
|
|
514
|
+
textArea.select()
|
|
474
515
|
try {
|
|
475
|
-
document.execCommand("copy")
|
|
476
|
-
showFeedback()
|
|
477
|
-
}
|
|
478
|
-
|
|
516
|
+
document.execCommand("copy")
|
|
517
|
+
showFeedback()
|
|
518
|
+
}
|
|
519
|
+
catch (err) {
|
|
520
|
+
console.error("Fallback: Oops, unable to copy", err)
|
|
479
521
|
}
|
|
480
|
-
document.body.removeChild(textArea)
|
|
522
|
+
document.body.removeChild(textArea)
|
|
481
523
|
}
|
|
482
524
|
}
|
|
483
525
|
|
|
484
526
|
// Render configuration form (when owner or settings missing)
|
|
485
527
|
function renderConfigurationForm(basicData) {
|
|
486
|
-
const mainContent = document.getElementById("main-content")
|
|
528
|
+
const mainContent = document.getElementById("main-content")
|
|
487
529
|
|
|
488
530
|
const html = `
|
|
489
531
|
<div class="header-logo text-center mb-4">
|
|
@@ -539,89 +581,93 @@ function renderConfigurationForm(basicData) {
|
|
|
539
581
|
</div>
|
|
540
582
|
</form>
|
|
541
583
|
</div>
|
|
542
|
-
|
|
584
|
+
`
|
|
543
585
|
|
|
544
|
-
mainContent.innerHTML = html
|
|
586
|
+
mainContent.innerHTML = html
|
|
545
587
|
|
|
546
588
|
// Initialize configuration fields
|
|
547
|
-
updateConfigFields()
|
|
589
|
+
updateConfigFields()
|
|
548
590
|
}
|
|
549
591
|
|
|
550
592
|
// Toggle Advanced Configuration
|
|
551
593
|
window.toggleAdvanced = function () {
|
|
552
|
-
isAdvancedConfigVisible = !isAdvancedConfigVisible
|
|
553
|
-
const container = document.getElementById("config-fields-container")
|
|
554
|
-
const icon = document.getElementById("advanced-icon")
|
|
594
|
+
isAdvancedConfigVisible = !isAdvancedConfigVisible
|
|
595
|
+
const container = document.getElementById("config-fields-container")
|
|
596
|
+
const icon = document.getElementById("advanced-icon")
|
|
555
597
|
|
|
556
598
|
if (isAdvancedConfigVisible) {
|
|
557
|
-
container.style.display = "block"
|
|
558
|
-
icon.classList.remove("fa-chevron-down")
|
|
559
|
-
icon.classList.add("fa-chevron-up")
|
|
560
|
-
} else {
|
|
561
|
-
container.style.display = "none";
|
|
562
|
-
icon.classList.remove("fa-chevron-up");
|
|
563
|
-
icon.classList.add("fa-chevron-down");
|
|
599
|
+
container.style.display = "block"
|
|
600
|
+
icon.classList.remove("fa-chevron-down")
|
|
601
|
+
icon.classList.add("fa-chevron-up")
|
|
564
602
|
}
|
|
565
|
-
|
|
603
|
+
else {
|
|
604
|
+
container.style.display = "none"
|
|
605
|
+
icon.classList.remove("fa-chevron-up")
|
|
606
|
+
icon.classList.add("fa-chevron-down")
|
|
607
|
+
}
|
|
608
|
+
}
|
|
566
609
|
|
|
567
610
|
// Select Network
|
|
568
611
|
window.selectNetwork = function (network) {
|
|
569
612
|
if (networkConfigs && !networkConfigs[network]) {
|
|
570
|
-
showErrorModal("This network is not supported")
|
|
571
|
-
return
|
|
613
|
+
showErrorModal("This network is not supported")
|
|
614
|
+
return
|
|
572
615
|
}
|
|
573
|
-
document.getElementById("network").value = network
|
|
616
|
+
document.getElementById("network").value = network
|
|
574
617
|
|
|
575
618
|
// Update active button state
|
|
576
619
|
document.querySelectorAll(".btn-network").forEach((btn) => {
|
|
577
620
|
if (btn.dataset.value === network) {
|
|
578
|
-
btn.classList.add("active")
|
|
579
|
-
}
|
|
580
|
-
|
|
621
|
+
btn.classList.add("active")
|
|
622
|
+
}
|
|
623
|
+
else {
|
|
624
|
+
btn.classList.remove("active")
|
|
581
625
|
}
|
|
582
|
-
})
|
|
626
|
+
})
|
|
583
627
|
|
|
584
628
|
// Toggle mainnet badge
|
|
585
|
-
const badge = document.getElementById("mainnet-badge")
|
|
629
|
+
const badge = document.getElementById("mainnet-badge")
|
|
586
630
|
if (badge) {
|
|
587
|
-
badge.style.display = network === "mainnet" ? "inline-block" : "none"
|
|
631
|
+
badge.style.display = network === "mainnet" ? "inline-block" : "none"
|
|
588
632
|
}
|
|
589
633
|
|
|
590
|
-
updateConfigFields()
|
|
591
|
-
}
|
|
634
|
+
updateConfigFields()
|
|
635
|
+
}
|
|
592
636
|
|
|
593
|
-
window.copyToClipboard = copyToClipboard
|
|
637
|
+
window.copyToClipboard = copyToClipboard
|
|
594
638
|
|
|
595
639
|
window.togglePassword = function (id) {
|
|
596
|
-
const input = document.getElementById(id)
|
|
640
|
+
const input = document.getElementById(id)
|
|
597
641
|
if (input.type === "password") {
|
|
598
|
-
input.type = "text"
|
|
599
|
-
}
|
|
600
|
-
|
|
642
|
+
input.type = "text"
|
|
643
|
+
}
|
|
644
|
+
else {
|
|
645
|
+
input.type = "password"
|
|
601
646
|
}
|
|
602
|
-
}
|
|
647
|
+
}
|
|
603
648
|
|
|
604
649
|
// Render basic information list
|
|
605
650
|
function renderBasicInfoList(data) {
|
|
606
|
-
let html = ""
|
|
607
|
-
const excludeKeys = ["settings"]
|
|
651
|
+
let html = ""
|
|
652
|
+
const excludeKeys = ["settings"]
|
|
608
653
|
|
|
609
654
|
for (const key in data) {
|
|
610
|
-
if (excludeKeys.includes(key))
|
|
655
|
+
if (excludeKeys.includes(key))
|
|
656
|
+
continue
|
|
611
657
|
|
|
612
|
-
const value = data[key]
|
|
613
|
-
const status = getStatusClass(value)
|
|
658
|
+
const value = data[key]
|
|
659
|
+
const status = getStatusClass(value)
|
|
614
660
|
|
|
615
661
|
let valueHtml = `<span class="info-value ${status.class}">
|
|
616
662
|
<i class="${status.icon} status-icon"></i>${value}
|
|
617
|
-
</span
|
|
663
|
+
</span>`
|
|
618
664
|
|
|
619
665
|
if (key === "manageUrl") {
|
|
620
666
|
valueHtml = `<span class="info-value">
|
|
621
667
|
<a href="#" class="info-link" onclick="this.innerHTML='<i class=\\'fas fa-spinner fa-spin status-icon\\'></i>Loading...';setTimeout(()=>{window.location.href='${value}';},100);return false;">
|
|
622
668
|
<i class="fas fa-external-link-alt status-icon"></i>Click to manage
|
|
623
669
|
</a>
|
|
624
|
-
</span
|
|
670
|
+
</span>`
|
|
625
671
|
}
|
|
626
672
|
|
|
627
673
|
html += `
|
|
@@ -629,19 +675,19 @@ function renderBasicInfoList(data) {
|
|
|
629
675
|
<div class="info-key">${key}:</div>
|
|
630
676
|
<div class="info-value-container">${valueHtml}</div>
|
|
631
677
|
</li>
|
|
632
|
-
|
|
678
|
+
`
|
|
633
679
|
}
|
|
634
|
-
return html
|
|
680
|
+
return html
|
|
635
681
|
}
|
|
636
682
|
|
|
637
683
|
// Render settings list
|
|
638
684
|
function renderSettingsList(settings) {
|
|
639
|
-
let html = ""
|
|
685
|
+
let html = ""
|
|
640
686
|
for (const key in settings) {
|
|
641
687
|
const value = Array.isArray(settings[key])
|
|
642
688
|
? settings[key].join(", ")
|
|
643
|
-
: settings[key]
|
|
644
|
-
const label = fieldLabels[key] || key
|
|
689
|
+
: settings[key]
|
|
690
|
+
const label = fieldLabels[key] || key
|
|
645
691
|
|
|
646
692
|
html += `
|
|
647
693
|
<li class="list-group-item">
|
|
@@ -650,24 +696,26 @@ function renderSettingsList(settings) {
|
|
|
650
696
|
<span class="info-value">${value}</span>
|
|
651
697
|
</div>
|
|
652
698
|
</li>
|
|
653
|
-
|
|
699
|
+
`
|
|
654
700
|
}
|
|
655
|
-
return html
|
|
701
|
+
return html
|
|
656
702
|
}
|
|
657
703
|
|
|
658
704
|
// Update configuration fields
|
|
659
705
|
function updateConfigFields() {
|
|
660
|
-
const networkInput = document.getElementById("network")
|
|
661
|
-
const container = document.getElementById("config-fields-container")
|
|
706
|
+
const networkInput = document.getElementById("network")
|
|
707
|
+
const container = document.getElementById("config-fields-container")
|
|
662
708
|
|
|
663
|
-
if (!networkInput || !container)
|
|
709
|
+
if (!networkInput || !container)
|
|
710
|
+
return
|
|
664
711
|
|
|
665
|
-
const selectedNetwork = networkInput.value
|
|
666
|
-
const config = networkConfigs[selectedNetwork]
|
|
712
|
+
const selectedNetwork = networkInput.value
|
|
713
|
+
const config = networkConfigs[selectedNetwork]
|
|
667
714
|
|
|
668
|
-
if (!config)
|
|
715
|
+
if (!config)
|
|
716
|
+
return
|
|
669
717
|
|
|
670
|
-
let fieldsHtml = ""
|
|
718
|
+
let fieldsHtml = ""
|
|
671
719
|
|
|
672
720
|
// Group fields by category
|
|
673
721
|
const groups = {
|
|
@@ -692,13 +740,14 @@ function updateConfigFields() {
|
|
|
692
740
|
"bitcoindIndex",
|
|
693
741
|
],
|
|
694
742
|
"Nostr Config": ["officialNostrPubKey", "nostrRelays"],
|
|
695
|
-
}
|
|
743
|
+
}
|
|
696
744
|
|
|
697
745
|
// Define editable fields per network
|
|
698
|
-
let editableFields = []
|
|
746
|
+
let editableFields = []
|
|
699
747
|
if (selectedNetwork === "regtest") {
|
|
700
|
-
editableFields = ["bitcoindPass"]
|
|
701
|
-
}
|
|
748
|
+
editableFields = ["bitcoindPass"]
|
|
749
|
+
}
|
|
750
|
+
else if (selectedNetwork === "mainnet") {
|
|
702
751
|
editableFields = [
|
|
703
752
|
"bitcoindRpcHost",
|
|
704
753
|
"bitcoindRpcPort",
|
|
@@ -708,26 +757,27 @@ function updateConfigFields() {
|
|
|
708
757
|
"bitcoindZmqRawTx",
|
|
709
758
|
"rgbProxy",
|
|
710
759
|
"bitcoindIndex",
|
|
711
|
-
]
|
|
712
|
-
}
|
|
713
|
-
|
|
760
|
+
]
|
|
761
|
+
}
|
|
762
|
+
else if (selectedNetwork === "testnet") {
|
|
763
|
+
editableFields = ["bitcoindPass"]
|
|
714
764
|
}
|
|
715
765
|
|
|
716
766
|
for (const groupName in groups) {
|
|
717
|
-
fieldsHtml += `<div class="config-section mb-4"
|
|
718
|
-
fieldsHtml += `<h6 class="text-lime mb-3">${groupName}</h6
|
|
719
|
-
fieldsHtml += `<div class="row g-3"
|
|
767
|
+
fieldsHtml += `<div class="config-section mb-4">`
|
|
768
|
+
fieldsHtml += `<h6 class="text-lime mb-3">${groupName}</h6>`
|
|
769
|
+
fieldsHtml += `<div class="row g-3">`
|
|
720
770
|
|
|
721
771
|
groups[groupName].forEach((key) => {
|
|
722
772
|
if (config[key] !== undefined) {
|
|
723
|
-
const label = fieldLabels[key] || key
|
|
773
|
+
const label = fieldLabels[key] || key
|
|
724
774
|
const value = Array.isArray(config[key])
|
|
725
775
|
? config[key].join(",")
|
|
726
|
-
: config[key]
|
|
776
|
+
: config[key]
|
|
727
777
|
|
|
728
|
-
const isEditable = editableFields.includes(key)
|
|
729
|
-
const isPassword = key.toLowerCase().includes("pass")
|
|
730
|
-
const inputType = isPassword ? "password" : "text"
|
|
778
|
+
const isEditable = editableFields.includes(key)
|
|
779
|
+
const isPassword = key.toLowerCase().includes("pass")
|
|
780
|
+
const inputType = isPassword ? "password" : "text"
|
|
731
781
|
|
|
732
782
|
fieldsHtml += `
|
|
733
783
|
<div class="col-md-4">
|
|
@@ -746,95 +796,100 @@ function updateConfigFields() {
|
|
|
746
796
|
}
|
|
747
797
|
</div>
|
|
748
798
|
</div>
|
|
749
|
-
|
|
799
|
+
`
|
|
750
800
|
}
|
|
751
|
-
})
|
|
801
|
+
})
|
|
752
802
|
|
|
753
|
-
fieldsHtml += `</div></div
|
|
803
|
+
fieldsHtml += `</div></div>`
|
|
754
804
|
}
|
|
755
805
|
|
|
756
|
-
container.innerHTML = fieldsHtml
|
|
806
|
+
container.innerHTML = fieldsHtml
|
|
757
807
|
}
|
|
758
808
|
|
|
759
809
|
// Handle configuration form submission
|
|
760
810
|
async function handleConfigFormSubmit(event) {
|
|
761
|
-
event.preventDefault()
|
|
762
|
-
const form = document.getElementById("configForm")
|
|
763
|
-
const submitBtn = document.getElementById("btn_config_submit")
|
|
811
|
+
event.preventDefault()
|
|
812
|
+
const form = document.getElementById("configForm")
|
|
813
|
+
const submitBtn = document.getElementById("btn_config_submit")
|
|
764
814
|
|
|
765
815
|
if (!form.checkValidity()) {
|
|
766
|
-
event.stopPropagation()
|
|
767
|
-
form.classList.add("was-validated")
|
|
768
|
-
return
|
|
816
|
+
event.stopPropagation()
|
|
817
|
+
form.classList.add("was-validated")
|
|
818
|
+
return
|
|
769
819
|
}
|
|
770
820
|
|
|
771
|
-
form.classList.add("was-validated")
|
|
772
|
-
submitBtn.setAttribute("disabled", "disabled")
|
|
821
|
+
form.classList.add("was-validated")
|
|
822
|
+
submitBtn.setAttribute("disabled", "disabled")
|
|
773
823
|
// Use the new loading spinner style
|
|
774
|
-
submitBtn.innerHTML =
|
|
775
|
-
const formData = new FormData(form)
|
|
776
|
-
const myHeaders = new Headers()
|
|
777
|
-
myHeaders.append("Content-Type", "application/json")
|
|
824
|
+
submitBtn.innerHTML = "<div class=\"loading\"></div>Save and Continue"
|
|
825
|
+
const formData = new FormData(form)
|
|
826
|
+
const myHeaders = new Headers()
|
|
827
|
+
myHeaders.append("Content-Type", "application/json")
|
|
778
828
|
// Extract owner
|
|
779
|
-
const owner = formData.get("owner")
|
|
829
|
+
const owner = formData.get("owner")
|
|
780
830
|
|
|
781
831
|
// Build settings object (exclude owner)
|
|
782
|
-
const settings = {}
|
|
832
|
+
const settings = {}
|
|
783
833
|
for (const [key, value] of formData.entries()) {
|
|
784
|
-
if (key === "owner")
|
|
834
|
+
if (key === "owner")
|
|
835
|
+
continue
|
|
785
836
|
|
|
786
837
|
if (key === "nostrRelays") {
|
|
787
838
|
// Handle comma-separated array
|
|
788
839
|
settings[key] = value
|
|
789
840
|
.split(",")
|
|
790
|
-
.map(
|
|
791
|
-
.filter(
|
|
792
|
-
}
|
|
793
|
-
|
|
841
|
+
.map(item => item.trim())
|
|
842
|
+
.filter(item => item)
|
|
843
|
+
}
|
|
844
|
+
else {
|
|
845
|
+
settings[key] = value
|
|
794
846
|
}
|
|
795
847
|
}
|
|
796
848
|
|
|
797
849
|
const raw = JSON.stringify({
|
|
798
850
|
owner,
|
|
799
851
|
settings,
|
|
800
|
-
})
|
|
852
|
+
})
|
|
801
853
|
|
|
802
854
|
const requestOptions = {
|
|
803
855
|
method: "POST",
|
|
804
856
|
headers: myHeaders,
|
|
805
857
|
body: raw,
|
|
806
858
|
redirect: "follow",
|
|
807
|
-
}
|
|
859
|
+
}
|
|
808
860
|
|
|
809
861
|
try {
|
|
810
|
-
const response = await fetch("/api/lnd/init", requestOptions)
|
|
811
|
-
const result = await response.json()
|
|
862
|
+
const response = await fetch("/api/lnd/init", requestOptions)
|
|
863
|
+
const result = await response.json()
|
|
812
864
|
|
|
813
865
|
if (result.code === 200) {
|
|
814
|
-
await getInfo(false)
|
|
815
|
-
showSuccessModal("Complete configuration saved successfully!")
|
|
816
|
-
}
|
|
817
|
-
|
|
866
|
+
await getInfo(false)
|
|
867
|
+
showSuccessModal("Complete configuration saved successfully!")
|
|
868
|
+
}
|
|
869
|
+
else {
|
|
870
|
+
showErrorModal(result.message || "Configuration failed")
|
|
818
871
|
}
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
872
|
+
}
|
|
873
|
+
catch (error) {
|
|
874
|
+
console.error("Network error:", error)
|
|
875
|
+
showErrorModal("Network error occurred")
|
|
876
|
+
}
|
|
877
|
+
finally {
|
|
878
|
+
submitBtn.removeAttribute("disabled")
|
|
879
|
+
submitBtn.innerHTML
|
|
880
|
+
= "<i class=\"fas fa-save\"></i> Save Complete Configuration"
|
|
826
881
|
}
|
|
827
882
|
}
|
|
828
883
|
|
|
829
884
|
// Page initialization
|
|
830
885
|
document.addEventListener("DOMContentLoaded", async () => {
|
|
831
886
|
// Get information
|
|
832
|
-
await getInfo()
|
|
887
|
+
await getInfo()
|
|
833
888
|
|
|
834
889
|
// Bind form submission events (using event delegation)
|
|
835
890
|
document.addEventListener("submit", (event) => {
|
|
836
891
|
if (event.target.id === "configForm") {
|
|
837
|
-
handleConfigFormSubmit(event)
|
|
892
|
+
handleConfigFormSubmit(event)
|
|
838
893
|
}
|
|
839
|
-
})
|
|
840
|
-
})
|
|
894
|
+
})
|
|
895
|
+
})
|