diff --git a/AppImage/components/app-updater-editor.tsx b/AppImage/components/app-updater-editor.tsx new file mode 100644 index 00000000..9637d361 --- /dev/null +++ b/AppImage/components/app-updater-editor.tsx @@ -0,0 +1,148 @@ +"use client" + +import { useId, useState } from "react" +import { Info, ExternalLink, Check, Loader2, Trash2 } from "lucide-react" +import { Button } from "./ui/button" +import { Textarea } from "./ui/textarea" +import { Label } from "./ui/label" +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "./ui/dialog" +import { useT } from "@/lib/i18n/provider" + +export type AppUpdateMethod = "none" | "helper" | "custom" + +export function AppUpdaterEditor({ method, command, helperAvailable, helperSlug, configured, saving, changed, + onMethodChange, onCommandChange, onSave, onCancel, onRemove }: { + method: AppUpdateMethod + command: string + helperAvailable: boolean + helperSlug?: string + configured: boolean + saving: boolean + changed: boolean + onMethodChange: (method: AppUpdateMethod) => void + onCommandChange: (command: string) => void + onSave: () => void + onCancel: () => void + onRemove: () => void +}) { + const t = useT() + const commandId = useId() + const [help, setHelp] = useState<"helper" | "custom" | null>(null) + const scriptUrl = helperSlug && /^[a-z0-9][a-z0-9._-]*$/.test(helperSlug) + ? `https://github.com/community-scripts/ProxmoxVE/blob/main/ct/${helperSlug}.sh` : null + // Keep the editable command readable. Download guards belong to the runner, + // which also protects this literal launcher when saved as a custom command. + const helperCommand = scriptUrl + ? `PHS_SILENT=1 bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${helperSlug}.sh)"` + : null + const valid = method === "helper" ? helperAvailable && !!helperCommand : method === "custom" && !!command.trim() + const displayedCommand = method === "helper" ? (helperCommand || "") : command + const editCommand = (value: string) => { + // Edited launchers belong to the existing custom-command execution path. + // Never leave the method as "helper": saving it would discard the edits. + if (method === "helper") { + if (value.trim() === (helperCommand || "").trim()) return + onMethodChange("custom") + } + onCommandChange(value) + } + const customExamples = [ + { title: "customScriptTitle", description: "customScriptDescription", command: "/opt/my-app/update.sh" }, + { title: "customPackageTitle", description: "customPackageDescription", command: "apt-get update &&\napt-get install -y --only-upgrade my-package" }, + { title: "customBinaryTitle", description: "customBinaryDescription", command: "install -b -m 0755 /tmp/my-app.new /opt/my-app/my-app &&\nsystemctl restart my-app" }, + ] + const onlineExampleCommand = `script=$(mktemp) || exit 1 +trap 'rm -f "$script"' EXIT +curl -fsSL 'https://example.com/my-app/update.sh' -o "$script" && +bash "$script"` + + return ( +
+

{t("vmLxc.updates.updaterChoiceHint")}

+
+ {([...(helperAvailable ? ["helper" as const] : []), "custom" as const]).map((choice) => ( +
+ + +
+ ))} +
+ {helperAvailable &&

{t("vmLxc.updates.helperDetectedChoice")}

} + {method === "helper" && (!helperAvailable || !helperCommand) && ( +

{t("vmLxc.updates.helperUnavailableChoice")}

+ )} + {(method === "helper" || method === "custom") && ( +
+ +