!74 vscode插件相关功能
Some checks failed
DevStar Studio CI Pipeline - dev branch / build-and-push-x86-64-docker-image (push) Failing after 17m34s

Merge pull request !74 from Levi Yan/feature-vscode-home
This commit is contained in:
孟宁
2025-06-25 03:20:10 +00:00
committed by Gitee
14 changed files with 1441 additions and 997 deletions

View File

@@ -13,7 +13,7 @@ type Devcontainer struct {
Id int64 `xorm:"BIGINT pk NOT NULL autoincr 'id' comment('主键devContainerId')"`
Name string `xorm:"VARCHAR(64) charset=utf8mb4 collate=utf8mb4_bin UNIQUE NOT NULL 'name' comment('devContainer名称自动生成')"`
DevcontainerHost string `xorm:"VARCHAR(256) charset=utf8mb4 collate=utf8mb4_bin NOT NULL 'devcontainer_host' comment('SSH Host')"`
DevcontainerPort uint16 `xorm:"SMALLINT UNSIGNED NOT NULL 'devcontainer_port' comment('SSH Port')"`
DevcontainerStatus uint16 `xorm:"SMALLINT UNSIGNED NOT NULL 'devcontainer_status' comment('SSH Status')"`
DevcontainerUsername string `xorm:"VARCHAR(32) charset=utf8mb4 collate=utf8mb4_bin NOT NULL 'devcontainer_username' comment('SSH Username')"`
DevcontainerWorkDir string `xorm:"VARCHAR(256) charset=utf8mb4 collate=utf8mb4_bin NOT NULL 'devcontainer_work_dir' comment('SSH 工作路径,典型值 ~/${project_name}256字节以内')"`
RepoId int64 `xorm:"BIGINT NOT NULL 'repo_id' comment('repository表主键')"`

View File

@@ -1020,8 +1020,8 @@ visibility.private_tooltip=仅对您已加入的组织的成员可见。
[repo]
dev_container = 开发容器
dev_container_empty = 您还没有该仓库的开发容器配置
dev_container_invalid_config_prompt = 开发容器配置无效:上传有效的 devcontainer.json 至默认分支,且确保仓库未处于存档状态
dev_container_empty = 本仓库没有开发容器配置
dev_container_invalid_config_prompt = 开发容器配置无效:需要上传有效的 devcontainer.json 至默认分支,且确保仓库未处于存档状态
dev_container_control.update = 保存开发容器
dev_container_control.create = 创建开发容器
dev_container_control.creation_success_for_user = 用户 '%s' 已成功创建开发容器

View File

@@ -6,7 +6,7 @@ NAME=DevStar-Studio
IMAGE_REGISTRY_USER=mengning997
IMAGE_NAME=devstar-studio
VERSION=latest # DevStar Studio的默认版本为最新版本
PORT=8080 # 设置端口默认值为 8080
PORT=80 # 设置端口默认值为 80
SSH_PORT=2222 # 设置ssh默认端口号2222
DATA_DIR=${HOME}/devstar_data
APP_INI=${DATA_DIR}/app.ini
@@ -151,7 +151,7 @@ function usage {
success "DevStar usage help:"
success " help, -h, --help, Help information"
success " start Start DevStar Studio"
success " --port=<arg> Specify the port number (default port is 8080)"
success " --port=<arg> Specify the port number (default port is 80)"
success " --ssh-port=<arg> Specify the ssh-port number (default ssh-port is 2222)"
success " --version=<arg> Specify the DevStar Studio Image Version (default verson is latest)"
success " --image=<arg> Specify the DevStar Studio Image example: devstar-studio:latest "

View File

@@ -3,6 +3,7 @@ package devcontainer
import (
"strconv"
"code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/log"
web_module "code.gitea.io/gitea/modules/web"
Result "code.gitea.io/gitea/routers/entity"
@@ -58,6 +59,20 @@ func CreateRepoDevcontainer(ctx *context.Context) {
}
// 4. 调用 API Service 层创建 DevContainer
repo, err := repo.GetRepositoryByID(ctx, repoId)
if err != nil {
errCreateDevcontainer := Result.ResultType{
Code: Result.RespFailedCreateDevcontainer.Code,
Msg: Result.RespFailedCreateDevcontainer.Msg,
Data: map[string]string{
"ErrorMsg": "repo not found",
},
}
errCreateDevcontainer.RespondJson2HttpResponseWriter(ctx.Resp)
return
}
devcontainer_service.CreateDevcontainerJSON(ctx, repo, ctx.Doer)
opts := &devcontainer_service.CreateDevcontainerOptions{
Actor: ctx.Doer,
RepoId: repoId,

View File

@@ -48,6 +48,16 @@ func GetRepoDevContainerDetails(ctx *context.Context) {
ctx.Data["InitializedContainer"] = false
}
}
ctx.Data["isAdmin"] = false
if ctx.Doer.IsAdmin {
ctx.Data["isAdmin"] = true
ctx.Data["canRead"] = true
} else {
canRead, _ := devcontainer_service.CanCreateDevcontainer(ctx, ctx.Repo.Repository.ID, ctx.Doer.ID)
ctx.Data["canRead"] = canRead
isAdmin, _ := devcontainer_service.IsOwner(ctx, ctx.Repo.Repository.ID, ctx.Doer.ID)
ctx.Data["isAdmin"] = isAdmin
}
//ctx.Repo.RepoLink == ctx.Repo.Repository.Link()
devContainerMetadata, err := devcontainer_service.GetRepoDevcontainerDetails(ctx, opts)
@@ -58,12 +68,41 @@ func GetRepoDevContainerDetails(ctx *context.Context) {
if hasDevContainer {
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
isValidRepoDevcontainerJson := isValidRepoDevcontainerJsonFile(ctx)
hasDockerfile := isValidRepoDevcontainerDockerfile(ctx)
if !hasDevContainer && !isValidRepoDevcontainerJson {
ctx.Flash.Error(ctx.Tr("repo.dev_container_invalid_config_prompt"), true)
}
ctx.Data["HasDockerfile"] = false
if hasDockerfile {
ctx.Data["HasDockerfile"] = true
}
// 从devcontainer.json文件提取image字段解析成仓库地址、命名空间、镜像名
devcontainerJson, err := devcontainer_service.GetDevcontainerJsonModel(ctx, ctx.Repo.Repository)
if err == nil {
@@ -112,7 +151,11 @@ func GetRepoDevContainerDetails(ctx *context.Context) {
}
func CreateRepoDevContainerConfiguration(ctx *context.Context) {
devcontainer_service.CreateDevcontainerJSON(ctx)
if !ctx.Doer.IsAdmin {
ctx.Flash.Error("permisson denied", true)
return
}
devcontainer_service.CreateDevcontainerJSON(ctx, ctx.Repo.Repository, ctx.Doer)
ctx.Redirect(path.Join(ctx.Repo.RepoLink, "/dev-container"))
}
func ParseImageName(imageName string) (registry, namespace, repo, tag string) {
@@ -173,6 +216,28 @@ func isValidRepoDevcontainerJsonFile(ctx *context.Context) bool {
return true
}
// 辅助判断当前仓库的当前分支是否存在有效的 /.devcontainer/Dockerfile
func isValidRepoDevcontainerDockerfile(ctx *context.Context) bool {
// 1. 仓库非空,且非 Archived 状态
if ctx.Repo.Repository.IsEmpty || ctx.Repo.Repository.IsArchived {
return false
}
// 2. 当前分支的目录 .devcontainer 下存在 devcontainer.json 文件
dockerfilePath, err := devcontainer_service.GetDockerfilePath(ctx, ctx.Repo.Repository)
if err != nil {
return false
}
dockerfileExists, err := ctx.Repo.FileExists(".devcontainer/"+dockerfilePath, ctx.Repo.BranchName)
if err != nil || !dockerfileExists {
return false
}
// 3. TODO: DevContainer 格式正确
return true
}
// 辅助判断当前用户在当前仓库是否已有 Dev Container
func isUserDevcontainerAlreadyInRepository(ctx *context.Context) bool {
@@ -186,7 +251,10 @@ func isUserDevcontainerAlreadyInRepository(ctx *context.Context) bool {
}
func UpdateRepoDevContainerForCurrentActor(ctx *context.Context) {
if !ctx.Doer.IsAdmin {
ctx.Flash.Error("permisson denied", true)
return
}
opt := &devcontainer_service.RepoDevcontainerOptions{
Actor: ctx.Doer,
Repository: ctx.Repo.Repository,
@@ -224,7 +292,6 @@ func UpdateRepoDevContainerForCurrentActor(ctx *context.Context) {
// 删除仓库 当前用户 Dev Container
func DeleteRepoDevContainerForCurrentActor(ctx *context.Context) {
if isUserDevcontainerAlreadyInRepository(ctx) {
opts := &devcontainer_service.RepoDevcontainerOptions{
Actor: ctx.Doer,
@@ -315,6 +382,7 @@ func RestartContainer(ctx *context.Context) {
if err != nil {
ctx.Flash.Error("fail to restart container")
}
ctx.JSON(http.StatusOK, map[string]string{})
}
func StopContainer(ctx *context.Context) {
@@ -328,5 +396,6 @@ func StopContainer(ctx *context.Context) {
if err != nil {
ctx.Flash.Error("fail to stop container")
}
ctx.JSON(http.StatusOK, map[string]string{})
}

View File

@@ -1353,7 +1353,7 @@ func registerRoutes(m *web.Router) {
m.Group("/{username}/{reponame}/dev-container", func() { // repo Dev Container
m.Group("", func() {
m.Combo("").Get(devcontainer_web.GetRepoDevContainerDetails)
}, reqSignIn, context.RepoAssignment, reqRepoCodeReader, context.RepoRef(), canEnableEditor, context.RepoMustNotBeArchived(), repo.MustBeEditable)
}, context.RepoRef())
m.Get("/createConfiguration", devcontainer_web.CreateRepoDevContainerConfiguration)
m.Get("/create", devcontainer_web.CreateRepoDevContainer, context.RepoMustNotBeArchived()) // 仓库状态非 Archived 才可以创建 DevContainer
@@ -1367,7 +1367,7 @@ func registerRoutes(m *web.Router) {
// 1. 已登录
// 2. repo 信息已加载到 Gitea Web Context (否则无法判定当前repo是否有写入Code权限从而返回无权访问错误码 HTTP 404)
// 3. 具有code写入权限
reqSignIn, context.RepoAssignment, reqRepoCodeWriter,
reqSignIn, context.RepoAssignment, reqRepoCodeReader,
)
m.Group("/{username}/{reponame}", func() { // repo tags

View File

@@ -15,15 +15,17 @@ import (
"code.gitea.io/gitea/models/db"
devcontainer_model "code.gitea.io/gitea/models/devcontainer"
devcontainer_models_errors "code.gitea.io/gitea/models/devcontainer/errors"
"code.gitea.io/gitea/models/perm"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/docker"
devcontainer_k8s_agent_module "code.gitea.io/gitea/modules/k8s"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
gitea_context "code.gitea.io/gitea/services/context"
devcontainer_service_errors "code.gitea.io/gitea/services/devcontainer/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"code.gitea.io/gitea/services/devstar_ssh_key_pair/api_service"
"github.com/google/uuid"
@@ -128,7 +130,7 @@ func GetRepoDevcontainerDetails(ctx context.Context, opts *RepoDevcontainerOptio
"devcontainer.id AS devcontainer_id,"+
"devcontainer.name AS devcontainer_name,"+
"devcontainer.devcontainer_host AS devcontainer_host,"+
"devcontainer.devcontainer_port AS devcontainer_port,"+
"devcontainer.devcontainer_status AS devcontainer_status,"+
"devcontainer.devcontainer_username AS devcontainer_username,"+
"devcontainer.devcontainer_work_dir AS devcontainer_work_dir,"+
"devcontainer.repo_id AS repo_id,"+
@@ -191,6 +193,7 @@ func CreateRepoDevcontainer(ctx context.Context, opts *CreateRepoDevcontainerOpt
DevcontainerHost: cfg.Section("server").Key("DOMAIN").Value(),
DevcontainerUsername: "root",
DevcontainerWorkDir: "/data/workspace",
DevcontainerStatus: 0,
RepoId: opts.Repository.ID,
UserId: opts.Actor.ID,
CreatedUnix: unixTimestamp,
@@ -200,7 +203,20 @@ func CreateRepoDevcontainer(ctx context.Context, opts *CreateRepoDevcontainerOpt
Image: devContainerJson.Image,
GitRepositoryURL: strings.TrimSuffix(setting.AppURL, "/") + opts.Repository.Link(),
}
rowsAffect, err := db.GetEngine(ctx).
Table("devcontainer").
Insert(newDevcontainer.Devcontainer)
if err != nil {
return devcontainer_models_errors.ErrFailedToOperateDevcontainerDB{
Action: fmt.Sprintf("insert new DevContainer for user '%s' in repo '%s'", username, repoName),
Message: err.Error(),
}
} else if rowsAffect == 0 {
return devcontainer_models_errors.ErrFailedToOperateDevcontainerDB{
Action: fmt.Sprintf("insert new DevContainer for user '%s' in repo '%s'", username, repoName),
Message: "expected 1 row to be inserted, but got 0",
}
}
// 在数据库事务中创建 Dev Container 分配资源,出错时自动回滚相对应数据库字段,保证数据一致
dbTransactionErr := db.WithTx(ctx, func(ctx context.Context) error {
var err error
@@ -246,21 +262,6 @@ func CreateRepoDevcontainer(ctx context.Context, opts *CreateRepoDevcontainerOpt
Message: err.Error(),
}
}
// 2. 根据分配的 NodePort 更新数据库字段
rowsAffect, err := db.GetEngine(ctx).
Table("devcontainer").
Insert(newDevcontainer.Devcontainer)
if err != nil {
return devcontainer_models_errors.ErrFailedToOperateDevcontainerDB{
Action: fmt.Sprintf("insert new DevContainer for user '%s' in repo '%s'", username, repoName),
Message: err.Error(),
}
} else if rowsAffect == 0 {
return devcontainer_models_errors.ErrFailedToOperateDevcontainerDB{
Action: fmt.Sprintf("insert new DevContainer for user '%s' in repo '%s'", username, repoName),
Message: "expected 1 row to be inserted, but got 0",
}
}
return nil
})
@@ -436,7 +437,8 @@ func Get_IDE_TerminalURL(ctx *gitea_context.Context, devcontainer *RepoDevContai
}
return "://mengning.devstar/" +
"openProject?host=" + devcontainer.DevContainerHost +
"openProject?host=" + devcontainer.RepoName +
"&hostname=" + devcontainer.DevContainerHost +
"&port=" + port +
"&username=" + devcontainer.DevContainerUsername +
"&path=" + devcontainer.DevContainerWorkDir +
@@ -733,3 +735,40 @@ func StopDevcontainer(gitea_ctx context.Context, opts *RepoDevContainer) error {
}
}
func CanCreateDevcontainer(gitea_ctx context.Context, repoID, userID int64) (bool, error) {
e := db.GetEngine(gitea_ctx)
teamMember, err := e.Table("team_user").
Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id").
Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id").
Where("`team_repo`.repo_id = ? AND (`team_unit`.access_mode >= ? OR (`team_unit`.access_mode = ? AND `team_unit`.`type` = ?))",
repoID, perm.AccessModeWrite, perm.AccessModeRead, unit.TypeCode).
And("team_user.uid = ?", userID).Exist()
if err != nil {
return false, nil
}
if teamMember {
return true, nil
}
return repo_model.IsCollaborator(gitea_ctx, repoID, userID)
}
func IsOwner(gitea_ctx context.Context, repoID, userID int64) (bool, error) {
e := db.GetEngine(gitea_ctx)
teamMember, err := e.Table("team_user").
Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id").
Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id").
Where("`team_repo`.repo_id = ? AND `team_unit`.access_mode = ? ",
repoID, perm.AccessModeAdmin).
And("team_user.uid = ?", userID).Exist()
if err != nil {
return false, nil
}
if teamMember {
return true, nil
}
return e.Get(&repo_model.Collaboration{RepoID: repoID, UserID: userID, Mode: 3})
}

View File

@@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/models/db"
devcontainer_model "code.gitea.io/gitea/models/devcontainer"
"code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
@@ -33,7 +34,7 @@ type DevStarJSON struct {
RunArgs []string
}
func CreateDevcontainerJSON(ctx *gitea_context.Context) {
func CreateDevcontainerJSON(ctx *gitea_context.Context, repo *repo.Repository, doer *user.User) {
jsonString := `{
"image":"mcr.microsoft.com/devcontainers/base:dev-ubuntu-20.04",
"forwardPorts": [
@@ -55,7 +56,7 @@ func CreateDevcontainerJSON(ctx *gitea_context.Context) {
"8888:8888"
]
}`
_, err := files_service.ChangeRepoFiles(db.DefaultContext, ctx.Repo.Repository, ctx.Doer, &files_service.ChangeRepoFilesOptions{
_, err := files_service.ChangeRepoFiles(db.DefaultContext, repo, doer, &files_service.ChangeRepoFilesOptions{
Files: []*files_service.ChangeRepoFile{
{
Operation: "create",
@@ -210,8 +211,16 @@ func GetFileContentByPath(ctx context.Context, repo *repo.Repository, path strin
func GetDevcontainerJsonString(ctx context.Context, repo *repo.Repository) (string, error) {
return GetFileContentByPath(ctx, repo, ".devcontainer/devcontainer.json")
}
func GetDockerfileContent(ctx context.Context, repo *repo.Repository) (string, error) {
dockerfilePath, err := GetDockerfilePath(ctx, repo)
if err != nil {
return "", err
}
return GetFileContentByPath(ctx, repo, ".devcontainer/"+dockerfilePath)
}
func GetDockerfilePath(ctx context.Context, repo *repo.Repository) (string, error) {
devcontainerJSONContent, err := GetDevcontainerJsonString(ctx, repo)
var devContainerJson *devcontainer_model.DevContainerJSON
if err != nil {
@@ -230,10 +239,11 @@ func GetDockerfileContent(ctx context.Context, repo *repo.Repository) (string, e
log.Error("Failed to unmarshal .devcontainer/devcontainer.json: %v", err)
return "", err
}
if devContainerJson.Build.Dockerfile == "" {
return "", nil
if devContainerJson.Build == nil || devContainerJson.Build.Dockerfile == "" {
return "", fmt.Errorf("devcontainer.json error")
}
return GetFileContentByPath(ctx, repo, ".devcontainer/"+devContainerJson.Build.Dockerfile)
log.Info("%vsdadasdsa", devContainerJson.Build.Dockerfile)
return devContainerJson.Build.Dockerfile, nil
}
// 移除 JSON 文件中的注释

View File

@@ -21,9 +21,8 @@ type RepoDevContainer struct {
RepoOwnerName string `json:"repo_owner_name" xorm:"repo_owner_name"`
RepoLink string `json:"repo_link" xorm:"repo_link"`
RepoDescription string `json:"repoDescription,omitempty" xorm:"repo_description"`
// 实时查询获取,不再从数据库获取
DevContainerPort uint16 `json:"devContainerPort,omitempty"`
DevContainerStatus uint16 `json:"devContainerStatus,omitempty" xorm:"devcontainer_status"`
}
// RepoDevcontainerOptions 仓库 Dev Container 条件,注意仓库的所有者可能与当前操作用户不一致!
@@ -75,6 +74,7 @@ type CreateDevcontainerOptions struct {
type OpenDevcontainerAppDispatcherOptions struct {
Name string `json:"name"`
Wait bool `json:"wait"`
Status uint16
Port uint16
UserPublicKey string
RepoID int64
@@ -111,4 +111,5 @@ type CreateDevcontainerDTO struct {
GitRepositoryURL string
Image string
DockerfileContent string
DevcontainerPort uint16
}

View File

@@ -173,18 +173,14 @@ func SaveDevcontainer(ctx *gitea_web_context.Context, opts *UpdateDevcontainerOp
return fmt.Errorf("创建docker client失败 %v", err)
}
defer cli.Close()
if opts.SaveMethod == "Container" {
// 获取容器ID
containerID, err := docker_module.GetContainerID(cli, opts.DevContainerName)
dbEngine := db.GetEngine(*ctx)
_, err = dbEngine.Table("devcontainer").
Where("user_id = ? AND repo_id = ? ", opts.Actor.ID, opts.Repository.ID).
Update(&devcontainer_model.Devcontainer{DevcontainerStatus: 6})
if err != nil {
return fmt.Errorf("获取容器ID失败 %v", err)
log.Info("err %v", err)
}
// 提交容器
_, err = cli.ContainerCommit(ctx, containerID, types.ContainerCommitOptions{Reference: imageRef})
if err != nil {
return fmt.Errorf("提交容器失败 %v", err)
}
} else if opts.SaveMethod == "DockerFile" {
if opts.SaveMethod == "on" {
// 创建构建上下文包含Dockerfile的tar包
var buf bytes.Buffer
tw := tar.NewWriter(&buf)
@@ -218,6 +214,18 @@ func SaveDevcontainer(ctx *gitea_web_context.Context, opts *UpdateDevcontainerOp
log.Info(err.Error())
return err
}
} else {
// 获取容器ID
containerID, err := docker_module.GetContainerID(cli, opts.DevContainerName)
if err != nil {
return fmt.Errorf("获取容器ID失败 %v", err)
}
// 提交容器
_, err = cli.ContainerCommit(ctx, containerID, types.ContainerCommitOptions{Reference: imageRef})
if err != nil {
return fmt.Errorf("提交容器失败 %v", err)
}
}
// 推送到仓库
@@ -232,13 +240,26 @@ func SaveDevcontainer(ctx *gitea_web_context.Context, opts *UpdateDevcontainerOp
// 使用正则表达式查找并替换 image 字段的值
newJSONStr := re.ReplaceAllString(devcontainerJson, `"image": "`+imageRef+`"`)
_, err = dbEngine.Table("devcontainer").
Where("user_id = ? AND repo_id = ? ", opts.Actor.ID, opts.Repository.ID).
Update(&devcontainer_model.Devcontainer{DevcontainerStatus: 4})
if err != nil {
log.Info("err %v", err)
}
return UpdateDevcontainerJSON(ctx, newJSONStr)
}
func PullImageAsyncAndStartContainer(ctx *context.Context, cli *client.Client, dockerHost string, opts *docker_module.CreateDevcontainerOptions) error {
var stdoutScanner, stderrScanner *bufio.Scanner
// 创建扫描器来读取输出
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包
@@ -295,7 +316,6 @@ func PullImageAsyncAndStartContainer(ctx *context.Context, cli *client.Client, d
stderrScanner = bufio.NewScanner(stderr)
}
dbEngine := db.GetEngine(*ctx)
var pullImageOutput = devcontainer_models.DevcontainerOutput{
Output: "",
ListId: 0,
@@ -308,7 +328,8 @@ func PullImageAsyncAndStartContainer(ctx *context.Context, cli *client.Client, d
log.Info("Failed to insert record: %v", err)
return err
}
_, err := dbEngine.Table("devcontainer_output").
_, err = dbEngine.Table("devcontainer_output").
Where("user_id = ? AND repo_id = ? ", opts.UserId, opts.RepoId).
Get(&pullImageOutput)
if err != nil {
@@ -401,7 +422,13 @@ func PullImageAsyncAndStartContainer(ctx *context.Context, cli *client.Client, d
Update(&devcontainer_models.DevcontainerOutput{Status: "success"})
// 创建并启动容器
output, err := docker_module.CreateAndStartContainer(cli, opts)
_, 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)
}
@@ -423,7 +450,12 @@ func PullImageAsyncAndStartContainer(ctx *context.Context, cli *client.Client, d
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 {
@@ -465,6 +497,12 @@ func PullImageAsyncAndStartContainer(ctx *context.Context, cli *client.Client, d
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
@@ -482,6 +520,13 @@ func DockerRestartContainer(gitea_ctx *gitea_web_context.Context, opts *RepoDevC
if err != nil {
return fmt.Errorf("获取容器ID失败 %v", err)
}
dbEngine := db.GetEngine(ctx)
_, err = dbEngine.Table("devcontainer").
Where("user_id = ? AND repo_id = ? ", opts.UserId, opts.RepoId).
Update(&devcontainer_model.Devcontainer{DevcontainerStatus: 5})
if err != nil {
log.Info("err %v", err)
}
timeout := 10 // 超时时间(秒)
err = cli.ContainerRestart(context.Background(), containerID, container.StopOptions{
Timeout: &timeout,
@@ -499,7 +544,7 @@ func DockerRestartContainer(gitea_ctx *gitea_web_context.Context, opts *RepoDevC
cmd := []string{"/home/devcontainer_restart.sh"}
postCreateCommand := append(cmd, devContainerJson.PostCreateCommand...)
// 创建 exec 实例
dbEngine := db.GetEngine(ctx)
var buffer string = ""
var state int = 2
_, err = dbEngine.Table("devcontainer_output").
@@ -572,6 +617,13 @@ func DockerRestartContainer(gitea_ctx *gitea_web_context.Context, opts *RepoDevC
return err
}
}
_, err = dbEngine.Table("devcontainer").
Where("user_id = ? AND repo_id = ? ", opts.UserId, opts.RepoId).
Update(&devcontainer_model.Devcontainer{DevcontainerStatus: 4})
log.Info("DockerRestartContainerDockerRestartContainerDockerRestartContainer")
if err != nil {
log.Info("err %v", err)
}
return nil
}
func DockerStopContainer(ctx *context.Context, opts *RepoDevContainer) error {
@@ -586,6 +638,13 @@ func DockerStopContainer(ctx *context.Context, opts *RepoDevContainer) error {
if err != nil {
return fmt.Errorf("获取容器ID失败 %v", err)
}
dbEngine := db.GetEngine(*ctx)
_, err = dbEngine.Table("devcontainer").
Where("user_id = ? AND repo_id = ? ", opts.UserId, opts.RepoId).
Update(&devcontainer_model.Devcontainer{DevcontainerStatus: 6})
if err != nil {
log.Info("err %v", err)
}
timeout := 10 // 超时时间(秒)
err = cli.ContainerStop(context.Background(), containerID, container.StopOptions{
Timeout: &timeout,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -8,13 +8,17 @@
<!-- 开始Dev Container 正文内容 - 左侧主展示区 -->
<div class="issue-content-left">
{{if not .HasValidDevContainerJSON}}
<div class="empty-placeholder">
{{svg "octicon-container" 48}}
<h2>{{ctx.Locale.Tr "repo.dev_container_empty"}}</h2>
{{if .isAdmin}}
<form method="get" action="{{.CreateDevcontainerSettingUrl}}" class="ui edit form">
<button class="ui primary button" type="submit">Create</button>
</form>
{{end}}
</div>
{{else}}
<div class="ui container">
@@ -49,9 +53,15 @@
{{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 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>
{{if .isAdmin}}
{{if .Running}}
<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>
{{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>
@@ -60,10 +70,16 @@
<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 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}}
<div class="item">
{{if not .isCreatingDevcontainer}}
{{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>
{{else}}
<div class="button flex-text-inline" disabled>Permission Denied</div>
{{end}}
{{else}}
<div class="button flex-text-inline" disabled>{{svg "octicon-terminal" 14 "tw-mr-2"}} Running</div>
{{end}}
@@ -138,15 +154,14 @@
<div class="content">
<form class="ui form tw-max-w-2xl tw-m-auto" id="updateForm" onsubmit="submitForm(event)">
<div class="inline field">
<label>SaveMethod</label>
<div class="ui selection owner dropdown">
<input type="hidden" id="SaveMethod" name="SaveMethod" value="{{.SaveMethod}}">
<div class="default text">Container</div>
<div class="menu">
{{range .SaveMethods}}
<div class="item" data-value="{{.}}">{{.}}</div>
<div class="ui checkbox">
{{if not .HasDockerfile}}
<input type="checkbox" id="SaveMethod" name="SaveMethod" disabled>
{{else}}
<input type="checkbox" id="SaveMethod" name="SaveMethod" value="on">
{{end}}
</div>
<label for="SaveMethod">Build From Dockerfile</label>
</div>
</div>
<div class="required field ">

View File

@@ -139,7 +139,7 @@
{{end}}
<!-- 定义tab DevContainer List -->
{{if .Permission.CanWrite ctx.Consts.RepoUnitTypeCode}}
{{if .Permission.CanRead ctx.Consts.RepoUnitTypeCode}}
<a class="{{if .PageIsRepoDevcontainerDetails}}active {{end}}item" href="{{.RepoLink}}/dev-container">
{{svg "octicon-container"}} {{ctx.Locale.Tr "repo.dev_container"}}
</a>