pinokiod 3.230.0 → 3.232.0

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.
@@ -12,6 +12,7 @@
12
12
  <link href="/noty.css" rel="stylesheet"/>
13
13
  <link href="/style.css" rel="stylesheet"/>
14
14
  <link href="/urldropdown.css" rel="stylesheet" />
15
+ <link href="/tab-link-popover.css" rel="stylesheet" />
15
16
  <% if (agent === "electron") { %>
16
17
  <link href="/electron.css" rel="stylesheet"/>
17
18
  <% } %>
@@ -316,6 +317,8 @@ iframe {
316
317
  <script src="/opener.js"></script>
317
318
  <script src="/nav.js"></script>
318
319
  <script src="/report.js"></script>
320
+ <script src="/tab-link-popover.js"></script>
321
+ <script src="/container-tab-link.js"></script>
319
322
  </head>
320
323
  <body class='<%=theme%>' data-agent="<%=agent%>">
321
324
  <!--
@@ -535,6 +535,9 @@ body.dark aside .current.selected {
535
535
  <button class='btn browse' data-src="<%=item.review_url%>">
536
536
  <i class="fa-regular fa-message"></i> Forum
537
537
  </button>
538
+ <button class='btn browse' data-src="<%=item.view_url%>">
539
+ <i class="fa-regular fa-eye"></i> View
540
+ </button>
538
541
  <button class='btn open-menu'>
539
542
  <i class="fa-solid fa-bars"></i><span> Menu</span>
540
543
  </button>
@@ -626,6 +629,9 @@ body.dark aside .current.selected {
626
629
  <button class='btn browse' data-src="<%=item.review_url%>">
627
630
  <i class="fa-regular fa-message"></i> Forum
628
631
  </button>
632
+ <button class='btn browse' data-src="<%=item.view_url%>">
633
+ <i class="fa-regular fa-eye"></i> View
634
+ </button>
629
635
  <button class='btn open-menu'>
630
636
  <i class="fa-solid fa-bars"></i><span> Menu</span>
631
637
  </button>
@@ -29,6 +29,11 @@
29
29
  text-decoration: none;
30
30
  color: black;
31
31
  }
32
+ a.explain {
33
+ color: rgba(127, 91, 243, 0.9);
34
+ text-decoration: underline;
35
+ cursor: pointer;
36
+ }
32
37
  .status {
33
38
  padding: 10px;
34
39
  margin: 10px;
@@ -157,6 +162,76 @@ body.dark .context-menu-wrapper {
157
162
  text-align: left;
158
163
  color: black;
159
164
  }
165
+ .get-dns-btn {
166
+ margin-top: 10px;
167
+ display: inline-flex;
168
+ align-items: center;
169
+ gap: 6px;
170
+ font-size: 12px;
171
+ padding: 6px 10px;
172
+ justify-content: center;
173
+ max-width: 200px;
174
+ }
175
+ .dns-modal .header-description {
176
+ display: flex;
177
+ align-items: center;
178
+ gap: 10px;
179
+ margin-bottom: 16px;
180
+ }
181
+ .dns-modal .header-description .bubble {
182
+ background: rgba(0,0,0,0.05);
183
+ padding: 12px;
184
+ border-radius: 8px;
185
+ font-size: 13px;
186
+ line-height: 1.4;
187
+ }
188
+ body.dark .dns-modal .header-description .bubble {
189
+ background: rgba(255,255,255,0.08);
190
+ }
191
+ .dns-modal .address-section {
192
+ display: grid;
193
+ grid-template-columns: repeat(2, minmax(0, 1fr));
194
+ gap: 10px;
195
+ margin-bottom: 15px;
196
+ }
197
+ .dns-modal .address-section input {
198
+ padding: 10px;
199
+ border-radius: 6px;
200
+ border: 1px solid rgba(0,0,0,0.15);
201
+ font-size: 14px;
202
+ }
203
+ body.dark .dns-modal .address-section input {
204
+ background: rgba(255,255,255,0.05);
205
+ color: white;
206
+ border-color: rgba(255,255,255,0.18);
207
+ }
208
+ .dns-modal .address-result {
209
+ font-weight: bold;
210
+ display: flex;
211
+ align-items: center;
212
+ justify-content: center;
213
+ gap: 10px;
214
+ font-size: 16px;
215
+ padding: 30px 10px;
216
+ background: rgba(0,0,0,0.04);
217
+ border-radius: 8px;
218
+ }
219
+ body.dark .dns-modal .address-result {
220
+ background: rgba(255,255,255,0.08);
221
+ }
222
+ .dns-progress-modal {
223
+ margin-top: 15px;
224
+ }
225
+ .dns-modal-terminal-container {
226
+ width: 100%;
227
+ height: 280px;
228
+ background: #000;
229
+ border-radius: 8px;
230
+ overflow: hidden;
231
+ }
232
+ .dns-modal-terminal-container .xterm .xterm-viewport {
233
+ width: initial !important;
234
+ }
160
235
  .blank {
161
236
  width: 50px;
162
237
  height: 50px;
@@ -566,7 +641,6 @@ document.addEventListener('DOMContentLoaded', function() {
566
641
  </h1>
567
642
  </header>
568
643
  <main>
569
- <div id='terminal' class='hidden'></div>
570
644
  <div class='container'>
571
645
  <form class='search'>
572
646
  <!--
@@ -592,7 +666,7 @@ document.addEventListener('DOMContentLoaded', function() {
592
666
  <h2><i class="fa-solid fa-wifi"></i> Local network</h2><div>accessible from any machine on the local network</div>
593
667
  </div>
594
668
  <div class='section'>
595
- <h2><i class="fa-solid fa-podcast"></i> Peer</h2><div>accessible from any pinokio peer on the local network <a class='explain' data-type='peer'>How to start a peer</a></div>
669
+ <h2><i class="fa-solid fa-podcast"></i> Peer</h2><div>accessible from any pinokio peer on the local network <a class='explain' data-type='peer'>What is a "Peer"?</a></div>
596
670
  </div>
597
671
  </div>
598
672
  </div>
@@ -616,10 +690,36 @@ document.addEventListener('DOMContentLoaded', function() {
616
690
  <div class='title'><i class="fa-solid fa-circle"></i><span><%=item.title || item.name%></span></div>
617
691
  <div class='grid-3'>
618
692
  <div class='section'>
693
+ <%
694
+ let default_dns_name = ''
695
+ if (item.internal_router && item.internal_router.length > 0) {
696
+ const firstDomain = item.internal_router[0]
697
+ if (typeof firstDomain === 'string') {
698
+ if (firstDomain.endsWith('.localhost')) {
699
+ default_dns_name = firstDomain.replace(/\.localhost$/, '')
700
+ } else {
701
+ default_dns_name = firstDomain
702
+ }
703
+ }
704
+ }
705
+ if (!default_dns_name) {
706
+ default_dns_name = (item.port ? item.port.toString() : (item.name || '')).toString()
707
+ }
708
+ default_dns_name = default_dns_name.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-').replace(/^-+|-+$/g, '')
709
+ if (!default_dns_name && item.port) {
710
+ default_dns_name = item.port.toString()
711
+ }
712
+ %>
619
713
  <% item.internal_router.forEach((domain) => { %>
620
714
  <a class='net' target="_blank" href="https://<%=domain%>">https://<%=domain%></a>
621
715
  <% }) %>
622
716
  <a class='net' target="_blank" href="http://localhost:<%=item.port%>">http://localhost:<%=item.port%></a>
717
+ <% if (allow_dns_creation && item.port) { %>
718
+ <button type='button' class='btn get-dns-btn' data-port="<%=item.port%>" data-default-dns="<%=default_dns_name%>" data-process-title="<%=item.title || item.name%>">
719
+ <i class="fa-solid fa-globe"></i>
720
+ <span>Get .localhost domain</span>
721
+ </button>
722
+ <% } %>
623
723
  </div>
624
724
  <div class='section'>
625
725
  <% if (item.external_ip) { %>
@@ -799,6 +899,19 @@ document.addEventListener('DOMContentLoaded', function() {
799
899
  <% } %>
800
900
  </div>
801
901
  </div>
902
+ <template id='dns-modal-template'>
903
+ <div class='dns-modal'>
904
+ <div class='address-section'>
905
+ <input type='text' placeholder='domain name' id='dns-modal-name'>
906
+ <input type='number' placeholder='port number' id='dns-modal-port' min='1'>
907
+ </div>
908
+ <div class='address-result' id='dns-modal-result'>
909
+ <div>https://[NAME].localhost</div>
910
+ <i class='fa-solid fa-arrow-right'></i>
911
+ <div>http://localhost:[PORT]</div>
912
+ </div>
913
+ </div>
914
+ </template>
802
915
  <aside>
803
916
  <div class='btn-tab'>
804
917
  <button type='button' class='btn' id='create-launcher-button'><i class="fa-solid fa-plus"></i><div class='caption'>Create</div></button>
@@ -836,6 +949,324 @@ document.addEventListener('DOMContentLoaded', function() {
836
949
  </aside>
837
950
  </main>
838
951
  <script>
952
+ const dnsCwd = "<%- typeof cwd !== 'undefined' ? JSON.stringify(cwd).slice(1, -1) : '' %>"
953
+ const dnsModalTemplate = document.querySelector('#dns-modal-template')
954
+ const peerName = "<%= peer && peer.name ? peer.name : '' %>".trim().toLowerCase()
955
+ const allowDnsCreation = <%= allow_dns_creation ? 'true' : 'false' %>
956
+ let dnsModalActive = false
957
+ let dnsTerminalInstance = null
958
+ let dnsSocketInstance = null
959
+ let dnsResizeObserver = null
960
+
961
+ const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
962
+
963
+ const normalizeHost = (value) => {
964
+ if (!value) return null
965
+ let str = String(value).trim().toLowerCase()
966
+ if (!str) return null
967
+ str = str.replace(/^https?:\/\//, '')
968
+ const host = str.split('/')[0]
969
+ return host || null
970
+ }
971
+
972
+ const waitForDnsPropagation = async (dnsName, { timeout = 20000, interval = 1000 } = {}) => {
973
+ const base = (dnsName || '').trim().toLowerCase()
974
+ if (!base) return false
975
+ const targets = new Set()
976
+ const pushTarget = (val) => {
977
+ const host = normalizeHost(val)
978
+ if (host) targets.add(host)
979
+ }
980
+ pushTarget(`${base}.localhost`)
981
+ if (peerName) {
982
+ pushTarget(`${base}.${peerName}.localhost`)
983
+ }
984
+ const deadline = Date.now() + timeout
985
+ while (Date.now() < deadline) {
986
+ try {
987
+ const info = await fetch('/pinokio/peer', { headers: { 'cache-control': 'no-cache' } }).then((res) => {
988
+ if (!res.ok) throw new Error('peer info unavailable')
989
+ return res.json()
990
+ })
991
+ const advertisedHosts = new Set()
992
+ const addHost = (value) => {
993
+ const host = normalizeHost(value)
994
+ if (host) advertisedHosts.add(host)
995
+ }
996
+ if (info && Array.isArray(info.router_info)) {
997
+ info.router_info.forEach((proc) => {
998
+ (proc.internal_router || []).forEach(addHost)
999
+ (proc.external_router || []).forEach(addHost)
1000
+ })
1001
+ }
1002
+ if (info && info.router) {
1003
+ Object.values(info.router).forEach((list) => {
1004
+ if (Array.isArray(list)) {
1005
+ list.forEach(addHost)
1006
+ }
1007
+ })
1008
+ }
1009
+ const found = Array.from(targets).some((host) => advertisedHosts.has(host))
1010
+ if (found) {
1011
+ return true
1012
+ }
1013
+ } catch (err) {
1014
+ console.warn('router poll failed', err)
1015
+ }
1016
+ await delay(interval)
1017
+ }
1018
+ return false
1019
+ }
1020
+
1021
+ const renderDnsPreview = (popup, name, port) => {
1022
+ const preview = popup.querySelector('#dns-modal-result')
1023
+ if (!preview) return
1024
+ const safeName = (name && name.length > 0) ? name : '[NAME]'
1025
+ const safePort = (port && port.length > 0) ? port : '[PORT]'
1026
+ preview.innerHTML = ''
1027
+ const left = document.createElement('div')
1028
+ left.textContent = `https://${safeName}.localhost`
1029
+ const icon = document.createElement('i')
1030
+ icon.className = 'fa-solid fa-arrow-right'
1031
+ const right = document.createElement('div')
1032
+ right.textContent = `http://localhost:${safePort}`
1033
+ preview.append(left, icon, right)
1034
+ }
1035
+
1036
+ const promptForDnsMapping = async ({ port, name, title }) => {
1037
+ if (!dnsModalTemplate) {
1038
+ alert('DNS modal unavailable')
1039
+ return null
1040
+ }
1041
+ const html = dnsModalTemplate.innerHTML
1042
+ const result = await Swal.fire({
1043
+ title: 'Get .localhost domain',
1044
+ html,
1045
+ confirmButtonText: 'Create domain',
1046
+ // showCancelButton: true,
1047
+ // focusConfirm: false,
1048
+ // allowOutsideClick: false,
1049
+ customClass: {
1050
+ popup: 'dns-modal-popup'
1051
+ },
1052
+ didOpen: () => {
1053
+ dnsModalActive = true
1054
+ const popup = Swal.getPopup()
1055
+ const nameInput = popup.querySelector('#dns-modal-name')
1056
+ const portInput = popup.querySelector('#dns-modal-port')
1057
+ if (portInput) {
1058
+ portInput.value = port || ''
1059
+ }
1060
+ const updatePreview = () => {
1061
+ renderDnsPreview(popup, nameInput ? nameInput.value.trim() : '', portInput ? portInput.value.trim() : '')
1062
+ }
1063
+ nameInput && nameInput.addEventListener('input', updatePreview)
1064
+ portInput && portInput.addEventListener('input', updatePreview)
1065
+ updatePreview()
1066
+ if (nameInput) {
1067
+ setTimeout(() => nameInput.focus(), 50)
1068
+ }
1069
+ },
1070
+ preConfirm: () => {
1071
+ const popup = Swal.getPopup()
1072
+ const nameInput = popup.querySelector('#dns-modal-name')
1073
+ const portInput = popup.querySelector('#dns-modal-port')
1074
+ const dnsName = nameInput ? nameInput.value.trim() : ''
1075
+ const dnsPort = portInput ? portInput.value.trim() : ''
1076
+ if (!dnsName) {
1077
+ Swal.showValidationMessage('Enter a domain name')
1078
+ return false
1079
+ }
1080
+ if (!dnsPort) {
1081
+ Swal.showValidationMessage('Enter a port number')
1082
+ return false
1083
+ }
1084
+ if (!/^\d+$/.test(dnsPort)) {
1085
+ Swal.showValidationMessage('Port must be numeric')
1086
+ return false
1087
+ }
1088
+ return { dnsName, dnsPort }
1089
+ }
1090
+ })
1091
+ if (!result.isConfirmed) {
1092
+ return null
1093
+ }
1094
+ return result.value
1095
+ }
1096
+
1097
+ const createDnsTerminal = async (container) => {
1098
+ if (!container) {
1099
+ throw new Error('Terminal container missing')
1100
+ }
1101
+ if (typeof Terminal === 'undefined') {
1102
+ throw new Error('Terminal unavailable')
1103
+ }
1104
+ let config = {
1105
+ scrollback: 9999999,
1106
+ fontSize: 12,
1107
+ theme: document.body.classList.contains('dark') ? xtermTheme.FrontEndDelight : xtermTheme.Tomorrow,
1108
+ }
1109
+ try {
1110
+ const res = await fetch('/xterm_config').then((r) => r.json())
1111
+ if (res && res.config) {
1112
+ config = res.config
1113
+ }
1114
+ } catch (err) {
1115
+ console.warn('Failed to load xterm config', err)
1116
+ }
1117
+ const term = new Terminal(config)
1118
+ dnsTerminalInstance = term
1119
+ const fitAddon = new FitAddon.FitAddon()
1120
+ term.loadAddon(fitAddon)
1121
+ if (typeof WebLinksAddon !== 'undefined' && WebLinksAddon.WebLinksAddon) {
1122
+ term.loadAddon(new WebLinksAddon.WebLinksAddon())
1123
+ }
1124
+ container.innerHTML = ''
1125
+ term.open(container)
1126
+ fitAddon.fit()
1127
+ if (window.PinokioTouch && typeof window.PinokioTouch.bindTerminalFocus === 'function') {
1128
+ window.PinokioTouch.bindTerminalFocus(term, container)
1129
+ }
1130
+ const observer = new ResizeObserver(() => {
1131
+ try {
1132
+ fitAddon.fit()
1133
+ } catch (_) {}
1134
+ })
1135
+ observer.observe(container)
1136
+ return { term, observer }
1137
+ }
1138
+
1139
+ const showDnsError = (message) => {
1140
+ Swal.close()
1141
+ Swal.fire('Domain error', message, 'error')
1142
+ }
1143
+
1144
+ const startDnsCreationSocket = (term, dnsName, dnsPort) => {
1145
+ if (dnsSocketInstance && typeof dnsSocketInstance.close === 'function') {
1146
+ try { dnsSocketInstance.close() } catch (_) {}
1147
+ }
1148
+ const socket = new Socket()
1149
+ dnsSocketInstance = socket
1150
+ const write = (text) => {
1151
+ if (text && text !== '\u0007') {
1152
+ term.write(text)
1153
+ }
1154
+ }
1155
+ const config = {
1156
+ name: dnsName,
1157
+ startType: 'new',
1158
+ projectType: 'dns',
1159
+ dnsPort,
1160
+ }
1161
+ socket.run({
1162
+ id: 'kernel.proto.create',
1163
+ method: 'kernel.proto.create',
1164
+ cwd: dnsCwd,
1165
+ client: {
1166
+ cols: term.cols,
1167
+ rows: term.rows,
1168
+ },
1169
+ params: config,
1170
+ }, (packet) => {
1171
+ if (packet.type === 'stream') {
1172
+ if (packet.data && packet.data.raw) {
1173
+ write(packet.data.raw)
1174
+ } else if (packet.data && packet.data.json) {
1175
+ write(JSON.stringify(packet.data.json).replace(/\n/g, '\r\n'))
1176
+ write('\r\n')
1177
+ } else if (packet.data && packet.data.json2) {
1178
+ write(JSON.stringify(packet.data.json2, null, 2).replace(/\n/g, '\r\n'))
1179
+ write('\r\n')
1180
+ }
1181
+ } else if (packet.type === 'result') {
1182
+ if (packet.data && packet.data.error) {
1183
+ write(`\r\n${packet.data.error}\r\n`)
1184
+ showDnsError(packet.data.error)
1185
+ } else {
1186
+ write('\r\nDomain created. Waiting for router to update...\r\n')
1187
+ const hostname = `${dnsName}.localhost`
1188
+ waitForDnsPropagation(dnsName)
1189
+ .then((ready) => {
1190
+ if (ready) {
1191
+ write(`Router detected ${hostname}. Reloading...\r\n`)
1192
+ } else {
1193
+ write('Router update took longer than expected. Reloading anyway...\r\n')
1194
+ }
1195
+ })
1196
+ .catch((err) => {
1197
+ console.warn('dns propagation wait failed', err)
1198
+ write('Unable to confirm router update. Reloading...\r\n')
1199
+ })
1200
+ .finally(() => {
1201
+ setTimeout(() => {
1202
+ window.location.reload()
1203
+ }, 500)
1204
+ })
1205
+ }
1206
+ } else if (packet.type === 'error') {
1207
+ const message = (packet.data && (packet.data.error || packet.data.message)) || 'Unknown error'
1208
+ write(`\r\n${message}\r\n`)
1209
+ showDnsError(message)
1210
+ }
1211
+ }).then(() => {
1212
+ dnsSocketInstance = null
1213
+ }).catch((err) => {
1214
+ console.error('DNS creation failed', err)
1215
+ showDnsError(err.message || 'Unable to create domain')
1216
+ })
1217
+ }
1218
+
1219
+ const runDnsCreation = ({ dnsName, dnsPort }) => {
1220
+ Swal.fire({
1221
+ title: 'Creating domain',
1222
+ html: `<div class='dns-progress-modal'><div class='dns-modal-terminal-container'></div></div>`,
1223
+ showConfirmButton: false,
1224
+ allowOutsideClick: false,
1225
+ allowEscapeKey: false,
1226
+ didOpen: async () => {
1227
+ try {
1228
+ const popup = Swal.getPopup()
1229
+ const container = popup.querySelector('.dns-modal-terminal-container')
1230
+ const { term, observer } = await createDnsTerminal(container)
1231
+ dnsResizeObserver = observer
1232
+ startDnsCreationSocket(term, dnsName, dnsPort)
1233
+ } catch (err) {
1234
+ console.error('Failed to initialize DNS terminal', err)
1235
+ showDnsError(err.message || 'Unable to start terminal')
1236
+ }
1237
+ },
1238
+ willClose: () => {
1239
+ if (dnsSocketInstance && typeof dnsSocketInstance.close === 'function') {
1240
+ try { dnsSocketInstance.close() } catch (_) {}
1241
+ dnsSocketInstance = null
1242
+ }
1243
+ if (dnsResizeObserver) {
1244
+ try { dnsResizeObserver.disconnect() } catch (_) {}
1245
+ dnsResizeObserver = null
1246
+ }
1247
+ dnsTerminalInstance = null
1248
+ dnsModalActive = false
1249
+ }
1250
+ })
1251
+ }
1252
+
1253
+ document.addEventListener('click', async (event) => {
1254
+ if (!allowDnsCreation) return
1255
+ const trigger = event.target.closest('.get-dns-btn')
1256
+ if (!trigger) {
1257
+ return
1258
+ }
1259
+ event.preventDefault()
1260
+ const port = trigger.getAttribute('data-port') || ''
1261
+ const defaultName = trigger.getAttribute('data-default-dns') || ''
1262
+ const processTitle = trigger.getAttribute('data-process-title') || ''
1263
+ const response = await promptForDnsMapping({ port, name: defaultName, title: processTitle })
1264
+ if (!response) {
1265
+ return
1266
+ }
1267
+ runDnsCreation(response)
1268
+ })
1269
+
839
1270
  let list = []
840
1271
  document.querySelectorAll(".line.index").forEach((el, index) => {
841
1272
  list.push({
@@ -1219,14 +1650,24 @@ document.addEventListener("click", async (e) => {
1219
1650
 
1220
1651
 
1221
1652
 
1222
- if (document.querySelector(".container")) {
1223
- document.querySelector(".container").addEventListener("click", async (e) => {
1224
- e.stopPropagation()
1225
- })
1226
- }
1227
-
1653
+ // if (document.querySelector(".container")) {
1654
+ // document.querySelector(".container").addEventListener("click", async (e) => {
1655
+ // e.stopPropagation()
1656
+ // })
1657
+ // }
1228
1658
 
1229
1659
  })
1660
+ let interval = setInterval(() => {
1661
+ if (dnsModalActive) return
1662
+ fetch("/net/<%=selected_name%>/diff").then((res) => {
1663
+ return res.json()
1664
+ }).then((res) => {
1665
+ console.log(res)
1666
+ if (res.diff) {
1667
+ location.href = location.href
1668
+ }
1669
+ })
1670
+ }, 2000)
1230
1671
  </script>
1231
1672
  </body>
1232
1673
  </html>
@@ -1197,11 +1197,13 @@ document.addEventListener('DOMContentLoaded', function() {
1197
1197
  </aside>
1198
1198
  </main>
1199
1199
  <script>
1200
+ let interval
1200
1201
  document.querySelector("#reset-label").addEventListener("click", async (e) => {
1201
1202
  e.preventDefault()
1202
1203
  e.stopPropagation()
1203
1204
  let ok = confirm("Are you sure you want to reset the network config? (The peer router will be updated to the latest version; 2. A new HTTPS certificate will be generated)")
1204
1205
  if (ok) {
1206
+ clearInterval(interval)
1205
1207
  let r = await fetch("/network/reset", {
1206
1208
  method: "post",
1207
1209
  headers: {
@@ -1366,6 +1368,7 @@ document.querySelector("main").addEventListener("click", async (e) => {
1366
1368
  }
1367
1369
  */
1368
1370
  console.log(body)
1371
+ clearInterval(interval)
1369
1372
  let r = await fetch("/network", {
1370
1373
  method: "post",
1371
1374
  headers: { "Content-Type": "application/json" },
@@ -1547,8 +1550,8 @@ setInterval(() => {
1547
1550
  })
1548
1551
  }, 2000)
1549
1552
  <% } %>
1550
- <% if (peer_active) { %>
1551
- setInterval(() => {
1553
+ <% if (peer_active && processes.length === 0) { %>
1554
+ interval = setInterval(() => {
1552
1555
  fetch("/peer_check").then((res) => {
1553
1556
  return res.json()
1554
1557
  }).then((res) => {
@@ -1558,7 +1561,6 @@ setInterval(() => {
1558
1561
  })
1559
1562
  }, 2000)
1560
1563
  <% } %>
1561
-
1562
1564
  </script>
1563
1565
  </body>
1564
1566
  </html>
@@ -46,7 +46,7 @@
46
46
  </div>
47
47
  <% } else { %>
48
48
  <div class='loader shutdown'>
49
- <i class="fa-solid fa-circle-stop"></i>
49
+ <i class="fa-solid fa-stop"></i>
50
50
  </div>
51
51
  <% } %>
52
52
  <% } else { %>
@@ -65,7 +65,7 @@
65
65
  <i class='fa-solid fa-spin fa-circle-notch'></i>
66
66
  <i class="fa-solid fa-chevron-right"></i>
67
67
  -->
68
- <i class="fa-solid fa-circle-stop"></i>
68
+ <i class="fa-solid fa-stop"></i>
69
69
  </div>
70
70
  <% } %>
71
71
  <% if (item.arrow) { %>
@@ -11,7 +11,7 @@
11
11
  <% } else { %>
12
12
  <div class='loader shutdown'>
13
13
  <button class='btn'>
14
- <i class="fa-solid fa-square"></i> Stop
14
+ <i class="fa-solid fa-stop"></i> Stop
15
15
  </button>
16
16
  </div>
17
17
  <% } %>