pinokiod 3.6.28 → 3.6.30
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/kernel/api/index.js +96 -27
- package/kernel/bin/index.js +122 -52
- package/kernel/bin/node.js +5 -0
- package/kernel/index.js +17 -1
- package/kernel/shell.js +12 -4
- package/kernel/sysinfo.js +38 -17
- package/kernel/util.js +28 -7
- package/package.json +1 -1
- package/server/index.js +170 -108
- package/server/public/pinokio-black.png +0 -0
- package/server/public/pinokio-black2.png +0 -0
- package/server/public/style.css +5 -0
- package/server/socket.js +82 -35
- package/server/views/app.ejs +14 -2
- package/server/views/editor.ejs +12 -2
- package/server/views/index.ejs +27 -36
- package/server/views/terminal.ejs +39 -14
package/server/socket.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
const WebSocket = require('ws');
|
|
2
|
+
const Util = require("../kernel/util")
|
|
2
3
|
class Socket {
|
|
3
4
|
constructor(parent) {
|
|
4
5
|
this.buffer = {}
|
|
5
6
|
this.connected = {}
|
|
7
|
+
this.active_shell = {}
|
|
6
8
|
this.parent = parent
|
|
7
9
|
this.server = parent.server
|
|
8
10
|
// this.kernel = parent.kernel
|
|
@@ -25,42 +27,65 @@ class Socket {
|
|
|
25
27
|
// look for repos that match
|
|
26
28
|
|
|
27
29
|
if (req.uri) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
ws.send(JSON.stringify({
|
|
43
|
-
data: this.parent.kernel.api.running[id] ? true : false
|
|
44
|
-
}))
|
|
45
|
-
} else if (req.stop) {
|
|
46
|
-
this.parent.kernel.api.stop({ params: req })
|
|
30
|
+
if (req.mode === "open") {
|
|
31
|
+
// get the default script and respond
|
|
32
|
+
let id = this.parent.kernel.api.filePath(req.uri)
|
|
33
|
+
try {
|
|
34
|
+
let default_url = await this.parent.kernel.api.get_default(id)
|
|
35
|
+
console.log({ id, default_url })
|
|
36
|
+
ws.send(JSON.stringify({
|
|
37
|
+
data: {
|
|
38
|
+
uri: default_url
|
|
39
|
+
}
|
|
40
|
+
}))
|
|
41
|
+
} catch (e) {
|
|
42
|
+
console.log(e)
|
|
43
|
+
}
|
|
47
44
|
} else {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
45
|
+
console.log("REQ.URI", req.uri)
|
|
46
|
+
if (req.uri.startsWith("http")) {
|
|
47
|
+
// open
|
|
48
|
+
Util.openfs(req.uri, { mode: "open" })
|
|
49
|
+
} else {
|
|
50
|
+
/******************************************************************
|
|
51
|
+
*
|
|
52
|
+
* req.uri is ALWAYS either of the two:
|
|
53
|
+
* 1. ~/...
|
|
54
|
+
* 2. https://github.com/...
|
|
55
|
+
*
|
|
56
|
+
* Need to turn it into absolute file path before subscribing
|
|
57
|
+
*
|
|
58
|
+
******************************************************************/
|
|
59
|
+
|
|
60
|
+
// req.uri is always http or absolute path
|
|
61
|
+
let id = this.parent.kernel.api.filePath(req.uri)
|
|
62
|
+
|
|
63
|
+
if (req.status) {
|
|
64
|
+
ws.send(JSON.stringify({
|
|
65
|
+
data: this.parent.kernel.api.running[id] ? true : false
|
|
66
|
+
}))
|
|
67
|
+
} else if (req.stop) {
|
|
68
|
+
this.parent.kernel.api.stop({ params: req })
|
|
69
|
+
} else {
|
|
70
|
+
let buf = this.buffer[id]
|
|
71
|
+
let sh = this.active_shell[id]
|
|
72
|
+
this.subscribe(ws, id, buf, sh)
|
|
73
|
+
if (req.mode !== "listen") {
|
|
74
|
+
// Run only if currently not running
|
|
75
|
+
if (!this.parent.kernel.api.running[id]) {
|
|
76
|
+
|
|
77
|
+
// clear the log first
|
|
78
|
+
|
|
79
|
+
await this.parent.kernel.clearLog(id)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
this.parent.kernel.api.process(req)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
60
85
|
}
|
|
61
86
|
}
|
|
62
|
-
}
|
|
63
87
|
|
|
88
|
+
}
|
|
64
89
|
} else if (req.method) {
|
|
65
90
|
this.subscribe(ws, req.method)
|
|
66
91
|
if (req.mode !== "listen") {
|
|
@@ -69,7 +94,6 @@ class Socket {
|
|
|
69
94
|
} else if (req.emit) {
|
|
70
95
|
this.parent.kernel.shell.emit(req)
|
|
71
96
|
} else if (req.key && req.id) {
|
|
72
|
-
console.log( { req })
|
|
73
97
|
this.parent.kernel.shell.emit({
|
|
74
98
|
id: req.id,
|
|
75
99
|
emit: req.key
|
|
@@ -84,11 +108,16 @@ class Socket {
|
|
|
84
108
|
headers.push('Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept');
|
|
85
109
|
});
|
|
86
110
|
}
|
|
87
|
-
subscribe(ws, id) {
|
|
111
|
+
subscribe(ws, id, buf, sh) {
|
|
88
112
|
|
|
89
113
|
if (this.parent.kernel.api.running[id]) {
|
|
90
114
|
ws.send(JSON.stringify({
|
|
91
|
-
type: "connect"
|
|
115
|
+
type: "connect",
|
|
116
|
+
data: {
|
|
117
|
+
id,
|
|
118
|
+
state: buf,
|
|
119
|
+
shell: sh
|
|
120
|
+
}
|
|
92
121
|
}))
|
|
93
122
|
}
|
|
94
123
|
|
|
@@ -126,6 +155,15 @@ class Socket {
|
|
|
126
155
|
});
|
|
127
156
|
}
|
|
128
157
|
|
|
158
|
+
if (!this.buffer[id]) {
|
|
159
|
+
this.buffer[id] = ""
|
|
160
|
+
}
|
|
161
|
+
if (e.data && e.data.raw) this.buffer[id] += e.data.raw
|
|
162
|
+
|
|
163
|
+
if (e.data && e.data.shell_id) {
|
|
164
|
+
this.active_shell[id] = e.data.shell_id
|
|
165
|
+
}
|
|
166
|
+
|
|
129
167
|
// send to caller session
|
|
130
168
|
if (e.caller) {
|
|
131
169
|
let caller
|
|
@@ -154,6 +192,15 @@ class Socket {
|
|
|
154
192
|
}
|
|
155
193
|
});
|
|
156
194
|
}
|
|
195
|
+
|
|
196
|
+
if (!this.buffer[caller]) {
|
|
197
|
+
this.buffer[caller] = ""
|
|
198
|
+
}
|
|
199
|
+
if (e.data.raw) this.buffer[caller] += e.data.raw
|
|
200
|
+
|
|
201
|
+
if (e.data && e.data.shell_id) {
|
|
202
|
+
this.active_shell[caller] = e.data.shell_id
|
|
203
|
+
}
|
|
157
204
|
}
|
|
158
205
|
}
|
|
159
206
|
}
|
package/server/views/app.ejs
CHANGED
|
@@ -29,7 +29,9 @@
|
|
|
29
29
|
*/
|
|
30
30
|
}
|
|
31
31
|
#menu.margin {
|
|
32
|
+
/*
|
|
32
33
|
padding: 5px;
|
|
34
|
+
*/
|
|
33
35
|
}
|
|
34
36
|
#menu.margin i {
|
|
35
37
|
display: none;
|
|
@@ -580,7 +582,7 @@ body.dark .loader-container {
|
|
|
580
582
|
<div class='header-top header-item'>
|
|
581
583
|
<div class='app-info'>
|
|
582
584
|
<% if (config.icon) { %>
|
|
583
|
-
<img src="<%=
|
|
585
|
+
<img src="<%=config.icon%>"/>
|
|
584
586
|
<% } else { %>
|
|
585
587
|
<i class="fa-regular fa-circle-dot"></i>
|
|
586
588
|
<% } %>
|
|
@@ -595,6 +597,15 @@ body.dark .loader-container {
|
|
|
595
597
|
<a target="<%=src%>" href="<%=src%>" class='btn header-item frame-link' data-index="0">
|
|
596
598
|
<div class='tab'><i class="fa-regular fa-folder-open"></i> Files <div class='disk-usage'></div></div>
|
|
597
599
|
</a>
|
|
600
|
+
<div class='shells'>
|
|
601
|
+
<% shells.forEach((shell, index) => { %>
|
|
602
|
+
<a target="SHELL<%=index%>" data-mode="" href="<%=shell%>" class='btn header-item frame-link'>
|
|
603
|
+
<div class='tab'>
|
|
604
|
+
<i class='fa-solid fa-terminal'></i> <%=shell%></i>
|
|
605
|
+
</div>
|
|
606
|
+
</a>
|
|
607
|
+
<% }) %>
|
|
608
|
+
</div>
|
|
598
609
|
<a target="/share/<%=name%>" href="/share/<%=name%>", class="btn header-item frame-link" data-index="1" data-mode="refresh">
|
|
599
610
|
<div class='tab'><i class="fa-solid fa-wifi"></i> Share</div>
|
|
600
611
|
</a>
|
|
@@ -653,7 +664,7 @@ body.dark .loader-container {
|
|
|
653
664
|
<div class='bar'>
|
|
654
665
|
<button class='btn2' id='menu-mobile'><i class="fa-solid fa-bars"></i></button>
|
|
655
666
|
<button class='btn2' id='menu'>
|
|
656
|
-
<img src="<%=
|
|
667
|
+
<img src="<%=config.icon%>"/>
|
|
657
668
|
<i class="fa-solid fa-arrow-right-to-bracket fa-rotate-180"></i>
|
|
658
669
|
</button>
|
|
659
670
|
<% if (agent === "electron") { %>
|
|
@@ -1510,6 +1521,7 @@ body.dark .loader-container {
|
|
|
1510
1521
|
console.log("asideHidden", asideHidden)
|
|
1511
1522
|
if (!asideHidden) {
|
|
1512
1523
|
// refresh only if aside
|
|
1524
|
+
console.log("type", event.data.type)
|
|
1513
1525
|
if (event.data.type === 'disconnect') {
|
|
1514
1526
|
console.log("Refresh 1")
|
|
1515
1527
|
refresh()
|
package/server/views/editor.ejs
CHANGED
|
@@ -113,6 +113,7 @@ body.frozen {
|
|
|
113
113
|
}
|
|
114
114
|
</style>
|
|
115
115
|
<script>
|
|
116
|
+
let shell_id
|
|
116
117
|
document.addEventListener("DOMContentLoaded", async () => {
|
|
117
118
|
<% if (error) { %>
|
|
118
119
|
document.querySelector(".requirements .content").innerHTML = '<div class="loading"><i class="fa-solid fa-circle-exclamation"></i> <%=error%></div>'
|
|
@@ -331,8 +332,17 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
331
332
|
|
|
332
333
|
} else if (packet.type === 'connect') {
|
|
333
334
|
// document.querySelector(".terminal-container").classList.remove("hidden")
|
|
334
|
-
|
|
335
|
-
|
|
335
|
+
if (packet.data.id) {
|
|
336
|
+
shell_id = packet.data.id
|
|
337
|
+
}
|
|
338
|
+
if (packet.data) {
|
|
339
|
+
if (packet.data.shell) {
|
|
340
|
+
shell_id = packet.data.shell
|
|
341
|
+
}
|
|
342
|
+
if (packet.data.state) {
|
|
343
|
+
this.write(packet.data.state)
|
|
344
|
+
}
|
|
345
|
+
}
|
|
336
346
|
/*
|
|
337
347
|
for(let i=0; i<message.length; i++) {
|
|
338
348
|
await new Promise((r, reject) => {
|
package/server/views/index.ejs
CHANGED
|
@@ -246,32 +246,6 @@ body.dark .browser-options-row {
|
|
|
246
246
|
<div>Serve Pinokio, installed apps, and even external apps (example: Ollama) to any device on the same network.</div>
|
|
247
247
|
</div>
|
|
248
248
|
</div>
|
|
249
|
-
<!--
|
|
250
|
-
<div class='browser-options-row'>
|
|
251
|
-
<a href="/proxy" class='flexible'>
|
|
252
|
-
<h3><i class="fa-solid fa-wifi"></i> Local Share</h3>
|
|
253
|
-
<div>Access Pinokio, Ollama, and other 3rd party apps from any device on the same network. By default it's off.</div>
|
|
254
|
-
</a>
|
|
255
|
-
<% if (false) { %>
|
|
256
|
-
<% if (proxy) { %>
|
|
257
|
-
<a id='wifi-stop' data-proxy="<%=proxy.proxy%>" class='btn'><div><i class="fa-solid fa-stop"></i> Stop</div></a>
|
|
258
|
-
<div class='flexible'>
|
|
259
|
-
<h3><i class="fa-solid fa-wifi"></i> Local Share</h3>
|
|
260
|
-
<div>Access Pinokio from any device on the same network. By default it's off.</div>
|
|
261
|
-
<span class='badge'><i class="fa-solid fa-circle-notch fa-spin"></i> Running at <a href="<%=proxy.proxy%>" target="_blank" features="browser"><%=proxy.proxy%></a></span>
|
|
262
|
-
<a id='wifi-qr' class='badge'><i class="fa-solid fa-qrcode"></i> Scan QR Code</a>
|
|
263
|
-
</div>
|
|
264
|
-
<% } else { %>
|
|
265
|
-
<a id='wifi' class='btn'><div><i class="fa-solid fa-play"></i> Start</div></a>
|
|
266
|
-
<a id='wifi-starting' class='btn disabled hidden'><div><i class="fa-solid fa-circle-notch fa-spin"></i> Starting</div></a>
|
|
267
|
-
<div class='flexible'>
|
|
268
|
-
<h3><i class="fa-solid fa-wifi"></i> Local Share</h3>
|
|
269
|
-
<div>Access Pinokio from any device on the same network. By default it's off.</div>
|
|
270
|
-
</div>
|
|
271
|
-
<% } %>
|
|
272
|
-
<% } %>
|
|
273
|
-
</div>
|
|
274
|
-
-->
|
|
275
249
|
<div class='browser-options-row hidden'>
|
|
276
250
|
<% if (cloudflare_pub) { %>
|
|
277
251
|
<a id='cloudflare-stop' class='btn'><div><i class="fa-solid fa-stop"></i> Stop</div></a>
|
|
@@ -292,17 +266,10 @@ body.dark .browser-options-row {
|
|
|
292
266
|
</div>
|
|
293
267
|
</div>
|
|
294
268
|
<% } %>
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
<a class='type-tab' href="/"><img src='/pinokio-<%=theme=='dark' ? 'white' : 'black'%>.png'> Apps</a>
|
|
299
|
-
<a class='type-tab selected' href="/?mode=task"> <img src='/<%=theme=='dark' ? 'term-dark.png' : 'term-white.png'%>'> Tasks</a>
|
|
300
|
-
<% } else { %>
|
|
301
|
-
<a class='type-tab selected' href="/"><img src='/pinokio-<%=theme=='dark' ? 'white' : 'black'%>.png'> Apps</a>
|
|
302
|
-
<a class='type-tab' href="/?mode=task"> <img src='/<%=theme=='dark' ? 'term-dark.png' : 'term-white.png'%>'> Tasks</a>
|
|
303
|
-
<% } %>
|
|
269
|
+
<div id='fsmenu'>
|
|
270
|
+
<div class='flexible'></div>
|
|
271
|
+
<button id='newfolder' class='btn'><i class="fa-solid fa-folder-plus"></i> New Folder</button>
|
|
304
272
|
</div>
|
|
305
|
-
-->
|
|
306
273
|
<% if (running.length > 0) { %>
|
|
307
274
|
<div class='running-apps'>
|
|
308
275
|
<div class='header-label'>Running</div>
|
|
@@ -1094,6 +1061,30 @@ if (document.querySelector("#genlog")) {
|
|
|
1094
1061
|
})
|
|
1095
1062
|
})
|
|
1096
1063
|
}
|
|
1064
|
+
document.querySelector("#newfolder").addEventListener("click", async (e) => {
|
|
1065
|
+
let folder = prompt("Enter a folder name for the new folder")
|
|
1066
|
+
if (folder.includes(' ')) {
|
|
1067
|
+
alert("Please use a folder name without a space")
|
|
1068
|
+
return
|
|
1069
|
+
}
|
|
1070
|
+
let res = await fetch("/mkdir", {
|
|
1071
|
+
method: "post",
|
|
1072
|
+
headers: {
|
|
1073
|
+
"Content-Type": "application/json"
|
|
1074
|
+
},
|
|
1075
|
+
body: JSON.stringify({
|
|
1076
|
+
folder
|
|
1077
|
+
})
|
|
1078
|
+
}).then((res) => {
|
|
1079
|
+
return res.json()
|
|
1080
|
+
})
|
|
1081
|
+
if (res.error) {
|
|
1082
|
+
alert(res.error)
|
|
1083
|
+
} else {
|
|
1084
|
+
console.log(res)
|
|
1085
|
+
location.href = res.success
|
|
1086
|
+
}
|
|
1087
|
+
})
|
|
1097
1088
|
|
|
1098
1089
|
</script>
|
|
1099
1090
|
</body>
|
|
@@ -138,9 +138,22 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
138
138
|
class RPC {
|
|
139
139
|
constructor() {
|
|
140
140
|
this.socket = new Socket()
|
|
141
|
+
this.buffer = []
|
|
141
142
|
}
|
|
142
143
|
write(text) {
|
|
143
|
-
|
|
144
|
+
// console.log(text)
|
|
145
|
+
// this.buffer += text;
|
|
146
|
+
// if (!this.timeout) {
|
|
147
|
+
// this.timeout = setTimeout(() => {
|
|
148
|
+
// console.log("write", this.buffer)
|
|
149
|
+
// this.term.write(this.buffer);
|
|
150
|
+
// this.buffer = "";
|
|
151
|
+
// this.timeout = null;
|
|
152
|
+
// }, 50);
|
|
153
|
+
// }
|
|
154
|
+
if (text !== "\u0007") {
|
|
155
|
+
this.term.write(text)
|
|
156
|
+
}
|
|
144
157
|
this.dirty = true
|
|
145
158
|
}
|
|
146
159
|
finished() {
|
|
@@ -204,7 +217,6 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
204
217
|
rows: this.term.rows,
|
|
205
218
|
}
|
|
206
219
|
}, async (packet) => {
|
|
207
|
-
console.log({ packet })
|
|
208
220
|
if (packet.type === 'start') {
|
|
209
221
|
refreshParent(packet)
|
|
210
222
|
reloadMemory()
|
|
@@ -241,6 +253,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
241
253
|
shell_id = packet.data.id
|
|
242
254
|
}
|
|
243
255
|
if (packet.data.raw) {
|
|
256
|
+
// console.log({ packet })
|
|
244
257
|
this.write(packet.data.raw)
|
|
245
258
|
} else if (packet.data.json) {
|
|
246
259
|
this.write(JSON.stringify(packet.data.json).replace(/\n/g, "\r\n"))
|
|
@@ -261,8 +274,14 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
261
274
|
document.querySelector(".run .play").classList.remove("hidden")
|
|
262
275
|
document.querySelector(".run .stop").classList.add("hidden")
|
|
263
276
|
} else if (packet.type === 'connect') {
|
|
264
|
-
|
|
265
|
-
|
|
277
|
+
if (packet.data) {
|
|
278
|
+
if (packet.data.shell) {
|
|
279
|
+
shell_id = packet.data.shell
|
|
280
|
+
}
|
|
281
|
+
if (packet.data.state) {
|
|
282
|
+
this.write(packet.data.state)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
266
285
|
/*
|
|
267
286
|
for(let i=0; i<message.length; i++) {
|
|
268
287
|
await new Promise((r, reject) => {
|
|
@@ -471,7 +490,6 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
471
490
|
document.querySelector("#error-screen").classList.add("hidden")
|
|
472
491
|
})
|
|
473
492
|
*/
|
|
474
|
-
console.log("instance", instance)
|
|
475
493
|
instance.mark(packet.event)
|
|
476
494
|
let element = document.querySelector("mark")
|
|
477
495
|
element.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
@@ -578,13 +596,19 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
578
596
|
}
|
|
579
597
|
const term = new Terminal(config)
|
|
580
598
|
term.open(document.querySelector("#terminal"))
|
|
581
|
-
|
|
582
599
|
term.attachCustomKeyEventHandler(event => {
|
|
583
|
-
if (event.ctrlKey && event.key === 'c'
|
|
584
|
-
|
|
600
|
+
if ((event.ctrlKey || event.metaKey) && event.key === 'c') {
|
|
601
|
+
const selection = term.getSelection();
|
|
602
|
+
if (selection) {
|
|
603
|
+
navigator.clipboard.writeText(selection);
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
585
606
|
}
|
|
586
|
-
if (event.ctrlKey && event.key === 'v'
|
|
587
|
-
|
|
607
|
+
if ((event.ctrlKey || event.metaKey) && event.key === 'v') {
|
|
608
|
+
navigator.clipboard.readText().then((text) => {
|
|
609
|
+
term.write(text);
|
|
610
|
+
})
|
|
611
|
+
return false
|
|
588
612
|
}
|
|
589
613
|
return true;
|
|
590
614
|
});
|
|
@@ -596,7 +620,6 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
596
620
|
//term.resize(cols, rows);
|
|
597
621
|
|
|
598
622
|
term.onKey(({ key }) => {
|
|
599
|
-
console.log({ key })
|
|
600
623
|
if (this.socket) {
|
|
601
624
|
if (shell_id) {
|
|
602
625
|
this.socket.run({
|
|
@@ -801,14 +824,16 @@ const reloadMemory = async () => {
|
|
|
801
824
|
<span class='starting hidden'><i class="fa-solid fa-circle-notch fa-spin"></i> Starting...</span>
|
|
802
825
|
<span class='stop hidden'><i class="fa-solid fa-stop"></i> Stop</span>
|
|
803
826
|
</div>
|
|
804
|
-
|
|
827
|
+
<% if (locals.filepath) { %>
|
|
828
|
+
<button class='btn' id='open-fs' data-filepath="<%=filepath%>"><i class="fa-solid fa-eye"></i> View File</button>
|
|
829
|
+
<% } %>
|
|
805
830
|
<!--
|
|
806
831
|
<button class='btn' id='open-log' data-logpath="<%=logpath%>"><i class="fa-solid fa-align-left"></i> View Logs</button>
|
|
807
832
|
-->
|
|
808
833
|
</div>
|
|
809
834
|
<% } %>
|
|
810
835
|
</header>
|
|
811
|
-
<% if (requirements_pending) { %>
|
|
836
|
+
<% if (locals.requirements_pending) { %>
|
|
812
837
|
<div class='requirements'>
|
|
813
838
|
<div class='content'>
|
|
814
839
|
<div class='loading'>
|
|
@@ -817,7 +842,7 @@ const reloadMemory = async () => {
|
|
|
817
842
|
</div>
|
|
818
843
|
</div>
|
|
819
844
|
<% } else { %>
|
|
820
|
-
<% if (install_required) { %>
|
|
845
|
+
<% if (locals.install_required) { %>
|
|
821
846
|
<div class='requirements'>
|
|
822
847
|
<div class='content'>
|
|
823
848
|
<div class='title'><i class="fa-solid fa-circle-exclamation"></i> Installation required</div>
|