pinokiod 5.3.12 → 5.3.14
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/package.json +1 -1
- package/script/index.js +5 -5
- package/server/index.js +4 -96
- package/server/views/app.ejs +7 -256
- package/server/views/checkpoints.ejs +2 -166
package/package.json
CHANGED
package/script/index.js
CHANGED
|
@@ -57,11 +57,11 @@ const server = new Server({
|
|
|
57
57
|
profile: (gitRemote) => {
|
|
58
58
|
return `https://pinokiocomputer.github.io/home/item?uri=${gitRemote}&display=profile`
|
|
59
59
|
},
|
|
60
|
-
site: "https://
|
|
61
|
-
discover_dark: "https://
|
|
62
|
-
discover_light: "https://
|
|
63
|
-
portal: "https://
|
|
64
|
-
docs: "https://
|
|
60
|
+
site: "https://pinokio.co",
|
|
61
|
+
discover_dark: "https://beta.pinokio.co",
|
|
62
|
+
discover_light: "https://beta.pinokio.co",
|
|
63
|
+
portal: "https://beta.pinokio.co",
|
|
64
|
+
docs: "https://pinokio.co/docs",
|
|
65
65
|
install: "https://pinokiocomputer.github.io/program.pinokio.computer/#/?id=install",
|
|
66
66
|
agent: "web",
|
|
67
67
|
store: new Store()
|
package/server/index.js
CHANGED
|
@@ -4818,35 +4818,10 @@ class Server {
|
|
|
4818
4818
|
})
|
|
4819
4819
|
}))
|
|
4820
4820
|
this.app.get("/checkpoints/registry", ex(async (req, res) => {
|
|
4821
|
-
|
|
4822
|
-
const registryUrlPrefill = typeof req.query.registry_url === "string" ? req.query.registry_url.trim() : ""
|
|
4823
|
-
res.render("checkpoints_registry", {
|
|
4824
|
-
registryConnectUrl,
|
|
4825
|
-
registryUrlPrefill,
|
|
4826
|
-
theme: this.theme,
|
|
4827
|
-
agent: req.agent,
|
|
4828
|
-
})
|
|
4821
|
+
res.status(404).send("Not found")
|
|
4829
4822
|
}))
|
|
4830
4823
|
this.app.post("/checkpoints/registry", ex(async (req, res) => {
|
|
4831
|
-
|
|
4832
|
-
? req.body.connectUrl
|
|
4833
|
-
: (Object.prototype.hasOwnProperty.call(req.body || {}, "registry_url")
|
|
4834
|
-
? req.body.registry_url
|
|
4835
|
-
: (Object.prototype.hasOwnProperty.call(req.body || {}, "connectEndpoint")
|
|
4836
|
-
? req.body.connectEndpoint
|
|
4837
|
-
: (Object.prototype.hasOwnProperty.call(req.query || {}, "connectUrl")
|
|
4838
|
-
? req.query.connectUrl
|
|
4839
|
-
: (Object.prototype.hasOwnProperty.call(req.query || {}, "registry_url")
|
|
4840
|
-
? req.query.registry_url
|
|
4841
|
-
: (Object.prototype.hasOwnProperty.call(req.query || {}, "connectEndpoint")
|
|
4842
|
-
? req.query.connectEndpoint
|
|
4843
|
-
: "")))))
|
|
4844
|
-
const connectUrl = connectRaw != null ? String(connectRaw).trim() : ""
|
|
4845
|
-
await this.updateEnvironmentVars({
|
|
4846
|
-
PINOKIO_REGISTRY_CONNECT_URL: connectUrl,
|
|
4847
|
-
PINOKIO_REGISTRY_CONNECT_ENDPOINT: ""
|
|
4848
|
-
})
|
|
4849
|
-
res.json({ ok: true, connectUrl: connectUrl || null })
|
|
4824
|
+
res.status(404).json({ ok: false, error: "Not found" })
|
|
4850
4825
|
}))
|
|
4851
4826
|
this.app.get("/registry/ping.png", ex(async (_req, res) => {
|
|
4852
4827
|
res.setHeader('Content-Type', 'image/png')
|
|
@@ -5111,74 +5086,7 @@ class Server {
|
|
|
5111
5086
|
}))
|
|
5112
5087
|
|
|
5113
5088
|
this.app.post("/checkpoints/publish", ex(async (req, res) => {
|
|
5114
|
-
|
|
5115
|
-
if (!registryEnabled) {
|
|
5116
|
-
res.status(404).json({ ok: false, error: "Not found" })
|
|
5117
|
-
return
|
|
5118
|
-
}
|
|
5119
|
-
const snapshotRaw = typeof req.query.snapshotId === 'string' || typeof req.query.snapshotId === 'number' ? req.query.snapshotId : ''
|
|
5120
|
-
const snapshotId = snapshotRaw === 'latest' ? 'latest' : String(snapshotRaw || '')
|
|
5121
|
-
if (!snapshotId) {
|
|
5122
|
-
res.status(400).json({ ok: false, error: "Missing snapshotId" })
|
|
5123
|
-
return
|
|
5124
|
-
}
|
|
5125
|
-
const payload = await this.kernel.git.readCheckpointPayload(snapshotId)
|
|
5126
|
-
if (!payload || !payload.app || !payload.hash) {
|
|
5127
|
-
res.status(404).json({ ok: false, error: "Snapshot not found" })
|
|
5128
|
-
return
|
|
5129
|
-
}
|
|
5130
|
-
|
|
5131
|
-
const registry = await this.getRegistryConfig()
|
|
5132
|
-
const baseUrl = registry && registry.url ? String(registry.url).replace(/\/$/, '') : null
|
|
5133
|
-
const apiKey = registry && registry.apiKey ? String(registry.apiKey) : null
|
|
5134
|
-
const connectUrl = await this.getRegistryConnectUrl().catch(() => null)
|
|
5135
|
-
|
|
5136
|
-
if (!baseUrl || !apiKey) {
|
|
5137
|
-
await this.kernel.git.setCheckpointSync(payload.app, snapshotId, { status: "needs_link", at: Date.now() }).catch(() => {})
|
|
5138
|
-
res.json({ ok: true, publish: { ok: false, code: "not_linked", connectUrl } })
|
|
5139
|
-
return
|
|
5140
|
-
}
|
|
5141
|
-
|
|
5142
|
-
try {
|
|
5143
|
-
await this.kernel.git.setCheckpointSync(payload.app, snapshotId, { status: "syncing", at: Date.now() })
|
|
5144
|
-
const filePath = this.kernel.git.checkpointFilePath(payload.hash)
|
|
5145
|
-
const checkpoint = filePath ? JSON.parse(await fs.promises.readFile(filePath, "utf8")) : null
|
|
5146
|
-
const system = payload.system && typeof payload.system === "object" ? payload.system : {
|
|
5147
|
-
platform: payload.platform || null,
|
|
5148
|
-
arch: payload.arch || null,
|
|
5149
|
-
gpu: payload.gpu || null,
|
|
5150
|
-
ram: typeof payload.ram === "number" ? payload.ram : null,
|
|
5151
|
-
vram: typeof payload.vram === "number" ? payload.vram : null,
|
|
5152
|
-
}
|
|
5153
|
-
const response = await axios.post(
|
|
5154
|
-
`${baseUrl}/checkpoints`,
|
|
5155
|
-
{
|
|
5156
|
-
hash: String(payload.hash),
|
|
5157
|
-
visibility: "public",
|
|
5158
|
-
checkpoint,
|
|
5159
|
-
system,
|
|
5160
|
-
},
|
|
5161
|
-
{ headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` } }
|
|
5162
|
-
)
|
|
5163
|
-
const remoteId = response && response.data
|
|
5164
|
-
? (response.data.checkpoint && response.data.checkpoint.id ? response.data.checkpoint.id : (response.data.id ? response.data.id : null))
|
|
5165
|
-
: null
|
|
5166
|
-
const viewUrl = await this.getRegistryViewUrl(payload.hash)
|
|
5167
|
-
await this.kernel.git.setCheckpointSync(payload.app, snapshotId, { status: "published", at: Date.now(), remoteId })
|
|
5168
|
-
await this.kernel.git.setCheckpointDecision(payload.app, snapshotId, "published").catch(() => {})
|
|
5169
|
-
res.json({ ok: true, publish: { ok: true, remoteId, hash: payload.hash, url: viewUrl } })
|
|
5170
|
-
} catch (error) {
|
|
5171
|
-
const status = error && error.response && error.response.status ? Number(error.response.status) : null
|
|
5172
|
-
const message = error && error.message ? error.message : String(error)
|
|
5173
|
-
if (status === 401 || status === 403) {
|
|
5174
|
-
await this.updateEnvironmentVars({ PINOKIO_REGISTRY_API_KEY: "" }).catch(() => {})
|
|
5175
|
-
await this.kernel.git.setCheckpointSync(payload.app, snapshotId, { status: "needs_link", at: Date.now(), error: message }).catch(() => {})
|
|
5176
|
-
res.json({ ok: true, publish: { ok: false, code: "not_linked", connectUrl } })
|
|
5177
|
-
return
|
|
5178
|
-
}
|
|
5179
|
-
await this.kernel.git.setCheckpointSync(payload.app, snapshotId, { status: "error", at: Date.now(), error: message }).catch(() => {})
|
|
5180
|
-
res.json({ ok: true, publish: { ok: false, code: "error", error: message } })
|
|
5181
|
-
}
|
|
5089
|
+
res.status(404).json({ ok: false, error: "Not found" })
|
|
5182
5090
|
}))
|
|
5183
5091
|
|
|
5184
5092
|
this.app.post("/checkpoints/decision", ex(async (req, res) => {
|
|
@@ -9315,7 +9223,7 @@ class Server {
|
|
|
9315
9223
|
title: name,
|
|
9316
9224
|
url: gitRemote,
|
|
9317
9225
|
//redirect_uri: "http://localhost:3001/apps/redirect?git=" + gitRemote,
|
|
9318
|
-
redirect_uri:
|
|
9226
|
+
redirect_uri: `${this.portal}/resolve?url=${encodeURIComponent(gitRemote)}`,
|
|
9319
9227
|
platform: this.kernel.platform,
|
|
9320
9228
|
theme: this.theme,
|
|
9321
9229
|
agent: req.agent,
|
package/server/views/app.ejs
CHANGED
|
@@ -3702,12 +3702,11 @@ body.dark .snapshot-footer-input input {
|
|
|
3702
3702
|
<iframe class='selected' src="<%=editor_tab%>"></iframe>
|
|
3703
3703
|
<% } %>
|
|
3704
3704
|
</main>
|
|
3705
|
-
<% const registryEnabledValue = typeof registryEnabled === "undefined" ? false : registryEnabled; %>
|
|
3706
3705
|
<% const snapshotFooterAllowed = typeof snapshotFooterEnabled === "undefined" ? false : snapshotFooterEnabled; %>
|
|
3707
3706
|
<% if (snapshotFooterAllowed) { %>
|
|
3708
|
-
<div class='snapshot-footer hidden' data-workspace="<%=name%>"
|
|
3709
|
-
<div class="snapshot-footer-save
|
|
3710
|
-
<div class='caption' title="
|
|
3707
|
+
<div class='snapshot-footer hidden' data-workspace="<%=name%>">
|
|
3708
|
+
<div class="snapshot-footer-save">
|
|
3709
|
+
<div class='caption' title="Saves a tiny JSON checkpoint of exact commit hashes (no files).">
|
|
3711
3710
|
Snapshot installed modules information and restore later
|
|
3712
3711
|
</div>
|
|
3713
3712
|
<div class="snapshot-footer-actions" style="display:flex; gap:10px; flex-wrap:wrap; align-items:center;">
|
|
@@ -3718,15 +3717,6 @@ body.dark .snapshot-footer-input input {
|
|
|
3718
3717
|
<button type="button" class="btn snapshot-btn-dismiss"><i class="fa-solid fa-circle-xmark"></i> Dismiss</button>
|
|
3719
3718
|
</div>
|
|
3720
3719
|
</div>
|
|
3721
|
-
<% if (registryEnabledValue) { %>
|
|
3722
|
-
<div class="snapshot-footer-publish <%= pendingSnapshotId ? '' : 'hidden' %>" aria-live="polite">
|
|
3723
|
-
<div class="snapshot-footer-publish-text">Local save success! Publish to the registry?</div>
|
|
3724
|
-
<div class="snapshot-footer-actions" style="display:flex; gap:10px; flex-wrap:wrap; align-items:center;">
|
|
3725
|
-
<button type="button" class="btn btn-primary snapshot-btn-publish"><i class="fa-solid fa-cloud-arrow-up"></i> Publish Checkpoint</button>
|
|
3726
|
-
<button type="button" class="btn snapshot-btn-later">Later</button>
|
|
3727
|
-
</div>
|
|
3728
|
-
</div>
|
|
3729
|
-
<% } %>
|
|
3730
3720
|
</div>
|
|
3731
3721
|
<% } %>
|
|
3732
3722
|
</div>
|
|
@@ -9723,22 +9713,15 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
9723
9713
|
if (!footer) return
|
|
9724
9714
|
const saveBtn = footer.querySelector(".snapshot-btn-save")
|
|
9725
9715
|
const savePanel = footer.querySelector(".snapshot-footer-save")
|
|
9726
|
-
const publishPanel = footer.querySelector(".snapshot-footer-publish")
|
|
9727
|
-
const publishBtn = footer.querySelector(".snapshot-btn-publish")
|
|
9728
|
-
const laterBtn = footer.querySelector(".snapshot-btn-later")
|
|
9729
9716
|
const dismissBtn = footer.querySelector(".snapshot-btn-dismiss")
|
|
9730
9717
|
const howBtn = footer.querySelector(".snapshot-btn-how")
|
|
9731
|
-
const publishText = publishPanel ? publishPanel.querySelector(".snapshot-footer-publish-text") : null
|
|
9732
|
-
const publishActions = publishPanel ? publishPanel.querySelector(".snapshot-footer-actions") : null
|
|
9733
9718
|
if (!saveBtn) return
|
|
9734
|
-
const registryEnabled = footer.getAttribute("data-registry-enabled") === "1"
|
|
9735
9719
|
const workspace = footer.getAttribute("data-workspace")
|
|
9736
9720
|
if (!workspace) {
|
|
9737
9721
|
footer.classList.add("hidden")
|
|
9738
9722
|
return
|
|
9739
9723
|
}
|
|
9740
9724
|
footer.classList.add("hidden")
|
|
9741
|
-
let pendingSnapshotId = footer.getAttribute("data-pending-snapshot-id") || ""
|
|
9742
9725
|
const dismissStorageKey = `pinokio.snapshot-footer.dismissed:${workspace}`
|
|
9743
9726
|
const getDismissedState = () => {
|
|
9744
9727
|
try {
|
|
@@ -9763,57 +9746,29 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
9763
9746
|
document.documentElement.classList.remove("snapshot-footer-dismissed")
|
|
9764
9747
|
}
|
|
9765
9748
|
}
|
|
9766
|
-
let savedSnapshotId = null
|
|
9767
9749
|
const saveOriginal = saveBtn.innerHTML
|
|
9768
|
-
const publishOriginal = publishBtn ? publishBtn.innerHTML : ""
|
|
9769
9750
|
const setSaveLoading = (isLoading, primaryText) => {
|
|
9770
9751
|
saveBtn.disabled = isLoading
|
|
9771
9752
|
if (primaryText) {
|
|
9772
9753
|
saveBtn.innerHTML = primaryText
|
|
9773
9754
|
}
|
|
9774
9755
|
}
|
|
9775
|
-
const setPublishLoading = (isLoading, primaryText) => {
|
|
9776
|
-
if (publishBtn) publishBtn.disabled = isLoading
|
|
9777
|
-
if (laterBtn) laterBtn.disabled = isLoading
|
|
9778
|
-
if (primaryText && publishBtn) {
|
|
9779
|
-
publishBtn.innerHTML = primaryText
|
|
9780
|
-
}
|
|
9781
|
-
}
|
|
9782
|
-
const showPublishPanel = () => {
|
|
9783
|
-
if (!registryEnabled) {
|
|
9784
|
-
footer.classList.add("hidden")
|
|
9785
|
-
return
|
|
9786
|
-
}
|
|
9787
|
-
if (publishPanel) publishPanel.classList.remove("hidden")
|
|
9788
|
-
if (savePanel) savePanel.classList.add("hidden")
|
|
9789
|
-
}
|
|
9790
|
-
const showSavePanel = () => {
|
|
9791
|
-
if (savePanel) savePanel.classList.remove("hidden")
|
|
9792
|
-
if (publishPanel) publishPanel.classList.add("hidden")
|
|
9793
|
-
}
|
|
9794
9756
|
const applySnapshotStatus = (status) => {
|
|
9795
9757
|
const hasSnapshots = !!(status && status.hasSnapshots)
|
|
9796
|
-
|
|
9797
|
-
footer.setAttribute("data-pending-snapshot-id", pendingSnapshotId)
|
|
9798
|
-
const shouldShow = !hasSnapshots || (registryEnabled && pendingSnapshotId)
|
|
9758
|
+
const shouldShow = !hasSnapshots
|
|
9799
9759
|
if (!shouldShow) {
|
|
9800
9760
|
setDismissedClass(false)
|
|
9801
9761
|
footer.classList.add("hidden")
|
|
9802
9762
|
return
|
|
9803
9763
|
}
|
|
9804
|
-
const dismissed =
|
|
9764
|
+
const dismissed = getDismissedState()
|
|
9805
9765
|
setDismissedClass(dismissed)
|
|
9806
9766
|
if (dismissed) {
|
|
9807
9767
|
footer.classList.add("hidden")
|
|
9808
9768
|
return
|
|
9809
9769
|
}
|
|
9810
9770
|
footer.classList.remove("hidden")
|
|
9811
|
-
if (
|
|
9812
|
-
savedSnapshotId = pendingSnapshotId
|
|
9813
|
-
showPublishPanel()
|
|
9814
|
-
} else {
|
|
9815
|
-
showSavePanel()
|
|
9816
|
-
}
|
|
9771
|
+
if (savePanel) savePanel.classList.remove("hidden")
|
|
9817
9772
|
}
|
|
9818
9773
|
const loadSnapshotStatus = async () => {
|
|
9819
9774
|
try {
|
|
@@ -9888,141 +9843,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
9888
9843
|
})
|
|
9889
9844
|
}
|
|
9890
9845
|
|
|
9891
|
-
const waitForRegistryLink = async () => {
|
|
9892
|
-
const startedAt = Date.now()
|
|
9893
|
-
while (Date.now() - startedAt < 120000) {
|
|
9894
|
-
try {
|
|
9895
|
-
const s = await fetch("/api/registry/status", { method: "GET", headers: { "Accept": "application/json" } })
|
|
9896
|
-
if (s.ok) {
|
|
9897
|
-
const data = await s.json()
|
|
9898
|
-
if (data && data.linked) return true
|
|
9899
|
-
}
|
|
9900
|
-
} catch (_) {}
|
|
9901
|
-
await new Promise((r) => setTimeout(r, 2000))
|
|
9902
|
-
}
|
|
9903
|
-
return false
|
|
9904
|
-
}
|
|
9905
|
-
|
|
9906
|
-
const publishSnapshot = async (snapshotId) => {
|
|
9907
|
-
const qs = new URLSearchParams()
|
|
9908
|
-
qs.set("snapshotId", String(snapshotId))
|
|
9909
|
-
const res = await fetch(`/checkpoints/publish?${qs.toString()}`, { method: "POST", headers: { "Accept": "application/json" } })
|
|
9910
|
-
if (!res.ok) return { ok: false }
|
|
9911
|
-
try { return await res.json() } catch (_) { return { ok: false } }
|
|
9912
|
-
}
|
|
9913
|
-
|
|
9914
9846
|
let isSaving = false
|
|
9915
|
-
let isPublishing = false
|
|
9916
|
-
const showPublishedLink = (publishUrl) => {
|
|
9917
|
-
showPublishPanel()
|
|
9918
|
-
if (publishText) publishText.textContent = "Published."
|
|
9919
|
-
if (publishActions) {
|
|
9920
|
-
publishActions.innerHTML = publishUrl
|
|
9921
|
-
? `<a class="btn btn-primary" href="${escapePublishUrl(publishUrl)}" target="_blank" rel="noopener"><i class="fa-solid fa-arrow-up-right-from-square"></i> View published checkpoint</a>`
|
|
9922
|
-
: ''
|
|
9923
|
-
}
|
|
9924
|
-
}
|
|
9925
|
-
const escapePublishUrl = (value) => {
|
|
9926
|
-
if (value === null || value === undefined) {
|
|
9927
|
-
return ''
|
|
9928
|
-
}
|
|
9929
|
-
return String(value).replace(/[&<>"']/g, (match) => {
|
|
9930
|
-
switch (match) {
|
|
9931
|
-
case '&':
|
|
9932
|
-
return '&'
|
|
9933
|
-
case '<':
|
|
9934
|
-
return '<'
|
|
9935
|
-
case '>':
|
|
9936
|
-
return '>'
|
|
9937
|
-
case '"':
|
|
9938
|
-
return '"'
|
|
9939
|
-
case '\'':
|
|
9940
|
-
return '''
|
|
9941
|
-
default:
|
|
9942
|
-
return match
|
|
9943
|
-
}
|
|
9944
|
-
})
|
|
9945
|
-
}
|
|
9946
|
-
|
|
9947
|
-
const promptRegistryConnect = async (suggestedConnectUrl) => {
|
|
9948
|
-
const connectHtml = `
|
|
9949
|
-
<div class="pinokio-modal-surface">
|
|
9950
|
-
<div class="pinokio-modal-header">
|
|
9951
|
-
<div class="pinokio-modal-icon"><i class="fa-solid fa-link"></i></div>
|
|
9952
|
-
<div class="pinokio-modal-heading">
|
|
9953
|
-
<div class="pinokio-modal-title">Connect to Registry</div>
|
|
9954
|
-
<div class="pinokio-modal-subtitle">
|
|
9955
|
-
Enter your registry connect URL to link Pinokio, then we’ll retry the publish.
|
|
9956
|
-
</div>
|
|
9957
|
-
</div>
|
|
9958
|
-
</div>
|
|
9959
|
-
<div class="pinokio-modal-body">
|
|
9960
|
-
<div class="pinokio-modal-form">
|
|
9961
|
-
<div>
|
|
9962
|
-
<label class="pinokio-modal-label" for="pinokio-registry-connect-url">Registry URL</label>
|
|
9963
|
-
<input
|
|
9964
|
-
id="pinokio-registry-connect-url"
|
|
9965
|
-
class="pinokio-modal-input"
|
|
9966
|
-
type="url"
|
|
9967
|
-
placeholder="https://your-registry/connect/pinokio"
|
|
9968
|
-
autocomplete="off"
|
|
9969
|
-
/>
|
|
9970
|
-
</div>
|
|
9971
|
-
</div>
|
|
9972
|
-
</div>
|
|
9973
|
-
</div>
|
|
9974
|
-
`
|
|
9975
|
-
const choice = await Swal.fire({
|
|
9976
|
-
html: connectHtml,
|
|
9977
|
-
backdrop: 'rgba(9,11,15,0.65)',
|
|
9978
|
-
width: 'min(520px, 92vw)',
|
|
9979
|
-
showCancelButton: true,
|
|
9980
|
-
showConfirmButton: true,
|
|
9981
|
-
confirmButtonText: "Open connect page",
|
|
9982
|
-
cancelButtonText: "Cancel",
|
|
9983
|
-
buttonsStyling: false,
|
|
9984
|
-
focusConfirm: false,
|
|
9985
|
-
customClass: {
|
|
9986
|
-
popup: 'pinokio-modern-modal',
|
|
9987
|
-
htmlContainer: 'pinokio-modern-html',
|
|
9988
|
-
closeButton: 'pinokio-modern-close',
|
|
9989
|
-
confirmButton: 'pinokio-modern-confirm',
|
|
9990
|
-
cancelButton: 'pinokio-modern-cancel'
|
|
9991
|
-
},
|
|
9992
|
-
didOpen: () => {
|
|
9993
|
-
const input = document.getElementById("pinokio-registry-connect-url")
|
|
9994
|
-
if (input) {
|
|
9995
|
-
input.value = suggestedConnectUrl || ""
|
|
9996
|
-
try { input.focus() } catch (_) {}
|
|
9997
|
-
try { input.select() } catch (_) {}
|
|
9998
|
-
}
|
|
9999
|
-
},
|
|
10000
|
-
preConfirm: () => {
|
|
10001
|
-
const input = document.getElementById("pinokio-registry-connect-url")
|
|
10002
|
-
const v = input && input.value != null ? String(input.value).trim() : ""
|
|
10003
|
-
if (!v) {
|
|
10004
|
-
Swal.showValidationMessage("Registry URL is required")
|
|
10005
|
-
return false
|
|
10006
|
-
}
|
|
10007
|
-
return v
|
|
10008
|
-
}
|
|
10009
|
-
})
|
|
10010
|
-
if (!choice || !choice.isConfirmed) return false
|
|
10011
|
-
const connectUrl = choice.value ? String(choice.value).trim() : ""
|
|
10012
|
-
if (!connectUrl) return false
|
|
10013
|
-
try {
|
|
10014
|
-
window.open(connectUrl, "pinokio-registry-connect")
|
|
10015
|
-
} catch (_) {
|
|
10016
|
-
window.location.href = connectUrl
|
|
10017
|
-
}
|
|
10018
|
-
const linked = await waitForRegistryLink()
|
|
10019
|
-
if (!linked) {
|
|
10020
|
-
Swal.fire({ icon: "error", title: "Not connected", text: "Could not confirm the registry connection." })
|
|
10021
|
-
return false
|
|
10022
|
-
}
|
|
10023
|
-
return true
|
|
10024
|
-
}
|
|
10025
|
-
|
|
10026
9847
|
const handleSave = async (event) => {
|
|
10027
9848
|
event.preventDefault()
|
|
10028
9849
|
if (isSaving) return
|
|
@@ -10108,10 +9929,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
10108
9929
|
Swal.fire({ icon: "error", title: "Error", text: "Save failed" })
|
|
10109
9930
|
return
|
|
10110
9931
|
}
|
|
10111
|
-
|
|
10112
|
-
setSaveLoading(false, saveOriginal)
|
|
9932
|
+
setSaveLoading(false, `<i class="fa-solid fa-circle-check"></i> Saved`)
|
|
10113
9933
|
saveBtn.disabled = true
|
|
10114
|
-
showPublishPanel()
|
|
10115
9934
|
} catch (_) {
|
|
10116
9935
|
setSaveLoading(false, saveOriginal)
|
|
10117
9936
|
Swal.fire({ icon: "error", title: "Error", text: "Save failed" })
|
|
@@ -10120,72 +9939,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
10120
9939
|
}
|
|
10121
9940
|
}
|
|
10122
9941
|
|
|
10123
|
-
const handlePublish = async (event) => {
|
|
10124
|
-
event.preventDefault()
|
|
10125
|
-
if (isPublishing || !savedSnapshotId) return
|
|
10126
|
-
isPublishing = true
|
|
10127
|
-
setPublishLoading(true, `<i class="fa-solid fa-circle-notch fa-spin"></i> Publishing...`)
|
|
10128
|
-
try {
|
|
10129
|
-
const published = await publishSnapshot(savedSnapshotId)
|
|
10130
|
-
if (published && published.publish && published.publish.ok) {
|
|
10131
|
-
const publishUrl = published && published.publish && published.publish.url ? String(published.publish.url) : ""
|
|
10132
|
-
showPublishedLink(publishUrl)
|
|
10133
|
-
return
|
|
10134
|
-
}
|
|
10135
|
-
if (published && published.publish && published.publish.code === "not_linked") {
|
|
10136
|
-
const suggestedConnectUrl = published.publish.connectUrl ? String(published.publish.connectUrl) : ""
|
|
10137
|
-
const linked = await promptRegistryConnect(suggestedConnectUrl)
|
|
10138
|
-
if (!linked) {
|
|
10139
|
-
setPublishLoading(false, publishOriginal)
|
|
10140
|
-
return
|
|
10141
|
-
}
|
|
10142
|
-
const retry = await publishSnapshot(savedSnapshotId)
|
|
10143
|
-
if (retry && retry.publish && retry.publish.ok) {
|
|
10144
|
-
footer.classList.add("hidden")
|
|
10145
|
-
return
|
|
10146
|
-
}
|
|
10147
|
-
const retryMsg = retry && retry.publish && retry.publish.error ? retry.publish.error : "Publish failed"
|
|
10148
|
-
Swal.fire({ icon: "error", title: "Error", text: retryMsg })
|
|
10149
|
-
setPublishLoading(false, publishOriginal)
|
|
10150
|
-
return
|
|
10151
|
-
}
|
|
10152
|
-
const msg = published && published.publish && published.publish.error ? published.publish.error : "Publish failed"
|
|
10153
|
-
Swal.fire({ icon: "error", title: "Error", text: msg })
|
|
10154
|
-
setPublishLoading(false, publishOriginal)
|
|
10155
|
-
} catch (_) {
|
|
10156
|
-
setPublishLoading(false, publishOriginal)
|
|
10157
|
-
Swal.fire({ icon: "error", title: "Error", text: "Publish failed" })
|
|
10158
|
-
} finally {
|
|
10159
|
-
isPublishing = false
|
|
10160
|
-
}
|
|
10161
|
-
}
|
|
10162
|
-
|
|
10163
|
-
const handleLater = async (event) => {
|
|
10164
|
-
event.preventDefault()
|
|
10165
|
-
if (!savedSnapshotId) {
|
|
10166
|
-
footer.classList.add("hidden")
|
|
10167
|
-
return
|
|
10168
|
-
}
|
|
10169
|
-
setPublishLoading(true)
|
|
10170
|
-
try {
|
|
10171
|
-
const qs = new URLSearchParams()
|
|
10172
|
-
qs.set("snapshotId", String(savedSnapshotId))
|
|
10173
|
-
qs.set("decision", "later")
|
|
10174
|
-
const res = await fetch(`/checkpoints/decision?${qs.toString()}`, {
|
|
10175
|
-
method: "POST",
|
|
10176
|
-
headers: { "Accept": "application/json" }
|
|
10177
|
-
})
|
|
10178
|
-
const payload = res.ok ? await res.json().catch(() => null) : null
|
|
10179
|
-
if (!payload || !payload.ok) {
|
|
10180
|
-
throw new Error("Failed to save decision")
|
|
10181
|
-
}
|
|
10182
|
-
footer.classList.add("hidden")
|
|
10183
|
-
} catch (_) {
|
|
10184
|
-
Swal.fire({ icon: "error", title: "Error", text: "Could not save decision" })
|
|
10185
|
-
setPublishLoading(false, publishOriginal)
|
|
10186
|
-
}
|
|
10187
|
-
}
|
|
10188
|
-
|
|
10189
9942
|
const handleDismiss = (event) => {
|
|
10190
9943
|
event.preventDefault()
|
|
10191
9944
|
setDismissedState(true)
|
|
@@ -10194,8 +9947,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
10194
9947
|
}
|
|
10195
9948
|
|
|
10196
9949
|
saveBtn.addEventListener("click", handleSave)
|
|
10197
|
-
if (registryEnabled && publishBtn) publishBtn.addEventListener("click", handlePublish)
|
|
10198
|
-
if (registryEnabled && laterBtn) laterBtn.addEventListener("click", handleLater)
|
|
10199
9950
|
if (dismissBtn) dismissBtn.addEventListener("click", handleDismiss)
|
|
10200
9951
|
})
|
|
10201
9952
|
</script>
|
|
@@ -403,12 +403,10 @@ body.dark .backup-loading .backup-loading-text {
|
|
|
403
403
|
window.backupItems = <%- JSON.stringify(items || []) %>;
|
|
404
404
|
window.backupAutoInstall = <%- JSON.stringify(autoInstall || null) %>;
|
|
405
405
|
window.backupImportError = <%- JSON.stringify(importError || null) %>;
|
|
406
|
-
window.registryEnabled = <%- JSON.stringify(!!registryEnabled) %>;
|
|
407
406
|
document.addEventListener("DOMContentLoaded", () => {
|
|
408
407
|
const items = Array.isArray(window.backupItems) ? window.backupItems : []
|
|
409
408
|
const autoInstall = window.backupAutoInstall && typeof window.backupAutoInstall === "object" ? window.backupAutoInstall : null
|
|
410
409
|
const importError = typeof window.backupImportError === "string" && window.backupImportError.trim() ? window.backupImportError.trim() : null
|
|
411
|
-
const registryEnabled = window.registryEnabled === true
|
|
412
410
|
|
|
413
411
|
const escapeHtml = (value) => {
|
|
414
412
|
const str = value == null ? '' : String(value)
|
|
@@ -508,12 +506,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
508
506
|
metaParts.push(`${repos.length} repo${repos.length === 1 ? '' : 's'}`)
|
|
509
507
|
if (when) metaParts.push(when)
|
|
510
508
|
const metaText = metaParts.join(" · ")
|
|
511
|
-
const
|
|
512
|
-
const publishMarkup = registryEnabled
|
|
513
|
-
? (syncStatus === "published"
|
|
514
|
-
? `<span class="badge">Cloud saved</span>`
|
|
515
|
-
: `<button type="button" class="url-modal-button confirm snapshot-publish" data-snapshot-id="${snap.id}">Save to Cloud</button>`)
|
|
516
|
-
: ""
|
|
509
|
+
const publishMarkup = ""
|
|
517
510
|
const installed = Array.isArray(installedNames) ? installedNames : []
|
|
518
511
|
const installedMarkup = installed.length
|
|
519
512
|
? `<div class="installed-list"><span>Installed:</span> ${installed.map((name) => `<a href="/p/${encodeURIComponent(name)}" class="installed-link">/p/${escapeHtml(name)}</a>`).join(", ")}</div>`
|
|
@@ -594,8 +587,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
594
587
|
const closeBtn = container.querySelector('.url-modal-close')
|
|
595
588
|
const installButtons = Array.from(container.querySelectorAll(".snapshot-install"))
|
|
596
589
|
const deleteButtons = Array.from(container.querySelectorAll(".snapshot-delete"))
|
|
597
|
-
const
|
|
598
|
-
const actionButtons = installButtons.concat(deleteButtons, publishButtons)
|
|
590
|
+
const actionButtons = installButtons.concat(deleteButtons)
|
|
599
591
|
const loadingOverlay = container.querySelector("#backup-loading")
|
|
600
592
|
const loadingText = loadingOverlay ? loadingOverlay.querySelector(".backup-loading-text") : null
|
|
601
593
|
const defaultLoadingText = loadingText ? loadingText.textContent : ""
|
|
@@ -881,157 +873,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
881
873
|
}
|
|
882
874
|
}, 0)
|
|
883
875
|
|
|
884
|
-
const waitForRegistryLink = async () => {
|
|
885
|
-
const startedAt = Date.now()
|
|
886
|
-
while (Date.now() - startedAt < 120000) {
|
|
887
|
-
try {
|
|
888
|
-
const s = await fetch("/api/registry/status", { method: "GET", headers: { "Accept": "application/json" } })
|
|
889
|
-
if (s.ok) {
|
|
890
|
-
const data = await s.json()
|
|
891
|
-
if (data && data.linked) return true
|
|
892
|
-
}
|
|
893
|
-
} catch (_) {}
|
|
894
|
-
await new Promise((r) => setTimeout(r, 2000))
|
|
895
|
-
}
|
|
896
|
-
return false
|
|
897
|
-
}
|
|
898
|
-
const publishSnapshot = async (snapshotId, allowConnect = true) => {
|
|
899
|
-
const btn = publishButtons.find((b) => b.dataset.snapshotId === String(snapshotId))
|
|
900
|
-
const original = btn ? btn.textContent : "Save to Cloud"
|
|
901
|
-
if (btn) {
|
|
902
|
-
btn.disabled = true
|
|
903
|
-
btn.textContent = "Saving..."
|
|
904
|
-
}
|
|
905
|
-
try {
|
|
906
|
-
const qs = new URLSearchParams()
|
|
907
|
-
qs.set("snapshotId", String(snapshotId))
|
|
908
|
-
const res = await fetch(`/checkpoints/publish?${qs.toString()}`, { method: "POST", headers: { "Accept": "application/json" } })
|
|
909
|
-
const payload = res && res.ok ? await res.json() : null
|
|
910
|
-
if (payload && payload.publish && payload.publish.ok) {
|
|
911
|
-
const publishUrl = payload && payload.publish && payload.publish.url ? String(payload.publish.url) : ""
|
|
912
|
-
const linkHtml = publishUrl
|
|
913
|
-
? `<a href="${escapeHtml(publishUrl)}" target="_blank" rel="noopener">View published checkpoint</a>`
|
|
914
|
-
: ""
|
|
915
|
-
Swal.fire({
|
|
916
|
-
icon: "success",
|
|
917
|
-
title: "Saved to Cloud",
|
|
918
|
-
html: linkHtml || undefined,
|
|
919
|
-
showConfirmButton: true,
|
|
920
|
-
confirmButtonText: "Close"
|
|
921
|
-
}).then(() => {
|
|
922
|
-
window.location.reload()
|
|
923
|
-
})
|
|
924
|
-
return
|
|
925
|
-
}
|
|
926
|
-
if (allowConnect && payload && payload.publish && payload.publish.code === "not_linked") {
|
|
927
|
-
const suggestedConnectUrl = payload.publish.connectUrl ? String(payload.publish.connectUrl) : ""
|
|
928
|
-
const connectUrl = await new Promise((resolve) => {
|
|
929
|
-
let settled = false
|
|
930
|
-
const cleanup = (value) => {
|
|
931
|
-
if (settled) return
|
|
932
|
-
settled = true
|
|
933
|
-
try { Swal.close() } catch (_) {}
|
|
934
|
-
resolve(value)
|
|
935
|
-
}
|
|
936
|
-
const html = `
|
|
937
|
-
<div class="backup-modal-wrapper">
|
|
938
|
-
<div class="url-modal-content backup-modal-content" role="dialog" aria-modal="true" aria-labelledby="registry-connect-title" aria-describedby="registry-connect-description">
|
|
939
|
-
<button type="button" class="url-modal-close" aria-label="Close">×</button>
|
|
940
|
-
<div class="backup-modal-header">
|
|
941
|
-
<h3 id="registry-connect-title">Connect to Registry</h3>
|
|
942
|
-
<p class="backup-modal-description" id="registry-connect-description">Enter your registry connect URL to link Pinokio, then we’ll retry the cloud save.</p>
|
|
943
|
-
</div>
|
|
944
|
-
<div class="backup-modal folder-row" style="margin-top: 0;">
|
|
945
|
-
<label for="registry-connect-url">Registry URL</label>
|
|
946
|
-
<input id="registry-connect-url" class="url-modal-input" type="url" value="${escapeAttr(suggestedConnectUrl)}" placeholder="https://your-registry/connect/pinokio" autocomplete="off" />
|
|
947
|
-
<div class="folder-hint" id="registry-connect-hint"></div>
|
|
948
|
-
</div>
|
|
949
|
-
<div class="url-modal-actions">
|
|
950
|
-
<button type="button" class="url-modal-button cancel" data-action="cancel">Cancel</button>
|
|
951
|
-
<button type="button" class="url-modal-button confirm" data-action="confirm">Open connect page</button>
|
|
952
|
-
</div>
|
|
953
|
-
</div>
|
|
954
|
-
</div>
|
|
955
|
-
`
|
|
956
|
-
Swal.fire({
|
|
957
|
-
html,
|
|
958
|
-
showConfirmButton: false,
|
|
959
|
-
showCancelButton: false,
|
|
960
|
-
allowOutsideClick: true,
|
|
961
|
-
customClass: { popup: 'backup-modal-shell' },
|
|
962
|
-
didOpen: () => {
|
|
963
|
-
const container = Swal.getHtmlContainer()
|
|
964
|
-
if (!container) return
|
|
965
|
-
const input = container.querySelector("#registry-connect-url")
|
|
966
|
-
const hint = container.querySelector("#registry-connect-hint")
|
|
967
|
-
const closeBtn = container.querySelector(".url-modal-close")
|
|
968
|
-
const cancelBtn = container.querySelector('[data-action="cancel"]')
|
|
969
|
-
const confirmBtn = container.querySelector('[data-action="confirm"]')
|
|
970
|
-
const validate = () => {
|
|
971
|
-
const val = input && input.value != null ? String(input.value).trim() : ""
|
|
972
|
-
if (!val) {
|
|
973
|
-
if (hint) hint.textContent = "Registry URL required."
|
|
974
|
-
return null
|
|
975
|
-
}
|
|
976
|
-
if (hint) hint.textContent = ""
|
|
977
|
-
return val
|
|
978
|
-
}
|
|
979
|
-
if (input) {
|
|
980
|
-
try { input.focus() } catch (_) {}
|
|
981
|
-
input.addEventListener("keydown", (e) => {
|
|
982
|
-
if (e.key === "Enter") {
|
|
983
|
-
e.preventDefault()
|
|
984
|
-
const val = validate()
|
|
985
|
-
if (val) cleanup(val)
|
|
986
|
-
}
|
|
987
|
-
})
|
|
988
|
-
}
|
|
989
|
-
if (confirmBtn) {
|
|
990
|
-
confirmBtn.addEventListener("click", () => {
|
|
991
|
-
const val = validate()
|
|
992
|
-
if (val) cleanup(val)
|
|
993
|
-
})
|
|
994
|
-
}
|
|
995
|
-
if (cancelBtn) cancelBtn.addEventListener("click", () => cleanup(null))
|
|
996
|
-
if (closeBtn) closeBtn.addEventListener("click", () => cleanup(null))
|
|
997
|
-
}
|
|
998
|
-
}).then(() => cleanup(null))
|
|
999
|
-
})
|
|
1000
|
-
|
|
1001
|
-
if (!connectUrl) return
|
|
1002
|
-
try {
|
|
1003
|
-
window.open(connectUrl, "pinokio-registry-connect")
|
|
1004
|
-
} catch (_) {
|
|
1005
|
-
window.location.href = connectUrl
|
|
1006
|
-
return
|
|
1007
|
-
}
|
|
1008
|
-
const linked = await waitForRegistryLink()
|
|
1009
|
-
if (linked) {
|
|
1010
|
-
await publishSnapshot(snapshotId, false)
|
|
1011
|
-
return
|
|
1012
|
-
}
|
|
1013
|
-
Swal.fire({ icon: "error", title: "Not connected", text: "Could not confirm the registry connection." })
|
|
1014
|
-
return
|
|
1015
|
-
}
|
|
1016
|
-
const msg = payload && payload.publish && payload.publish.error ? payload.publish.error : "Cloud save failed"
|
|
1017
|
-
Swal.fire({ icon: "error", title: "Error", text: msg })
|
|
1018
|
-
} catch (error) {
|
|
1019
|
-
Swal.fire({ icon: "error", title: "Error", text: error && error.message ? error.message : "Cloud save failed" })
|
|
1020
|
-
} finally {
|
|
1021
|
-
if (btn) {
|
|
1022
|
-
btn.disabled = false
|
|
1023
|
-
btn.textContent = original
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
publishButtons.forEach((btn) => {
|
|
1029
|
-
btn.addEventListener("click", () => {
|
|
1030
|
-
const snapshotId = btn.getAttribute("data-snapshot-id") || ''
|
|
1031
|
-
if (snapshotId) publishSnapshot(snapshotId)
|
|
1032
|
-
})
|
|
1033
|
-
})
|
|
1034
|
-
|
|
1035
876
|
const handleCancel = () => Swal.close()
|
|
1036
877
|
closeBtn && closeBtn.addEventListener("click", handleCancel)
|
|
1037
878
|
}
|
|
@@ -1076,11 +917,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
1076
917
|
<i class="fa-solid fa-folder-open"></i> Open checkpoints folder
|
|
1077
918
|
</button>
|
|
1078
919
|
<% } %>
|
|
1079
|
-
<% if (registryEnabled) { %>
|
|
1080
|
-
<a class="btn" href="/checkpoints/registry">
|
|
1081
|
-
<i class="fa-solid fa-link"></i> Registry settings
|
|
1082
|
-
</a>
|
|
1083
|
-
<% } %>
|
|
1084
920
|
</div>
|
|
1085
921
|
<% if (items && items.length > 0) { %>
|
|
1086
922
|
<% items.forEach((item) => { %>
|