Compare commits
1 Commits
devcontain
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 4858414c6b |
1
devcontainer_after_init.sh
Normal file
1
devcontainer_after_init.sh
Normal file
@@ -0,0 +1 @@
|
||||
ls
|
||||
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.8249fcf680b639f40957.js
Normal file
3
public/assets/terminal/app.8249fcf680b639f40957.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.8249fcf680b639f40957.js.map
Normal file
1
public/assets/terminal/app.8249fcf680b639f40957.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.8249fcf680b639f40957.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
@@ -3,9 +3,15 @@ package devcontainer
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
devcontainer_models "code.gitea.io/gitea/models/devcontainer"
|
||||
@@ -119,6 +125,20 @@ func GetRepoDevContainerDetails(ctx *context.Context) {
|
||||
if err == nil {
|
||||
ctx.Data["WebSSHUrl"] = webTerminalURL
|
||||
}
|
||||
// 解析URL
|
||||
u, err := url.Parse(webTerminalURL)
|
||||
if err != nil {
|
||||
log.Info("URL解析失败: %v", err)
|
||||
}
|
||||
// 分离主机和端口
|
||||
terminalHost, terminalPort, err := net.SplitHostPort(u.Host)
|
||||
if err != nil {
|
||||
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)
|
||||
log.Info("URL解析失败: %v", err)
|
||||
} else {
|
||||
ctx.Data["TerminalParams"] = "ip=" + terminalHost + "&port=" + terminalPort + "&user=" + ctx.Doer.Name + "&repo=" + ctx.Repo.Repository.Name + "&repoid=" + strconv.FormatInt(ctx.Repo.Repository.ID, 10) + "&userid=" + strconv.FormatInt(ctx.Doer.ID, 10)
|
||||
log.Info("TerminalParams: %v", ctx.Data["TerminalParams"])
|
||||
}
|
||||
terminalURL, err := devcontainer_service.Get_IDE_TerminalURL(ctx, &devContainerMetadata)
|
||||
if err == nil {
|
||||
ctx.Data["VSCodeUrl"] = "vscode" + terminalURL
|
||||
@@ -310,6 +330,8 @@ func DeleteRepoDevContainerForCurrentActor(ctx *context.Context) {
|
||||
|
||||
type OutputResponse struct {
|
||||
CurrentJob struct {
|
||||
IP string `json:"ip"`
|
||||
Port string `json:"port"`
|
||||
Title string `json:"title"`
|
||||
Detail string `json:"detail"`
|
||||
Steps []*ViewJobStep `json:"steps"`
|
||||
@@ -329,28 +351,73 @@ type ViewStepLogLine struct {
|
||||
}
|
||||
|
||||
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
|
||||
dbEngine := db.GetEngine(*ctx)
|
||||
|
||||
err := dbEngine.Table("devcontainer_output").
|
||||
Where("user_id = ? AND repo_id = ?", ctx.Doer.ID, ctx.Repo.Repository.ID).
|
||||
var status string
|
||||
_, 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)
|
||||
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if len(devContainerOutput) > 0 {
|
||||
resp := &OutputResponse{}
|
||||
resp.CurrentJob.Title = ctx.Repo.Repository.Name + " Devcontainer Info"
|
||||
resp.CurrentJob.Detail = "success"
|
||||
for _, item := range devContainerOutput {
|
||||
if item.Status != "success" {
|
||||
resp.CurrentJob.Detail = "running"
|
||||
if devContainerOutput[0].Status == "success" && devContainerOutput[1].Status == "success" {
|
||||
resp.CurrentJob.Detail = "created"
|
||||
}
|
||||
resp.CurrentJob.Detail = status
|
||||
if status == "4" {
|
||||
// 获取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)
|
||||
}
|
||||
// 分离主机和端口
|
||||
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 = append(logLines, ViewStepLogLine{
|
||||
Index: 1,
|
||||
@@ -399,3 +466,105 @@ func StopContainer(ctx *context.Context) {
|
||||
|
||||
ctx.JSON(http.StatusOK, map[string]string{})
|
||||
}
|
||||
|
||||
type ParamInfo struct {
|
||||
RepoID string
|
||||
UserID string
|
||||
}
|
||||
|
||||
func GetCommand(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 commandInfo = ParamInfo{
|
||||
RepoID: query.Get("repo"),
|
||||
UserID: query.Get("user"),
|
||||
}
|
||||
|
||||
var status string
|
||||
_, err := db.GetEngine(ctx).
|
||||
Table("devcontainer").
|
||||
Select("devcontainer_status").
|
||||
Where("user_id = ? AND repo_id = ?", commandInfo.UserID, commandInfo.RepoID).
|
||||
Get(&status)
|
||||
if status == "4" {
|
||||
var scriptContent []byte
|
||||
_, err = os.Stat("devcontainer_after_init.sh")
|
||||
if os.IsNotExist(err) {
|
||||
_, err = os.Stat("/app/gitea/devcontainer_after_init.sh")
|
||||
if os.IsNotExist(err) {
|
||||
return
|
||||
} else {
|
||||
scriptContent, err = os.ReadFile("/app/gitea/devcontainer_after_init.sh")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
scriptContent, err = os.ReadFile("devcontainer_after_init.sh")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
ctx.JSON(http.StatusOK, map[string]string{"command": string(scriptContent)})
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, map[string]string{"command": "ls"})
|
||||
}
|
||||
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)
|
||||
|
||||
}
|
||||
|
||||
@@ -474,6 +474,13 @@ func registerRoutes(m *web.Router) {
|
||||
// Especially some AJAX requests, we can reduce middleware number to improve performance.
|
||||
|
||||
m.Get("/", Home)
|
||||
m.Get("/command", devcontainer_web.GetCommand)
|
||||
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("/sitemap.xml", sitemapEnabled, ignExploreSignIn, HomeSitemap)
|
||||
m.Group("/.well-known", func() {
|
||||
@@ -1361,8 +1368,10 @@ func registerRoutes(m *web.Router) {
|
||||
m.Get("/output", devcontainer_web.GetContainerOutput)
|
||||
m.Get("/restart", devcontainer_web.RestartContainer)
|
||||
m.Get("/stop", devcontainer_web.StopContainer)
|
||||
m.Get("/terminalToken", devcontainer_web.GetTerminalToken)
|
||||
m.Post("/delete", devcontainer_web.DeleteRepoDevContainerForCurrentActor)
|
||||
m.Post("/update", devcontainer_web.UpdateRepoDevContainerForCurrentActor)
|
||||
|
||||
},
|
||||
// 进入 Dev Container 编辑页面需要符合条件:
|
||||
// 1. 已登录
|
||||
|
||||
@@ -37,10 +37,10 @@
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
<iframe src="http://localhost:3000/assets/terminal/index.html?{{.TerminalParams}}" width="100%" height="400" frameborder="0">您的浏览器不支持iframe</iframe>
|
||||
</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}}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user