336 lines
12 KiB
Go
336 lines
12 KiB
Go
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)
|
|
}
|