lnlink-server 1.0.0 → 1.0.2

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