Skip to content

Add Toolbox Installers section to 1Panel GUI#5

Draft
Copilot wants to merge 3 commits intodev-v2from
copilot/add-toolbox-installers-section
Draft

Add Toolbox Installers section to 1Panel GUI#5
Copilot wants to merge 3 commits intodev-v2from
copilot/add-toolbox-installers-section

Conversation

Copy link
Copy Markdown

Copilot AI commented Mar 23, 2026

Adds a one-click installer UI to the Toolbox section for 7 developer tools (Claude Code, Codex CLI, Odoo ERP, Shopify CLI, Contentful CLI, Strapi CMS, React Bricks CMS), backed by the existing ci/install_*.sh scripts.

Backend (agent/)

  • app/api/v2/toolbox_installer.go — Two new BaseApi methods:
    • InstallTool (POST /api/v2/toolbox/installer/install) — resolves tool key → ci/*.sh script path via static map, executes via bash with INSTALL_TOOL env var, returns combined stdout/stderr
    • GetToolInstallStatus (GET /api/v2/toolbox/installer/status/:tool) — runs a per-tool presence check command and returns {installed, version}
    • Tool key validated with ^[a-z0-9-]+$ regex before any map lookup or subprocess exec
  • app/dto/toolbox_installer.goInstallerToolReq, InstallerToolRes, InstallerToolStatus DTOs
  • router/ro_toolbox.go — Two routes added to existing ToolboxRouter

Frontend (frontend/src/)

  • views/toolbox/installers/index.vue — Card grid, one card per tool: installed/not-installed status tag (fetched on mount), Install button with loading state, log drawer showing stdout/stderr after install
  • api/modules/toolbox.tsinstallTool / getToolInstallStatus added to existing toolbox API module
  • routers/modules/toolbox.tsinstallers added as a child route under the existing /toolbox parent; discoverable via existing import.meta.glob
  • views/toolbox/index.vue — "Tool Installers" tab added to the RouterButton nav bar
  • lang/modules/en.ts / zh.tsmenu.toolboxInstallers and toolbox.installers.* keys added in both locales
Original prompt

Overview

Add a new "Toolbox Installers" section to the 1Panel GUI that lets users install the following tools directly from the panel with one click, with live log streaming and status tracking:

  1. claude-code (@anthropic-ai/claude-code)
  2. codex-cli (@openai/codex)
  3. Odoo ERP (Docker-based)
  4. Shopify CLI (@shopify/cli@latest)
  5. Contentful CLI (contentful-cli)
  6. Strapi CMS (create-strapi@latest)
  7. React Bricks CMS (create-reactbricks-app@latest)

Each tool uses the corresponding ci/install_*.sh script already present in the repo (from previous PRs). The UI calls a new backend API that runs the appropriate script via bash and streams logs back.


Architecture

The implementation follows the existing 1Panel patterns closely:

  • Backend (Go, agent): New API handler + service + router entry under /api/v2/toolbox
  • Frontend (Vue 3 + TypeScript): New route at /toolbox/installers with a card grid UI, install/status buttons, and a log drawer

Backend Changes

1. agent/app/api/v2/toolbox.goNEW FILE

package v2

import (
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/gin-gonic/gin"
)

// ToolboxApi handles toolbox installer endpoints.
type ToolboxApi struct{}

type installToolReq struct {
	Tool string `json:"tool" binding:"required"`
}

type installToolRes struct {
	Output string `json:"output"`
	Error  string `json:"error,omitempty"`
}

// scriptMap maps tool keys to their ci/ script filenames.
var scriptMap = map[string]string{
	"claude-code":    "install_ai_tools.sh",
	"codex-cli":      "install_ai_tools.sh",
	"odoo":           "install_odoo.sh",
	"shopify":        "install_shopify.sh",
	"contentful":     "install_contentful.sh",
	"strapi":         "install_strapi.sh",
	"react-bricks":   "install_react_bricks.sh",
}

// getCIDir returns the absolute path to the ci/ directory relative to the binary.
func getCIDir() string {
	exe, err := os.Executable()
	if err != nil {
		return "/opt/1panel/ci"
	}
	return filepath.Join(filepath.Dir(exe), "ci")
}

// InstallTool runs the installer script for the requested tool and returns combined stdout/stderr.
// POST /api/v2/toolbox/install
func (t *ToolboxApi) InstallTool(c *gin.Context) {
	var req installToolReq
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"message": "invalid request: " + err.Error()})
		return
	}

	scriptFile, ok := scriptMap[req.Tool]
	if !ok {
		c.JSON(http.StatusBadRequest, gin.H{"message": "unknown tool: " + req.Tool})
		return
	}

	ciDir := getCIDir()
	scriptPath := filepath.Join(ciDir, scriptFile)

	if _, err := os.Stat(scriptPath); os.IsNotExist(err) {
		c.JSON(http.StatusInternalServerError, gin.H{
			"message": "installer script not found at " + scriptPath,
		})
		return
	}

	if runtime.GOOS == "windows" {
		c.JSON(http.StatusBadRequest, gin.H{"message": "toolbox installers require Linux/macOS"})
		return
	}

	// Pass the tool name as INSTALL_TOOL env var so a shared script can branch if needed.
	cmd := exec.CommandContext(c.Request.Context(), "bash", scriptPath)
	cmd.Env = append(os.Environ(), "INSTALL_TOOL="+req.Tool)

	out, err := cmd.CombinedOutput()
	res := installToolRes{Output: strings.TrimSpace(string(out))}
	if err != nil {
		res.Error = err.Error()
		c.JSON(http.StatusInternalServerError, res)
		return
	}
	c.JSON(http.StatusOK, res)
}

// GetToolStatus checks whether a tool's binary / container is present.
// GET /api/v2/toolbox/status/:tool
func (t *ToolboxApi) GetToolStatus(c *gin.Context) {
	tool := c.Param("tool")

	type statusRes struct {
		Installed bool   `json:"installed"`
		Version   string `json:"version,omitempty"`
	}

	checkCmd := map[string][]string{
		"claude-code":  {"claude", "--version"},
		"codex-cli":    {"codex", "--version"},
		"odoo":         {"docker", "inspect", "--format={{.State.Status}}", "odoo"},
		"shopify":      {"shopify", "version"},
		"contentful":   {"contentful", "--version"},
		"strapi":       {"node", "-e", "require('@strapi/strapi')"},
		"react-bricks": {"node", "-e", "require('react-bricks')"},
	}

	args, ok := checkCmd[tool]
	if !ok {
		c.JSON(http.StatusBadRequest, gin.H{"message": "unknown tool: " + tool})
		return
	}

	out, err := exec.Command(args[0], args[1:]...).CombinedOutput()
	if err != nil {
		c.JSON(http.StatusOK, statusRes{Installed: false})
		return
	}
	c.JSON(http.StatusOK, statusRes{
		Installed: true,
		Version:   strings.TrimSpace(string(out)),
	})
}

2. agent/router/ro_toolbox.goNEW FILE

Register the two new endpoints:

package router

import (
	v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2"
	"github.com/gin-gonic/gin"
)

type ToolboxRouter struct{}

func (t *ToolboxRouter) InitRouter(Router *gin.RouterGroup) {
	toolboxRouter := Router.Group("toolbox")
	api := v2.ToolboxApi{}
	{
		toolboxRouter.POST("/install", api.InstallTool)
		toolboxRouter.GET("/status/:tool", api.GetToolStat...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

*This pull request was created from Copilot chat.*
>

<!-- START COPILOT CODING AGENT TIPS -->
---Quickly spin up Copilot coding agent tasks from anywhere on your macOS or Windows machine with [Raycast](https://gh.io/cca-raycast-docs).

Copilot AI and others added 2 commits March 23, 2026 09:37
Copilot AI changed the title [WIP] Add Toolbox Installers section to 1Panel GUI Add Toolbox Installers section to 1Panel GUI Mar 23, 2026
Copilot AI requested a review from nexusct March 23, 2026 09:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants