pinokiod 7.5.44 → 7.5.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/package.json +1 -1
  2. package/server/index.js +403 -24
  3. package/server/public/style.css +0 -264
  4. package/server/public/urldropdown.css +7 -0
  5. package/server/views/app.ejs +2 -0
  6. package/server/views/app_search_test.ejs +2 -0
  7. package/server/views/connect/x.ejs +2 -0
  8. package/server/views/container.ejs +2 -0
  9. package/server/views/create.ejs +3 -0
  10. package/server/views/editor.ejs +3 -0
  11. package/server/views/env_editor.ejs +2 -0
  12. package/server/views/explore.ejs +2 -0
  13. package/server/views/general_editor.ejs +3 -0
  14. package/server/views/help.ejs +2 -0
  15. package/server/views/index.ejs +539 -2
  16. package/server/views/init/index.ejs +2 -0
  17. package/server/views/keys.ejs +2 -0
  18. package/server/views/mini.ejs +2 -0
  19. package/server/views/net.ejs +2 -0
  20. package/server/views/network.ejs +887 -94
  21. package/server/views/partials/app_navheader.ejs +2 -0
  22. package/server/views/partials/home_action_modal.ejs +22 -0
  23. package/server/views/partials/home_server_popover.ejs +21 -0
  24. package/server/views/partials/home_server_popover_assets.ejs +1262 -0
  25. package/server/views/partials/main_sidebar.ejs +1 -221
  26. package/server/views/pro.ejs +7 -0
  27. package/server/views/prototype/index.ejs +2 -0
  28. package/server/views/review.ejs +2 -0
  29. package/server/views/screenshots.ejs +2 -0
  30. package/server/views/settings.ejs +2 -0
  31. package/server/views/shell.ejs +7 -0
  32. package/server/views/task.ejs +2 -0
  33. package/server/views/terminal.ejs +2 -0
  34. package/server/views/terminals.ejs +2 -0
  35. package/test/launch-settings-ui.test.js +355 -0
  36. package/test/main-sidebar.test.js +26 -16
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "7.5.44",
3
+ "version": "7.5.45",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/server/index.js CHANGED
@@ -737,6 +737,336 @@ class Server {
737
737
  }
738
738
  return mem
739
739
  }
