pinokiod 1.3.55 → 1.3.61

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.
@@ -0,0 +1,22 @@
1
+ const set = require("../set")
2
+ const rm = require("../rm")
3
+ const Environment = require('../../environment')
4
+ const Util = require("../../util")
5
+ class Env {
6
+ async set(req, ondata, kernel) {
7
+ /*
8
+ req := {
9
+ "method": "env.set",
10
+ "params": {
11
+ <key>: <val>,
12
+ <key>: <val>,
13
+ }
14
+ }
15
+ */
16
+ // write to current app folder's ENVIRONMENT
17
+
18
+ let api_path = await Util.api_path(req.parent.path, kernel)
19
+ await Util.update_env(api_path, req.params)
20
+ }
21
+ }
22
+ module.exports = Env
@@ -129,11 +129,13 @@ class FS {
129
129
  ondata({ raw: "\r\ncreating a shared drive:\r\n" + JSON.stringify(req.params, null, 2).replace(/\n/g, "\r\n") })
130
130
 
131
131
 
132
- let api_path = await Util.api_path(req.parent.path, kernel)
133
- let current_env = await Environment.get(api_path)
132
+ // start with the proces.env => merge pinokio global ENVIRONMENT => merge app ENVIRONMENT
134
133
  let default_env = await Environment.get(kernel.homedir)
135
- current_env = Object.assign(process.env, default_env, current_env)
134
+ let api_path = await Util.api_path(req.parent.path, kernel)
135
+ let api_env = await Environment.get(api_path)
136
+ let current_env = Object.assign(process.env, default_env, api_env)
136
137
 
138
+ // if PINOKIO_DRIVE environment variable is specified, use this custom value for the drive path instead of ~/pinokio/drive
137
139
  let env_drive_path = current_env.PINOKIO_DRIVE
138
140
  let drive_home
139
141
  if (env_drive_path) {
@@ -560,7 +560,6 @@ class Api {
560
560
  // 6. rpc must have method names
561
561
  if (rpc.method) {
562
562
 
563
-
564
563
  // 7. resolve the rpc
565
564
  let resolved = await this.resolveMethod(rpc, cwd)
566
565
 
@@ -1051,20 +1050,34 @@ class Api {
1051
1050
  }
1052
1051
  }
1053
1052
  } else if (request.method) {
1054
-
1055
- if (request.params && request.params.path) {
1056
- if (request.cwd) {
1057
- console.log("request.cwd", request.cwd)
1058
- let rendered_cwd = this.kernel.template.render(request.cwd, { kernel: this.kernel }, { allow_undefined: true })
1059
- console.log("rendered_cwd", rendered_cwd)
1060
- request.params.path = this.resolvePath(rendered_cwd, request.params.path)
1053
+ let pass = 0;
1054
+ while(true) {
1055
+ request = this.kernel.template.render(request, { kernel: this.kernel })
1056
+ if (this.kernel.template.istemplate(request)) {
1057
+ pass++;
1058
+ if (pass >= 4) {
1059
+ // only try 4 times
1060
+ break;
1061
+ } else {
1062
+ continue;
1063
+ }
1061
1064
  } else {
1062
- request.params.path = this.resolvePath(this.userdir, request.params.path)
1065
+ break;
1063
1066
  }
1064
1067
  }
1068
+ if (this.kernel.template.istemplate(request)) {
1069
+ console.log("something wrong with the request", request)
1070
+ return
1071
+ }
1072
+
1073
+ // replace {{{ }}} with {{ }}
1074
+ request = this.kernel.template.flatten(request)
1075
+
1076
+ if (request.params && request.params.path) {
1077
+ request.params.path = this.resolvePath(this.userdir, request.params.path)
1078
+ }
1065
1079
 
1066
1080
  // Kernel Call
1067
- console.log("REQUEST", request)
1068
1081
  let resolved = await this.resolveMethod(request)
1069
1082
  try {
1070
1083
  resolved.dirname = resolved.dirname
@@ -160,7 +160,6 @@ const init_folders = async (homedir) => {
160
160
  }
161
161
  }
162
162
  }
163
-
164
163
  const get = async (homedir) => {
165
164
  const env_path = path.resolve(homedir, "ENVIRONMENT")
166
165
  const current_env = await util.parse_env(env_path)
package/kernel/index.js CHANGED
@@ -175,7 +175,6 @@ class Kernel {
175
175
  }
176
176
 
177
177
  async init(options) {
178
- this.port = options.port
179
178
  let home = this.store.get("home")
180
179
 
181
180
  // reset shells if they exist
package/kernel/shell.js CHANGED
@@ -112,19 +112,22 @@ class Shell {
112
112
 
113
113
  this.env.CMAKE_OBJECT_PATH_MAX = 1024
114
114
 
115
- console.log("THIS.env before", this.env)
116
- let home_env = await Environment.get(this.kernel.homedir)
117
- console.log("home_env before", home_env)
118
- this.env = Object.assign(this.env, home_env)
119
- console.log("home_env after", this.env)
115
+ // First override this.env with system env
116
+ let system_env = await Environment.get(this.kernel.homedir)
117
+ this.env = Object.assign(this.env, system_env)
118
+
119
+ // if the shell is running from a script file, the params.$parent will include the path to the parent script
120
+ // this means we need to apply app environment as well
120
121
  if (params.$parent) {
121
122
  let api_path = await Util.api_path(params.$parent.path, this.kernel)
123
+
124
+ // initialize folders
122
125
  await Environment.init_folders(api_path)
123
- let current_env = await Environment.get(api_path)
124
- console.log({ current_env, api_path })
125
- this.env = Object.assign(this.env, current_env)
126
+
127
+ // apply app env to this.env
128
+ let app_env = await Environment.get(api_path)
129
+ this.env = Object.assign(this.env, app_env)
126
130
  }
127
- console.log("THIS.env after", this.env)
128
131
  let PATH_KEY;
129
132
  if (this.env.Path) {
130
133
  PATH_KEY = "Path"
package/kernel/shells.js CHANGED
@@ -160,7 +160,6 @@ class Shells {
160
160
  matches[2] += "g" // if g option is not included, include it (need it for matchAll)
161
161
  }
162
162
  let re = new RegExp(matches[1], matches[2])
163
- console.log("stream", stream)
164
163
  let line = stream.cleaned.replaceAll(/[\r\n]/g, "")
165
164
  //let rendered_event = [...stream.cleaned.matchAll(re)]
166
165
  let rendered_event = [...line.matchAll(re)]
@@ -222,7 +221,6 @@ class Shells {
222
221
  for(let i=0; i<shells.length; i++) {
223
222
  let shell = shells[i]
224
223
  if (shell.id === request.id) {
225
- console.log("FOUND SHELL TO KILL", shell.id)
226
224
  shell.kill(message, true)
227
225
  }
228
226
  }
@@ -235,7 +233,6 @@ class Shells {
235
233
  for(let i=0; i<shells.length; i++) {
236
234
  let shell = shells[i]
237
235
  if (shell.group === request.group) {
238
- console.log("kill shell ", request.group, i, shell)
239
236
  shell.kill(message, true)
240
237
  }
241
238
  }
package/kernel/util.js CHANGED
@@ -1,6 +1,17 @@
1
1
  const fs = require('fs')
2
2
  const path = require('path')
3
3
  const dotenv = require('dotenv')
4
+
5
+ const {auto: normalizeEOL} = require("eol");
6
+ const {EOL} = require("os");
7
+ const breakPattern = /\n/g;
8
+ const breakReplacement = "\\n";
9
+ const groupPattern = /\$/g;
10
+ const groupReplacement = "$$$";
11
+ const h = "[^\\S\\r\\n]"; // simulate `\h`
12
+ const returnPattern = /\r/g;
13
+ const returnReplacement = "\\r";
14
+
4
15
  const parse_env = async (filename) => {
5
16
  try {
6
17
  const buf = await fs.promises.readFile(filename)
@@ -10,6 +21,52 @@ const parse_env = async (filename) => {
10
21
  return {}
11
22
  }
12
23
  }
24
+
25
+ const parse_env_detail = async (filename) => {
26
+ // takes env and returns an array
27
+ const config = parse_env(filename)
28
+ /*
29
+ config := {
30
+ key1: val1,
31
+ key2; val2,
32
+ ...
33
+ }
34
+ */
35
+ const keys = Object.keys(config)
36
+ const str = await fs.promises.readFile(filename, "utf8")
37
+ const lines = str.split(EOL)
38
+
39
+ let items = []
40
+
41
+ for(let line of lines) {
42
+ if (line.trim().startsWith("#")) {
43
+ items.push({
44
+ type: "comment",
45
+ val: line
46
+ })
47
+ } else {
48
+ const buf = Buffer.from(line)
49
+ const parsed = dotenv.parse(buf)
50
+ const key = Object.keys(parsed)
51
+ if (key.length > 0) {
52
+ items.push({ key: key[0], val: parsed[key[0]] })
53
+ }
54
+ }
55
+ }
56
+ /*
57
+ items := [{
58
+ type: "comment",
59
+ val: "###################################",
60
+ }, {
61
+ type: "comment",
62
+ val: "# this is a comment",
63
+ }, {
64
+ key: "TMPDIR",
65
+ val: "/tmp"
66
+ }]
67
+ */
68
+ return items
69
+ }
13
70
  const api_path = async (fullpath, kernel) => {
14
71
  let api_path = `${kernel.homedir}${path.sep}api`
15
72
  let rel_path = path.relative(api_path, fullpath)
@@ -17,6 +74,55 @@ const api_path = async (fullpath, kernel) => {
17
74
  let current_api_path = `${api_path}${path.sep}${api_name}`
18
75
  return current_api_path
19
76
  }
77
+ const escapeStringRegexp = (string) => {
78
+ if (typeof string !== 'string') {
79
+ throw new TypeError('Expected a string');
80
+ }
81
+
82
+ // Escape characters with special meaning either inside or outside character sets.
83
+ // Use a simple backslash escape when it’s always valid, and a `\xnn` escape when the simpler form would be disallowed by Unicode patterns’ stricter grammar.
84
+ return string
85
+ .replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')
86
+ .replace(/-/g, '\\x2d');
87
+ }
88
+
89
+ const update_env = async (filepath, changes) => {
90
+ const envString = await fs.promises.readFile(filepath, "utf8")
91
+ console.log({ filepath, envString, changes })
92
+ let hasAppended = false;
93
+ console.log("changes", changes)
94
+ const newval = Object.keys(changes).reduce((result, varname) => {
95
+ const value = changes[varname].toString()
96
+ .replace(breakPattern, breakReplacement)
97
+ .replace(returnPattern, returnReplacement)
98
+ .trim();
99
+ const safeName = escapeStringRegexp(varname);
100
+ const varPattern = new RegExp(`^(${h}*${safeName}${h}*=${h}*)\\S*(${h}*)$`, "gm");
101
+ if (varPattern.test(result)) {
102
+ const safeValue = value.replace(groupPattern, groupReplacement);
103
+ return result.replace(varPattern, `$1${safeValue}$2`);
104
+ } else if (result === "") {
105
+ hasAppended = true;
106
+ return `${varname}=${value}${EOL}`;
107
+ } else if (!result.endsWith(EOL) && !hasAppended) {
108
+ hasAppended = true;
109
+ // Add an extra break between previously defined and newly appended variable
110
+ return `${result}${EOL}${EOL}${varname}=${value}`;
111
+ } else if (!result.endsWith(EOL)) {
112
+ // Add break for appended variable
113
+ return `${result}${EOL}${varname}=${value}`;
114
+ } else if (result.endsWith(EOL) && !hasAppended) {
115
+ hasAppended = true;
116
+ // Add an extra break between previously defined and newly appended variable
117
+ return `${result}${EOL}${varname}=${value}${EOL}`;
118
+ } else /*if (result.endsWith(EOL))*/ {
119
+ // Add break for appended variable
120
+ return `${result}${varname}=${value}${EOL}`;
121
+ }
122
+ }, normalizeEOL(envString));
123
+ console.log("updated ENVIRONMENT", newval)
124
+ await fs.promises.writeFile(filepath, newval)
125
+ };
20
126
  module.exports = {
21
- parse_env, api_path
127
+ parse_env, api_path, update_env, parse_env_detail
22
128
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "1.3.55",
3
+ "version": "1.3.61",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -21,6 +21,7 @@
21
21
  "decompress": "^4.2.1",
22
22
  "dotenv": "^16.4.5",
23
23
  "ejs": "^3.1.9",
24
+ "eol": "^0.9.1",
24
25
  "express": "^4.18.2",
25
26
  "express-http-proxy-cp": "^2.0.3",
26
27
  "express-session": "^1.17.3",
package/server/index.js CHANGED
@@ -36,6 +36,7 @@ const Kernel = require("../kernel")
36
36
  const packagejson = require("../package.json")
37
37
  const Environment = require("../kernel/environment")
38
38
  const Cloudflare = require("../kernel/api/cloudflare")
39
+ const Util = require("../kernel/util")
39
40
  class Server {
40
41
  constructor(config) {
41
42
  this.tabs = {}
@@ -238,9 +239,15 @@ class Server {
238
239
  await fs.promises.writeFile(current, content)
239
240
  }
240
241
 
242
+ let mode = "run"
243
+ console.log("req.query", req.query)
244
+ if (req.query && req.query.mode) {
245
+ mode = req.query.mode
246
+ }
241
247
 
242
248
  //res.render("browser", {
243
249
  res.render("browser-experimental", {
250
+ mode,
244
251
  port: this.port,
245
252
  mem,
246
253
  type,
@@ -1272,9 +1279,22 @@ class Server {
1272
1279
  if (config.menu) {
1273
1280
 
1274
1281
  config.menu = [{
1282
+ base: "/",
1275
1283
  text: "Configure",
1276
- href: "ENVIRONMENT?mode=source",
1284
+ href: `env/api/${name}/ENVIRONMENT`,
1277
1285
  icon: "fa-solid fa-gear"
1286
+ // }, {
1287
+ // base: "/",
1288
+ // text: "Public Share",
1289
+ // action: {
1290
+ // method: "env.set",
1291
+ // params: {
1292
+ // "PINOKIO_SHARE_CLOUDFLARE": true,
1293
+ // "PINOKIO_SHARE_LOCAL": true
1294
+ // }
1295
+ // },
1296
+ // href: `env/api/${name}/ENVIRONMENT`,
1297
+ // icon: "fa-solid fa-gear"
1278
1298
  }].concat(config.menu)
1279
1299
 
1280
1300
  for(let i=0; i<config.menu.length; i++) {
@@ -1285,14 +1305,18 @@ class Server {
1285
1305
  menuitem.menu = m.menu
1286
1306
  }
1287
1307
 
1288
- if (menuitem.href && !menuitem.href.startsWith("http")) {
1289
-
1290
- // href resolution
1291
- let absolute = path.resolve(__dirname, ...pathComponents, menuitem.href)
1292
- let seed = path.resolve(__dirname)
1293
- let p = absolute.replace(seed, "")
1294
- let link = p.split(/[\/\\]/).filter((x) => { return x }).join("/")
1295
- config.menu[i].href = "/api/" + name + "/" + link
1308
+ if (menuitem.base === "/") {
1309
+ config.menu[i].href = menuitem.base + menuitem.href
1310
+ } else {
1311
+ if (menuitem.href && !menuitem.href.startsWith("http")) {
1312
+
1313
+ // href resolution
1314
+ let absolute = path.resolve(__dirname, ...pathComponents, menuitem.href)
1315
+ let seed = path.resolve(__dirname)
1316
+ let p = absolute.replace(seed, "")
1317
+ let link = p.split(/[\/\\]/).filter((x) => { return x }).join("/")
1318
+ config.menu[i].href = "/api/" + name + "/" + link
1319
+ }
1296
1320
  }
1297
1321
 
1298
1322
  if (menuitem.href && menuitem.params) {
@@ -2118,6 +2142,47 @@ class Server {
2118
2142
  res.json({ error: "type must be 'local' or 'cloudflare'" })
2119
2143
  }
2120
2144
  })
2145
+ this.app.post("/env", async (req, res) => {
2146
+ console.log("req.body", req.body)
2147
+ let fullpath = path.resolve(this.kernel.homedir, req.body.filepath)
2148
+ let updated = req.body.vals
2149
+ await Util.update_env(fullpath, updated)
2150
+ res.json({})
2151
+ })
2152
+ this.app.get("/env/*", async (req, res) => {
2153
+ let pathComponents = req.params[0].split("/")
2154
+ let filepath = path.resolve(this.kernel.homedir, req.params[0])
2155
+ const items = await Util.parse_env_detail(filepath)
2156
+ res.render("env_editor", {
2157
+ items,
2158
+ theme: this.theme,
2159
+ filepath,
2160
+ agent: this.agent,
2161
+ })
2162
+ })
2163
+ this.app.get("/share/:name", async (req, res) => {
2164
+ let filepath = path.resolve(this.kernel.homedir, "api", req.params.name, "ENVIRONMENT")
2165
+ //let filepath = path.resolve(this.kernel.homedir, req.params[0])
2166
+ console.log("filepath", filepath)
2167
+ const config = await Util.parse_env(filepath)
2168
+ console.log("config", config)
2169
+ const keys = [
2170
+ "PINOKIO_SHARE_CLOUDFLARE",
2171
+ "PINOKIO_SHARE_LOCAL",
2172
+ ]
2173
+ for(let key of keys) {
2174
+ if (!config[key]) {
2175
+ config[key] = ""
2176
+ }
2177
+ }
2178
+ res.render("share_editor", {
2179
+ keys,
2180
+ config,
2181
+ theme: this.theme,
2182
+ filepath,
2183
+ agent: this.agent,
2184
+ })
2185
+ })
2121
2186
  this.app.get("/edit/*", async (req, res) => {
2122
2187
  let pathComponents = req.params[0].split("/")
2123
2188
  let filepath = path.resolve(this.kernel.homedir, req.params[0])
@@ -2292,8 +2357,13 @@ class Server {
2292
2357
  this.app.get("/pinokio/launch/:name", async (req, res) => {
2293
2358
  this.chrome(req, res, "launch")
2294
2359
  })
2360
+ this.app.get("/pinokio/browser/:name/browse", async (req, res) => {
2361
+ console.log("browse mode")
2362
+ this.chrome(req, res, "browse")
2363
+ })
2295
2364
  this.app.get("/pinokio/browser/:name", async (req, res) => {
2296
- this.chrome(req, res, "browser")
2365
+ console.log("run mode")
2366
+ this.chrome(req, res, "run")
2297
2367
  })
2298
2368
  this.app.post("/pinokio/delete", async (req, res) => {
2299
2369
  try {
@@ -21,7 +21,10 @@
21
21
  --light-thin: rgba(0,0,0,0.1);
22
22
  --dark-thin: rgba(255,255,255,0.03);
23
23
 
24
+ /*
24
25
  --dark-color: rgba(255,255,255,0.8);
26
+ */
27
+ --dark-color: white;
25
28
  --light-color: rgba(0,0,0,0.9);
26
29
 
27
30
  --dark-fade: rgba(255,255,255,0.6);
@@ -894,13 +897,11 @@ body.dark .readme a {
894
897
  body.dark .line {
895
898
  border-bottom: 1px solid var(--dark-thin);
896
899
  }
897
- body.dark .line.selected {
900
+ body.dark .line.selected, body.dark .line:hover {
898
901
  /*
899
902
  border-left: 6px solid white;
900
903
  */
901
904
  background: rgba(80, 100, 70, 0.1);
902
- border-top: 1px solid rgba(80, 100, 70, 0.1);
903
- border-bottom: 1px solid rgba(80, 100, 70, 0.1);
904
905
  }
905
906
  /*
906
907
  body.dark .line:hover {
@@ -910,13 +911,11 @@ body.dark .line:hover {
910
911
  border-left: 6px solid black;
911
912
  }
912
913
  */
913
- .line.selected {
914
+ .line.selected, .line:hover {
914
915
  /*
915
916
  border-left: 6px solid black;
916
917
  */
917
918
  background: rgba(70, 200, 250, 0.1);
918
- border-top: 1px solid rgba(70, 200, 250, 0.3);
919
- border-bottom: 1px solid rgba(70, 200, 250, 0.3);
920
919
  }
921
920
  .line {
922
921
  text-decoration: none;
@@ -1554,7 +1553,7 @@ nav.error-message {
1554
1553
  color: #8fd400;
1555
1554
  }
1556
1555
  */
1557
- .running-apps .spinner {
1556
+ .running-apps .spinner, .not-running-apps .spinner {
1558
1557
  width: 50px;
1559
1558
  text-align: center;
1560
1559
  font-size: 20px;
@@ -263,6 +263,12 @@ body.dark .frame-link.selected {
263
263
  padding: 5px;
264
264
  */
265
265
  }
266
+ .loader .btn:hover {
267
+ color: royalblue;
268
+ }
269
+ .loader .btn {
270
+ padding: 4px 8px;
271
+ }
266
272
  .frame-link .tab {
267
273
  flex-grow: 1;
268
274
  /*
@@ -536,6 +542,9 @@ body.dark .loader-container {
536
542
  <a target="<%=src%>" href="<%=src%>" class='btn header-item frame-link' data-index="0">
537
543
  <div class='tab'><i class="fa-regular fa-folder-open"></i> Files</div>
538
544
  </a>
545
+ <a target="/share/<%=name%>" href="/share/<%=name%>", class="btn header-item frame-link" data-index="1">
546
+ <div class='tab'><i class="fa-solid fa-wifi"></i> Share</div>
547
+ </a>
539
548
  </div>
540
549
  <div class='m h menu' data-type='h'>
541
550
  <% if (config.menu) { %>
@@ -713,7 +722,10 @@ const nav = (type) => {
713
722
  const renderSelection = (e, force) => {
714
723
  console.log("# 1")
715
724
  // if the menu has a default item, or if the url specifies a selection
716
- let selection = document.querySelector("[data-default]")
725
+ let selection = null;
726
+ <% if (type === "run") { %>
727
+ selection = document.querySelector("[data-default]")
728
+ <% } %>
717
729
  if (selection || location.hash && location.hash.length > 0) {
718
730
  console.log("# 2")
719
731
 
@@ -1248,8 +1260,11 @@ document.querySelector("aside").addEventListener("click", async (e) => {
1248
1260
  e.stopPropagation()
1249
1261
  let link = target.closest("[href]")
1250
1262
  let src = new URL(link.href).pathname
1263
+ n.Noty({
1264
+ text: `stopping ${src}`,
1265
+ silent: true
1266
+ })
1251
1267
  console.log("src", src)
1252
- debugger
1253
1268
 
1254
1269
  let socket = new Socket()
1255
1270
  socket.run({
@@ -1262,7 +1277,7 @@ document.querySelector("aside").addEventListener("click", async (e) => {
1262
1277
  if (packet.type === "result") {
1263
1278
  socket.close()
1264
1279
  n.Noty({
1265
- text: `stopped ${src}`,
1280
+ text: `stopped`,
1266
1281
  silent: true
1267
1282
  })
1268
1283
  refresh(true)
@@ -0,0 +1,183 @@
1
+ <html class='columns'>
2
+ <head>
3
+ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
4
+ <script src="/hotkeys.min.js"></script>
5
+ <script src="/ace/ace.js"></script>
6
+ <script src="/noty.js"></script>
7
+ <script src="/notyq.js"></script>
8
+ <script src="/xterm.js"></script>
9
+ <script src="/xterm-addon-fit.js"></script>
10
+ <script src="/xterm-addon-web-links.js"></script>
11
+ <script src="/xterm-theme.js"></script>
12
+ <script src="/xterm-addon-search.js"></script>
13
+ <script src="/xterm-addon-search-bar.js"></script>
14
+ <script src="/sweetalert2.js"></script>
15
+ <script src="/Socket.js"></script>
16
+ <script src="/common.js"></script>
17
+ <script src="/he.js"></script>
18
+ <script src="/opener.js"></script>
19
+ <script src="/nav.js"></script>
20
+ <script src="/autoexpand.js"></script>
21
+ <script src="/modalinput.js"></script>
22
+ <script src="/simplemodal.js"></script>
23
+ <script src="/waitmodal.js"></script>
24
+ <script src="/notifyinput.js"></script>
25
+ <script src="/report.js"></script>
26
+ <link href="/xterm.min.css" rel="stylesheet" />
27
+ <link href="/css/fontawesome.min.css" rel="stylesheet">
28
+ <link href="/css/solid.min.css" rel="stylesheet">
29
+ <link href="/css/regular.min.css" rel="stylesheet">
30
+ <link href="/css/brands.min.css" rel="stylesheet">
31
+ <link href="/style.css" rel="stylesheet"/>
32
+ <link href="/noty.css" rel="stylesheet"/>
33
+ <% if (agent === "electron") { %>
34
+ <link href="/electron.css" rel="stylesheet"/>
35
+ <% } %>
36
+ <style>
37
+ body.frozen {
38
+ display: flex;
39
+ flex-direction: column;
40
+ height: 100%;
41
+ }
42
+ #table > table {
43
+ border-left: 2px solid black;
44
+ padding-left: 5px;
45
+ margin: 10px 0;
46
+ }
47
+ .highlight {
48
+ background: yellow;
49
+ width: 100px;
50
+ }
51
+ th {
52
+ background: rgba(0,0,0,0.1);
53
+ padding: 10px;
54
+ font-size: 12px;
55
+ }
56
+ td {
57
+ font-size: 12px;
58
+ padding: 10px;
59
+ }
60
+ td.val {
61
+ background: rgba(0,0,0,0.06);
62
+ }
63
+ td.key {
64
+ background: rgba(0,0,0,0.1);
65
+ }
66
+ .button {
67
+ cursor: pointer;
68
+ background: rgba(0,0,0,0.8);
69
+ color: white;
70
+ padding: 5px 10px;
71
+ border-radius: 4px;
72
+ }
73
+ .button:hover {
74
+
75
+ }
76
+ #editor {
77
+ box-sizing: border-box;
78
+ max-width: 800px;
79
+ padding: 20px;
80
+ }
81
+ header .runner {
82
+ padding: 10px 0 0;
83
+ }
84
+ .disabled {
85
+ opacity: 0.7;
86
+ }
87
+ body.dark .env-item input[type=text] {
88
+ background: white;
89
+ }
90
+ .env-item input[type=text] {
91
+ width: 100%;
92
+ padding: 10px;
93
+ box-sizing: border-box;
94
+ border: none;
95
+ background: rgba(0,0,100, 0.1);
96
+ }
97
+ body.dark .env-item label {
98
+ color: cornflowerblue;
99
+ }
100
+ .env-item label {
101
+ color: royalblue;
102
+ display: block;
103
+ margin-bottom: 5px;
104
+ font-weight: bold;
105
+ font-size: 18px;
106
+ }
107
+ .env-item {
108
+ padding-top: 10px;
109
+ padding-bottom: 30px;
110
+ }
111
+ body.dark .comment {
112
+ border-left: 2px solid white;
113
+ }
114
+ .comment {
115
+ opacity: 0.8;
116
+ border-left: 2px solid black;
117
+ padding-left: 10px;
118
+ }
119
+ #save {
120
+ background: royalblue;
121
+ }
122
+ </style>
123
+ <script>
124
+ document.addEventListener("DOMContentLoaded", async () => {
125
+ const n = new N()
126
+ document.querySelector("#save").addEventListener("click", async (e) => {
127
+ // save
128
+ const vals = {}
129
+ const els = document.querySelectorAll(".env-item")
130
+ for(let el of els) {
131
+ let input = el.querySelector("input[type=text]")
132
+ vals[input.id] = input.value
133
+ }
134
+ console.log("vals", vals)
135
+ let res = await fetch("/env", {
136
+ method: "POST",
137
+ headers: {
138
+ "Content-Type": "application/json"
139
+ },
140
+ body: JSON.stringify({
141
+ filepath: location.pathname.slice(5),
142
+ vals
143
+ })
144
+ }).then((res) => {
145
+ return res.json()
146
+ })
147
+ console.log("res" ,res)
148
+ n.Noty({
149
+ text: `Updated!`
150
+ })
151
+ })
152
+ })
153
+ </script>
154
+ </head>
155
+ <body class='columns <%=theme%>'>
156
+ <header class='navheader grabbable'>
157
+ <h1>
158
+ <a class='home' href="/"><i class="fa-solid fa-house"></i></a>
159
+ <%=filepath%>
160
+ </h1>
161
+ <div class='runner'>
162
+ <div id='save' class='btn'>
163
+ <span class='save'><i class="fa-solid fa-check"></i> Save</span>
164
+ </div>
165
+ <button class='btn' id='open-fs' data-filepath="<%=filepath%>"><i class="fa-solid fa-eye"></i> Open in File Explorer</button>
166
+ </div>
167
+ </header>
168
+ <main>
169
+ <div id='editor'>
170
+ <% items.forEach((item) => { %>
171
+ <% if (item.type === "comment") { %>
172
+ <div class='comment'><%=item.val.replaceAll(/^[ #]+/g, "")%></div>
173
+ <% } else { %>
174
+ <div class='env-item'>
175
+ <label for="<%=item.key%>"><%=item.key%></label>
176
+ <input id="<%=item.key%>" type="text" value="<%=item.val%>">
177
+ </div>
178
+ <% } %>
179
+ <% }) %>
180
+ </div>
181
+ </main>
182
+ </body>
183
+ </html>
@@ -146,9 +146,9 @@ document.addEventListener("DOMContentLoaded", async () => {
146
146
  this.socket.close()
147
147
  this.socket.run({
148
148
  method: "fs.write",
149
- cwd: "{{kernel.homedir}}",
149
+ //cwd: "{{kernel.homedir}}",
150
150
  params: {
151
- path: filepath,
151
+ path: `{{path.resolve(kernel.homedir, "${filepath}")}}`,
152
152
  text: str,
153
153
  }
154
154
  }, (packet) => {
@@ -146,6 +146,10 @@ body.dark .item select {
146
146
  padding: 10px 20px;
147
147
  text-align: center;
148
148
  }
149
+ body.dark .btn {
150
+ color: white;
151
+ border: 1px solid rgba(255,255,255,0.3) !important;
152
+ }
149
153
  body.light {
150
154
  /*
151
155
  background: var(--light-nav-bg);
@@ -112,13 +112,7 @@ a.badge {
112
112
  }
113
113
  body.dark .btn {
114
114
  color: white;
115
- /*
116
- color: cornflowerblue;
117
- */
118
115
  border: 1px solid rgba(255,255,255,0.3);
119
- /*
120
- border: 1px solid rgba(100, 149, 237, 0.3);
121
- */
122
116
  }
123
117
  body.dark .badge {
124
118
  background: rgba(255,255,255,0.1);
@@ -288,16 +282,19 @@ body.dark .browser-options-row {
288
282
  <div class='uri'><i class="fa-regular fa-folder"></i><%=item.url%></div>
289
283
  <div class='title'><%=item.name%></div>
290
284
  <div class='description'><%=item.description%></div>
291
- <% if (item.running_scripts) { %>
292
- <% item.running_scripts.forEach((s) => { %>
293
- <div>
294
- <button class='btn shutdown' data-src="<%=s.path%>">
295
- <i class="fa-solid fa-stop"></i> Stop <%=s.name%>&nbsp;
296
- <i class='fa-solid fa-spin fa-circle-notch'></i>
297
- </button>
298
- </div>
299
- <% }) %>
300
- <% } %>
285
+ <div>
286
+ <button class='btn browse' data-src="<%=item.browser_url%>/browse">
287
+ <i class="fa-regular fa-folder-open"></i>
288
+ </button>
289
+ <% if (item.running_scripts) { %>
290
+ <% item.running_scripts.forEach((s) => { %>
291
+ <button class='btn shutdown' data-src="<%=s.path%>">
292
+ <i class="fa-solid fa-stop"></i> Stop <%=s.name%>&nbsp;
293
+ <i class='fa-solid fa-spin fa-circle-notch'></i>
294
+ </button>
295
+ <% }) %>
296
+ <% } %>
297
+ </div>
301
298
  <% if (item.shortcuts && item.shortcuts.length > 0) { %>
302
299
  <div class='btns'>
303
300
  <% item.shortcuts.forEach((btn) => { %>
@@ -336,6 +333,9 @@ body.dark .browser-options-row {
336
333
  <div class='uri'><i class="fa-regular fa-folder"></i><%=item.url%></div>
337
334
  <div class='title'><%=item.name%></div>
338
335
  <div class='description'><%=item.description%></div>
336
+ <button class='btn browse' data-src="<%=item.browser_url%>/browse">
337
+ <i class="fa-regular fa-folder-open"></i>
338
+ </button>
339
339
  <% if (item.shortcuts && item.shortcuts.length > 0) { %>
340
340
  <div class='btns'>
341
341
  <% item.shortcuts.forEach((btn) => { %>
@@ -346,6 +346,9 @@ body.dark .browser-options-row {
346
346
  </div>
347
347
  <% } %>
348
348
  </div>
349
+ <div class='spinner'>
350
+ <i class="fa-solid fa-chevron-right"></i>
351
+ </div>
349
352
  </h3>
350
353
  </a>
351
354
  <% }) %>
@@ -729,7 +732,6 @@ document.addEventListener("click", (e) => {
729
732
  e.stopPropagation()
730
733
  let src = target.getAttribute("data-src")
731
734
  console.log("src", src)
732
- debugger
733
735
 
734
736
  socket.run({
735
737
  method: "kernel.api.stop",
@@ -756,6 +758,21 @@ document.addEventListener("click", (e) => {
756
758
  }
757
759
 
758
760
 
761
+ debugger
762
+ if (e.target.classList.contains("browse")) {
763
+ target = e.target
764
+ } else {
765
+ target = e.target.closest(".browse")
766
+ }
767
+ if (target) {
768
+ e.preventDefault()
769
+ e.stopPropagation()
770
+ let src = target.getAttribute("data-src")
771
+ location.href = src
772
+ return
773
+ }
774
+
775
+
759
776
  if (document.querySelector(".container")) {
760
777
  document.querySelector(".container").addEventListener("click", async (e) => {
761
778
  let el = e.target.closest(".menu-btn")
@@ -30,7 +30,9 @@
30
30
  <i class='fa-solid fa-spin fa-circle-notch'></i>
31
31
  <i class="fa-solid fa-chevron-right"></i>
32
32
  -->
33
- <i class="fa-solid fa-xmark"></i>
33
+ <button class='btn'>
34
+ <i class="fa-solid fa-square"></i> Stop
35
+ </button>
34
36
  </div>
35
37
  <% } %>
36
38
  </a>
@@ -145,6 +145,10 @@ body.dark .item select {
145
145
  padding: 10px 20px;
146
146
  text-align: center;
147
147
  }
148
+ body.dark .btn {
149
+ color: white;
150
+ border: 1px solid rgba(255,255,255,0.3) !important;
151
+ }
148
152
  body.light {
149
153
  /*
150
154
  background: var(--light-nav-bg);
@@ -298,7 +302,7 @@ hr {
298
302
  <br>
299
303
  <div>Edit the system ENVIRONMENT file to customize pinokio system wide settings, such as where files are stored, terminal behavior, etc.</div>
300
304
  <br>
301
- <a class='btn' href="/edit/ENVIRONMENT">Edit ENVIRONMENT</a>
305
+ <a class='btn' href="/env/ENVIRONMENT">Edit ENVIRONMENT</a>
302
306
  </div>
303
307
  <hr/>
304
308
  <div class='troubleshoot'>
@@ -0,0 +1,196 @@
1
+ <html class='columns'>
2
+ <head>
3
+ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
4
+ <script src="/hotkeys.min.js"></script>
5
+ <script src="/ace/ace.js"></script>
6
+ <script src="/noty.js"></script>
7
+ <script src="/notyq.js"></script>
8
+ <script src="/xterm.js"></script>
9
+ <script src="/xterm-addon-fit.js"></script>
10
+ <script src="/xterm-addon-web-links.js"></script>
11
+ <script src="/xterm-theme.js"></script>
12
+ <script src="/xterm-addon-search.js"></script>
13
+ <script src="/xterm-addon-search-bar.js"></script>
14
+ <script src="/sweetalert2.js"></script>
15
+ <script src="/Socket.js"></script>
16
+ <script src="/common.js"></script>
17
+ <script src="/he.js"></script>
18
+ <script src="/opener.js"></script>
19
+ <script src="/nav.js"></script>
20
+ <script src="/autoexpand.js"></script>
21
+ <script src="/modalinput.js"></script>
22
+ <script src="/simplemodal.js"></script>
23
+ <script src="/waitmodal.js"></script>
24
+ <script src="/notifyinput.js"></script>
25
+ <script src="/report.js"></script>
26
+ <link href="/xterm.min.css" rel="stylesheet" />
27
+ <link href="/css/fontawesome.min.css" rel="stylesheet">
28
+ <link href="/css/solid.min.css" rel="stylesheet">
29
+ <link href="/css/regular.min.css" rel="stylesheet">
30
+ <link href="/css/brands.min.css" rel="stylesheet">
31
+ <link href="/style.css" rel="stylesheet"/>
32
+ <link href="/noty.css" rel="stylesheet"/>
33
+ <% if (agent === "electron") { %>
34
+ <link href="/electron.css" rel="stylesheet"/>
35
+ <% } %>
36
+ <style>
37
+ body.frozen {
38
+ display: flex;
39
+ flex-direction: column;
40
+ height: 100%;
41
+ }
42
+ #table > table {
43
+ border-left: 2px solid black;
44
+ padding-left: 5px;
45
+ margin: 10px 0;
46
+ }
47
+ .highlight {
48
+ background: yellow;
49
+ width: 100px;
50
+ }
51
+ th {
52
+ background: rgba(0,0,0,0.1);
53
+ padding: 10px;
54
+ font-size: 12px;
55
+ }
56
+ td {
57
+ font-size: 12px;
58
+ padding: 10px;
59
+ }
60
+ td.val {
61
+ background: rgba(0,0,0,0.06);
62
+ }
63
+ td.key {
64
+ background: rgba(0,0,0,0.1);
65
+ }
66
+ .button {
67
+ cursor: pointer;
68
+ background: rgba(0,0,0,0.8);
69
+ color: white;
70
+ padding: 5px 10px;
71
+ border-radius: 4px;
72
+ }
73
+ .button:hover {
74
+
75
+ }
76
+ #editor {
77
+ box-sizing: border-box;
78
+ max-width: 800px;
79
+ padding: 20px;
80
+ }
81
+ header .runner {
82
+ padding: 10px 0 0;
83
+ }
84
+ .disabled {
85
+ opacity: 0.7;
86
+ }
87
+ body.dark .env-item input[type=text] {
88
+ background: white;
89
+ }
90
+ .env-item input[type=text] {
91
+ width: 100%;
92
+ padding: 10px;
93
+ box-sizing: border-box;
94
+ border: none;
95
+ background: rgba(0,0,100, 0.1);
96
+ }
97
+ body.dark .env-item label {
98
+ color: cornflowerblue;
99
+ }
100
+ .env-item a {
101
+ color: royalblue;
102
+ text-decoration: none;
103
+ }
104
+ .env-item div {
105
+ padding: 5px;
106
+ }
107
+ .env-item label {
108
+ color: royalblue;
109
+ display: inline-block;
110
+ margin-bottom: 5px;
111
+ font-weight: bold;
112
+ }
113
+ .env-item {
114
+ padding-top: 10px;
115
+ padding-bottom: 30px;
116
+ }
117
+ body.dark .comment {
118
+ border-left: 2px solid white;
119
+ }
120
+ .comment {
121
+ opacity: 0.8;
122
+ border-left: 2px solid black;
123
+ padding-left: 10px;
124
+ }
125
+ #save {
126
+ background: royalblue;
127
+ }
128
+ hr {
129
+ margin: 20px 0;
130
+ background: rgba(255,255,255,0.1);
131
+ border: none;
132
+ height: 1px;
133
+ }
134
+ </style>
135
+ <script>
136
+ document.addEventListener("DOMContentLoaded", async () => {
137
+ const n = new N()
138
+ document.querySelector("#save").addEventListener("click", async (e) => {
139
+ // save
140
+ const vals = {}
141
+ const els = document.querySelectorAll(".env-item input[type=checkbox]")
142
+ for(let el of els) {
143
+ vals[el.id] = el.checked
144
+ }
145
+ let res = await fetch("/env", {
146
+ method: "POST",
147
+ headers: {
148
+ "Content-Type": "application/json"
149
+ },
150
+ body: JSON.stringify({
151
+ filepath: "api/" + location.pathname.slice(7) + "/ENVIRONMENT",
152
+ vals
153
+ })
154
+ }).then((res) => {
155
+ return res.json()
156
+ })
157
+ n.Noty({
158
+ text: `Updated! Restart the app to apply the change.`
159
+ })
160
+ })
161
+ })
162
+ </script>
163
+ </head>
164
+ <body class='columns <%=theme%>'>
165
+ <main>
166
+ <div id='editor'>
167
+ <h1>Share Apps</h1>
168
+ <div>You can open up your local Pinokio node to the outside world, or any other device on the same network, instantly.</div>
169
+ <hr>
170
+ <div class='env-item'>
171
+ <% if (config.PINOKIO_SHARE_CLOUDFLARE.toString() === "true") { %>
172
+ <input type="checkbox" id="PINOKIO_SHARE_CLOUDFLARE" name="cloudflare" checked />
173
+ <% } else { %>
174
+ <input type="checkbox" id="PINOKIO_SHARE_CLOUDFLARE" name="cloudflare" />
175
+ <% } %>
176
+ <label for="cloudflare">Share Publicly</label>
177
+ <div>This will set the PINOKIO_SHARE_CLOUDFLARE environment variable to true, and share your local app to the public internet, powered by <a href="https://www.cloudflare.com/products/tunnel/" target="_blank" features="browser">Cloudflare Tunnel</a></div>
178
+ </div>
179
+ <div class='env-item'>
180
+ <% if (config.PINOKIO_SHARE_LOCAL.toString() === "true") { %>
181
+ <input type="checkbox" id="PINOKIO_SHARE_LOCAL" name="local" checked />
182
+ <% } else { %>
183
+ <input type="checkbox" id="PINOKIO_SHARE_LOCAL" name="local" />
184
+ <% } %>
185
+ <label for="local">Share with Local Network Devices</label>
186
+ <div>This will set the PINOKIO_SHARE_LOCAL environment variable to true, and share your local app to any device on the same local network</div>
187
+ </div>
188
+ <div class='runner'>
189
+ <div id='save' class='btn'>
190
+ <span class='save'><i class="fa-solid fa-check"></i> Save</span>
191
+ </div>
192
+ </div>
193
+ </div>
194
+ </main>
195
+ </body>
196
+ </html>