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.
- package/dist/app.js +409 -357
- package/dist/binaries.json +12 -7
- package/dist/build-info.json +2 -1
- package/dist/index.js +690 -122
- package/dist/index.js.map +4 -4
- 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 +3 -2
- package/dist/public/css/initOwner.css +578 -426
- package/dist/public/img/logo.svg +1 -0
- package/dist/public/init.html +5 -5
- package/dist/public/js/init.js +644 -258
- package/dist/setting.regtest.json +1 -1
- package/package.json +4 -3
package/dist/public/js/init.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
67
|
-
bitcoindPass: "Bitcoind Password",
|
|
96
|
+
// Bitcoin Config
|
|
68
97
|
bitcoindRpcHost: "Bitcoind RPC Host",
|
|
69
|
-
bitcoindRpcPort: "
|
|
70
|
-
bitcoindUser: "
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
officialLndPeer: "
|
|
76
|
-
officialLndPeerHost: "
|
|
77
|
-
|
|
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
|
-
|
|
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(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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 (
|
|
115
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
155
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
|
231
|
+
// Render configured view
|
|
165
232
|
function renderConfiguredView(basicData, settings) {
|
|
166
|
-
const mainContent = document.getElementById("main-content")
|
|
167
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
211
|
-
<div class="
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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-
|
|
226
|
-
<
|
|
227
|
-
<div class="
|
|
228
|
-
<
|
|
229
|
-
<
|
|
230
|
-
|
|
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
|
|
247
|
-
<button type="submit" id="btn_config_submit" class="btn btn-
|
|
248
|
-
|
|
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="
|
|
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])
|
|
300
|
-
|
|
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
|
|
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 (!
|
|
320
|
-
return
|
|
663
|
+
if (!networkInput || !container) return;
|
|
321
664
|
|
|
322
|
-
const selectedNetwork =
|
|
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 = "
|
|
670
|
+
let fieldsHtml = "";
|
|
329
671
|
|
|
330
672
|
// Group fields by category
|
|
331
673
|
const groups = {
|
|
332
|
-
"Bitcoin
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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-
|
|
341
|
-
fieldsHtml += `<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])
|
|
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
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
</
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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
|
+
});
|