Compare commits
6 Commits
devcontain
...
feature-pe
| Author | SHA1 | Date | |
|---|---|---|---|
| 93c7bd5f53 | |||
| 6f4a0ae8c1 | |||
| 3dedd24e49 | |||
| ef6a6b3b02 | |||
| b61f7048df | |||
| 4f195b16e0 |
1
devcontainer_after_init.sh
Normal file
1
devcontainer_after_init.sh
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ls
|
||||||
@@ -2,7 +2,6 @@ package docker
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/tar"
|
"archive/tar"
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
@@ -223,7 +222,7 @@ func PullImageSync(cli *client.Client, dockerHost string, image string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateAndStartContainer(cli *client.Client, opts *CreateDevcontainerOptions) (string, error) {
|
func CreateAndStartContainer(cli *client.Client, opts *CreateDevcontainerOptions) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
// 创建容器配置
|
// 创建容器配置
|
||||||
config := &container.Config{
|
config := &container.Config{
|
||||||
@@ -255,41 +254,16 @@ func CreateAndStartContainer(cli *client.Client, opts *CreateDevcontainerOptions
|
|||||||
resp, err := cli.ContainerCreate(ctx, config, hostConfig, nil, nil, opts.Name)
|
resp, err := cli.ContainerCreate(ctx, config, hostConfig, nil, nil, opts.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Info("fail to create container %v", err)
|
log.Info("fail to create container %v", err)
|
||||||
return "", err
|
return err
|
||||||
}
|
}
|
||||||
// 启动容器
|
// 启动容器
|
||||||
err = cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})
|
err = cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Info("fail to start container %v", err)
|
log.Info("fail to start container %v", err)
|
||||||
return "", err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建 tar 归档文件
|
return nil
|
||||||
var buf bytes.Buffer
|
|
||||||
tw := tar.NewWriter(&buf)
|
|
||||||
defer tw.Close()
|
|
||||||
|
|
||||||
// 添加文件到 tar 归档
|
|
||||||
addFileToTar(tw, "devcontainer_init.sh", opts.InitializeCommand, 0777)
|
|
||||||
addFileToTar(tw, "devcontainer_restart.sh", opts.RestartCommand, 0777)
|
|
||||||
|
|
||||||
//ExecCommandInContainer(&ctx, cli, resp.ID, "touch /home/devcontainer_init.sh && chomd +x /home/devcontainer_init.sh")
|
|
||||||
err = cli.CopyToContainer(ctx, resp.ID, "/home", bytes.NewReader(buf.Bytes()), types.CopyToContainerOptions{})
|
|
||||||
if err != nil {
|
|
||||||
log.Info("%v", err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
// 获取日志流
|
|
||||||
out, _ := cli.ContainerLogs(ctx, resp.ID, types.ContainerLogsOptions{
|
|
||||||
ShowStdout: true,
|
|
||||||
ShowStderr: true,
|
|
||||||
Follow: false,
|
|
||||||
})
|
|
||||||
defer out.Close()
|
|
||||||
// 5. 解析日志
|
|
||||||
var stdoutBuf, stderrBuf bytes.Buffer
|
|
||||||
_, _ = stdcopy.StdCopy(&stdoutBuf, &stderrBuf, out)
|
|
||||||
return stdoutBuf.String() + "\n" + stderrBuf.String(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// addFileToTar 将文件添加到 tar 归档
|
// addFileToTar 将文件添加到 tar 归档
|
||||||
@@ -307,3 +281,53 @@ func addFileToTar(tw *tar.Writer, filename string, content string, mode int64) e
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ImageExists 检查指定镜像是否存在
|
||||||
|
// 返回值:
|
||||||
|
// - bool: 镜像是否存在(true=存在,false=不存在)
|
||||||
|
// - error: 非空表示检查过程中发生错误
|
||||||
|
func ImageExists(imageName string) (bool, error) {
|
||||||
|
// 上下文
|
||||||
|
ctx := context.Background()
|
||||||
|
// 创建 Docker 客户端
|
||||||
|
cli, err := CreateDockerClient(&ctx)
|
||||||
|
if err != nil {
|
||||||
|
return false, err // 其他错误
|
||||||
|
}
|
||||||
|
// 获取镜像信息
|
||||||
|
_, _, err = cli.ImageInspectWithRaw(ctx, imageName)
|
||||||
|
if err != nil {
|
||||||
|
return false, err // 其他错误
|
||||||
|
}
|
||||||
|
return true, nil // 镜像存在
|
||||||
|
}
|
||||||
|
func CheckDirExists(cli *client.Client, containerID, dirPath string) (bool, error) {
|
||||||
|
// 创建 exec 配置
|
||||||
|
execConfig := types.ExecConfig{
|
||||||
|
Cmd: []string{"test", "-d", dirPath}, // 检查目录是否存在
|
||||||
|
AttachStdout: true,
|
||||||
|
AttachStderr: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建 exec 实例
|
||||||
|
execResp, err := cli.ContainerExecCreate(context.Background(), containerID, execConfig)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("创建 exec 实例失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行命令
|
||||||
|
var exitCode int
|
||||||
|
err = cli.ContainerExecStart(context.Background(), execResp.ID, types.ExecStartCheck{})
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("启动 exec 命令失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取命令执行结果
|
||||||
|
resp, err := cli.ContainerExecInspect(context.Background(), execResp.ID)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("获取 exec 结果失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
exitCode = resp.ExitCode
|
||||||
|
return exitCode == 0, nil // 退出码为 0 表示目录存在
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,6 +22,4 @@ type CreateDevcontainerOptions struct {
|
|||||||
PostCreateCommand []string
|
PostCreateCommand []string
|
||||||
Binds []string
|
Binds []string
|
||||||
PortBindings nat.PortMap
|
PortBindings nat.PortMap
|
||||||
InitializeCommand string
|
|
||||||
RestartCommand string
|
|
||||||
}
|
}
|
||||||
|
|||||||
2
public/assets/terminal/app.180babeee880c26cc052.css
Normal file
2
public/assets/terminal/app.180babeee880c26cc052.css
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{border:0;height:0;left:-9999em;margin:0;opacity:0;overflow:hidden;padding:0;position:absolute;resize:none;top:0;white-space:nowrap;width:0;z-index:-5}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;bottom:0;cursor:default;left:0;overflow-y:scroll;position:absolute;right:0;top:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{left:0;position:absolute;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;left:-9999em;line-height:normal;position:absolute;top:0;visibility:hidden}.xterm.enable-mouse-events{cursor:default}.xterm .xterm-cursor-pointer,.xterm.xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{bottom:0;color:transparent;left:0;pointer-events:none;position:absolute;right:0;top:0;z-index:10}.xterm .xterm-accessibility-tree:not(.debug) ::selection{color:transparent}.xterm .xterm-accessibility-tree{user-select:text;white-space:pre}.xterm .live-region{height:1px;left:-9999px;overflow:hidden;position:absolute;width:1px}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{position:absolute;z-index:6}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{pointer-events:none;position:absolute;right:0;top:0;z-index:8}.xterm-decoration-top{position:relative;z-index:2}.modal{align-items:center;display:flex;overflow:hidden;position:fixed;z-index:40}.modal,.modal-background{bottom:0;left:0;right:0;top:0}.modal-background{background-color:rgba(74,74,74,.8);position:absolute}.modal-content{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}.modal-content .box{background-color:#fff;color:#4a4a4a;display:block;padding:1.25rem}.modal-content header{border-bottom:1px solid #ddd;font-weight:700;margin-bottom:10px;padding-bottom:10px;text-align:center}.modal-content .file-input{height:.01em;left:0;outline:none;position:absolute;top:0;width:.01em}.modal-content .file-cta{align-items:center;background-color:#f5f5f5;border-color:#dbdbdb;border-radius:3px;box-shadow:none;color:#6200ee;cursor:pointer;display:inline-flex;font-size:1em;font-weight:500;height:2.25em;justify-content:flex-start;line-height:1.5;outline:none;padding:calc(.375em - 1px) 1em;position:relative;vertical-align:top;white-space:nowrap}@media print,screen and (min-width:769px){.modal-content{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}body,html{height:100%;margin:0;min-height:100%;overflow:hidden}#terminal-container{height:100%;margin:0 auto;padding:0;width:auto}#terminal-container .terminal{height:calc(100% - 10px);padding:5px}
|
||||||
|
/*# sourceMappingURL=app.180babeee880c26cc052.css.map*/
|
||||||
1
public/assets/terminal/app.180babeee880c26cc052.css.map
Normal file
1
public/assets/terminal/app.180babeee880c26cc052.css.map
Normal file
File diff suppressed because one or more lines are too long
3
public/assets/terminal/app.82d3ff4ffb6a27df9ae0.js
Normal file
3
public/assets/terminal/app.82d3ff4ffb6a27df9ae0.js
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
/*! crc32.js (C) 2014-present SheetJS -- http://sheetjs.com */
|
||||||
1
public/assets/terminal/app.82d3ff4ffb6a27df9ae0.js.map
Normal file
1
public/assets/terminal/app.82d3ff4ffb6a27df9ae0.js.map
Normal file
File diff suppressed because one or more lines are too long
BIN
public/assets/terminal/favicon.png
Normal file
BIN
public/assets/terminal/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
1
public/assets/terminal/index.html
Normal file
1
public/assets/terminal/index.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"><meta name="mobile-web-app-capable" content="yes"><title>ttyd - Terminal</title><link inline rel="icon" type="image/png" href="favicon.png"><link inline rel="stylesheet" type="text/css" href="app.180babeee880c26cc052.css"></head><body><script inline type="text/javascript" src="app.82d3ff4ffb6a27df9ae0.js"></script></body></html>
|
||||||
4
public/assets/terminal/inline.html
Normal file
4
public/assets/terminal/inline.html
Normal file
File diff suppressed because one or more lines are too long
@@ -1,16 +1,25 @@
|
|||||||
package devcontainer
|
package devcontainer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
clictx "context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
devcontainer_models "code.gitea.io/gitea/models/devcontainer"
|
devcontainer_models "code.gitea.io/gitea/models/devcontainer"
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
|
docker_module "code.gitea.io/gitea/modules/docker"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
devcontainer_service "code.gitea.io/gitea/services/devcontainer"
|
devcontainer_service "code.gitea.io/gitea/services/devcontainer"
|
||||||
)
|
)
|
||||||
@@ -28,26 +37,7 @@ func GetRepoDevContainerDetails(ctx *context.Context) {
|
|||||||
Actor: ctx.Doer,
|
Actor: ctx.Doer,
|
||||||
Repository: ctx.Repo.Repository,
|
Repository: ctx.Repo.Repository,
|
||||||
}
|
}
|
||||||
var devContainerOutput []devcontainer_models.DevcontainerOutput
|
|
||||||
dbEngine := db.GetEngine(*ctx)
|
|
||||||
|
|
||||||
err := dbEngine.Table("devcontainer_output").
|
|
||||||
Where("user_id = ? AND repo_id = ?", ctx.Doer.ID, ctx.Repo.Repository.ID).
|
|
||||||
Find(&devContainerOutput)
|
|
||||||
ctx.Data["isCreatingDevcontainer"] = false
|
|
||||||
ctx.Data["InitializedContainer"] = true
|
|
||||||
if err == nil && len(devContainerOutput) > 0 {
|
|
||||||
ctx.Data["isCreatingDevcontainer"] = true
|
|
||||||
}
|
|
||||||
var created bool = false
|
|
||||||
for _, item := range devContainerOutput {
|
|
||||||
if item.ListId > 0 && item.Status == "success" {
|
|
||||||
created = true
|
|
||||||
}
|
|
||||||
if item.Status != "success" {
|
|
||||||
ctx.Data["InitializedContainer"] = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ctx.Data["isAdmin"] = false
|
ctx.Data["isAdmin"] = false
|
||||||
if ctx.Doer.IsAdmin {
|
if ctx.Doer.IsAdmin {
|
||||||
ctx.Data["isAdmin"] = true
|
ctx.Data["isAdmin"] = true
|
||||||
@@ -61,37 +51,13 @@ func GetRepoDevContainerDetails(ctx *context.Context) {
|
|||||||
|
|
||||||
//ctx.Repo.RepoLink == ctx.Repo.Repository.Link()
|
//ctx.Repo.RepoLink == ctx.Repo.Repository.Link()
|
||||||
devContainerMetadata, err := devcontainer_service.GetRepoDevcontainerDetails(ctx, opts)
|
devContainerMetadata, err := devcontainer_service.GetRepoDevcontainerDetails(ctx, opts)
|
||||||
hasDevContainer := err == nil && devContainerMetadata.DevContainerId > 0 && created
|
hasDevContainer := err == nil && devContainerMetadata.DevContainerId > 0
|
||||||
ctx.Data["HasDevContainer"] = hasDevContainer
|
ctx.Data["HasDevContainer"] = hasDevContainer
|
||||||
ctx.Data["HasDevContainerSetting"] = false
|
ctx.Data["HasDevContainerSetting"] = false
|
||||||
|
|
||||||
if hasDevContainer {
|
if hasDevContainer {
|
||||||
ctx.Data["DevContainer"] = devContainerMetadata
|
ctx.Data["DevContainer"] = devContainerMetadata
|
||||||
}
|
}
|
||||||
ctx.Data["PullImage"] = false
|
|
||||||
ctx.Data["CreateContainer"] = false
|
|
||||||
ctx.Data["Init"] = false
|
|
||||||
ctx.Data["Running"] = false
|
|
||||||
ctx.Data["RestartOrStop"] = false
|
|
||||||
ctx.Data["Save"] = false
|
|
||||||
switch devContainerMetadata.DevContainerStatus {
|
|
||||||
case 1:
|
|
||||||
ctx.Data["PullImage"] = true
|
|
||||||
case 2:
|
|
||||||
ctx.Data["CreateContainer"] = true
|
|
||||||
case 3:
|
|
||||||
ctx.Data["Init"] = true
|
|
||||||
case 4:
|
|
||||||
ctx.Data["Running"] = true
|
|
||||||
case 5:
|
|
||||||
ctx.Data["Restart"] = true
|
|
||||||
case 6:
|
|
||||||
ctx.Data["Stop"] = true
|
|
||||||
case 7:
|
|
||||||
ctx.Data["Save"] = true
|
|
||||||
default:
|
|
||||||
log.Info("unknown status")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 检查当前仓库的当前分支是否存在有效的 /.devcontainer/devcontainer.json
|
// 2. 检查当前仓库的当前分支是否存在有效的 /.devcontainer/devcontainer.json
|
||||||
isValidRepoDevcontainerJson := isValidRepoDevcontainerJsonFile(ctx)
|
isValidRepoDevcontainerJson := isValidRepoDevcontainerJsonFile(ctx)
|
||||||
@@ -114,11 +80,18 @@ func GetRepoDevContainerDetails(ctx *context.Context) {
|
|||||||
ctx.Data["ImageName"] = "dev-" + ctx.Repo.Repository.Name + ":latest"
|
ctx.Data["ImageName"] = "dev-" + ctx.Repo.Repository.Name + ":latest"
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取WebSSH服务端口
|
ctx.Data["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)
|
||||||
webTerminalURL, err := devcontainer_service.GetWebTerminalURL(ctx, devContainerMetadata.DevContainerName)
|
if setting.Devcontainer.Agent == setting.KUBERNETES || setting.Devcontainer.Agent == "k8s" {
|
||||||
if err == nil {
|
// 获取WebSSH服务端口
|
||||||
ctx.Data["WebSSHUrl"] = webTerminalURL
|
webTerminalURL, err := devcontainer_service.GetWebTerminalURL(ctx, devContainerMetadata.DevContainerName)
|
||||||
|
if err == nil {
|
||||||
|
ctx.Data["WebSSHUrl"] = webTerminalURL
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
ctx.Data["WebSSHUrl"] = "http://localhost:3000/assets/terminal/index.html?type=docker&" + fmt.Sprintf("%v", ctx.Data["TerminalParams"])
|
||||||
}
|
}
|
||||||
|
|
||||||
terminalURL, err := devcontainer_service.Get_IDE_TerminalURL(ctx, &devContainerMetadata)
|
terminalURL, err := devcontainer_service.Get_IDE_TerminalURL(ctx, &devContainerMetadata)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
ctx.Data["VSCodeUrl"] = "vscode" + terminalURL
|
ctx.Data["VSCodeUrl"] = "vscode" + terminalURL
|
||||||
@@ -310,6 +283,8 @@ func DeleteRepoDevContainerForCurrentActor(ctx *context.Context) {
|
|||||||
|
|
||||||
type OutputResponse struct {
|
type OutputResponse struct {
|
||||||
CurrentJob struct {
|
CurrentJob struct {
|
||||||
|
IP string `json:"ip"`
|
||||||
|
Port string `json:"port"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Detail string `json:"detail"`
|
Detail string `json:"detail"`
|
||||||
Steps []*ViewJobStep `json:"steps"`
|
Steps []*ViewJobStep `json:"steps"`
|
||||||
@@ -329,28 +304,74 @@ type ViewStepLogLine struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetContainerOutput(ctx *context.Context) {
|
func GetContainerOutput(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()
|
||||||
|
var paramInfo = ParamInfo{
|
||||||
|
RepoID: query.Get("repo"),
|
||||||
|
UserID: query.Get("user"),
|
||||||
|
}
|
||||||
var devContainerOutput []devcontainer_models.DevcontainerOutput
|
var devContainerOutput []devcontainer_models.DevcontainerOutput
|
||||||
dbEngine := db.GetEngine(*ctx)
|
dbEngine := db.GetEngine(*ctx)
|
||||||
|
|
||||||
err := dbEngine.Table("devcontainer_output").
|
var status string
|
||||||
Where("user_id = ? AND repo_id = ?", ctx.Doer.ID, ctx.Repo.Repository.ID).
|
_, err := db.GetEngine(ctx).
|
||||||
|
Table("devcontainer").
|
||||||
|
Select("devcontainer_status").
|
||||||
|
Where("user_id = ? AND repo_id = ?", paramInfo.UserID, paramInfo.RepoID).
|
||||||
|
Get(&status)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var containerName string
|
||||||
|
_, err = db.GetEngine(ctx).
|
||||||
|
Table("devcontainer").
|
||||||
|
Select("name").
|
||||||
|
Where("user_id = ? AND repo_id = ?", paramInfo.UserID, paramInfo.RepoID).
|
||||||
|
Get(&containerName)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = dbEngine.Table("devcontainer_output").
|
||||||
|
Where("user_id = ? AND repo_id = ?", paramInfo.UserID, paramInfo.RepoID).
|
||||||
Find(&devContainerOutput)
|
Find(&devContainerOutput)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(devContainerOutput) > 0 {
|
if len(devContainerOutput) > 0 {
|
||||||
resp := &OutputResponse{}
|
resp := &OutputResponse{}
|
||||||
resp.CurrentJob.Title = ctx.Repo.Repository.Name + " Devcontainer Info"
|
resp.CurrentJob.Title = ctx.Repo.Repository.Name + " Devcontainer Info"
|
||||||
resp.CurrentJob.Detail = "success"
|
resp.CurrentJob.Detail = status
|
||||||
for _, item := range devContainerOutput {
|
if status == "4" {
|
||||||
if item.Status != "success" {
|
// 获取WebSSH服务端口
|
||||||
resp.CurrentJob.Detail = "running"
|
webTerminalURL, err := devcontainer_service.GetWebTerminalURL(ctx, containerName)
|
||||||
if devContainerOutput[0].Status == "success" && devContainerOutput[1].Status == "success" {
|
if err == nil {
|
||||||
resp.CurrentJob.Detail = "created"
|
log.Info("URL解析失败: %v", err)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// 解析URL
|
||||||
|
u, err := url.Parse(webTerminalURL)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("URL解析失败: %v", err)
|
||||||
|
}
|
||||||
|
// 分离主机和端口
|
||||||
|
terminalHost, terminalPort, err := net.SplitHostPort(u.Host)
|
||||||
|
resp.CurrentJob.IP = terminalHost
|
||||||
|
resp.CurrentJob.Port = terminalPort
|
||||||
|
if err != nil {
|
||||||
|
log.Info("URL解析失败: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, item := range devContainerOutput {
|
||||||
logLines := []ViewStepLogLine{}
|
logLines := []ViewStepLogLine{}
|
||||||
logLines = append(logLines, ViewStepLogLine{
|
logLines = append(logLines, ViewStepLogLine{
|
||||||
Index: 1,
|
Index: 1,
|
||||||
@@ -399,3 +420,221 @@ func StopContainer(ctx *context.Context) {
|
|||||||
|
|
||||||
ctx.JSON(http.StatusOK, map[string]string{})
|
ctx.JSON(http.StatusOK, map[string]string{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ParamInfo struct {
|
||||||
|
RepoID string
|
||||||
|
UserID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCommand(ctx *context.Context) {
|
||||||
|
query := ctx.Req.URL.Query()
|
||||||
|
var commandInfo = ParamInfo{
|
||||||
|
RepoID: query.Get("repo"),
|
||||||
|
UserID: query.Get("user"),
|
||||||
|
}
|
||||||
|
dbEngine := db.GetEngine(*ctx)
|
||||||
|
var status uint16
|
||||||
|
var containerName string
|
||||||
|
var containerID uint16
|
||||||
|
var work_dir string
|
||||||
|
var cmd string
|
||||||
|
var flag uint16
|
||||||
|
//当前状态
|
||||||
|
_, err := dbEngine.
|
||||||
|
Table("devcontainer").
|
||||||
|
Select("devcontainer_status, id, name, devcontainer_work_dir").
|
||||||
|
Where("user_id = ? AND repo_id = ?", commandInfo.UserID, commandInfo.RepoID).
|
||||||
|
Get(&status, &containerID, &containerName, &work_dir)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Error: %v\n", err)
|
||||||
|
}
|
||||||
|
flag = status
|
||||||
|
|
||||||
|
switch status {
|
||||||
|
case 0:
|
||||||
|
if containerID > 0 {
|
||||||
|
flag = 1
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
|
devContainerJson, err := devcontainer_service.GetDevcontainerJsonModel(ctx, ctx.Repo.Repository)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Error : %v\n", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
buffer, err := docker_module.ImageExists(devContainerJson.Image)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Error : %v\n", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if buffer {
|
||||||
|
flag = 2
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
// 创建docker client
|
||||||
|
cliCtx := clictx.Background()
|
||||||
|
cli, err := docker_module.CreateDockerClient(&cliCtx)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
defer cli.Close()
|
||||||
|
containerID, err := docker_module.GetContainerID(cli, containerName)
|
||||||
|
containerStatus, err := docker_module.GetContainerStatus(cli, containerID)
|
||||||
|
if containerStatus == "running" {
|
||||||
|
flag = 3
|
||||||
|
}
|
||||||
|
case 3:
|
||||||
|
// 创建docker client
|
||||||
|
cliCtx := clictx.Background()
|
||||||
|
cli, err := docker_module.CreateDockerClient(&cliCtx)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
defer cli.Close()
|
||||||
|
containerID, err := docker_module.GetContainerID(cli, containerName)
|
||||||
|
dirStatus, err := docker_module.CheckDirExists(cli, containerID, work_dir)
|
||||||
|
if dirStatus {
|
||||||
|
flag = 4
|
||||||
|
}
|
||||||
|
case 4:
|
||||||
|
cmd = "docker exec -it --workdir " + work_dir + " " + containerName + " /bin/bash -c \"echo 'Successfully connected to the container';bash\"\n"
|
||||||
|
default:
|
||||||
|
log.Info("other status")
|
||||||
|
}
|
||||||
|
|
||||||
|
if flag != status {
|
||||||
|
//下一条指令
|
||||||
|
_, err = dbEngine.Table("devcontainer_output").
|
||||||
|
Select("command").
|
||||||
|
Where("user_id = ? AND repo_id = ? AND list_id = ?", commandInfo.UserID, commandInfo.RepoID, flag).
|
||||||
|
Get(&cmd)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Error : %v\n", err)
|
||||||
|
}
|
||||||
|
_, err = dbEngine.Table("devcontainer").
|
||||||
|
Where("user_id = ? AND repo_id = ? ", commandInfo.UserID, commandInfo.RepoID).
|
||||||
|
Update(&devcontainer_models.Devcontainer{DevcontainerStatus: flag})
|
||||||
|
if err != nil {
|
||||||
|
log.Info("err %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 设置 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", "*")
|
||||||
|
ctx.JSON(http.StatusOK, map[string]string{"command": cmd, "status": fmt.Sprint(flag)})
|
||||||
|
}
|
||||||
|
func GetTerminalToken(ctx *context.Context) {
|
||||||
|
query := ctx.Req.URL.Query()
|
||||||
|
var containerName string
|
||||||
|
_, err := db.GetEngine(ctx).
|
||||||
|
Table("devcontainer").
|
||||||
|
Select("name").
|
||||||
|
Where("user_id = ? AND repo_id = ?", query.Get("user"), query.Get("repo")).
|
||||||
|
Get(&containerName)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if containerName == "" {
|
||||||
|
ctx.Error(404, "Service not found")
|
||||||
|
}
|
||||||
|
// 获取WebSSH服务端口
|
||||||
|
webTerminalURL, err := devcontainer_service.GetWebTerminalURL(ctx, containerName)
|
||||||
|
if err == nil {
|
||||||
|
log.Info("URL解析失败: %v", err)
|
||||||
|
}
|
||||||
|
// 解析URL
|
||||||
|
u, err := url.Parse(webTerminalURL)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("URL解析失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 分离主机和端口
|
||||||
|
terminalHost, terminalPort, err := net.SplitHostPort(u.Host)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
log.Info("URL解析失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &http.Client{Timeout: 10 * time.Second}
|
||||||
|
resp, err := client.Get("http://" + terminalHost + ":" + terminalPort + "/token")
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to connect terminal: %v", err)
|
||||||
|
ctx.Error(http.StatusInternalServerError, "Failed to connect terminal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to read response body: %v", err)
|
||||||
|
ctx.Error(http.StatusInternalServerError, "Failed to read response body")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Resp.Header().Set("Content-Type", resp.Header.Get("Content-Type"))
|
||||||
|
ctx.Status(resp.StatusCode)
|
||||||
|
_, _ = ctx.Write(body)
|
||||||
|
|
||||||
|
}
|
||||||
|
func GetContainerStatus(ctx *context.Context) {
|
||||||
|
var status string
|
||||||
|
var id int
|
||||||
|
var containerName string
|
||||||
|
var flag uint16
|
||||||
|
|
||||||
|
log.Info("GetContainerStatus %v 666 %v", ctx.Doer.ID, ctx.Repo.Repository.ID)
|
||||||
|
_, err := db.GetEngine(ctx).
|
||||||
|
Table("devcontainer").
|
||||||
|
Select("devcontainer_status, id, name").
|
||||||
|
Where("user_id = ? AND repo_id = ?", ctx.Doer.ID, ctx.Repo.Repository.ID).
|
||||||
|
Get(&status, &id, &containerName)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Error: %v\n", err)
|
||||||
|
}
|
||||||
|
if id == 0 {
|
||||||
|
status = "-"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch status {
|
||||||
|
case "5":
|
||||||
|
// 创建docker client
|
||||||
|
cliCtx := clictx.Background()
|
||||||
|
cli, err := docker_module.CreateDockerClient(&cliCtx)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
defer cli.Close()
|
||||||
|
containerID, err := docker_module.GetContainerID(cli, containerName)
|
||||||
|
containerStatus, err := docker_module.GetContainerStatus(cli, containerID)
|
||||||
|
if containerStatus == "running" {
|
||||||
|
flag = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
case "6":
|
||||||
|
// 创建docker client
|
||||||
|
cliCtx := clictx.Background()
|
||||||
|
cli, err := docker_module.CreateDockerClient(&cliCtx)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
defer cli.Close()
|
||||||
|
containerID, err := docker_module.GetContainerID(cli, containerName)
|
||||||
|
containerStatus, err := docker_module.GetContainerStatus(cli, containerID)
|
||||||
|
if containerStatus == "exited" {
|
||||||
|
flag = 8
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Info("other status")
|
||||||
|
}
|
||||||
|
if fmt.Sprintf("%d", flag) != status {
|
||||||
|
_, err = db.GetEngine(ctx).Table("devcontainer").
|
||||||
|
Where("user_id = ? AND repo_id = ? ", ctx.Doer.ID, ctx.Repo.Repository.ID).
|
||||||
|
Update(&devcontainer_models.Devcontainer{DevcontainerStatus: flag})
|
||||||
|
if err != nil {
|
||||||
|
log.Info("err %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.JSON(http.StatusOK, map[string]string{"status": status})
|
||||||
|
}
|
||||||
|
|||||||
@@ -474,6 +474,13 @@ func registerRoutes(m *web.Router) {
|
|||||||
// Especially some AJAX requests, we can reduce middleware number to improve performance.
|
// Especially some AJAX requests, we can reduce middleware number to improve performance.
|
||||||
|
|
||||||
m.Get("/", Home)
|
m.Get("/", Home)
|
||||||
|
|
||||||
|
m.Methods("OPTIONS", "/command", func(ctx *context.Context) {
|
||||||
|
ctx.Resp.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
ctx.Resp.Header().Set("Access-Control-Allow-Methods", "GET, POST")
|
||||||
|
ctx.Resp.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
||||||
|
|
||||||
|
})
|
||||||
m.Get("/devstar-home", DevstarHome)
|
m.Get("/devstar-home", DevstarHome)
|
||||||
m.Get("/sitemap.xml", sitemapEnabled, ignExploreSignIn, HomeSitemap)
|
m.Get("/sitemap.xml", sitemapEnabled, ignExploreSignIn, HomeSitemap)
|
||||||
m.Group("/.well-known", func() {
|
m.Group("/.well-known", func() {
|
||||||
@@ -733,7 +740,6 @@ func registerRoutes(m *web.Router) {
|
|||||||
m.Get("", admin.Dashboard)
|
m.Get("", admin.Dashboard)
|
||||||
m.Get("/system_status", admin.SystemStatus)
|
m.Get("/system_status", admin.SystemStatus)
|
||||||
m.Post("", web.Bind(forms.AdminDashboardForm{}), admin.DashboardPost)
|
m.Post("", web.Bind(forms.AdminDashboardForm{}), admin.DashboardPost)
|
||||||
m.Post("/logo", web.Bind(forms.AvatarForm{}), admin.LogoPost)
|
|
||||||
|
|
||||||
m.Get("/self_check", admin.SelfCheck)
|
m.Get("/self_check", admin.SelfCheck)
|
||||||
m.Post("/self_check", admin.SelfCheckPost)
|
m.Post("/self_check", admin.SelfCheckPost)
|
||||||
@@ -1355,14 +1361,17 @@ func registerRoutes(m *web.Router) {
|
|||||||
m.Group("", func() {
|
m.Group("", func() {
|
||||||
m.Combo("").Get(devcontainer_web.GetRepoDevContainerDetails)
|
m.Combo("").Get(devcontainer_web.GetRepoDevContainerDetails)
|
||||||
}, context.RepoRef())
|
}, context.RepoRef())
|
||||||
|
m.Get("/command", devcontainer_web.GetCommand)
|
||||||
|
m.Get("/status", devcontainer_web.GetContainerStatus)
|
||||||
m.Get("/createConfiguration", devcontainer_web.CreateRepoDevContainerConfiguration)
|
m.Get("/createConfiguration", devcontainer_web.CreateRepoDevContainerConfiguration)
|
||||||
m.Get("/create", devcontainer_web.CreateRepoDevContainer, context.RepoMustNotBeArchived()) // 仓库状态非 Archived 才可以创建 DevContainer
|
m.Get("/create", devcontainer_web.CreateRepoDevContainer, context.RepoMustNotBeArchived()) // 仓库状态非 Archived 才可以创建 DevContainer
|
||||||
m.Get("/output", devcontainer_web.GetContainerOutput)
|
m.Get("/output", devcontainer_web.GetContainerOutput)
|
||||||
m.Get("/restart", devcontainer_web.RestartContainer)
|
m.Get("/restart", devcontainer_web.RestartContainer)
|
||||||
m.Get("/stop", devcontainer_web.StopContainer)
|
m.Get("/stop", devcontainer_web.StopContainer)
|
||||||
|
m.Get("/terminalToken", devcontainer_web.GetTerminalToken)
|
||||||
m.Post("/delete", devcontainer_web.DeleteRepoDevContainerForCurrentActor)
|
m.Post("/delete", devcontainer_web.DeleteRepoDevContainerForCurrentActor)
|
||||||
m.Post("/update", devcontainer_web.UpdateRepoDevContainerForCurrentActor)
|
m.Post("/update", devcontainer_web.UpdateRepoDevContainerForCurrentActor)
|
||||||
|
|
||||||
},
|
},
|
||||||
// 进入 Dev Container 编辑页面需要符合条件:
|
// 进入 Dev Container 编辑页面需要符合条件:
|
||||||
// 1. 已登录
|
// 1. 已登录
|
||||||
|
|||||||
@@ -454,25 +454,7 @@ func GetWebTerminalURL(ctx context.Context, devcontainerName string) (string, er
|
|||||||
devcontainerApp.Status.ExtraPortsAssigned)
|
devcontainerApp.Status.ExtraPortsAssigned)
|
||||||
return "", fmt.Errorf("ttyd port (7681) not found for container: %s", devcontainerName)
|
return "", fmt.Errorf("ttyd port (7681) not found for container: %s", devcontainerName)
|
||||||
case setting.DOCKER:
|
case setting.DOCKER:
|
||||||
cli, err := docker.CreateDockerClient(&ctx)
|
return "http://localhost:7681/?", nil
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer cli.Close()
|
|
||||||
containerID, err := docker.GetContainerID(cli, devcontainerName)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
port, err := docker.GetMappedPort(cli, containerID, "7681")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Failed to load custom conf '%s': %v", setting.CustomConf, err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return "http://" + cfg.Section("server").Key("DOMAIN").Value() + ":" + port + "/", nil
|
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf("unknown agent")
|
return "", fmt.Errorf("unknown agent")
|
||||||
}
|
}
|
||||||
@@ -943,7 +925,7 @@ func claimDevcontainerResource(ctx *context.Context, newDevContainer *CreateDevc
|
|||||||
parsedURL.Host = newHost
|
parsedURL.Host = newHost
|
||||||
// 生成git仓库的 URL
|
// 生成git仓库的 URL
|
||||||
newURL := parsedURL.String()
|
newURL := parsedURL.String()
|
||||||
|
newDevContainer.GitRepositoryURL = newURL
|
||||||
// Read the init script from file
|
// Read the init script from file
|
||||||
var initializeScriptContent, restartScriptContent []byte
|
var initializeScriptContent, restartScriptContent []byte
|
||||||
_, err = os.Stat("devcontainer_init.sh")
|
_, err = os.Stat("devcontainer_init.sh")
|
||||||
|
|||||||
@@ -2,11 +2,9 @@ package devcontainer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/tar"
|
"archive/tar"
|
||||||
"bufio"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -16,6 +14,7 @@ import (
|
|||||||
devcontainer_models "code.gitea.io/gitea/models/devcontainer"
|
devcontainer_models "code.gitea.io/gitea/models/devcontainer"
|
||||||
docker_module "code.gitea.io/gitea/modules/docker"
|
docker_module "code.gitea.io/gitea/modules/docker"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
gitea_web_context "code.gitea.io/gitea/services/context"
|
gitea_web_context "code.gitea.io/gitea/services/context"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
@@ -33,12 +32,9 @@ func CreateDevcontainer(ctx *context.Context, newDevContainer *CreateDevcontaine
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer cli.Close()
|
defer cli.Close()
|
||||||
|
// 加入22 集合
|
||||||
// 加入22 7681集合
|
|
||||||
natPort22 := nat.Port("22/tcp")
|
natPort22 := nat.Port("22/tcp")
|
||||||
natPort7681 := nat.Port("7681/tcp")
|
|
||||||
devContainerJSON.ForwardPorts[natPort22] = struct{}{}
|
devContainerJSON.ForwardPorts[natPort22] = struct{}{}
|
||||||
devContainerJSON.ForwardPorts[natPort7681] = struct{}{}
|
|
||||||
// 2. 创建容器
|
// 2. 创建容器
|
||||||
opts := &docker_module.CreateDevcontainerOptions{
|
opts := &docker_module.CreateDevcontainerOptions{
|
||||||
DockerfileContent: newDevContainer.DockerfileContent,
|
DockerfileContent: newDevContainer.DockerfileContent,
|
||||||
@@ -56,8 +52,6 @@ func CreateDevcontainer(ctx *context.Context, newDevContainer *CreateDevcontaine
|
|||||||
ContainerEnv: devContainerJSON.ContainerEnv,
|
ContainerEnv: devContainerJSON.ContainerEnv,
|
||||||
PostCreateCommand: append([]string{"/home/devcontainer_init.sh"}, devContainerJSON.PostCreateCommand...),
|
PostCreateCommand: append([]string{"/home/devcontainer_init.sh"}, devContainerJSON.PostCreateCommand...),
|
||||||
ForwardPorts: devContainerJSON.ForwardPorts,
|
ForwardPorts: devContainerJSON.ForwardPorts,
|
||||||
InitializeCommand: initializeScript,
|
|
||||||
RestartCommand: restartScript,
|
|
||||||
}
|
}
|
||||||
var flag string
|
var flag string
|
||||||
for _, content := range devContainerJSON.RunArgs {
|
for _, content := range devContainerJSON.RunArgs {
|
||||||
@@ -250,261 +244,46 @@ func SaveDevcontainer(ctx *gitea_web_context.Context, opts *UpdateDevcontainerOp
|
|||||||
}
|
}
|
||||||
|
|
||||||
func PullImageAsyncAndStartContainer(ctx *context.Context, cli *client.Client, dockerHost string, opts *docker_module.CreateDevcontainerOptions) error {
|
func PullImageAsyncAndStartContainer(ctx *context.Context, cli *client.Client, dockerHost string, opts *docker_module.CreateDevcontainerOptions) error {
|
||||||
var stdoutScanner, stderrScanner *bufio.Scanner
|
|
||||||
// 创建扫描器来读取输出
|
|
||||||
dbEngine := db.GetEngine(*ctx)
|
dbEngine := db.GetEngine(*ctx)
|
||||||
|
|
||||||
_, err := dbEngine.Table("devcontainer").
|
|
||||||
Where("user_id = ? AND repo_id = ? ", opts.UserId, opts.RepoId).
|
|
||||||
Update(&devcontainer_model.Devcontainer{DevcontainerStatus: 1})
|
|
||||||
if err != nil {
|
|
||||||
log.Info("err %v", err)
|
|
||||||
}
|
|
||||||
if opts.DockerfileContent != "" {
|
|
||||||
|
|
||||||
// 创建构建上下文(包含Dockerfile的tar包)
|
|
||||||
var buf bytes.Buffer
|
|
||||||
tw := tar.NewWriter(&buf)
|
|
||||||
defer tw.Close()
|
|
||||||
// 添加Dockerfile到tar包
|
|
||||||
dockerfile := "Dockerfile"
|
|
||||||
content := []byte(opts.DockerfileContent)
|
|
||||||
header := &tar.Header{
|
|
||||||
Name: dockerfile,
|
|
||||||
Size: int64(len(content)),
|
|
||||||
Mode: 0644,
|
|
||||||
}
|
|
||||||
if err := tw.WriteHeader(header); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if _, err := tw.Write(content); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
// 执行镜像构建
|
|
||||||
opts.Image = fmt.Sprintf("%d", opts.UserId) + "-" + fmt.Sprintf("%d", opts.RepoId) + "-dockerfileimage"
|
|
||||||
buildOptions := types.ImageBuildOptions{
|
|
||||||
Tags: []string{opts.Image}, // 镜像标签
|
|
||||||
}
|
|
||||||
|
|
||||||
buildResponse, err := cli.ImageBuild(
|
|
||||||
context.Background(),
|
|
||||||
&buf,
|
|
||||||
buildOptions,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
log.Info(err.Error())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
stdoutScanner = bufio.NewScanner(buildResponse.Body)
|
|
||||||
} else {
|
|
||||||
script := "docker " + "-H " + dockerHost + " pull " + opts.Image
|
|
||||||
cmd := exec.Command("sh", "-c", script)
|
|
||||||
// 获取标准输出和标准错误输出的管道
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
stderr, err := cmd.StderrPipe()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = cmd.Start()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
stdoutScanner = bufio.NewScanner(stdout)
|
|
||||||
stderrScanner = bufio.NewScanner(stderr)
|
|
||||||
}
|
|
||||||
|
|
||||||
var pullImageOutput = devcontainer_models.DevcontainerOutput{
|
|
||||||
Output: "",
|
|
||||||
ListId: 0,
|
|
||||||
Status: "running",
|
|
||||||
UserId: opts.UserId,
|
|
||||||
RepoId: opts.RepoId,
|
|
||||||
Command: "Pull Image",
|
|
||||||
}
|
|
||||||
if _, err := dbEngine.Table("devcontainer_output").Insert(&pullImageOutput); err != nil {
|
|
||||||
log.Info("Failed to insert record: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = dbEngine.Table("devcontainer_output").
|
|
||||||
Where("user_id = ? AND repo_id = ? ", opts.UserId, opts.RepoId).
|
|
||||||
Get(&pullImageOutput)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("err %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := dbEngine.Table("devcontainer_output").Insert(&devcontainer_models.DevcontainerOutput{
|
if _, err := dbEngine.Table("devcontainer_output").Insert(&devcontainer_models.DevcontainerOutput{
|
||||||
Output: "",
|
Output: "",
|
||||||
Status: "running",
|
ListId: 1,
|
||||||
|
Status: "waitting",
|
||||||
UserId: opts.UserId,
|
UserId: opts.UserId,
|
||||||
RepoId: opts.RepoId,
|
RepoId: opts.RepoId,
|
||||||
Command: "Initialize Workspace",
|
Command: "docker " + "-H " + dockerHost + " pull " + opts.Image + "\n",
|
||||||
ListId: 1,
|
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Info("Failed to insert record: %v", err)
|
log.Info("Failed to insert record: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = dbEngine.Table("devcontainer_output").Insert(&devcontainer_models.DevcontainerOutput{
|
if _, err := dbEngine.Table("devcontainer_output").Insert(&devcontainer_models.DevcontainerOutput{
|
||||||
Output: "",
|
Output: "",
|
||||||
Status: "running",
|
Status: "waitting",
|
||||||
UserId: opts.UserId,
|
UserId: opts.UserId,
|
||||||
RepoId: opts.RepoId,
|
RepoId: opts.RepoId,
|
||||||
Command: "Initialize DevStar",
|
Command: `docker run -d --name ` + opts.Name + ` -p 22 ` + opts.Image + ` /bin/bash -c "tail -f /dev/null"` + "\n",
|
||||||
ListId: 2,
|
ListId: 2,
|
||||||
})
|
}); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Info("Failed to insert record: %v", err)
|
log.Info("Failed to insert record: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf)
|
||||||
if len(opts.PostCreateCommand) > 1 {
|
if err != nil {
|
||||||
_, err := dbEngine.Table("devcontainer_output").Insert(&devcontainer_models.DevcontainerOutput{
|
log.Error("Failed to load custom conf '%s': %v", setting.CustomConf, err)
|
||||||
Output: "",
|
return err
|
||||||
Status: "running",
|
}
|
||||||
UserId: opts.UserId,
|
if _, err := dbEngine.Table("devcontainer_output").Insert(&devcontainer_models.DevcontainerOutput{
|
||||||
RepoId: opts.RepoId,
|
Output: "",
|
||||||
Command: "Run postCreateCommand",
|
Status: "waitting",
|
||||||
ListId: 3,
|
UserId: opts.UserId,
|
||||||
})
|
RepoId: opts.RepoId,
|
||||||
if err != nil {
|
Command: `docker exec ` + opts.Name + ` sh -c "echo \"` + cfg.Section("server").Key("DOMAIN").Value() + ` host.docker.internal\" | tee -a /etc/hosts;apt update;apt install -y git ; git clone ` + opts.GitRepositoryURL + " " + "/data/workspace" + ` "` + "\n",
|
||||||
log.Info("Failed to insert record: %v", err)
|
ListId: 3,
|
||||||
return err
|
}); err != nil {
|
||||||
}
|
log.Info("Failed to insert record: %v", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用 goroutine 来读取标准输出
|
|
||||||
go func() {
|
|
||||||
var output string
|
|
||||||
var cur int = 0
|
|
||||||
for stdoutScanner.Scan() {
|
|
||||||
output += "\n" + stdoutScanner.Text()
|
|
||||||
cur++
|
|
||||||
if cur%10 == 0 {
|
|
||||||
_, err = dbEngine.Table("devcontainer_output").
|
|
||||||
Where("id = ?", pullImageOutput.Id).
|
|
||||||
Update(&devcontainer_models.DevcontainerOutput{
|
|
||||||
Output: output})
|
|
||||||
if err != nil {
|
|
||||||
log.Info("err %v", err)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if stderrScanner != nil {
|
|
||||||
for stderrScanner.Scan() {
|
|
||||||
output += "\n" + stderrScanner.Text()
|
|
||||||
cur++
|
|
||||||
if cur%10 == 0 {
|
|
||||||
_, err = dbEngine.Table("devcontainer_output").
|
|
||||||
Where("id = ?", pullImageOutput.Id).
|
|
||||||
Update(&devcontainer_models.DevcontainerOutput{
|
|
||||||
Output: output})
|
|
||||||
if err != nil {
|
|
||||||
log.Info("err %v", err)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, err = dbEngine.Table("devcontainer_output").
|
|
||||||
Where("id = ?", pullImageOutput.Id).
|
|
||||||
Update(&devcontainer_models.DevcontainerOutput{
|
|
||||||
Output: output})
|
|
||||||
if err != nil {
|
|
||||||
log.Info("err %v", err)
|
|
||||||
|
|
||||||
}
|
|
||||||
dbEngine.Table("devcontainer_output").
|
|
||||||
Where("id = ?", pullImageOutput.Id).
|
|
||||||
Update(&devcontainer_models.DevcontainerOutput{Status: "success"})
|
|
||||||
|
|
||||||
// 创建并启动容器
|
|
||||||
_, err := dbEngine.Table("devcontainer").
|
|
||||||
Where("user_id = ? AND repo_id = ? ", opts.UserId, opts.RepoId).
|
|
||||||
Update(&devcontainer_model.Devcontainer{DevcontainerStatus: 2})
|
|
||||||
if err != nil {
|
|
||||||
log.Info("err %v", err)
|
|
||||||
}
|
|
||||||
output, err = docker_module.CreateAndStartContainer(cli, opts)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("创建或启动容器失败: %v", err)
|
|
||||||
}
|
|
||||||
containerID, err := docker_module.GetContainerID(cli, opts.Name)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("获取容器ID:%v", err)
|
|
||||||
}
|
|
||||||
portInfo, err := docker_module.GetAllMappedPort(cli, containerID)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("创建或启动容器失败:%v", err)
|
|
||||||
}
|
|
||||||
// 存储到数据库
|
|
||||||
if _, err := dbEngine.Table("devcontainer_output").
|
|
||||||
Where("user_id = ? AND repo_id = ? AND list_id = ?", opts.UserId, opts.RepoId, 1).
|
|
||||||
Update(&devcontainer_models.DevcontainerOutput{
|
|
||||||
Output: output + portInfo,
|
|
||||||
Status: "success",
|
|
||||||
}); err != nil {
|
|
||||||
log.Info("Error storing output for command %v: %v\n", opts.CommandList[2], err)
|
|
||||||
}
|
|
||||||
// 创建 exec 实例
|
|
||||||
_, err = dbEngine.Table("devcontainer").
|
|
||||||
Where("user_id = ? AND repo_id = ? ", opts.UserId, opts.RepoId).
|
|
||||||
Update(&devcontainer_model.Devcontainer{DevcontainerStatus: 3})
|
|
||||||
if err != nil {
|
|
||||||
log.Info("err %v", err)
|
|
||||||
}
|
|
||||||
var buffer string = ""
|
|
||||||
var state int = 2
|
|
||||||
for index, cmd := range opts.PostCreateCommand {
|
|
||||||
|
|
||||||
if index == 1 {
|
|
||||||
_, err = dbEngine.Table("devcontainer_output").
|
|
||||||
Where("user_id = ? AND repo_id = ? AND list_id = ?", opts.UserId, opts.RepoId, state).
|
|
||||||
Update(&devcontainer_models.DevcontainerOutput{
|
|
||||||
Status: "success",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Error storing output for command %v: %v\n", cmd, err)
|
|
||||||
}
|
|
||||||
buffer = ""
|
|
||||||
state = 3
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
output, err = docker_module.ExecCommandInContainer(ctx, cli, containerID, cmd)
|
|
||||||
buffer += output
|
|
||||||
if err != nil {
|
|
||||||
log.Info("执行命令失败:%v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := dbEngine.Table("devcontainer_output").
|
|
||||||
Where("user_id = ? AND repo_id = ? AND list_id = ?", opts.UserId, opts.RepoId, state).
|
|
||||||
Update(&devcontainer_models.DevcontainerOutput{
|
|
||||||
Output: buffer,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Error storing output for command %v: %v\n", cmd, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, err = dbEngine.Table("devcontainer_output").
|
|
||||||
Where("user_id = ? AND repo_id = ? AND list_id = ?", opts.UserId, opts.RepoId, state).
|
|
||||||
Update(&devcontainer_models.DevcontainerOutput{
|
|
||||||
Status: "success",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Error storing output for command pull image: %v\n", err)
|
|
||||||
}
|
|
||||||
_, err = dbEngine.Table("devcontainer").
|
|
||||||
Where("user_id = ? AND repo_id = ? ", opts.UserId, opts.RepoId).
|
|
||||||
Update(&devcontainer_model.Devcontainer{DevcontainerStatus: 4})
|
|
||||||
if err != nil {
|
|
||||||
log.Info("err %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func DockerRestartContainer(gitea_ctx *gitea_web_context.Context, opts *RepoDevContainer) error {
|
func DockerRestartContainer(gitea_ctx *gitea_web_context.Context, opts *RepoDevContainer) error {
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ func RegistRunner(ctx context.Context, token string) error {
|
|||||||
Binds: binds,
|
Binds: binds,
|
||||||
ContainerEnv: env,
|
ContainerEnv: env,
|
||||||
}
|
}
|
||||||
_, err = docker_module.CreateAndStartContainer(cli, opts)
|
err = docker_module.CreateAndStartContainer(cli, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("创建并注册Runner失败:%v", err)
|
return fmt.Errorf("创建并注册Runner失败:%v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,10 +37,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
<iframe src="{{.WebSSHUrl}}" width="100%" style="height: 100vh;" frameborder="0">您的浏览器不支持iframe</iframe>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="repo-devcontainer-view" data-outputLink="{{.Repository.Link}}/dev-container/output"></div>
|
<!--<div id="repo-devcontainer-view" data-outputLink="{{.Repository.Link}}/dev-container/output"></div>-->
|
||||||
|
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
@@ -52,37 +52,27 @@
|
|||||||
<div class="ui relaxed list">
|
<div class="ui relaxed list">
|
||||||
|
|
||||||
{{if .HasDevContainer}}
|
{{if .HasDevContainer}}
|
||||||
<div class="item"><a class="delete-button flex-text-inline" data-modal="#delete-repo-devcontainer-of-user-modal" href="#" data-url="{{.Repository.Link}}/dev-container/delete">{{svg "octicon-trash" 14}}{{ctx.Locale.Tr "repo.dev_container_control.delete"}}</a></div>
|
<div style=" display: none;" id="deleteContainer" class="item"><a class="delete-button flex-text-inline" data-modal="#delete-repo-devcontainer-of-user-modal" href="#" data-url="{{.Repository.Link}}/dev-container/delete">{{svg "octicon-trash" 14}}{{ctx.Locale.Tr "repo.dev_container_control.delete"}}</a></div>
|
||||||
{{if .isAdmin}}
|
{{if .isAdmin}}
|
||||||
|
<div style=" display: none;" id="updateContainer" class="item"><a class="delete-button flex-text-inline" style="color:black; " data-modal-id="updatemodal" href="#">{{svg "octicon-database"}}{{ctx.Locale.Tr "repo.dev_container_control.update"}}</a></div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
{{if .Running}}
|
<div style=" display: none;" id="restartContainer" class="item"><button id="restartButton" class="flex-text-inline" style="color:black; " onclick="handleClick(event, '{{.Repository.Link}}/dev-container/restart', document.getElementById('stopButton'))" >{{svg "octicon-terminal" 14 "tw-mr-2"}} Restart Dev Container</button></div>
|
||||||
<div class="item"><a class="delete-button flex-text-inline" style="color:black;" data-modal-id="updatemodal" href="#">{{svg "octicon-database"}}{{ctx.Locale.Tr "repo.dev_container_control.update"}}</a></div>
|
<div style=" display: none;" id="stopContainer" class="item"><button id="stopButton" class="flex-text-inline" style="color:black; " onclick="handleClick(event, '{{.Repository.Link}}/dev-container/stop', document.getElementById('restartButton'))" >{{svg "octicon-terminal" 14 "tw-mr-2"}} Stop Dev Container</button></div>
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
{{if .InitializedContainer}}
|
|
||||||
{{if not .Restart}}
|
|
||||||
{{if not .Stop}}
|
|
||||||
<div class="item"><button id="restartButton" class="flex-text-inline" style="color:black;" onclick="handleClick(event, '{{.Repository.Link}}/dev-container/restart', document.getElementById('stopButton'))" >{{svg "octicon-terminal" 14 "tw-mr-2"}} Restart Dev Container</button></div>
|
|
||||||
<div class="item"><button id="stopButton" class="flex-text-inline" style="color:black;" onclick="handleClick(event, '{{.Repository.Link}}/dev-container/stop', document.getElementById('restartButton'))" >{{svg "octicon-terminal" 14 "tw-mr-2"}} Stop Dev Container</button></div>
|
|
||||||
|
|
||||||
<div class="item"><a class="flex-text-inline" style="color:black;" href="{{.WebSSHUrl}}" target="_blank">{{svg "octicon-code" 14}}open with WebTerminal</a></div>
|
<div style=" display: none;" id="webTerminal" class="item"><a class="flex-text-inline" style="color:black; " href="{{.WebSSHUrl}}" target="_blank">{{svg "octicon-code" 14}}open with WebTerminal</a></div>
|
||||||
<div class="item"><a class="flex-text-inline" style="color:black;" onclick="window.location.href = '{{.VSCodeUrl}}'">{{svg "octicon-code" 14}}open with VSCode</a ></div>
|
<div style=" display: none;" id="vsTerminal" class="item"><a class="flex-text-inline" style="color:black; " onclick="window.location.href = '{{.VSCodeUrl}}'">{{svg "octicon-code" 14}}open with VSCode</a ></div>
|
||||||
<div class="item"><a class="flex-text-inline" style="color:black;" onclick="window.location.href = '{{.CursorUrl}}'">{{svg "octicon-code" 14}}open with Cursor</a ></div>
|
<div style=" display: none;" id="cursorTerminal" class="item"><a class="flex-text-inline" style="color:black; " onclick="window.location.href = '{{.CursorUrl}}'">{{svg "octicon-code" 14}}open with Cursor</a ></div>
|
||||||
<div class="item"><a class="flex-text-inline" style="color:black;" onclick="window.location.href = '{{.WindsurfUrl}}'">{{svg "octicon-code" 14}}open with Windsurf</a ></div>
|
<div style=" display: none;" id="windsurfTerminal" class="item"><a class="flex-text-inline" style="color:black;" onclick="window.location.href = '{{.WindsurfUrl}}'">{{svg "octicon-code" 14}}open with Windsurf</a ></div>
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
{{else if .HasValidDevContainerJSON}}
|
{{else if .HasValidDevContainerJSON}}
|
||||||
<div class="item">
|
<div class="item">
|
||||||
{{if not .isCreatingDevcontainer}}
|
<div>
|
||||||
{{if .canRead}}
|
{{if .canRead}}
|
||||||
<a class="button flex-text-inline" href="{{.Repository.Link}}/dev-container/create">{{svg "octicon-terminal" 14 "tw-mr-2"}} Create Dev Container</a>
|
<a class="button flex-text-inline" href="{{.Repository.Link}}/dev-container/create">{{svg "octicon-terminal" 14 "tw-mr-2"}} Create Dev Container</a>
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="button flex-text-inline" disabled>Permission Denied</div>
|
<div class="button flex-text-inline" disabled>Permission Denied</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{else}}
|
</div>
|
||||||
<div class="button flex-text-inline" disabled>{{svg "octicon-terminal" 14 "tw-mr-2"}} Running</div>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if not .HasValidDevContainerJSON}}
|
{{if not .HasValidDevContainerJSON}}
|
||||||
@@ -229,5 +219,145 @@ function handleClick(event, targetLink, other) {
|
|||||||
location.reload();
|
location.reload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
var status = '-'
|
||||||
|
var intervalID
|
||||||
|
const deleteContainer = document.getElementById('deleteContainer');
|
||||||
|
const updateContainer = document.getElementById('updateContainer');
|
||||||
|
const restartContainer = document.getElementById('restartContainer');
|
||||||
|
const stopContainer = document.getElementById('stopContainer');
|
||||||
|
const webTerminal = document.getElementById('webTerminal');
|
||||||
|
const vsTerminal = document.getElementById('vsTerminal');
|
||||||
|
const cursorTerminal = document.getElementById('cursorTerminal');
|
||||||
|
const windsurfTerminal = document.getElementById('windsurfTerminal');
|
||||||
|
|
||||||
|
function concealElement(){
|
||||||
|
if (deleteContainer){
|
||||||
|
deleteContainer.style.display = 'none';
|
||||||
|
}
|
||||||
|
if (updateContainer) {
|
||||||
|
updateContainer.style.display = 'none';
|
||||||
|
}
|
||||||
|
if (restartContainer) {
|
||||||
|
restartContainer.style.display = 'none';
|
||||||
|
}
|
||||||
|
if (stopContainer) {
|
||||||
|
stopContainer.style.display = 'none';
|
||||||
|
}
|
||||||
|
if (webTerminal) {
|
||||||
|
webTerminal.style.display = 'none';
|
||||||
|
}
|
||||||
|
if (vsTerminal) {
|
||||||
|
vsTerminal.style.display = 'none';
|
||||||
|
}
|
||||||
|
if (cursorTerminal) {
|
||||||
|
cursorTerminal.style.display = 'none';
|
||||||
|
}
|
||||||
|
if (windsurfTerminal) {
|
||||||
|
windsurfTerminal.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function displayElement(){
|
||||||
|
if (deleteContainer){
|
||||||
|
deleteContainer.style.display = 'block';
|
||||||
|
}
|
||||||
|
if (updateContainer) {
|
||||||
|
updateContainer.style.display = 'block';
|
||||||
|
}
|
||||||
|
if (restartContainer) {
|
||||||
|
restartContainer.style.display = 'block';
|
||||||
|
}
|
||||||
|
if (stopContainer) {
|
||||||
|
stopContainer.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (webTerminal) {
|
||||||
|
webTerminal.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vsTerminal) {
|
||||||
|
vsTerminal.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursorTerminal) {
|
||||||
|
cursorTerminal.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (windsurfTerminal) {
|
||||||
|
windsurfTerminal.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getStatus() {
|
||||||
|
fetch(
|
||||||
|
'{{.Repository.Link}}'+'/dev-container/status'
|
||||||
|
)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
|
||||||
|
if (data.status == '-') {
|
||||||
|
clearInterval(intervalID);
|
||||||
|
} else if (data.status == '0' || data.status == '1' || data.status == '2') {
|
||||||
|
concealElement();
|
||||||
|
}else if (data.status == '3') {
|
||||||
|
concealElement();
|
||||||
|
if (deleteContainer){
|
||||||
|
deleteContainer.style.display = 'block';
|
||||||
|
}
|
||||||
|
}else if (data.status == '4') {
|
||||||
|
displayElement();
|
||||||
|
clearInterval(intervalID);
|
||||||
|
}else if (data.status == '5') {
|
||||||
|
concealElement();
|
||||||
|
if (deleteContainer){
|
||||||
|
deleteContainer.style.display = 'block';
|
||||||
|
}
|
||||||
|
if (updateContainer) {
|
||||||
|
updateContainer.style.display = 'block';
|
||||||
|
}
|
||||||
|
}else if (data.status == '6') {
|
||||||
|
concealElement();
|
||||||
|
if (deleteContainer){
|
||||||
|
deleteContainer.style.display = 'block';
|
||||||
|
}
|
||||||
|
if (updateContainer) {
|
||||||
|
updateContainer.style.display = 'block';
|
||||||
|
}
|
||||||
|
}else if (data.status == '7') {
|
||||||
|
concealElement();
|
||||||
|
}else if (data.status == '8') {
|
||||||
|
concealElement();
|
||||||
|
if (deleteContainer){
|
||||||
|
deleteContainer.style.display = 'block';
|
||||||
|
}
|
||||||
|
if (updateContainer) {
|
||||||
|
updateContainer.style.display = 'block';
|
||||||
|
}
|
||||||
|
if (restartContainer) {
|
||||||
|
restartContainer.style.display = 'block';
|
||||||
|
}
|
||||||
|
clearInterval(intervalID);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
intervalID = setInterval(getStatus, 3000);
|
||||||
|
if (restartContainer) {
|
||||||
|
restartContainer.addEventListener('click', function(event) {
|
||||||
|
// 处理点击逻辑
|
||||||
|
intervalID = setInterval(getStatus, 3000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (stopContainer) {
|
||||||
|
stopContainer.addEventListener('click', function(event) {
|
||||||
|
// 处理点击逻辑
|
||||||
|
intervalID = setInterval(getStatus, 3000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{{template "base/footer" .}}
|
{{template "base/footer" .}}
|
||||||
|
|||||||
Reference in New Issue
Block a user