package devcontainer import ( "encoding/json" "fmt" "io" "net/http" "path" "strconv" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/services/context" devcontainer_service "code.gitea.io/gitea/services/devcontainer" ) const ( tplGetDevContainerDetails templates.TplName = "repo/devcontainer/details" ) // 获取仓库 Dev Container 详细信息 // GET /{username}/{reponame}/devcontainer func GetDevContainerDetails(ctx *context.Context) { if ctx.Doer == nil { ctx.HTML(http.StatusForbidden, "") return } var err error cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf) log.Info("setting.CustomConf %s", setting.CustomConf) log.Info("cfg.Section().Key().Value() %s", cfg.Section("server").Key("ROOT_URL").Value()) if err != nil { ctx.Flash.Error(err.Error(), true) } ctx.Data["isAdmin"], err = devcontainer_service.IsAdmin(ctx, ctx.Doer, ctx.Repo.Repository.ID) if err != nil { log.Info(err.Error()) ctx.Flash.Error(err.Error(), true) } ctx.Data["HasDevContainer"], err = devcontainer_service.HasDevContainer(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID) if err != nil { log.Info(err.Error()) ctx.Flash.Error(err.Error(), true) } ctx.Data["ValidateDevContainerConfiguration"] = true ctx.Data["HasDevContainerConfiguration"], err = devcontainer_service.HasDevContainerConfiguration(ctx, ctx.Repo) if err != nil { log.Info(err.Error()) ctx.Data["ValidateDevContainerConfiguration"] = false ctx.Flash.Error(err.Error(), true) } if ctx.Data["HasDevContainerConfiguration"] == false { ctx.Data["ValidateDevContainerConfiguration"] = false } ctx.Data["HasDevContainerDockerfile"], err = devcontainer_service.HasDevContainerDockerFile(ctx, ctx.Repo) if err != nil { log.Info(err.Error()) ctx.Flash.Error(err.Error(), true) } if ctx.Data["HasDevContainer"] == true { configurationString, _ := devcontainer_service.GetDevcontainerConfigurationString(ctx, ctx.Repo.Repository) configurationModel, _ := devcontainer_service.UnmarshalDevcontainerConfigContent(configurationString) imageName := configurationModel.Image registry, namespace, repo, tag := devcontainer_service.ParseImageName(imageName) log.Info("%v %v", repo, tag) ctx.Data["RepositoryAddress"] = registry ctx.Data["RepositoryUsername"] = namespace ctx.Data["ImageName"] = "dev-" + ctx.Repo.Repository.Name + ":latest" if cfg.Section("k8s").Key("ENABLE").Value() == "true" { // 获取WebSSH服务端口 webTerminalURL, err := devcontainer_service.GetWebTerminalURL(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID) if err != nil { log.Info(err.Error()) ctx.Flash.Error(err.Error(), true) } else { ctx.Data["WebSSHUrl"] = webTerminalURL } } else { webTerminalContainerName := cfg.Section("devcontainer").Key("WEB_TERMINAL_CONTAINER").Value() isWebTerminalNotFound, err := devcontainer_service.IsContainerNotFound(ctx, webTerminalContainerName) if err != nil { ctx.Flash.Error(err.Error(), true) } var webTerminalStatus string if !isWebTerminalNotFound { webTerminalStatus, err = devcontainer_service.GetDevContainerStatusFromDocker(ctx, webTerminalContainerName) if err != nil { ctx.Flash.Error(err.Error(), true) } } if webTerminalContainerName == "" || isWebTerminalNotFound { ctx.Flash.Error("webTerminal do not exist. creating ....", true) err = devcontainer_service.RegistWebTerminal(ctx) if err != nil { ctx.Flash.Error(err.Error(), true) } ctx.Redirect(path.Join(ctx.Repo.RepoLink, "/devcontainer")) } else if webTerminalStatus != "running" && webTerminalStatus != "restarting" { err = devcontainer_service.DeleteDevContainerByDocker(ctx, webTerminalContainerName) if err != nil { ctx.Flash.Error(err.Error(), true) } err = devcontainer_service.RegistWebTerminal(ctx) if err != nil { ctx.Flash.Error(err.Error(), true) } ctx.Redirect(path.Join(ctx.Repo.RepoLink, "/devcontainer")) } else { rootPort, err := devcontainer_service.GetPortFromURL(cfg.Section("server").Key("ROOT_URL").Value()) if err != nil { ctx.Flash.Error(err.Error(), true) } terminalParams := "user=" + ctx.Doer.Name + "&repo=" + ctx.Repo.Repository.Name + "&repoid=" + strconv.FormatInt(ctx.Repo.Repository.ID, 10) + "&userid=" + strconv.FormatInt(ctx.Doer.ID, 10) + "&domain=" + cfg.Section("server").Key("DOMAIN").Value() + "&port=" + rootPort port, err := devcontainer_service.GetMappedPort(ctx, webTerminalContainerName, "7681") webTerminalURL, err := devcontainer_service.ReplacePortOfUrl(cfg.Section("server").Key("ROOT_URL").Value(), fmt.Sprintf("%d", port)) if err != nil { log.Info(err.Error()) ctx.Flash.Error(err.Error(), true) } ctx.Data["WebSSHUrl"] = webTerminalURL + "?type=docker&" + terminalParams } } terminalURL, err := devcontainer_service.Get_IDE_TerminalURL(ctx, ctx.Doer, ctx.Repo) if err == nil { ctx.Data["VSCodeUrl"] = "vscode" + terminalURL ctx.Data["CursorUrl"] = "cursor" + terminalURL ctx.Data["WindsurfUrl"] = "windsurf" + terminalURL } } // 3. 携带数据渲染页面,返回 ctx.Data["Title"] = ctx.Locale.Tr("repo.dev_container") ctx.Data["PageIsDevContainer"] = true ctx.Data["Repository"] = ctx.Repo.Repository ctx.Data["CreateDevcontainerSettingUrl"] = "/" + ctx.ContextUser.Name + "/" + ctx.Repo.Repository.Name + "/devcontainer/createConfiguration" ctx.Data["EditDevcontainerConfigurationUrl"] = ctx.Repo.RepoLink + "/_edit/" + ctx.Repo.Repository.DefaultBranch + "/.devcontainer/devcontainer.json" ctx.Data["TreeNames"] = []string{".devcontainer", "devcontainer.json"} ctx.Data["TreePaths"] = []string{".devcontainer", ".devcontainer/devcontainer.json"} ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src" ctx.Data["SaveMethods"] = []string{"Container", "DockerFile"} ctx.Data["SaveMethod"] = "Container" ctx.HTML(http.StatusOK, tplGetDevContainerDetails) } func GetDevContainerStatus(ctx *context.Context) { // 设置 CORS 响应头 ctx.Resp.Header().Set("Access-Control-Allow-Origin", "*") ctx.Resp.Header().Set("Access-Control-Allow-Methods", "*") ctx.Resp.Header().Set("Access-Control-Allow-Headers", "*") var userID string if ctx.Doer != nil { userID = fmt.Sprintf("%d", ctx.Doer.ID) } else { query := ctx.Req.URL.Query() userID = query.Get("user") } realTimeStatus, err := devcontainer_service.GetDevContainerStatus(ctx, userID, fmt.Sprintf("%d", ctx.Repo.Repository.ID)) if err != nil { log.Info("%v\n", err) } ctx.JSON(http.StatusOK, map[string]string{"status": realTimeStatus}) } func CreateDevContainerConfiguration(ctx *context.Context) { hasDevContainerConfiguration, err := devcontainer_service.HasDevContainerConfiguration(ctx, ctx.Repo) if err != nil { log.Info(err.Error()) ctx.Flash.Error(err.Error(), true) ctx.Redirect(path.Join(ctx.Repo.RepoLink, "/devcontainer")) return } if hasDevContainerConfiguration { ctx.Flash.Error("Already exist", true) ctx.Redirect(path.Join(ctx.Repo.RepoLink, "/devcontainer")) return } isAdmin, err := devcontainer_service.IsAdmin(ctx, ctx.Doer, ctx.Repo.Repository.ID) if err != nil { log.Info(err.Error()) ctx.Flash.Error(err.Error(), true) ctx.Redirect(path.Join(ctx.Repo.RepoLink, "/devcontainer")) return } if !isAdmin { ctx.Flash.Error("permisson denied", true) ctx.Redirect(path.Join(ctx.Repo.RepoLink, "/devcontainer")) return } err = devcontainer_service.CreateDevcontainerConfiguration(ctx.Repo.Repository, ctx.Doer) if err != nil { log.Info(err.Error()) ctx.Flash.Error(err.Error(), true) ctx.Redirect(path.Join(ctx.Repo.RepoLink, "/devcontainer")) return } ctx.Redirect(path.Join(ctx.Repo.RepoLink, "/devcontainer")) } func CreateDevContainer(ctx *context.Context) { hasDevContainer, err := devcontainer_service.HasDevContainer(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID) if err != nil { log.Info(err.Error()) ctx.Flash.Error(err.Error(), true) return } if hasDevContainer { ctx.Flash.Error("Already exist", true) return } err = devcontainer_service.CreateDevcontainerAPIService(ctx, ctx.Repo.Repository, ctx.Doer, []string{}, true) if err != nil { ctx.Flash.Error(err.Error(), true) } ctx.Redirect(path.Join(ctx.Repo.RepoLink, "/devcontainer")) } func DeleteDevContainer(ctx *context.Context) { hasDevContainer, err := devcontainer_service.HasDevContainer(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID) if err != nil { log.Info(err.Error()) ctx.Flash.Error(err.Error(), true) return } if !hasDevContainer { ctx.Flash.Error("Already Deleted.", true) return } err = devcontainer_service.DeleteDevContainer(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID) if err != nil { ctx.Flash.Error(err.Error(), true) } ctx.Redirect(path.Join(ctx.Repo.RepoLink, "/devcontainer")) } func RestartDevContainer(ctx *context.Context) { hasDevContainer, err := devcontainer_service.HasDevContainer(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID) if err != nil { log.Info(err.Error()) ctx.Flash.Error(err.Error(), true) return } if !hasDevContainer { log.Info(err.Error()) ctx.Flash.Error("Already delete", true) return } err = devcontainer_service.RestartDevContainer(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID) if err != nil { ctx.Flash.Error(err.Error(), true) } ctx.JSON(http.StatusOK, map[string]string{"status": "6"}) } func StopDevContainer(ctx *context.Context) { hasDevContainer, err := devcontainer_service.HasDevContainer(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID) if err != nil { log.Info(err.Error()) ctx.Flash.Error(err.Error(), true) return } if !hasDevContainer { ctx.Flash.Error("Already delete", true) return } err = devcontainer_service.StopDevContainer(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID) if err != nil { ctx.Flash.Error(err.Error(), true) } ctx.JSON(http.StatusOK, map[string]string{"status": "7"}) } func UpdateDevContainer(ctx *context.Context) { hasDevContainer, err := devcontainer_service.HasDevContainer(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID) if err != nil { log.Info(err.Error()) ctx.JSON(http.StatusOK, map[string]string{"message": err.Error()}) return } if !hasDevContainer { ctx.JSON(http.StatusOK, map[string]string{"message": "Already delete"}) return } // 取得参数 body, _ := io.ReadAll(ctx.Req.Body) var updateInfo devcontainer_service.UpdateInfo err = json.Unmarshal(body, &updateInfo) if err != nil { ctx.JSON(http.StatusOK, map[string]string{"message": err.Error()}) return } err = devcontainer_service.UpdateDevContainer(ctx, ctx.Doer, ctx.Repo.Repository, &updateInfo) if err != nil { ctx.JSON(http.StatusOK, map[string]string{"message": err.Error()}) return } ctx.JSON(http.StatusOK, map[string]string{"redirect": ctx.Repo.RepoLink + "/devcontainer", "message": "成功"}) } func GetTerminalCommand(ctx *context.Context) { // 设置 CORS 响应头 ctx.Resp.Header().Set("Access-Control-Allow-Origin", "*") ctx.Resp.Header().Set("Access-Control-Allow-Methods", "*") ctx.Resp.Header().Set("Access-Control-Allow-Headers", "*") query := ctx.Req.URL.Query() cmd, status, err := devcontainer_service.GetTerminalCommand(ctx, query.Get("user"), ctx.Repo.Repository) if err != nil { log.Info(err.Error()) status = "error" } ctx.JSON(http.StatusOK, map[string]string{"command": cmd, "status": status}) } func GetDevContainerOutput(ctx *context.Context) { // 设置 CORS 响应头 ctx.Resp.Header().Set("Access-Control-Allow-Origin", "*") ctx.Resp.Header().Set("Access-Control-Allow-Methods", "*") ctx.Resp.Header().Set("Access-Control-Allow-Headers", "*") output, err := devcontainer_service.GetDevContainerOutput(ctx, ctx.Doer, ctx.Repo.Repository) if err != nil { log.Info(err.Error()) } ctx.JSON(http.StatusOK, output) }