lnlink-server 1.0.1 → 1.0.3
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/README.md +461 -0
- package/{app.js → dist/app.js} +329 -321
- package/dist/binaries.json +25 -0
- package/{build-info.json → dist/build-info.json} +2 -1
- package/{index.js → dist/index.js} +690 -121
- package/dist/index.js.map +7 -0
- package/dist/node_modules/debug/.coveralls.yml +1 -0
- package/dist/node_modules/debug/.eslintrc +11 -0
- package/dist/node_modules/debug/.travis.yml +14 -0
- package/dist/node_modules/debug/CHANGELOG.md +362 -0
- package/dist/node_modules/debug/LICENSE +19 -0
- package/dist/node_modules/debug/Makefile +50 -0
- package/dist/node_modules/debug/README.md +312 -0
- package/dist/node_modules/debug/component.json +19 -0
- package/dist/node_modules/debug/karma.conf.js +70 -0
- package/dist/node_modules/debug/node.js +1 -0
- package/dist/node_modules/debug/package.json +49 -0
- package/dist/node_modules/debug/src/browser.js +185 -0
- package/dist/node_modules/debug/src/debug.js +202 -0
- package/dist/node_modules/debug/src/index.js +10 -0
- package/dist/node_modules/debug/src/inspector-log.js +15 -0
- package/dist/node_modules/debug/src/node.js +248 -0
- package/dist/node_modules/depd/History.md +96 -0
- package/dist/node_modules/depd/LICENSE +22 -0
- package/dist/node_modules/depd/Readme.md +280 -0
- package/dist/node_modules/depd/index.js +522 -0
- package/dist/node_modules/depd/lib/browser/index.js +77 -0
- package/dist/node_modules/depd/lib/compat/callsite-tostring.js +103 -0
- package/dist/node_modules/depd/lib/compat/event-listener-count.js +22 -0
- package/dist/node_modules/depd/lib/compat/index.js +79 -0
- package/dist/node_modules/depd/package.json +41 -0
- package/dist/node_modules/http-errors/HISTORY.md +132 -0
- package/dist/node_modules/http-errors/LICENSE +23 -0
- package/dist/node_modules/http-errors/README.md +135 -0
- package/dist/node_modules/http-errors/index.js +260 -0
- package/dist/node_modules/http-errors/package.json +48 -0
- package/dist/node_modules/inherits/LICENSE +16 -0
- package/dist/node_modules/inherits/README.md +42 -0
- package/dist/node_modules/inherits/inherits.js +7 -0
- package/dist/node_modules/inherits/inherits_browser.js +23 -0
- package/dist/node_modules/inherits/package.json +29 -0
- package/dist/node_modules/ms/index.js +152 -0
- package/dist/node_modules/ms/license.md +21 -0
- package/dist/node_modules/ms/package.json +37 -0
- package/dist/node_modules/ms/readme.md +51 -0
- package/dist/node_modules/setprototypeof/LICENSE +13 -0
- package/dist/node_modules/setprototypeof/README.md +26 -0
- package/dist/node_modules/setprototypeof/index.d.ts +2 -0
- package/dist/node_modules/setprototypeof/index.js +15 -0
- package/dist/node_modules/setprototypeof/package.json +25 -0
- package/dist/node_modules/statuses/HISTORY.md +65 -0
- package/dist/node_modules/statuses/LICENSE +23 -0
- package/dist/node_modules/statuses/README.md +127 -0
- package/dist/node_modules/statuses/codes.json +66 -0
- package/dist/node_modules/statuses/index.js +113 -0
- package/dist/node_modules/statuses/package.json +48 -0
- package/dist/package.json +62 -0
- package/dist/public/css/initOwner.css +705 -0
- package/dist/public/img/logo.svg +1 -0
- package/{public → dist/public}/init.html +5 -5
- package/dist/public/js/init.js +895 -0
- package/{setting.regtest.json → dist/setting.regtest.json} +2 -1
- package/package.json +51 -20
- package/binaries.json +0 -20
- package/index.js.map +0 -7
- package/public/css/initOwner.css +0 -553
- package/public/js/init.js +0 -454
- /package/{config.default.js → dist/config.default.js} +0 -0
- /package/{prisma → dist/prisma}/migrations/20250918020814_/migration.sql +0 -0
- /package/{prisma → dist/prisma}/migrations/20251114105314_auto_update/migration.sql +0 -0
- /package/{prisma → dist/prisma}/migrations/migration_lock.toml +0 -0
- /package/{prisma → dist/prisma}/schema.prisma +0 -0
- /package/{proto → dist/proto}/chainkit.proto +0 -0
- /package/{proto → dist/proto}/lightning.proto +0 -0
- /package/{proto → dist/proto}/lit-status.proto +0 -0
- /package/{proto → dist/proto}/looprpc/client.proto +0 -0
- /package/{proto → dist/proto}/price_oracle.proto +0 -0
- /package/{proto → dist/proto}/rfqrpc/rfq.proto +0 -0
- /package/{proto → dist/proto}/routerrpc/router.proto +0 -0
- /package/{proto → dist/proto}/signrpc/signer.proto +0 -0
- /package/{proto → dist/proto}/stateservice.proto +0 -0
- /package/{proto → dist/proto}/swapserverrpc/common.proto +0 -0
- /package/{proto → dist/proto}/tapchannel.proto +0 -0
- /package/{proto → dist/proto}/tapcommon.proto +0 -0
- /package/{proto → dist/proto}/taprootassets.proto +0 -0
- /package/{proto → dist/proto}/universe.proto +0 -0
- /package/{proto → dist/proto}/walletkit.proto +0 -0
- /package/{proto → dist/proto}/walletunlocker.proto +0 -0
- /package/{public → dist/public}/favicon.ico +0 -0
- /package/{setting.mainnet.json → dist/setting.mainnet.json} +0 -0
- /package/{setting.testnet.json → dist/setting.testnet.json} +0 -0
|
@@ -0,0 +1,895 @@
|
|
|
1
|
+
/* eslint-disable unused-imports/no-unused-vars */
|
|
2
|
+
/* eslint-disable no-unused-vars */
|
|
3
|
+
/* global bootstrap */
|
|
4
|
+
|
|
5
|
+
// Global toggle state
|
|
6
|
+
let isAdvancedConfigVisible = false
|
|
7
|
+
|
|
8
|
+
// Function to open external URL in default browser
|
|
9
|
+
function openExternalUrl(url) {
|
|
10
|
+
if (window.electronAPI && window.electronAPI.openExternal) {
|
|
11
|
+
window.electronAPI.openExternal(url)
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
// Fallback for non-Electron environments
|
|
15
|
+
window.open(url, "_blank")
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Network configuration presets
|
|
20
|
+
const defaultNetworkConfigs = {
|
|
21
|
+
regtest: {
|
|
22
|
+
network: "regtest",
|
|
23
|
+
},
|
|
24
|
+
testnet: {
|
|
25
|
+
network: "testnet",
|
|
26
|
+
},
|
|
27
|
+
mainnet: {
|
|
28
|
+
network: "mainnet",
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Initialize networkConfigs with default values
|
|
33
|
+
let networkConfigs = JSON.parse(JSON.stringify(defaultNetworkConfigs))
|
|
34
|
+
|
|
35
|
+
// Fetch network configurations from API
|
|
36
|
+
async function fetchNetworkConfigs() {
|
|
37
|
+
try {
|
|
38
|
+
const response = await fetch(
|
|
39
|
+
"https://dev-edge-api.unift.xyz/edge/nodeconfig/getList",
|
|
40
|
+
{
|
|
41
|
+
method: "POST",
|
|
42
|
+
headers: {
|
|
43
|
+
"Content-Type": "application/json",
|
|
44
|
+
},
|
|
45
|
+
body: JSON.stringify({}),
|
|
46
|
+
},
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
const result = await response.json()
|
|
50
|
+
|
|
51
|
+
if (result.code === "0" && result.data && Array.isArray(result.data)) {
|
|
52
|
+
const apiConfigs = {}
|
|
53
|
+
|
|
54
|
+
result.data.forEach((item) => {
|
|
55
|
+
if (!item.network)
|
|
56
|
+
return
|
|
57
|
+
const networkKey = item.network.toLowerCase()
|
|
58
|
+
|
|
59
|
+
apiConfigs[networkKey] = {
|
|
60
|
+
bitcoindIndex: item.rgbIndex,
|
|
61
|
+
bitcoindPass: item.bitcoindRpcPassword,
|
|
62
|
+
bitcoindRpcHost: item.bitcoindRpcHost,
|
|
63
|
+
bitcoindRpcPort: String(item.bitcoindRpcPort),
|
|
64
|
+
bitcoindUser: item.bitcoindRpcUser,
|
|
65
|
+
bitcoindZmqBlock: item.bitcoindZmqBlock,
|
|
66
|
+
bitcoindZmqRawTx: item.bitcoindZmqRawTx,
|
|
67
|
+
network: networkKey,
|
|
68
|
+
nostrRelays: item.nostrRelays ? [item.nostrRelays] : [],
|
|
69
|
+
officialLndPeer: item.lndPeerId,
|
|
70
|
+
officialLndPeerHost: item.lndPeerHost,
|
|
71
|
+
officialNostrPubKey: item.nostrPubkey,
|
|
72
|
+
officialRgbPeer: item.rgbPeerId,
|
|
73
|
+
officialRgbPeerHost: item.rgbPeerHost,
|
|
74
|
+
officialUniverseServer: item.lndUniverseServer,
|
|
75
|
+
priceOracle: item.lndPriceOracle,
|
|
76
|
+
rgbProxy: item.rgbProxy,
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
// Update global configs, strictly using API response if valid
|
|
81
|
+
if (Object.keys(apiConfigs).length > 0) {
|
|
82
|
+
networkConfigs = apiConfigs
|
|
83
|
+
console.log("Network configs updated from API")
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
console.warn(
|
|
88
|
+
"API returned success but no data or invalid format, using default configs.",
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
console.error(
|
|
94
|
+
"Error fetching network configs, using default configs:",
|
|
95
|
+
error,
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Field display name mapping
|
|
101
|
+
const fieldLabels = {
|
|
102
|
+
// Bitcoin Config
|
|
103
|
+
bitcoindRpcHost: "Bitcoind RPC Host",
|
|
104
|
+
bitcoindRpcPort: "Port",
|
|
105
|
+
bitcoindUser: "User",
|
|
106
|
+
bitcoindPass: "Password",
|
|
107
|
+
bitcoindZmqBlock: "ZMQ Block",
|
|
108
|
+
bitcoindZmqRawTx: "ZMQ Raw TX",
|
|
109
|
+
// LND Config
|
|
110
|
+
officialLndPeer: "Peer ID",
|
|
111
|
+
officialLndPeerHost: "Peer Host",
|
|
112
|
+
officialUniverseServer: "Universe Server",
|
|
113
|
+
priceOracle: "Price Oracle",
|
|
114
|
+
// RGB Config
|
|
115
|
+
officialRgbPeer: "Peer ID",
|
|
116
|
+
officialRgbPeerHost: "Peer Host",
|
|
117
|
+
rgbProxy: "Proxy",
|
|
118
|
+
bitcoindIndex: "Index",
|
|
119
|
+
// Nostr Config
|
|
120
|
+
officialNostrPubKey: "PubKey",
|
|
121
|
+
nostrRelays: "Relays",
|
|
122
|
+
network: "Network Type",
|
|
123
|
+
owner: "Owner Address",
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Show error modal
|
|
127
|
+
function showErrorModal(message) {
|
|
128
|
+
const errorModalBody = document.getElementById("errorModalBody")
|
|
129
|
+
errorModalBody.textContent = message
|
|
130
|
+
if (typeof bootstrap !== "undefined") {
|
|
131
|
+
const errorModal = new bootstrap.Modal(
|
|
132
|
+
document.getElementById("errorModal"),
|
|
133
|
+
)
|
|
134
|
+
errorModal.show()
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
console.error("Error:", message)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Show success modal
|
|
142
|
+
function showSuccessModal(message) {
|
|
143
|
+
const successModalBody = document.getElementById("successModalBody")
|
|
144
|
+
successModalBody.textContent = message
|
|
145
|
+
if (typeof bootstrap !== "undefined") {
|
|
146
|
+
const successModal = new bootstrap.Modal(
|
|
147
|
+
document.getElementById("successModal"),
|
|
148
|
+
)
|
|
149
|
+
successModal.show()
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
console.log("Success:", message)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Status determination function
|
|
157
|
+
function getStatusClass(value) {
|
|
158
|
+
const strValue = String(value).toLowerCase()
|
|
159
|
+
if (
|
|
160
|
+
strValue === "true"
|
|
161
|
+
|| strValue === "running"
|
|
162
|
+
|| strValue === "active"
|
|
163
|
+
|| strValue === "online"
|
|
164
|
+
) {
|
|
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"
|
|
172
|
+
) {
|
|
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" }
|
|
177
|
+
}
|
|
178
|
+
return { class: "", icon: "fas fa-info-circle" }
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Get system information
|
|
182
|
+
async function getInfo(showLoading = true) {
|
|
183
|
+
const mainContent = document.getElementById("main-content")
|
|
184
|
+
|
|
185
|
+
// Show loading state
|
|
186
|
+
if (showLoading) {
|
|
187
|
+
mainContent.innerHTML
|
|
188
|
+
= "<div class=\"loading-container\"><div class=\"loading\"></div><div>Loading information...</div></div>"
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
const res = await fetch("/api/lnd/info").then(res => res.json())
|
|
193
|
+
|
|
194
|
+
if (res.code === 200) {
|
|
195
|
+
if (res.data) {
|
|
196
|
+
const { owner, settings } = res.data
|
|
197
|
+
|
|
198
|
+
// If both owner and settings exist, show configured view
|
|
199
|
+
if (owner && settings) {
|
|
200
|
+
renderConfiguredView(res.data, settings)
|
|
201
|
+
}
|
|
202
|
+
// If missing owner or settings, show configuration form
|
|
203
|
+
else {
|
|
204
|
+
// Fetch network configs only when needed for configuration
|
|
205
|
+
await fetchNetworkConfigs()
|
|
206
|
+
|
|
207
|
+
fetch("https://api.example.com/login", {
|
|
208
|
+
method: "POST",
|
|
209
|
+
headers: {
|
|
210
|
+
"Content-Type": "application/json",
|
|
211
|
+
"Authorization": "Bearer your-token-here", // 可选
|
|
212
|
+
},
|
|
213
|
+
body: JSON.stringify({
|
|
214
|
+
username: "test",
|
|
215
|
+
password: "123456",
|
|
216
|
+
}),
|
|
217
|
+
})
|
|
218
|
+
renderConfigurationForm(res.data)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
else if (
|
|
223
|
+
res.code === 500
|
|
224
|
+
&& res.message === "Please init the wallet or init owner first"
|
|
225
|
+
) {
|
|
226
|
+
// Fetch network configs only when needed for configuration
|
|
227
|
+
await fetchNetworkConfigs()
|
|
228
|
+
|
|
229
|
+
// If wallet/owner not initialized, show configuration form
|
|
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>"
|
|
235
|
+
}
|
|
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>"
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Render configured view
|
|
245
|
+
function renderConfiguredView(basicData, settings) {
|
|
246
|
+
const mainContent = document.getElementById("main-content")
|
|
247
|
+
|
|
248
|
+
// Extract Node ID (Nostr PubKey)
|
|
249
|
+
const nodeId
|
|
250
|
+
= settings.officialNostrPubKey || basicData.officialNostrPubKey || "Unknown"
|
|
251
|
+
|
|
252
|
+
// Determine Service Status
|
|
253
|
+
// We check basicData for status indicators or default to design mocks if not present
|
|
254
|
+
// Assuming keys might exist, otherwise using placeholders for the design requirement
|
|
255
|
+
const rgbStatus = basicData.rgb || "Stopped" // Mock/Default based on design
|
|
256
|
+
const litdStatus = basicData.litd || "Stopped" // Mock/Default based on design
|
|
257
|
+
|
|
258
|
+
const isRgbRunning
|
|
259
|
+
= rgbStatus.toLowerCase() === "running" || rgbStatus === "true"
|
|
260
|
+
const isLitdRunning
|
|
261
|
+
= litdStatus.toLowerCase() === "running" || litdStatus === "true"
|
|
262
|
+
|
|
263
|
+
const rgbClass = isRgbRunning ? "running" : "stopped"
|
|
264
|
+
const litdClass = isLitdRunning ? "running" : "stopped"
|
|
265
|
+
|
|
266
|
+
const rgbText = isRgbRunning ? "Running" : "Stopped"
|
|
267
|
+
const litdText = isLitdRunning ? "Running" : "Stopped"
|
|
268
|
+
|
|
269
|
+
// Helper to shorten address in the middle
|
|
270
|
+
const shortenAddress = (address, chars = 10) => {
|
|
271
|
+
if (!address || address.length <= chars * 2)
|
|
272
|
+
return address || ""
|
|
273
|
+
return `${address.substring(0, chars)}...${address.substring(address.length - chars)}`
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const html = `
|
|
277
|
+
<div class="header-logo text-center mb-5">
|
|
278
|
+
<img src="./img/logo.svg" alt="NodeFlow Logo" style="height: 110px;">
|
|
279
|
+
</div>
|
|
280
|
+
|
|
281
|
+
<div class="welcome-container">
|
|
282
|
+
<h1 class="welcome-title">Welcome!</h1>
|
|
283
|
+
<p class="welcome-subtitle">Your nodes, managed effortlessly.</p>
|
|
284
|
+
|
|
285
|
+
<div class="status-card">
|
|
286
|
+
<!-- Owner Address -->
|
|
287
|
+
<div class="read-only-input-group">
|
|
288
|
+
<label class="card-label">Owner Address</label>
|
|
289
|
+
<div class="input-container" style="position: relative;">
|
|
290
|
+
<div class="read-only-input" id="display-owner">${shortenAddress(basicData.owner || "")}</div>
|
|
291
|
+
<button class="copy-icon-btn" onclick="copyToClipboard('${basicData.owner || ""}', this)">
|
|
292
|
+
<i class="far fa-copy"></i>
|
|
293
|
+
</button>
|
|
294
|
+
</div>
|
|
295
|
+
</div>
|
|
296
|
+
|
|
297
|
+
<!-- Node ID -->
|
|
298
|
+
<div class="read-only-input-group">
|
|
299
|
+
<label class="card-label">Node ID</label>
|
|
300
|
+
<div class="input-container" style="position: relative;">
|
|
301
|
+
<div class="read-only-input" id="display-node-id">${shortenAddress(nodeId)}</div>
|
|
302
|
+
<button class="copy-icon-btn" onclick="copyToClipboard('${nodeId}', this)">
|
|
303
|
+
<i class="far fa-copy"></i>
|
|
304
|
+
</button>
|
|
305
|
+
</div>
|
|
306
|
+
</div>
|
|
307
|
+
|
|
308
|
+
<!-- Service Status -->
|
|
309
|
+
<div class="service-status-section">
|
|
310
|
+
<label class="card-label">Service Status</label>
|
|
311
|
+
<div class="status-grid">
|
|
312
|
+
<div class="status-box">
|
|
313
|
+
<div class="status-name">
|
|
314
|
+
<span class="status-dot ${rgbClass}"></span> RGB
|
|
315
|
+
</div>
|
|
316
|
+
<span class="status-value ${rgbClass}">${rgbText}</span>
|
|
317
|
+
</div>
|
|
318
|
+
<div class="status-box">
|
|
319
|
+
<div class="status-name">
|
|
320
|
+
<span class="status-dot ${litdClass}"></span> LITD
|
|
321
|
+
</div>
|
|
322
|
+
<span class="status-value ${litdClass}">${litdText}</span>
|
|
323
|
+
</div>
|
|
324
|
+
</div>
|
|
325
|
+
</div>
|
|
326
|
+
|
|
327
|
+
<!-- Footer -->
|
|
328
|
+
<div class="card-footer-row">
|
|
329
|
+
<div class="regtest-badge">${settings.network || "Regtest"}</div>
|
|
330
|
+
<button class="settings-icon-btn" onclick="openSettingsModal()">
|
|
331
|
+
<i class="fas fa-cog"></i>
|
|
332
|
+
</button>
|
|
333
|
+
</div>
|
|
334
|
+
</div>
|
|
335
|
+
|
|
336
|
+
<button class="manage-btn" onclick="navigateToManage('${basicData.manageUrl || "#"}', this)">
|
|
337
|
+
Manage My Node <i class="fas fa-arrow-right"></i>
|
|
338
|
+
</button>
|
|
339
|
+
</div>
|
|
340
|
+
|
|
341
|
+
<!-- Settings Modal Placeholder -->
|
|
342
|
+
<div id="settings-modal-container"></div>
|
|
343
|
+
`
|
|
344
|
+
|
|
345
|
+
mainContent.innerHTML = html
|
|
346
|
+
|
|
347
|
+
// Store data globally for modal use
|
|
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
|
+
})
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Open Settings Modal
|
|
378
|
+
function openSettingsModal() {
|
|
379
|
+
const container = document.getElementById("settings-modal-container")
|
|
380
|
+
const settings = window.currentSettings || {}
|
|
381
|
+
const basicData = window.currentBasicData || {}
|
|
382
|
+
|
|
383
|
+
// Helper to create read-only field
|
|
384
|
+
const createField = (label, value, isPassword = false) => {
|
|
385
|
+
const type = isPassword ? "password" : "text"
|
|
386
|
+
const toggleHtml = isPassword
|
|
387
|
+
? `<i class="far fa-eye password-toggle-icon" onclick="togglePasswordVisibility(this)"></i>`
|
|
388
|
+
: ""
|
|
389
|
+
|
|
390
|
+
return `
|
|
391
|
+
<div class="settings-field">
|
|
392
|
+
<label>${label}</label>
|
|
393
|
+
<div class="settings-input-group">
|
|
394
|
+
<input type="${type}" class="settings-input" value="${value || ""}" readonly>
|
|
395
|
+
${toggleHtml}
|
|
396
|
+
</div>
|
|
397
|
+
</div>
|
|
398
|
+
`
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const modalHtml = `
|
|
402
|
+
<div class="settings-modal-overlay" onclick="closeSettingsModal(event)">
|
|
403
|
+
<div class="settings-modal" onclick="event.stopPropagation()">
|
|
404
|
+
<div class="settings-header">
|
|
405
|
+
<h3>Settings</h3>
|
|
406
|
+
<button class="close-modal-btn" onclick="closeSettingsModal()"><i class="fas fa-times"></i></button>
|
|
407
|
+
</div>
|
|
408
|
+
<div class="settings-content">
|
|
409
|
+
<!-- Node Information -->
|
|
410
|
+
<div class="settings-section-title">Node Information</div>
|
|
411
|
+
<div class="settings-grid">
|
|
412
|
+
${createField("Owner Address", basicData.owner)}
|
|
413
|
+
${createField("Node ID", settings.officialNostrPubKey || basicData.officialNostrPubKey)}
|
|
414
|
+
${createField("Network Type", settings.network)}
|
|
415
|
+
</div>
|
|
416
|
+
|
|
417
|
+
<!-- Bitcoin Core -->
|
|
418
|
+
<div class="settings-section-title">Bitcoin Core</div>
|
|
419
|
+
<div class="settings-grid">
|
|
420
|
+
${createField("Bitcoind RPC Host", settings.bitcoindRpcHost)}
|
|
421
|
+
${createField("Port", settings.bitcoindRpcPort)}
|
|
422
|
+
${createField("User", settings.bitcoindUser)}
|
|
423
|
+
${createField("Password", settings.bitcoindPass, true)}
|
|
424
|
+
${createField("ZMQ Block", settings.bitcoindZmqBlock)}
|
|
425
|
+
${createField("ZMQ Raw TX", settings.bitcoindZmqRawTx)}
|
|
426
|
+
</div>
|
|
427
|
+
|
|
428
|
+
<!-- LND -->
|
|
429
|
+
<div class="settings-section-title">LND</div>
|
|
430
|
+
<div class="settings-grid">
|
|
431
|
+
${createField("Peer ID", settings.officialLndPeer)}
|
|
432
|
+
${createField("Peer Host", settings.officialLndPeerHost)}
|
|
433
|
+
${createField("Universe Server", settings.officialUniverseServer)}
|
|
434
|
+
${createField("Price Oracle", settings.priceOracle)}
|
|
435
|
+
</div>
|
|
436
|
+
|
|
437
|
+
<!-- RGB -->
|
|
438
|
+
<div class="settings-section-title">RGB</div>
|
|
439
|
+
<div class="settings-grid">
|
|
440
|
+
${createField("Peer ID", settings.officialRgbPeer)}
|
|
441
|
+
${createField("Peer Host", settings.officialRgbPeerHost)}
|
|
442
|
+
${createField("Proxy", settings.rgbProxy)}
|
|
443
|
+
${createField("Index", settings.bitcoindIndex)}
|
|
444
|
+
</div>
|
|
445
|
+
|
|
446
|
+
<!-- Nostr -->
|
|
447
|
+
<div class="settings-section-title">Nostr</div>
|
|
448
|
+
<div class="settings-grid">
|
|
449
|
+
${createField("Nostr Relay", settings.nostrRelays)}
|
|
450
|
+
${createField("Nostr Private Key", settings.officialNostrPubKey)}
|
|
451
|
+
</div>
|
|
452
|
+
</div>
|
|
453
|
+
</div>
|
|
454
|
+
</div>
|
|
455
|
+
`
|
|
456
|
+
|
|
457
|
+
container.innerHTML = modalHtml
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Close Settings Modal
|
|
461
|
+
function closeSettingsModal(event) {
|
|
462
|
+
const container = document.getElementById("settings-modal-container")
|
|
463
|
+
container.innerHTML = ""
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Toggle Password Visibility
|
|
467
|
+
function togglePasswordVisibility(icon) {
|
|
468
|
+
const input = icon.previousElementSibling
|
|
469
|
+
if (input.type === "password") {
|
|
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")
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Copy to clipboard function
|
|
482
|
+
function copyToClipboard(text, triggerElement) {
|
|
483
|
+
const showFeedback = () => {
|
|
484
|
+
if (triggerElement) {
|
|
485
|
+
const icon = triggerElement.querySelector("i")
|
|
486
|
+
if (icon) {
|
|
487
|
+
const originalClass = icon.className
|
|
488
|
+
icon.className = "fas fa-check"
|
|
489
|
+
|
|
490
|
+
setTimeout(() => {
|
|
491
|
+
icon.className = originalClass
|
|
492
|
+
}, 2000)
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (navigator.clipboard && window.isSecureContext) {
|
|
498
|
+
navigator.clipboard.writeText(text).then(
|
|
499
|
+
() => {
|
|
500
|
+
showFeedback()
|
|
501
|
+
},
|
|
502
|
+
(err) => {
|
|
503
|
+
console.error("Could not copy text: ", err)
|
|
504
|
+
},
|
|
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()
|
|
515
|
+
try {
|
|
516
|
+
document.execCommand("copy")
|
|
517
|
+
showFeedback()
|
|
518
|
+
}
|
|
519
|
+
catch (err) {
|
|
520
|
+
console.error("Fallback: Oops, unable to copy", err)
|
|
521
|
+
}
|
|
522
|
+
document.body.removeChild(textArea)
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Render configuration form (when owner or settings missing)
|
|
527
|
+
function renderConfigurationForm(basicData) {
|
|
528
|
+
const mainContent = document.getElementById("main-content")
|
|
529
|
+
|
|
530
|
+
const html = `
|
|
531
|
+
<div class="header-logo text-center mb-4">
|
|
532
|
+
<img src="img/logo.svg" alt="NodeFlow" height="110">
|
|
533
|
+
</div>
|
|
534
|
+
|
|
535
|
+
<h2 class="text-center text-white mb-2">Node Initial Configuration</h2>
|
|
536
|
+
<p class="text-center text-muted mb-5">You can start the node after viewing and saving the configuration details.</p>
|
|
537
|
+
|
|
538
|
+
<div class="info-section">
|
|
539
|
+
<form class="needs-validation" novalidate id="configForm" autocomplete="off">
|
|
540
|
+
<!-- Owner Configuration -->
|
|
541
|
+
<div class="config-group">
|
|
542
|
+
<label for="owner" class="form-label text-muted">Owner Address</label>
|
|
543
|
+
<div class="input-group">
|
|
544
|
+
<input type="text" class="form-control" id="owner" name="owner" required
|
|
545
|
+
placeholder="Enter owner address" value="${basicData?.owner || ""}" readonly>
|
|
546
|
+
<span class="input-icon copy-btn" onclick="copyToClipboard(document.getElementById('owner').value, this)">
|
|
547
|
+
<i class="far fa-copy"></i>
|
|
548
|
+
</span>
|
|
549
|
+
</div>
|
|
550
|
+
<div class="invalid-feedback">
|
|
551
|
+
Please provide a valid owner address.
|
|
552
|
+
</div>
|
|
553
|
+
</div>
|
|
554
|
+
|
|
555
|
+
<!-- Network Configuration -->
|
|
556
|
+
<div class="config-group network-type mt-4">
|
|
557
|
+
<label class="form-label text-muted">Network Type</label>
|
|
558
|
+
<div class="network-selector d-flex gap-2">
|
|
559
|
+
<button type="button" class="btn btn-network active" data-value="regtest" onclick="selectNetwork('regtest')">Regtest</button>
|
|
560
|
+
<button type="button" class="btn btn-network" data-value="testnet" onclick="selectNetwork('testnet')">Testnet</button>
|
|
561
|
+
<button type="button" class="btn btn-network" data-value="mainnet" onclick="selectNetwork('mainnet')">Mainnet</button>
|
|
562
|
+
</div>
|
|
563
|
+
<input type="hidden" id="network" name="network" value="regtest">
|
|
564
|
+
</div>
|
|
565
|
+
|
|
566
|
+
<!-- Advanced Toggle -->
|
|
567
|
+
<div class="mt-3 mb-3 d-flex align-items-center justify-content-between">
|
|
568
|
+
<a href="javascript:void(0)" class="advanced-toggle text-decoration-none text-lime" onclick="toggleAdvanced()">
|
|
569
|
+
Show Advanced Configuration <i class="fas fa-chevron-down" id="advanced-icon"></i>
|
|
570
|
+
</a>
|
|
571
|
+
<span id="mainnet-badge" class="customizable-badge" style="display: none;">Customizable parameters available.</span>
|
|
572
|
+
</div>
|
|
573
|
+
|
|
574
|
+
<!-- Dynamic Configuration Fields -->
|
|
575
|
+
<div id="config-fields-container" style="display: none;"></div>
|
|
576
|
+
|
|
577
|
+
<div class="row mb-3 mt-4">
|
|
578
|
+
<button type="submit" id="btn_config_submit" class="btn btn-lime w-100 py-2 fw-bold">
|
|
579
|
+
Save and Continue
|
|
580
|
+
</button>
|
|
581
|
+
</div>
|
|
582
|
+
</form>
|
|
583
|
+
</div>
|
|
584
|
+
`
|
|
585
|
+
|
|
586
|
+
mainContent.innerHTML = html
|
|
587
|
+
|
|
588
|
+
// Initialize configuration fields
|
|
589
|
+
updateConfigFields()
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Toggle Advanced Configuration
|
|
593
|
+
window.toggleAdvanced = function () {
|
|
594
|
+
isAdvancedConfigVisible = !isAdvancedConfigVisible
|
|
595
|
+
const container = document.getElementById("config-fields-container")
|
|
596
|
+
const icon = document.getElementById("advanced-icon")
|
|
597
|
+
|
|
598
|
+
if (isAdvancedConfigVisible) {
|
|
599
|
+
container.style.display = "block"
|
|
600
|
+
icon.classList.remove("fa-chevron-down")
|
|
601
|
+
icon.classList.add("fa-chevron-up")
|
|
602
|
+
}
|
|
603
|
+
else {
|
|
604
|
+
container.style.display = "none"
|
|
605
|
+
icon.classList.remove("fa-chevron-up")
|
|
606
|
+
icon.classList.add("fa-chevron-down")
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// Select Network
|
|
611
|
+
window.selectNetwork = function (network) {
|
|
612
|
+
if (networkConfigs && !networkConfigs[network]) {
|
|
613
|
+
showErrorModal("This network is not supported")
|
|
614
|
+
return
|
|
615
|
+
}
|
|
616
|
+
document.getElementById("network").value = network
|
|
617
|
+
|
|
618
|
+
// Update active button state
|
|
619
|
+
document.querySelectorAll(".btn-network").forEach((btn) => {
|
|
620
|
+
if (btn.dataset.value === network) {
|
|
621
|
+
btn.classList.add("active")
|
|
622
|
+
}
|
|
623
|
+
else {
|
|
624
|
+
btn.classList.remove("active")
|
|
625
|
+
}
|
|
626
|
+
})
|
|
627
|
+
|
|
628
|
+
// Toggle mainnet badge
|
|
629
|
+
const badge = document.getElementById("mainnet-badge")
|
|
630
|
+
if (badge) {
|
|
631
|
+
badge.style.display = network === "mainnet" ? "inline-block" : "none"
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
updateConfigFields()
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
window.copyToClipboard = copyToClipboard
|
|
638
|
+
|
|
639
|
+
window.togglePassword = function (id) {
|
|
640
|
+
const input = document.getElementById(id)
|
|
641
|
+
if (input.type === "password") {
|
|
642
|
+
input.type = "text"
|
|
643
|
+
}
|
|
644
|
+
else {
|
|
645
|
+
input.type = "password"
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Render basic information list
|
|
650
|
+
function renderBasicInfoList(data) {
|
|
651
|
+
let html = ""
|
|
652
|
+
const excludeKeys = ["settings"]
|
|
653
|
+
|
|
654
|
+
for (const key in data) {
|
|
655
|
+
if (excludeKeys.includes(key))
|
|
656
|
+
continue
|
|
657
|
+
|
|
658
|
+
const value = data[key]
|
|
659
|
+
const status = getStatusClass(value)
|
|
660
|
+
|
|
661
|
+
let valueHtml = `<span class="info-value ${status.class}">
|
|
662
|
+
<i class="${status.icon} status-icon"></i>${value}
|
|
663
|
+
</span>`
|
|
664
|
+
|
|
665
|
+
if (key === "manageUrl") {
|
|
666
|
+
valueHtml = `<span class="info-value">
|
|
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;">
|
|
668
|
+
<i class="fas fa-external-link-alt status-icon"></i>Click to manage
|
|
669
|
+
</a>
|
|
670
|
+
</span>`
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
html += `
|
|
674
|
+
<li class="list-group-item">
|
|
675
|
+
<div class="info-key">${key}:</div>
|
|
676
|
+
<div class="info-value-container">${valueHtml}</div>
|
|
677
|
+
</li>
|
|
678
|
+
`
|
|
679
|
+
}
|
|
680
|
+
return html
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// Render settings list
|
|
684
|
+
function renderSettingsList(settings) {
|
|
685
|
+
let html = ""
|
|
686
|
+
for (const key in settings) {
|
|
687
|
+
const value = Array.isArray(settings[key])
|
|
688
|
+
? settings[key].join(", ")
|
|
689
|
+
: settings[key]
|
|
690
|
+
const label = fieldLabels[key] || key
|
|
691
|
+
|
|
692
|
+
html += `
|
|
693
|
+
<li class="list-group-item">
|
|
694
|
+
<div class="info-key">${label}:</div>
|
|
695
|
+
<div class="info-value-container">
|
|
696
|
+
<span class="info-value">${value}</span>
|
|
697
|
+
</div>
|
|
698
|
+
</li>
|
|
699
|
+
`
|
|
700
|
+
}
|
|
701
|
+
return html
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// Update configuration fields
|
|
705
|
+
function updateConfigFields() {
|
|
706
|
+
const networkInput = document.getElementById("network")
|
|
707
|
+
const container = document.getElementById("config-fields-container")
|
|
708
|
+
|
|
709
|
+
if (!networkInput || !container)
|
|
710
|
+
return
|
|
711
|
+
|
|
712
|
+
const selectedNetwork = networkInput.value
|
|
713
|
+
const config = networkConfigs[selectedNetwork]
|
|
714
|
+
|
|
715
|
+
if (!config)
|
|
716
|
+
return
|
|
717
|
+
|
|
718
|
+
let fieldsHtml = ""
|
|
719
|
+
|
|
720
|
+
// Group fields by category
|
|
721
|
+
const groups = {
|
|
722
|
+
"Bitcoin Config": [
|
|
723
|
+
"bitcoindRpcHost",
|
|
724
|
+
"bitcoindRpcPort",
|
|
725
|
+
"bitcoindUser",
|
|
726
|
+
"bitcoindPass",
|
|
727
|
+
"bitcoindZmqBlock",
|
|
728
|
+
"bitcoindZmqRawTx",
|
|
729
|
+
],
|
|
730
|
+
"LND Config": [
|
|
731
|
+
"officialLndPeer",
|
|
732
|
+
"officialLndPeerHost",
|
|
733
|
+
"officialUniverseServer",
|
|
734
|
+
"priceOracle",
|
|
735
|
+
],
|
|
736
|
+
"RGB Config": [
|
|
737
|
+
"officialRgbPeer",
|
|
738
|
+
"officialRgbPeerHost",
|
|
739
|
+
"rgbProxy",
|
|
740
|
+
"bitcoindIndex",
|
|
741
|
+
],
|
|
742
|
+
"Nostr Config": ["officialNostrPubKey", "nostrRelays"],
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// Define editable fields per network
|
|
746
|
+
let editableFields = []
|
|
747
|
+
if (selectedNetwork === "regtest") {
|
|
748
|
+
editableFields = ["bitcoindPass"]
|
|
749
|
+
}
|
|
750
|
+
else if (selectedNetwork === "mainnet") {
|
|
751
|
+
editableFields = [
|
|
752
|
+
"bitcoindRpcHost",
|
|
753
|
+
"bitcoindRpcPort",
|
|
754
|
+
"bitcoindUser",
|
|
755
|
+
"bitcoindPass",
|
|
756
|
+
"bitcoindZmqBlock",
|
|
757
|
+
"bitcoindZmqRawTx",
|
|
758
|
+
"rgbProxy",
|
|
759
|
+
"bitcoindIndex",
|
|
760
|
+
]
|
|
761
|
+
}
|
|
762
|
+
else if (selectedNetwork === "testnet") {
|
|
763
|
+
editableFields = ["bitcoindPass"]
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
for (const groupName in groups) {
|
|
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">`
|
|
770
|
+
|
|
771
|
+
groups[groupName].forEach((key) => {
|
|
772
|
+
if (config[key] !== undefined) {
|
|
773
|
+
const label = fieldLabels[key] || key
|
|
774
|
+
const value = Array.isArray(config[key])
|
|
775
|
+
? config[key].join(",")
|
|
776
|
+
: config[key]
|
|
777
|
+
|
|
778
|
+
const isEditable = editableFields.includes(key)
|
|
779
|
+
const isPassword = key.toLowerCase().includes("pass")
|
|
780
|
+
const inputType = isPassword ? "password" : "text"
|
|
781
|
+
|
|
782
|
+
fieldsHtml += `
|
|
783
|
+
<div class="col-md-4">
|
|
784
|
+
<label for="${key}" class="form-label text-muted small">${label}</label>
|
|
785
|
+
<div class="input-group">
|
|
786
|
+
<input type="${inputType}" class="form-control" id="${key}" name="${key}"
|
|
787
|
+
value="${value}" ${isEditable ? "" : "readonly"}>
|
|
788
|
+
${
|
|
789
|
+
isPassword && isEditable
|
|
790
|
+
? `
|
|
791
|
+
<span class="input-icon toggle-password" onclick="togglePassword('${key}')">
|
|
792
|
+
<i class="far fa-eye"></i>
|
|
793
|
+
</span>
|
|
794
|
+
`
|
|
795
|
+
: ""
|
|
796
|
+
}
|
|
797
|
+
</div>
|
|
798
|
+
</div>
|
|
799
|
+
`
|
|
800
|
+
}
|
|
801
|
+
})
|
|
802
|
+
|
|
803
|
+
fieldsHtml += `</div></div>`
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
container.innerHTML = fieldsHtml
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// Handle configuration form submission
|
|
810
|
+
async function handleConfigFormSubmit(event) {
|
|
811
|
+
event.preventDefault()
|
|
812
|
+
const form = document.getElementById("configForm")
|
|
813
|
+
const submitBtn = document.getElementById("btn_config_submit")
|
|
814
|
+
|
|
815
|
+
if (!form.checkValidity()) {
|
|
816
|
+
event.stopPropagation()
|
|
817
|
+
form.classList.add("was-validated")
|
|
818
|
+
return
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
form.classList.add("was-validated")
|
|
822
|
+
submitBtn.setAttribute("disabled", "disabled")
|
|
823
|
+
// Use the new loading spinner style
|
|
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")
|
|
828
|
+
// Extract owner
|
|
829
|
+
const owner = formData.get("owner")
|
|
830
|
+
|
|
831
|
+
// Build settings object (exclude owner)
|
|
832
|
+
const settings = {}
|
|
833
|
+
for (const [key, value] of formData.entries()) {
|
|
834
|
+
if (key === "owner")
|
|
835
|
+
continue
|
|
836
|
+
|
|
837
|
+
if (key === "nostrRelays") {
|
|
838
|
+
// Handle comma-separated array
|
|
839
|
+
settings[key] = value
|
|
840
|
+
.split(",")
|
|
841
|
+
.map(item => item.trim())
|
|
842
|
+
.filter(item => item)
|
|
843
|
+
}
|
|
844
|
+
else {
|
|
845
|
+
settings[key] = value
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
const raw = JSON.stringify({
|
|
850
|
+
owner,
|
|
851
|
+
settings,
|
|
852
|
+
})
|
|
853
|
+
|
|
854
|
+
const requestOptions = {
|
|
855
|
+
method: "POST",
|
|
856
|
+
headers: myHeaders,
|
|
857
|
+
body: raw,
|
|
858
|
+
redirect: "follow",
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
try {
|
|
862
|
+
const response = await fetch("/api/lnd/init", requestOptions)
|
|
863
|
+
const result = await response.json()
|
|
864
|
+
|
|
865
|
+
if (result.code === 200) {
|
|
866
|
+
await getInfo(false)
|
|
867
|
+
showSuccessModal("Complete configuration saved successfully!")
|
|
868
|
+
}
|
|
869
|
+
else {
|
|
870
|
+
showErrorModal(result.message || "Configuration failed")
|
|
871
|
+
}
|
|
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"
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// Page initialization
|
|
885
|
+
document.addEventListener("DOMContentLoaded", async () => {
|
|
886
|
+
// Get information
|
|
887
|
+
await getInfo()
|
|
888
|
+
|
|
889
|
+
// Bind form submission events (using event delegation)
|
|
890
|
+
document.addEventListener("submit", (event) => {
|
|
891
|
+
if (event.target.id === "configForm") {
|
|
892
|
+
handleConfigFormSubmit(event)
|
|
893
|
+
}
|
|
894
|
+
})
|
|
895
|
+
})
|