740
+ normalizeHomeShareCandidate(value) {
741
+ if (typeof value !== "string") {
742
+ return ""
743
+ }
744
+ let candidate = value.trim()
745
+ if (!candidate) {
746
+ return ""
747
+ }
748
+ if (candidate.startsWith("@")) {
749
+ candidate = candidate.slice(1).trim()
750
+ }
751
+ if (!candidate) {
752
+ return ""
753
+ }
754
+ let parsed
755
+ try {
756
+ parsed = new URL(candidate)
757
+ } catch (_) {
758
+ return ""
759
+ }
760
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
761
+ return ""
762
+ }
763
+ const hostname = (parsed.hostname || "").trim().toLowerCase()
764
+ const isLoopback = this.appRegistry && typeof this.appRegistry.isLoopbackHostname === "function"
765
+ ? this.appRegistry.isLoopbackHostname(hostname)
766
+ : LOOPBACK_HOSTS.has(hostname)
767
+ if (!isLoopback || !parsed.port) {
768
+ return ""
769
+ }
770
+ return parsed.toString()
771
+ }
772
+ findHomeShareReadyUrlFromMenu(menu = []) {
773
+ const stack = Array.isArray(menu) ? menu.slice() : []
774
+ while (stack.length > 0) {
775
+ const item = stack.shift()
776
+ if (!item || typeof item !== "object") {
777
+ continue
778
+ }
779
+ if (Array.isArray(item.menu)) {
780
+ stack.unshift(...item.menu)
781
+ }
782
+ for (const key of ["target_full", "target", "href"]) {
783
+ const candidate = this.normalizeHomeShareCandidate(item[key])
784
+ if (candidate) {
785
+ return candidate
786
+ }
787
+ }
788
+ }
789
+ return ""
790
+ }
791
+ async getNetworkSharingStatus() {
792
+ const https_active = !!(this.kernel.peer && this.kernel.peer.https_active)
793
+ let installed = false
794
+ let running = false
795
+ try {
796
+ if (this.kernel && typeof this.kernel.network_installed === "function") {
797
+ installed = !!(await this.kernel.network_installed())
798
+ } else {
799
+ const caddy = this.kernel.bin && this.kernel.bin.mod ? this.kernel.bin.mod.caddy : null
800
+ if (caddy && typeof caddy.installed === "function") {
801
+ installed = !!(await caddy.installed())
802
+ }
803
+ }
804
+ } catch (err) {
805
+ try {
806
+ const caddy = this.kernel.bin && this.kernel.bin.mod ? this.kernel.bin.mod.caddy : null
807
+ if (caddy && typeof caddy.installed === "function") {
808
+ installed = !!(await caddy.installed())
809
+ }
810
+ } catch (_) {
811
+ installed = false
812
+ }
813
+ }
814
+ try {
815
+ await axios.get("http://127.0.0.1:2019/config/", { timeout: 750 })
816
+ running = true
817
+ } catch (err) {
818
+ running = false
819
+ }
820
+ if (running) {
821
+ installed = true
822
+ }
823
+ return {
824
+ active: https_active,
825
+ installed,
826
+ running,
827
+ available: https_active && installed
828
+ }
829
+ }
830
+ homeServerStatusFromNetwork(networkStatus) {
831
+ if (!networkStatus || networkStatus.installed === false) {
832
+ return "setup"
833
+ }
834
+ if (!networkStatus.active) {
835
+ return "off"
836
+ }
837
+ if (!networkStatus.running) {
838
+ return "starting"
839
+ }
840
+ return "on"
841
+ }
842
+ buildHomeServerShellUrl(peerInfo) {
843
+ if (!peerInfo || typeof peerInfo !== "object") {
844
+ return ""
845
+ }
846
+ const host = typeof peerInfo.host === "string" && peerInfo.host.trim()
847
+ ? peerInfo.host.trim()
848
+ : Array.isArray(peerInfo.host_candidates)
849
+ ? ((peerInfo.host_candidates.find((candidate) => candidate && candidate.shareable && candidate.address) || {}).address || "")
850
+ : ""
851
+ const portMapping = peerInfo.port_mapping && typeof peerInfo.port_mapping === "object" ? peerInfo.port_mapping : {}
852
+ const mappedPort = portMapping[String(this.port)] || null
853
+ if (!host || !mappedPort) {
854
+ return ""
855
+ }
856
+ return `http://${host}:${mappedPort}`
857
+ }
858
+ collectHomeServerMachines(currentPeerInfo = null) {
859
+ const machines = []
860
+ const peers = this.getPeers()
861
+ const currentHost = currentPeerInfo && currentPeerInfo.host
862
+ ? String(currentPeerInfo.host)
863
+ : (this.kernel.peer && this.kernel.peer.host ? String(this.kernel.peer.host) : "")
864
+ const seen = new Set()
865
+ for (const peer of peers) {
866
+ if (!peer || !peer.host) {
867
+ continue
868
+ }
869
+ const host = String(peer.host)
870
+ if (currentHost && host === currentHost) {
871
+ continue
872
+ }
873
+ const name = peer.name ? String(peer.name) : host
874
+ const key = `${host}:${name}`
875
+ if (seen.has(key)) {
876
+ continue
877
+ }
878
+ seen.add(key)
879
+ const routerInfo = Array.isArray(peer.router_info)
880
+ ? peer.router_info
881
+ : (Array.isArray(peer.processes) ? peer.processes : [])
882
+ const installed = Array.isArray(peer.installed) ? peer.installed : []
883
+ const rewriteMapping = peer.rewrite_mapping && typeof peer.rewrite_mapping === "object"
884
+ ? peer.rewrite_mapping
885
+ : {}
886
+ const url = this.buildHomeServerShellUrl(peer)
887
+ machines.push({
888
+ name,
889
+ host,
890
+ platform: peer.platform ? String(peer.platform) : "",
891
+ url: url || null,
892
+ available: !!url,
893
+ current: false,
894
+ route_url: `/net/${encodeURIComponent(name)}`,
895
+ route_count: routerInfo.length + installed.length + Object.keys(rewriteMapping).length
896
+ })
897
+ }
898
+ return machines
899
+ }
900
+ normalizeHomeServerRouteUrl(value) {
901
+ const raw = typeof value === "string" ? value.trim() : ""
902
+ if (!raw) {
903
+ return ""
904
+ }
905
+ const withProtocol = /^https?:\/\//i.test(raw) ? raw : `http://${raw}`
906
+ try {
907
+ const parsed = new URL(withProtocol)
908
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
909
+ return ""
910
+ }
911
+ parsed.hash = ""
912
+ return parsed.toString().replace(/\/$/, "")
913
+ } catch (_) {
914
+ return ""
915
+ }
916
+ }
917
+ homeServerRouteKey(value) {
918
+ const normalized = this.normalizeHomeServerRouteUrl(value)
919
+ if (!normalized) {
920
+ return ""
921
+ }
922
+ try {
923
+ const parsed = new URL(normalized)
924
+ const pathname = parsed.pathname.replace(/\/+$/, "")
925
+ return `${parsed.protocol}//${parsed.host}${pathname}`.toLowerCase()
926
+ } catch (_) {
927
+ return normalized.toLowerCase().replace(/\/+$/, "")
928
+ }
929
+ }
930
+ collectHomeServerRoutes(currentPeerInfo = null, apps = [], excludedUrls = []) {
931
+ const peerInfo = currentPeerInfo || (
932
+ this.kernel && this.kernel.peer && this.kernel.peer.info && this.kernel.peer.host
933
+ ? this.kernel.peer.info[this.kernel.peer.host]
934
+ : null
935
+ )
936
+ const routerInfo = peerInfo && Array.isArray(peerInfo.router_info) ? peerInfo.router_info : []
937
+ const appUrlKeys = new Set()
938
+ for (const excludedUrl of Array.isArray(excludedUrls) ? excludedUrls : []) {
939
+ const excludedKey = this.homeServerRouteKey(excludedUrl)
940
+ if (excludedKey) {
941
+ appUrlKeys.add(excludedKey)
942
+ }
943
+ }
944
+ for (const app of Array.isArray(apps) ? apps : []) {
945
+ const appUrl = app && typeof app.url === "string" ? app.url : ""
946
+ const appKey = this.homeServerRouteKey(appUrl)
947
+ if (appKey) {
948
+ appUrlKeys.add(appKey)
949
+ }
950
+ const externalUrls = app && Array.isArray(app.external_ready_urls) ? app.external_ready_urls : []
951
+ for (const point of externalUrls) {
952
+ const pointUrl = point && typeof point.url === "string" ? point.url : ""
953
+ const pointKey = this.homeServerRouteKey(pointUrl)
954
+ if (pointKey) {
955
+ appUrlKeys.add(pointKey)
956
+ }
957
+ }
958
+ }
959
+
960
+ const seen = new Set()
961
+ const routes = []
962
+ for (const item of routerInfo) {
963
+ if (!item || typeof item !== "object") {
964
+ continue
965
+ }
966
+ const candidates = []
967
+ if (Array.isArray(item.external_hosts)) {
968
+ for (const hostEntry of item.external_hosts) {
969
+ if (!hostEntry || !hostEntry.url) {
970
+ continue
971
+ }
972
+ candidates.push({
973
+ url: hostEntry.url,
974
+ scope: hostEntry.scope || "",
975
+ interface: hostEntry.interface || ""
976
+ })
977
+ }
978
+ }
979
+ if (candidates.length === 0 && item.external_ip) {
980
+ candidates.push({ url: item.external_ip, scope: "lan", interface: "" })
981
+ }
982
+
983
+ const urls = []
984
+ for (const candidate of candidates) {
985
+ const url = this.normalizeHomeServerRouteUrl(candidate.url)
986
+ const key = this.homeServerRouteKey(url)
987
+ if (!url || !key || appUrlKeys.has(key) || seen.has(key)) {
988
+ continue
989
+ }
990
+ seen.add(key)
991
+ urls.push({
992
+ url,
993
+ scope: candidate.scope || "",
994
+ interface: candidate.interface || ""
995
+ })
996
+ }
997
+ if (urls.length === 0) {
998
+ continue
999
+ }
1000
+ const fallbackName = item.port ? `Port ${item.port}` : "Route"
1001
+ routes.push({
1002
+ name: item.title || item.name || fallbackName,
1003
+ port: item.port || "",
1004
+ url: urls[0].url,
1005
+ urls,
1006
+ state: "ready"
1007
+ })
1008
+ }
1009
+ routes.sort((a, b) => String(a.name || "").localeCompare(String(b.name || "")))
1010
+ return routes
1011
+ }
1012
+ async collectHomeServerApps() {
1013
+ const apps = []
1014
+ if (!this.kernel || !this.kernel.api || typeof this.kernel.api.listApps !== "function") {
1015
+ return apps
1016
+ }
1017
+ let entries = []
1018
+ try {
1019
+ entries = await this.kernel.api.listApps()
1020
+ } catch (_) {
1021
+ entries = []
1022
+ }
1023
+ for (const entry of entries) {
1024
+ const appId = entry && (entry.id || entry.name) ? String(entry.id || entry.name) : ""
1025
+ if (!appId) {
1026
+ continue
1027
+ }
1028
+ const appRoot = this.kernel.path("api", appId)
1029
+ let runtime = null
1030
+ try {
1031
+ runtime = this.appRegistry && typeof this.appRegistry.collectAppRuntime === "function"
1032
+ ? this.appRegistry.collectAppRuntime(appRoot)
1033
+ : null
1034
+ } catch (_) {
1035
+ runtime = null
1036
+ }
1037
+ if (!runtime || !runtime.running) {
1038
+ continue
1039
+ }
1040
+ let readyUrl = typeof runtime.ready_url === "string" ? runtime.ready_url : ""
1041
+ if (!readyUrl && entry.meta && Array.isArray(entry.meta.menu)) {
1042
+ readyUrl = this.findHomeShareReadyUrlFromMenu(entry.meta.menu)
1043
+ }
1044
+ let externalReadyUrls = []
1045
+ if (readyUrl && this.appRegistry && typeof this.appRegistry.buildExternalReadyUrls === "function") {
1046
+ try {
1047
+ externalReadyUrls = this.appRegistry.buildExternalReadyUrls(readyUrl)
1048
+ } catch (_) {
1049
+ externalReadyUrls = []
1050
+ }
1051
+ }
1052
+ apps.push({
1053
+ id: appId,
1054
+ name: (entry.title || entry.name || appId),
1055
+ icon: entry.icon || "/pinokio-black.png",
1056
+ ready_url: readyUrl,
1057
+ url: externalReadyUrls.length > 0 ? externalReadyUrls[0].url : "",
1058
+ external_ready_urls: externalReadyUrls,
1059
+ state: externalReadyUrls.length > 0 ? "ready" : (readyUrl ? "pending" : "starting")
1060
+ })
1061
+ }
1062
+ apps.sort((a, b) => {
1063
+ if (a.state !== b.state) {
1064
+ return a.state === "ready" ? -1 : 1
1065
+ }
1066
+ return String(a.name || "").localeCompare(String(b.name || ""))
1067
+ })
1068
+ return apps
1069
+ }
740
1070
  getItems(items, meta, p, preferenceMap = null) {
741
1071
  const preferences = preferenceMap instanceof Map ? preferenceMap : null
742
1072
  return items.map((x) => {
@@ -787,6 +1117,27 @@ class Server {
787
1117
  let dev_url = browser_url + "/dev"
788
1118
  let review_url = browser_url + "/review"
789
1119
  let files_url = browser_url + "/files"
1120
+ let readyUrl = ""
1121
+ let externalReadyUrls = []
1122
+ if (meta && this.appRegistry && typeof this.appRegistry.collectAppRuntime === "function") {
1123
+ try {
1124
+ const appRoot = this.kernel.path("api", x.name)
1125
+ const runtime = this.appRegistry.collectAppRuntime(appRoot)
1126
+ readyUrl = runtime && typeof runtime.ready_url === "string" ? runtime.ready_url : ""
1127
+ if (readyUrl && typeof this.appRegistry.buildExternalReadyUrls === "function") {
1128
+ externalReadyUrls = this.appRegistry.buildExternalReadyUrls(readyUrl)
1129
+ }
1130
+ } catch (_) {
1131
+ readyUrl = ""
1132
+ externalReadyUrls = []
1133
+ }
1134
+ }
1135
+ if (!readyUrl) {
1136
+ readyUrl = this.findHomeShareReadyUrlFromMenu(x.menu)
1137
+ if (readyUrl && this.appRegistry && typeof this.appRegistry.buildExternalReadyUrls === "function") {
1138
+ externalReadyUrls = this.appRegistry.buildExternalReadyUrls(readyUrl)
1139
+ }
1140
+ }
790
1141
 
791
1142
  let dns = this.kernel.pinokio_configs[x.name].dns
792
1143
  let routes = dns["@"]
@@ -831,6 +1182,8 @@ class Server {
831
1182
  view_url,
832
1183
  review_url,
833
1184
  files_url,
1185
+ ready_url: readyUrl,
1186
+ external_ready_urls: externalReadyUrls,
834
1187
  starred: Boolean(preference && preference.starred),
835
1188
  starred_at: preference && preference.starred_at ? preference.starred_at : null,
836
1189
  last_launch_at: preference && preference.last_launch_at ? preference.last_launch_at : null,
@@ -13825,7 +14178,7 @@ class Server {
13825
14178
  })
13826
14179
  }))
13827
14180
  this.app.get("/network", ex(async (req, res) => {
13828
- let protocol = req.get('X-Forwarded-Proto')
14181
+ let protocol = req.get('X-Forwarded-Proto') || "http"
13829
14182
  let { requirements, install_required, requirements_pending, error } = await this.kernel.bin.check({
13830
14183
  bin: this.kernel.bin.preset("network"),
13831
14184
  })
@@ -13925,11 +14278,14 @@ class Server {
13925
14278
 
13926
14279
  let current_urls = await this.current_urls(req.originalUrl.slice(1))
13927
14280
  let current_peer = this.kernel.peer.info ? this.kernel.peer.info[this.kernel.peer.host] : null
13928
- let host = null
13929
- if (current_peer) {
14281
+ let host = this.kernel.peer.host
14282
+ if (current_peer && current_peer.host) {
13930
14283
  host = current_peer.host
13931
14284
  }
13932
- let peer = current_peer
14285
+ let peer = current_peer || {
14286
+ host,
14287
+ name: this.kernel.peer.name
14288
+ }
13933
14289
 
13934
14290
  let processes = []
13935
14291
  try {
@@ -13959,19 +14315,32 @@ class Server {
13959
14315
 
13960
14316
  let list = this.getPeers()
13961
14317
  let installed = this.kernel.peer.info && this.kernel.peer.info[host] ? this.kernel.peer.info[host].installed : []
14318
+ let serverless_mapping = this.kernel.peer.info && this.kernel.peer.info[host] ? this.kernel.peer.info[host].rewrite_mapping : {}
14319
+ let serverless = Object.keys(serverless_mapping || {}).map((name) => {
14320
+ return serverless_mapping[name]
14321
+ })
13962
14322
 
13963
14323
  let static_routes = Object.keys(this.kernel.router.rewrite_mapping).map((key) => {
13964
14324
  return this.kernel.router.rewrite_mapping[key]
13965
14325
  })
14326
+ const showStaticRoutes = false
14327
+ const visibleStaticRoutes = showStaticRoutes ? static_routes : []
14328
+ const routeCount = processes.length + visibleStaticRoutes.length
14329
+ const allow_dns_creation = !!current_peer
13966
14330
  const peerAccess = await this.composePeerAccessPayload()
13967
14331
  res.render("network", {
13968
14332
  static_routes,
14333
+ visibleStaticRoutes,
14334
+ showStaticRoutes,
14335
+ routeCount,
13969
14336
  host,
14337
+ peer,
13970
14338
  favicons,
13971
14339
  titles,
13972
14340
  descriptions,
13973
14341
  processes,
13974
14342
  installed,
14343
+ serverless,
13975
14344
  error: null,
13976
14345
 
13977
14346
 
@@ -13984,6 +14353,7 @@ class Server {
13984
14353
  current_host: this.kernel.peer.host,
13985
14354
  peers,
13986
14355
  list,
14356
+ selected_name: this.kernel.peer.name,
13987
14357
  name: this.kernel.peer.name,
13988
14358
  https_active: this.kernel.router.active,
13989
14359
  peer_active: this.kernel.peer.active,
@@ -13999,7 +14369,10 @@ class Server {
13999
14369
  proxy: home_proxy,
14000
14370
  localhost: `http://localhost:${this.port}`,
14001
14371
  icon,
14002
- apps
14372
+ apps,
14373
+ cwd: this.kernel.path("api"),
14374
+ protocol,
14375
+ allow_dns_creation,
14003
14376
  })
14004
14377
  }))
14005
14378
  this.app.get("/getlog", ex(async (req, res) => {
@@ -15744,28 +16117,34 @@ class Server {
15744
16117
  }))
15745
16118
  this.app.get("/info/network-sharing", ex(async (req, res) => {
15746
16119
  res.set("Cache-Control", "no-store")
15747
- const https_active = !!(this.kernel.peer && this.kernel.peer.https_active)
15748
- let installed = false
15749
- let running = false
15750
- try {
15751
- const caddy = this.kernel.bin && this.kernel.bin.mod ? this.kernel.bin.mod.caddy : null
15752
- if (caddy && typeof caddy.installed === "function") {
15753
- installed = !!(await caddy.installed())
15754
- }
15755
- } catch (err) {
15756
- installed = false
15757
- }
16120
+ res.json(await this.getNetworkSharingStatus())
16121
+ }))
16122
+ this.app.get("/info/home-server", ex(async (req, res) => {
16123
+ res.set("Cache-Control", "no-store")
16124
+ const network = await this.getNetworkSharingStatus()
16125
+ const status = this.homeServerStatusFromNetwork(network)
16126
+ let peerInfo = null
15758
16127
  try {
15759
- await axios.get("http://127.0.0.1:2019/config/", { timeout: 750 })
15760
- running = true
15761
- } catch (err) {
15762
- running = false
16128
+ peerInfo = await this.kernel.peer.current_host()
16129
+ } catch (_) {
16130
+ peerInfo = null
15763
16131
  }
16132
+ const shellUrl = status === "on" ? this.buildHomeServerShellUrl(peerInfo) : ""
16133
+ const apps = status === "on" ? await this.collectHomeServerApps() : []
16134
+ const routes = status === "on" ? this.collectHomeServerRoutes(peerInfo, apps, [shellUrl]) : []
16135
+ const machines = status === "on" ? this.collectHomeServerMachines(peerInfo) : []
15764
16136
  res.json({
15765
- active: https_active,
15766
- installed,
15767
- running,
15768
- available: https_active && installed
16137
+ status,
16138
+ ...network,
16139
+ machine: peerInfo && peerInfo.name ? peerInfo.name : (this.kernel.peer && this.kernel.peer.name ? this.kernel.peer.name : ""),
16140
+ host: peerInfo && peerInfo.host ? peerInfo.host : "",
16141
+ shell: shellUrl ? {
16142
+ name: "Pinokio",
16143
+ url: shellUrl
16144
+ } : null,
16145
+ apps,
16146
+ routes,
16147
+ machines
15769
16148
  })
15770
16149
  }))
15771
16150
  this.app.get("/qr", ex(async (req, res) => {