pinokiod 7.5.21 → 7.5.24
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 +20 -8
- package/kernel/plugin_sources.js +8 -0
- package/package.json +1 -1
- package/server/index.js +113 -13
- package/server/lib/plugin_delete.js +89 -0
- package/server/public/common.js +6 -2
- package/server/public/plugin-detail.js +131 -1
- package/server/public/task-launcher.css +221 -17
- package/server/public/universal-launcher.js +2 -1
- package/server/socket.js +59 -99
- package/server/views/app.ejs +141 -20
- package/server/views/plugin_detail.ejs +52 -20
- package/server/views/plugins.ejs +14 -1
- package/server/views/terminal.ejs +11 -5
- package/test/launch-requirements.test.js +27 -0
- package/test/plugin-delete.test.js +143 -0
- package/test/plugin-detail-ui.test.js +269 -0
- package/test/plugin-detail-view.test.js +160 -0
- package/test/plugin-sources.test.js +19 -0
- package/test/terminal-log-paths.test.js +238 -0
- package/test/universal-launcher.smoke.spec.js +50 -0
package/kernel/api/index.js
CHANGED
|
@@ -575,20 +575,32 @@ class Api {
|
|
|
575
575
|
// 1. if the scropt has 'on.stop', run it when stopping
|
|
576
576
|
let requestPath = this.filePath(req.params.uri)
|
|
577
577
|
this.clearResolvedAction(requestPath)
|
|
578
|
-
let
|
|
579
|
-
|
|
578
|
+
let cwd
|
|
579
|
+
let script
|
|
580
|
+
try {
|
|
581
|
+
const resolved = await this.resolveScript(requestPath)
|
|
582
|
+
cwd = resolved.cwd
|
|
583
|
+
script = resolved.script
|
|
584
|
+
} catch (e) {
|
|
585
|
+
if (!e || e.code !== "ENOENT") {
|
|
586
|
+
throw e
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
if (script && script.on) {
|
|
580
590
|
if (script.on.stop) {
|
|
581
591
|
await this.process(script.on.stop)
|
|
582
592
|
}
|
|
583
593
|
}
|
|
584
594
|
// reset modules
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
595
|
+
if (cwd) {
|
|
596
|
+
let modpath = this.resolvePath(cwd, req.params.uri)
|
|
597
|
+
if (this.child_procs[modpath]) {
|
|
598
|
+
let child_procs = Array.from(this.child_procs[modpath])
|
|
599
|
+
for(let proc of child_procs) {
|
|
600
|
+
delete this.mods[proc]
|
|
601
|
+
}
|
|
602
|
+
delete this.child_procs[modpath]
|
|
590
603
|
}
|
|
591
|
-
delete this.child_procs[modpath]
|
|
592
604
|
}
|
|
593
605
|
|
|
594
606
|
|
package/kernel/plugin_sources.js
CHANGED
|
@@ -75,6 +75,13 @@ const normalizePluginPath = (value) => {
|
|
|
75
75
|
return normalized
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
const systemPluginPathForLocalPath = (value) => {
|
|
79
|
+
const normalized = normalizePluginPath(value)
|
|
80
|
+
const localPrefix = "/plugin/"
|
|
81
|
+
if (!normalized.startsWith(localPrefix)) return ""
|
|
82
|
+
return `${SYSTEM_PLUGIN_RUN_PREFIX}/${normalized.slice(localPrefix.length)}`
|
|
83
|
+
}
|
|
84
|
+
|
|
78
85
|
const pluginSelectionMatches = (src, selectedValue) => {
|
|
79
86
|
const selectedPlugin = normalizeSlashes(typeof selectedValue === "string" ? selectedValue.trim() : "")
|
|
80
87
|
if (!selectedPlugin || !src) return false
|
|
@@ -297,6 +304,7 @@ module.exports = {
|
|
|
297
304
|
isSystemPluginPath,
|
|
298
305
|
isLegacyPluginCodePath,
|
|
299
306
|
normalizePluginPath,
|
|
307
|
+
systemPluginPathForLocalPath,
|
|
300
308
|
pluginSelectionMatches,
|
|
301
309
|
resolveRunPath,
|
|
302
310
|
pluginPathToAbsolute,
|
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -78,6 +78,7 @@ const { createTerminalSessionHelpers } = require("./lib/terminal_session_helpers
|
|
|
78
78
|
const { createLauncherInstructionBootstrap } = require("./lib/launcher_instruction_bootstrap")
|
|
79
79
|
const { createTerminalGitResetHandler } = require("./lib/terminal_git_reset")
|
|
80
80
|
const { createDesktopEventRouter } = require("./lib/desktop_event_router")
|
|
81
|
+
const { deletePluginFolder } = require("./lib/plugin_delete")
|
|
81
82
|
const { createInjectRouter, resolveInjectList } = require("./lib/inject_router")
|
|
82
83
|
const { createTaskPackageService } = require("./lib/task_packages")
|
|
83
84
|
const { createTaskWorkspaceLinkService } = require("./lib/task_workspace_links")
|
|
@@ -7203,6 +7204,8 @@ class Server {
|
|
|
7203
7204
|
link: pluginItem?.link || "",
|
|
7204
7205
|
image: pluginItem?.image || null,
|
|
7205
7206
|
icon: pluginItem?.icon || null,
|
|
7207
|
+
source: pluginItem?.source || "",
|
|
7208
|
+
system: pluginItem?.system === true,
|
|
7206
7209
|
default: pluginItem?.default === true,
|
|
7207
7210
|
pluginPath: normalizedPluginPath,
|
|
7208
7211
|
pluginKey: normalizePluginLookupKey(normalizedPluginPath),
|
|
@@ -7335,7 +7338,18 @@ class Server {
|
|
|
7335
7338
|
if (!targetKey) {
|
|
7336
7339
|
return null
|
|
7337
7340
|
}
|
|
7338
|
-
|
|
7341
|
+
const exact = plugins.find((plugin) => plugin && plugin.pluginKey === targetKey)
|
|
7342
|
+
if (exact) {
|
|
7343
|
+
return exact
|
|
7344
|
+
}
|
|
7345
|
+
return findSystemPluginFallbackByPath(plugins, targetPath, targetKey)
|
|
7346
|
+
}
|
|
7347
|
+
const findSystemPluginFallbackByPath = (plugins, targetPath, targetKey = "") => {
|
|
7348
|
+
const systemFallbackKey = normalizePluginLookupKey(PluginSources.systemPluginPathForLocalPath(targetPath))
|
|
7349
|
+
if (!systemFallbackKey || systemFallbackKey === targetKey) {
|
|
7350
|
+
return null
|
|
7351
|
+
}
|
|
7352
|
+
return plugins.find((plugin) => plugin && plugin.system === true && plugin.pluginKey === systemFallbackKey) || null
|
|
7339
7353
|
}
|
|
7340
7354
|
const buildSerializedPluginFromValidation = (validation) => {
|
|
7341
7355
|
const context = validation && validation.context && typeof validation.context === "object"
|
|
@@ -7383,6 +7397,10 @@ class Server {
|
|
|
7383
7397
|
const pluginRoot = path.resolve(this.kernel.path("plugin"))
|
|
7384
7398
|
const managedRoot = path.resolve(pluginRoot, "code")
|
|
7385
7399
|
const normalizedPath = normalizePluginPath(plugin && plugin.pluginPath ? plugin.pluginPath : "")
|
|
7400
|
+
const isSystemPlugin = Boolean(plugin && (plugin.system === true || plugin.source === "system"))
|
|
7401
|
+
const systemPluginPath = PluginSources.isSystemPluginPath(normalizedPath)
|
|
7402
|
+
? normalizedPath
|
|
7403
|
+
: (isSystemPlugin ? PluginSources.systemPluginPathForLocalPath(normalizedPath) : "")
|
|
7386
7404
|
const emptyState = {
|
|
7387
7405
|
ownership: "bundled",
|
|
7388
7406
|
manageable: false,
|
|
@@ -7396,8 +7414,8 @@ class Server {
|
|
|
7396
7414
|
localLabel: "",
|
|
7397
7415
|
managedPrefix: "",
|
|
7398
7416
|
}
|
|
7399
|
-
if (
|
|
7400
|
-
const relativeSystemPath = normalizedPath.replace(/^\/pinokio\/run\/+/, "")
|
|
7417
|
+
if (isSystemPlugin || systemPluginPath) {
|
|
7418
|
+
const relativeSystemPath = (systemPluginPath || normalizedPath).replace(/^\/pinokio\/run\/+/, "")
|
|
7401
7419
|
return {
|
|
7402
7420
|
...emptyState,
|
|
7403
7421
|
ownership: "system",
|
|
@@ -7511,6 +7529,27 @@ class Server {
|
|
|
7511
7529
|
}
|
|
7512
7530
|
}
|
|
7513
7531
|
|
|
7532
|
+
if (localState.ownership === "system") {
|
|
7533
|
+
return {
|
|
7534
|
+
ownership: "system",
|
|
7535
|
+
manageable: false,
|
|
7536
|
+
canOpenFolder: false,
|
|
7537
|
+
dir: "",
|
|
7538
|
+
localLabel: localState.localLabel,
|
|
7539
|
+
remoteUrl: "",
|
|
7540
|
+
remoteWebUrl: "",
|
|
7541
|
+
githubConnected: false,
|
|
7542
|
+
gitInitialized: false,
|
|
7543
|
+
hasCommit: false,
|
|
7544
|
+
changeCount: 0,
|
|
7545
|
+
changes: [],
|
|
7546
|
+
branch: "HEAD",
|
|
7547
|
+
commitUrl: "",
|
|
7548
|
+
createUrl: "",
|
|
7549
|
+
pushUrl: "",
|
|
7550
|
+
}
|
|
7551
|
+
}
|
|
7552
|
+
|
|
7514
7553
|
if (localState.ownership === "managed") {
|
|
7515
7554
|
let changes = []
|
|
7516
7555
|
try {
|
|
@@ -7726,6 +7765,7 @@ class Server {
|
|
|
7726
7765
|
changePreview,
|
|
7727
7766
|
extraChangeCount: Math.max(changes.length - changePreview.length, 0),
|
|
7728
7767
|
canManageSource: ownership === "local",
|
|
7768
|
+
showSidebar: ownership !== "system",
|
|
7729
7769
|
canOpenFolder: Boolean(shareState && shareState.canOpenFolder && shareState.dir),
|
|
7730
7770
|
openFolderPath: shareState && shareState.canOpenFolder ? shareState.dir : "",
|
|
7731
7771
|
isManagedSource: ownership === "managed",
|
|
@@ -7758,17 +7798,29 @@ class Server {
|
|
|
7758
7798
|
res.redirect("/plugins")
|
|
7759
7799
|
return
|
|
7760
7800
|
}
|
|
7761
|
-
|
|
7762
|
-
|
|
7763
|
-
|
|
7764
|
-
|
|
7765
|
-
|
|
7766
|
-
|
|
7767
|
-
|
|
7768
|
-
|
|
7801
|
+
let plugins = null
|
|
7802
|
+
const loadPluginsOnce = async () => {
|
|
7803
|
+
if (!plugins) {
|
|
7804
|
+
plugins = await loadSerializedPlugins()
|
|
7805
|
+
}
|
|
7806
|
+
return plugins
|
|
7807
|
+
}
|
|
7808
|
+
let plugin = null
|
|
7809
|
+
if (PluginSources.systemPluginPathForLocalPath(requestedPath)) {
|
|
7810
|
+
plugin = findPluginByPath(await loadPluginsOnce(), requestedPath)
|
|
7811
|
+
}
|
|
7812
|
+
if (!plugin) {
|
|
7813
|
+
const validation = await contentValidation.validatePluginByPath(requestedPath)
|
|
7814
|
+
if (!validation.valid) {
|
|
7815
|
+
await this.renderInvalidContentPage(req, res, validation, {
|
|
7816
|
+
sidebarSelected: "plugins",
|
|
7817
|
+
backHref: "/plugins",
|
|
7818
|
+
backLabel: "Back to Plugins",
|
|
7819
|
+
})
|
|
7820
|
+
return
|
|
7821
|
+
}
|
|
7822
|
+
plugin = findPluginByPath(await loadPluginsOnce(), requestedPath) || buildSerializedPluginFromValidation(validation)
|
|
7769
7823
|
}
|
|
7770
|
-
const plugins = await loadSerializedPlugins()
|
|
7771
|
-
const plugin = findPluginByPath(plugins, requestedPath) || buildSerializedPluginFromValidation(validation)
|
|
7772
7824
|
if (!plugin) {
|
|
7773
7825
|
res.status(404).send("Plugin not found.")
|
|
7774
7826
|
return
|
|
@@ -7818,6 +7870,54 @@ class Server {
|
|
|
7818
7870
|
share: shareState
|
|
7819
7871
|
})
|
|
7820
7872
|
}))
|
|
7873
|
+
this.app.post("/plugin/delete", ex(async (req, res) => {
|
|
7874
|
+
const requestedPath = typeof req.body.path === "string" ? req.body.path.trim() : ""
|
|
7875
|
+
if (!requestedPath) {
|
|
7876
|
+
res.status(400).json({
|
|
7877
|
+
ok: false,
|
|
7878
|
+
error: "Plugin path is required."
|
|
7879
|
+
})
|
|
7880
|
+
return
|
|
7881
|
+
}
|
|
7882
|
+
try {
|
|
7883
|
+
const plugins = await loadSerializedPlugins()
|
|
7884
|
+
let plugin = findPluginByPath(plugins, requestedPath)
|
|
7885
|
+
if (!plugin) {
|
|
7886
|
+
const validation = await contentValidation.validatePluginByPath(requestedPath)
|
|
7887
|
+
if (!validation.valid) {
|
|
7888
|
+
res.status(404).json({
|
|
7889
|
+
ok: false,
|
|
7890
|
+
error: validation.message || "Plugin not found."
|
|
7891
|
+
})
|
|
7892
|
+
return
|
|
7893
|
+
}
|
|
7894
|
+
plugin = buildSerializedPluginFromValidation(validation)
|
|
7895
|
+
}
|
|
7896
|
+
if (!plugin) {
|
|
7897
|
+
res.status(404).json({
|
|
7898
|
+
ok: false,
|
|
7899
|
+
error: "Plugin not found."
|
|
7900
|
+
})
|
|
7901
|
+
return
|
|
7902
|
+
}
|
|
7903
|
+
|
|
7904
|
+
const target = await deletePluginFolder({ kernel: this.kernel, plugin })
|
|
7905
|
+
res.set("Cache-Control", "no-store")
|
|
7906
|
+
res.json({
|
|
7907
|
+
ok: true,
|
|
7908
|
+
redirect: "/plugins",
|
|
7909
|
+
deleted: {
|
|
7910
|
+
pluginPath: target.pluginPath,
|
|
7911
|
+
localLabel: target.localLabel,
|
|
7912
|
+
}
|
|
7913
|
+
})
|
|
7914
|
+
} catch (error) {
|
|
7915
|
+
res.status(error && error.status ? error.status : 500).json({
|
|
7916
|
+
ok: false,
|
|
7917
|
+
error: error && error.message ? error.message : "Failed to delete plugin."
|
|
7918
|
+
})
|
|
7919
|
+
}
|
|
7920
|
+
}))
|
|
7821
7921
|
this.app.get("/api/plugin/menu", ex(async (req, res) => {
|
|
7822
7922
|
try {
|
|
7823
7923
|
const workspacePath = resolveProjectShellWorkspace(req.query.workspace)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const fs = require("fs")
|
|
2
|
+
const path = require("path")
|
|
3
|
+
const PluginSources = require("../../kernel/plugin_sources")
|
|
4
|
+
|
|
5
|
+
const isPathInsideRoot = (candidatePath, rootPath) => {
|
|
6
|
+
const relative = path.relative(rootPath, candidatePath)
|
|
7
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative))
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const httpError = (status, message) => {
|
|
11
|
+
const error = new Error(message)
|
|
12
|
+
error.status = status
|
|
13
|
+
return error
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const assertFinalRmTargetInsidePluginRoot = async (target) => {
|
|
17
|
+
const pluginRoot = path.resolve(target.pluginRoot)
|
|
18
|
+
const rmPath = path.resolve(target.pluginDir)
|
|
19
|
+
if (!isPathInsideRoot(rmPath, pluginRoot) || rmPath === pluginRoot) {
|
|
20
|
+
throw httpError(403, "Plugin folder is outside the downloaded plugin folder.")
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const [realPluginRoot, realRmPath] = await Promise.all([
|
|
24
|
+
fs.promises.realpath(pluginRoot),
|
|
25
|
+
fs.promises.realpath(rmPath),
|
|
26
|
+
])
|
|
27
|
+
if (!isPathInsideRoot(realRmPath, realPluginRoot) || realRmPath === realPluginRoot) {
|
|
28
|
+
throw httpError(403, "Plugin folder is outside the downloaded plugin folder.")
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const resolvePluginDeleteTarget = ({ kernel, plugin }) => {
|
|
33
|
+
if (!kernel || typeof kernel.path !== "function") {
|
|
34
|
+
throw httpError(500, "Plugin home path is unavailable.")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const normalizedPath = PluginSources.normalizePluginPath(plugin && plugin.pluginPath ? plugin.pluginPath : "")
|
|
38
|
+
if (!normalizedPath) {
|
|
39
|
+
throw httpError(400, "Plugin path is required.")
|
|
40
|
+
}
|
|
41
|
+
if ((plugin && (plugin.system === true || plugin.source === "system")) || PluginSources.isSystemPluginPath(normalizedPath)) {
|
|
42
|
+
throw httpError(403, "Built-in plugins cannot be deleted.")
|
|
43
|
+
}
|
|
44
|
+
if (!normalizedPath.startsWith("/plugin/") || PluginSources.isLegacyPluginCodePath(normalizedPath)) {
|
|
45
|
+
throw httpError(403, "Only downloaded plugin folders can be deleted.")
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const pluginRoot = path.resolve(kernel.path("plugin"))
|
|
49
|
+
const pluginFilePath = path.resolve(kernel.path(normalizedPath.slice(1)))
|
|
50
|
+
if (!isPathInsideRoot(pluginFilePath, pluginRoot)) {
|
|
51
|
+
throw httpError(403, "Plugin path is outside the downloaded plugin folder.")
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const pluginDir = path.dirname(pluginFilePath)
|
|
55
|
+
if (pluginDir === pluginRoot) {
|
|
56
|
+
throw httpError(403, "The plugin root folder cannot be deleted from this page.")
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const relativeDir = path.relative(pluginRoot, pluginDir).split(path.sep).join("/")
|
|
60
|
+
return {
|
|
61
|
+
pluginPath: normalizedPath,
|
|
62
|
+
pluginRoot,
|
|
63
|
+
pluginFilePath,
|
|
64
|
+
pluginDir,
|
|
65
|
+
relativeDir,
|
|
66
|
+
localLabel: relativeDir ? `plugin/${relativeDir}` : "plugin",
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const deletePluginFolder = async ({ kernel, plugin }) => {
|
|
71
|
+
const target = resolvePluginDeleteTarget({ kernel, plugin })
|
|
72
|
+
|
|
73
|
+
const [fileStat, dirStat] = await Promise.all([
|
|
74
|
+
fs.promises.stat(target.pluginFilePath).catch(() => null),
|
|
75
|
+
fs.promises.stat(target.pluginDir).catch(() => null),
|
|
76
|
+
])
|
|
77
|
+
if (!fileStat || !fileStat.isFile() || !dirStat || !dirStat.isDirectory()) {
|
|
78
|
+
throw httpError(404, "Plugin folder was not found.")
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
await assertFinalRmTargetInsidePluginRoot(target)
|
|
82
|
+
await fs.promises.rm(target.pluginDir, { recursive: true, force: true })
|
|
83
|
+
return target
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = {
|
|
87
|
+
deletePluginFolder,
|
|
88
|
+
resolvePluginDeleteTarget,
|
|
89
|
+
}
|
package/server/public/common.js
CHANGED
|
@@ -3699,9 +3699,13 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
3699
3699
|
const type = trigger.dataset && typeof trigger.dataset.universalLauncherOpen === 'string'
|
|
3700
3700
|
? trigger.dataset.universalLauncherOpen.trim()
|
|
3701
3701
|
: '';
|
|
3702
|
-
|
|
3702
|
+
const mode = trigger.dataset && typeof trigger.dataset.universalLauncherMode === 'string'
|
|
3703
|
+
? trigger.dataset.universalLauncherMode.trim()
|
|
3704
|
+
: '';
|
|
3705
|
+
createLauncherDebugLog('universal launcher trigger clicked', { type, mode });
|
|
3703
3706
|
guardUniversalLauncher(api, {
|
|
3704
|
-
type: type || 'create_app'
|
|
3707
|
+
type: type || 'create_app',
|
|
3708
|
+
mode: mode || ''
|
|
3705
3709
|
});
|
|
3706
3710
|
});
|
|
3707
3711
|
});
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
uninstall: "fa-solid fa-trash-can",
|
|
35
35
|
update: "fa-solid fa-rotate-right"
|
|
36
36
|
};
|
|
37
|
+
const EVENT_PANEL_STATUS_EVENT = "pinokio:event-panel-status";
|
|
37
38
|
|
|
38
39
|
const noteEl = document.querySelector("[data-plugin-share-note]");
|
|
39
40
|
const nextTitleEl = document.querySelector("[data-plugin-share-next-title]");
|
|
@@ -43,6 +44,7 @@
|
|
|
43
44
|
const createCancel = document.querySelector("[data-plugin-share-create-cancel]");
|
|
44
45
|
const repoNameInput = document.querySelector("[data-plugin-share-repo-name]");
|
|
45
46
|
const visibilitySelect = document.querySelector("[data-plugin-share-visibility]");
|
|
47
|
+
const staticActions = document.querySelector("[data-plugin-share-static-actions]");
|
|
46
48
|
const remoteLink = document.querySelector("[data-plugin-share-remote-link]");
|
|
47
49
|
const refreshButton = document.querySelector("[data-plugin-share-refresh]");
|
|
48
50
|
const shareCopyEl = document.querySelector("[data-plugin-share-copy]");
|
|
@@ -59,9 +61,11 @@
|
|
|
59
61
|
const aiPermissionClear = document.querySelector("[data-plugin-ai-permission-clear]");
|
|
60
62
|
const aiPermissionPath = document.querySelector("[data-plugin-ai-permission-path]");
|
|
61
63
|
const aiPermissionStatus = document.querySelector("[data-plugin-ai-permission-status]");
|
|
64
|
+
const deleteButton = document.querySelector("[data-plugin-delete]");
|
|
62
65
|
|
|
63
66
|
let refreshInFlight = false;
|
|
64
67
|
let createFormOpen = false;
|
|
68
|
+
let activeActionModal = null;
|
|
65
69
|
const AI_CONSENT_PREFIX = "pinokio:ai-consent:";
|
|
66
70
|
|
|
67
71
|
function readDownloadState() {
|
|
@@ -182,6 +186,72 @@
|
|
|
182
186
|
}
|
|
183
187
|
}
|
|
184
188
|
|
|
189
|
+
async function confirmPluginDelete() {
|
|
190
|
+
if (!deleteButton || !plugin || !plugin.pluginPath) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const pluginTitle = plugin.title || "this plugin";
|
|
195
|
+
const localLabel = share && share.localLabel ? share.localLabel : "this plugin folder";
|
|
196
|
+
let confirmed = false;
|
|
197
|
+
if (window.Swal && typeof Swal.fire === "function") {
|
|
198
|
+
const result = await Swal.fire({
|
|
199
|
+
title: `Delete ${pluginTitle}?`,
|
|
200
|
+
html: `This permanently deletes <code>${escapeHtml(localLabel)}</code>. Only this plugin folder will be removed.`,
|
|
201
|
+
showCancelButton: true,
|
|
202
|
+
confirmButtonText: "Delete plugin",
|
|
203
|
+
cancelButtonText: "Cancel",
|
|
204
|
+
focusCancel: true,
|
|
205
|
+
reverseButtons: true,
|
|
206
|
+
buttonsStyling: false,
|
|
207
|
+
customClass: {
|
|
208
|
+
confirmButton: "task-button danger",
|
|
209
|
+
cancelButton: "task-button"
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
confirmed = !!(result && result.isConfirmed);
|
|
213
|
+
} else {
|
|
214
|
+
confirmed = window.confirm(`Delete ${pluginTitle}? This permanently deletes ${localLabel}.`);
|
|
215
|
+
}
|
|
216
|
+
if (!confirmed) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const originalHtml = deleteButton.innerHTML;
|
|
221
|
+
deleteButton.disabled = true;
|
|
222
|
+
deleteButton.classList.add("is-busy");
|
|
223
|
+
try {
|
|
224
|
+
const response = await fetch("/plugin/delete", {
|
|
225
|
+
method: "POST",
|
|
226
|
+
headers: { "Content-Type": "application/json" },
|
|
227
|
+
body: JSON.stringify({ path: plugin.pluginPath })
|
|
228
|
+
});
|
|
229
|
+
const payload = await response.json().catch(() => ({}));
|
|
230
|
+
if (!response.ok || !payload.ok) {
|
|
231
|
+
throw new Error(payload && payload.error ? payload.error : `HTTP ${response.status}`);
|
|
232
|
+
}
|
|
233
|
+
window.location.href = payload.redirect || "/plugins";
|
|
234
|
+
} catch (error) {
|
|
235
|
+
deleteButton.disabled = false;
|
|
236
|
+
deleteButton.classList.remove("is-busy");
|
|
237
|
+
deleteButton.innerHTML = originalHtml;
|
|
238
|
+
const message = error && error.message ? error.message : "Failed to delete plugin.";
|
|
239
|
+
if (window.Swal && typeof Swal.fire === "function") {
|
|
240
|
+
Swal.fire({
|
|
241
|
+
title: "Delete failed",
|
|
242
|
+
text: message,
|
|
243
|
+
confirmButtonText: "Close",
|
|
244
|
+
buttonsStyling: false,
|
|
245
|
+
customClass: {
|
|
246
|
+
confirmButton: "task-button"
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
} else {
|
|
250
|
+
alert(message);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
185
255
|
function clearNode(node) {
|
|
186
256
|
while (node && node.firstChild) {
|
|
187
257
|
node.removeChild(node.firstChild);
|
|
@@ -233,10 +303,38 @@
|
|
|
233
303
|
if (plugin.defaultCwd) {
|
|
234
304
|
params.set("cwd", plugin.defaultCwd);
|
|
235
305
|
}
|
|
306
|
+
params.set("__pinokio_event_panel", "1");
|
|
236
307
|
params.set("ts", String(Date.now()));
|
|
237
308
|
return `/action/${encodeURIComponent(actionType)}/${encodedPath}?${params.toString()}`;
|
|
238
309
|
}
|
|
239
310
|
|
|
311
|
+
function finishActionModal(modalState) {
|
|
312
|
+
if (!modalState || modalState.completed) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
modalState.completed = true;
|
|
316
|
+
Swal.close();
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function handleActionCompleteMessage(event) {
|
|
320
|
+
const data = event && event.data && typeof event.data === "object" ? event.data : null;
|
|
321
|
+
if (!data || data.e !== EVENT_PANEL_STATUS_EVENT || !activeActionModal) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
if (event.origin !== window.location.origin) {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
const modalState = activeActionModal;
|
|
328
|
+
if (data.success === false) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
const iframe = modalState.iframe;
|
|
332
|
+
if (!iframe || event.source !== iframe.contentWindow) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
finishActionModal(modalState);
|
|
336
|
+
}
|
|
337
|
+
|
|
240
338
|
function showActionModal(actionType) {
|
|
241
339
|
const targetUrl = buildActionUrl(actionType);
|
|
242
340
|
if (!targetUrl) {
|
|
@@ -261,6 +359,11 @@
|
|
|
261
359
|
</div>
|
|
262
360
|
</div>
|
|
263
361
|
`;
|
|
362
|
+
const modalState = {
|
|
363
|
+
iframe: null,
|
|
364
|
+
completed: false
|
|
365
|
+
};
|
|
366
|
+
activeActionModal = modalState;
|
|
264
367
|
|
|
265
368
|
Swal.fire({
|
|
266
369
|
html: modalHtml,
|
|
@@ -278,10 +381,19 @@
|
|
|
278
381
|
didOpen: (popup) => {
|
|
279
382
|
const iframe = popup.querySelector("iframe");
|
|
280
383
|
if (iframe) {
|
|
384
|
+
modalState.iframe = iframe;
|
|
281
385
|
iframe.dataset.forceVisible = "true";
|
|
282
386
|
iframe.classList.remove("hidden");
|
|
283
387
|
iframe.removeAttribute("hidden");
|
|
284
388
|
}
|
|
389
|
+
},
|
|
390
|
+
didClose: () => {
|
|
391
|
+
if (activeActionModal === modalState) {
|
|
392
|
+
activeActionModal = null;
|
|
393
|
+
}
|
|
394
|
+
if (modalState.completed) {
|
|
395
|
+
window.location.reload();
|
|
396
|
+
}
|
|
285
397
|
}
|
|
286
398
|
});
|
|
287
399
|
}
|
|
@@ -761,6 +873,9 @@
|
|
|
761
873
|
}
|
|
762
874
|
|
|
763
875
|
function renderRemoteState() {
|
|
876
|
+
const hasRemote = !!share.remoteUrl;
|
|
877
|
+
const hasRemoteWebUrl = !!share.remoteWebUrl;
|
|
878
|
+
const showStaticRefresh = hasRemote && !!share.githubConnected;
|
|
764
879
|
if (noteEl) {
|
|
765
880
|
if (!share.manageable) {
|
|
766
881
|
noteEl.textContent = "Read only";
|
|
@@ -776,8 +891,14 @@
|
|
|
776
891
|
noteEl.textContent = "Setup needed";
|
|
777
892
|
}
|
|
778
893
|
}
|
|
894
|
+
if (staticActions) {
|
|
895
|
+
staticActions.classList.toggle("task-hidden", !hasRemoteWebUrl && !showStaticRefresh);
|
|
896
|
+
}
|
|
897
|
+
if (refreshButton) {
|
|
898
|
+
refreshButton.classList.toggle("task-hidden", !showStaticRefresh);
|
|
899
|
+
}
|
|
779
900
|
if (remoteLink) {
|
|
780
|
-
if (
|
|
901
|
+
if (hasRemoteWebUrl) {
|
|
781
902
|
remoteLink.href = share.remoteWebUrl;
|
|
782
903
|
remoteLink.classList.remove("task-hidden", "is-disabled");
|
|
783
904
|
remoteLink.removeAttribute("aria-disabled");
|
|
@@ -936,6 +1057,8 @@
|
|
|
936
1057
|
});
|
|
937
1058
|
}
|
|
938
1059
|
|
|
1060
|
+
window.addEventListener("message", handleActionCompleteMessage);
|
|
1061
|
+
|
|
939
1062
|
document.querySelectorAll("[data-plugin-action]").forEach((button) => {
|
|
940
1063
|
button.addEventListener("click", (event) => {
|
|
941
1064
|
event.preventDefault();
|
|
@@ -998,6 +1121,13 @@
|
|
|
998
1121
|
updateAiPermissionStatus();
|
|
999
1122
|
}
|
|
1000
1123
|
|
|
1124
|
+
if (deleteButton) {
|
|
1125
|
+
deleteButton.addEventListener("click", (event) => {
|
|
1126
|
+
event.preventDefault();
|
|
1127
|
+
confirmPluginDelete();
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1001
1131
|
render();
|
|
1002
1132
|
applyDownloadedState();
|
|
1003
1133
|
})();
|