diff --git a/config/config.go b/config/config.go index 3e2efaa..e319403 100644 --- a/config/config.go +++ b/config/config.go @@ -4,34 +4,33 @@ var CF = new(configuration) // 只支持 float64、int、int64、bool、string类型 type configuration struct { - LogLevel string `default:"debug" describe:"日志等级[debug,info]"` - Listen string `default:":8797" describe:"监听端口"` - StorgeType string `default:"sqlite" describe:"存储引擎[sqlite、es、bleve]"` - EsUrl string `default:"" describe:"Elasticsearch url"` - EsIndex string `default:"server_log_v1" describe:"Elasticsearch index"` - EsUsername string `default:"" describe:"Elasticsearch用户名"` - EsPassword string `default:"" describe:"Elasticsearch密码"` - EsWindowLimit bool `default:"true" describe:"Es分页10000条限制"` - FileSizeLimit float64 `default:"10.0" describe:"文件大小限制(MB)"` - ProcessInputPrefix string `default:">" describe:"进程输入前缀"` - ProcessRestartsLimit int `default:"2" describe:"进程重启次数限制"` - ProcessMsgCacheLinesLimit int `default:"50" describe:"std进程缓存消息行数"` - ProcessMsgCacheBufLimit int `default:"4096" describe:"pty进程缓存消息字节长度"` - ProcessExpireTime int64 `default:"60" describe:"进程控制权过期时间(秒)"` - PerformanceInfoListLength int `default:"30" describe:"性能信息存储长度"` - PerformanceInfoInterval int `default:"60" describe:"监控获取间隔时间(秒)"` - TerminalConnectTimeout int `default:"10" describe:"终端连接超时时间(分钟)"` - UserPassWordMinLength int `default:"4" describe:"用户密码最小长度"` - LogMinLenth int `default:"0" describe:"过滤日志最小长度"` - LogHandlerPoolSize int `default:"10" describe:"日志处理并行数"` - PprofEnable bool `default:"true" describe:"启用pprof分析工具"` - KillWaitTime int `default:"5" describe:"kill信号等待时间(秒)"` - TaskTimeout int `default:"60" describe:"任务执行超时时间(秒)"` - TokenExpirationTime int64 `default:"720" describe:"token过期时间(小时)"` - WsHealthCheckInterval int `default:"3" describe:"ws主动健康检查间隔(秒)"` - CgroupPeriod int64 `default:"100000" describe:"CgroupPeriod"` - CgroupSwapLimit bool `default:"false" describe:"cgroup swap限制"` - CondWaitTime int `default:"30" describe:"长轮询等待时间(秒)"` - PerformanceCapacityDisplay bool `default:"false" describe:"性能资源容量显示"` - Tui bool `default:"-"` + LogLevel string `default:"debug" describe:"日志等级[debug,info]"` + Listen string `default:":8797" describe:"监听端口"` + StorgeType string `default:"sqlite" describe:"存储引擎[sqlite、es、bleve]"` + EsUrl string `default:"" describe:"Elasticsearch url"` + EsIndex string `default:"server_log_v1" describe:"Elasticsearch index"` + EsUsername string `default:"" describe:"Elasticsearch用户名"` + EsPassword string `default:"" describe:"Elasticsearch密码"` + EsWindowLimit bool `default:"true" describe:"Es分页10000条限制"` + FileSizeLimit float64 `default:"10.0" describe:"文件大小限制(MB)"` + ProcessInputPrefix string `default:">" describe:"进程输入前缀"` + ProcessRestartsLimit int `default:"2" describe:"进程重启次数限制"` + ProcessMsgCacheLinesLimit int `default:"50" describe:"std进程缓存消息行数"` + ProcessMsgCacheBufLimit int `default:"4096" describe:"pty进程缓存消息字节长度"` + ProcessExpireTime int64 `default:"60" describe:"进程控制权过期时间(秒)"` + PerformanceInfoListLength int `default:"30" describe:"性能信息存储长度"` + PerformanceInfoInterval int `default:"60" describe:"监控获取间隔时间(秒)"` + TerminalConnectTimeout int `default:"10" describe:"终端连接超时时间(分钟)"` + UserPassWordMinLength int `default:"4" describe:"用户密码最小长度"` + LogMinLenth int `default:"0" describe:"过滤日志最小长度"` + LogHandlerPoolSize int `default:"10" describe:"日志处理并行数"` + PprofEnable bool `default:"true" describe:"启用pprof分析工具"` + KillWaitTime int `default:"5" describe:"kill信号等待时间(秒)"` + TaskTimeout int `default:"60" describe:"任务执行超时时间(秒)"` + TokenExpirationTime int64 `default:"720" describe:"token过期时间(小时)"` + WsHealthCheckInterval int `default:"3" describe:"ws主动健康检查间隔(秒)"` + CgroupPeriod int64 `default:"100000" describe:"CgroupPeriod"` + CgroupSwapLimit bool `default:"false" describe:"cgroup swap限制"` + CondWaitTime int `default:"30" describe:"长轮询等待时间(秒)"` + Tui bool `default:"-"` } diff --git a/internal/app/api/proc.go b/internal/app/api/proc.go index 74df712..408e05e 100644 --- a/internal/app/api/proc.go +++ b/internal/app/api/proc.go @@ -45,12 +45,18 @@ func (p *procApi) DeleteNewProcess(ctx *gin.Context, req struct { func (p *procApi) KillProcess(ctx *gin.Context, req struct { Uuid int `form:"uuid" binding:"required"` }) (err error) { + if !hasOprPermission(ctx, req.Uuid, eum.OperationStop) { + return errors.New("not permission") + } return logic.ProcessCtlLogic.KillProcess(req.Uuid) } func (p *procApi) StartProcess(ctx *gin.Context, req struct { - Uuid int `form:"uuid" binding:"required"` + Uuid int `json:"uuid" binding:"required"` }) (err error) { + if !hasOprPermission(ctx, req.Uuid, eum.OperationStart) { + return errors.New("not permission") + } prod, err := logic.ProcessCtlLogic.GetProcess(req.Uuid) if err != nil { // 进程不存在则创建 proConfig, err := repository.ProcessRepository.GetProcessConfigById(req.Uuid) diff --git a/internal/app/api/ws.go b/internal/app/api/ws.go index 4c8e863..0c1535e 100644 --- a/internal/app/api/ws.go +++ b/internal/app/api/ws.go @@ -3,6 +3,7 @@ package api import ( "context" "errors" + "net/http" "strconv" "sync" "time" @@ -46,9 +47,15 @@ func (w *WsConnetInstance) Cancel() { var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, + CheckOrigin: func(r *http.Request) bool { + return true // 允许所有跨域请求 + }, } func (w *wsApi) WebsocketHandle(ctx *gin.Context, req model.WebsocketHandleReq) (err error) { + if !hasOprPermission(ctx, req.Uuid, eum.OperationTerminal) { + return errors.New("not permission") + } reqUser := getUserName(ctx) proc, err := logic.ProcessCtlLogic.GetProcess(req.Uuid) if err != nil { diff --git a/internal/app/logic/process_logic.go b/internal/app/logic/process_logic.go index 63da4c8..51ceaff 100644 --- a/internal/app/logic/process_logic.go +++ b/internal/app/logic/process_logic.go @@ -7,7 +7,6 @@ import ( "sync" "github.com/google/shlex" - "github.com/lzh-1625/go_process_manager/config" "github.com/lzh-1625/go_process_manager/internal/app/eum" "github.com/lzh-1625/go_process_manager/internal/app/model" "github.com/lzh-1625/go_process_manager/internal/app/repository" @@ -116,10 +115,8 @@ func (p *processCtlLogic) getProcessInfoList(processConfiglist []model.Process) pi.User = process.GetUserString() pi.Usage.Cpu = process.performanceStatus.cpu pi.Usage.Mem = process.performanceStatus.mem - if config.CF.PerformanceCapacityDisplay { - pi.Usage.CpuCapacity = float64(runtime.NumCPU()) * 100.0 - pi.Usage.MemCapacity = float64(utils.UnwarpIgnore(mem.VirtualMemory()).Total >> 10) - } + pi.Usage.CpuCapacity = float64(runtime.NumCPU()) * 100.0 + pi.Usage.MemCapacity = float64(utils.UnwarpIgnore(mem.VirtualMemory()).Total >> 10) pi.Usage.Time = process.performanceStatus.time pi.TermType = process.Type() pi.CgroupEnable = process.Config.cgroupEnable diff --git a/internal/app/middle/permission.go b/internal/app/middle/permission.go index 3eb6682..3fb311f 100644 --- a/internal/app/middle/permission.go +++ b/internal/app/middle/permission.go @@ -1,11 +1,7 @@ package middle import ( - "reflect" - "strconv" - "github.com/lzh-1625/go_process_manager/internal/app/eum" - "github.com/lzh-1625/go_process_manager/internal/app/repository" "github.com/gin-gonic/gin" ) @@ -20,24 +16,3 @@ func RolePermission(needPermission eum.Role) func(ctx *gin.Context) { ctx.Next() } } - -func OprPermission(op eum.OprPermission) func(ctx *gin.Context) { - return func(ctx *gin.Context) { - uuid, err := strconv.Atoi(ctx.Query("uuid")) - if err != nil { - rErr(ctx, -1, "Invalid parameters!", nil) - ctx.Abort() - return - } - if v, ok := ctx.Get(eum.CtxRole); !ok || v.(eum.Role) <= eum.RoleAdmin { - ctx.Next() - return - } - if !reflect.ValueOf(repository.PermissionRepository.GetPermission(ctx.GetString(eum.CtxUserName), uuid)).FieldByName(string(op)).Bool() { - rErr(ctx, -1, "Insufficient permissions; please check your access rights!", nil) - ctx.Abort() - return - } - ctx.Next() - } -} diff --git a/internal/app/middle/wait.go b/internal/app/middle/wait.go index e554c40..e75493f 100644 --- a/internal/app/middle/wait.go +++ b/internal/app/middle/wait.go @@ -43,7 +43,7 @@ func (p *waitCond) Trigger() { } func (p *waitCond) WaitGetMiddel(c *gin.Context) { - reqUser := c.GetHeader("token") + reqUser := c.GetHeader("Uuid") defer p.timeMap.Store(reqUser, p.ts) if ts, ok := p.timeMap.Load(reqUser); !ok || ts.(int64) > p.ts { c.Next() diff --git a/internal/app/model/process.go b/internal/app/model/process.go index 04f7024..80a89cc 100644 --- a/internal/app/model/process.go +++ b/internal/app/model/process.go @@ -9,7 +9,7 @@ type Process struct { Cwd string `gorm:"column:cwd" json:"cwd"` AutoRestart bool `gorm:"column:auto_restart" json:"autoRestart"` CompulsoryRestart bool `gorm:"column:compulsory_restart" json:"compulsoryRestart"` - PushIds string `gorm:"column:push_ids" json:"pushIds"` + PushIds string `gorm:"column:push_ids;type:json" json:"pushIds"` LogReport bool `gorm:"column:log_report" json:"logReport"` TermType eum.TerminalType `gorm:"column:term_type" json:"termType"` CgroupEnable bool `gorm:"column:cgroup_enable" json:"cgroupEnable"` diff --git a/internal/app/route/route.go b/internal/app/route/route.go index 4426fd9..bd6afeb 100644 --- a/internal/app/route/route.go +++ b/internal/app/route/route.go @@ -58,16 +58,16 @@ func routePathInit(r *gin.Engine) { { wsGroup := apiGroup.Group("/ws") { - wsGroup.GET("", middle.OprPermission(eum.OperationTerminal), bind(api.WsApi.WebsocketHandle, Query)) + wsGroup.GET("", bind(api.WsApi.WebsocketHandle, Query)) wsGroup.GET("/share", bind(api.WsApi.WebsocketShareHandle, Query)) } processGroup := apiGroup.Group("/process") { - processGroup.DELETE("", middle.OprPermission(eum.OperationStop), bind(api.ProcApi.KillProcess, Query)) + processGroup.DELETE("", bind(api.ProcApi.KillProcess, Query)) processGroup.GET("", bind(api.ProcApi.GetProcessList, None)) processGroup.GET("/wait", middle.ProcessWaitCond.WaitGetMiddel, bind(api.ProcApi.GetProcessList, None)) - processGroup.PUT("", middle.OprPermission(eum.OperationStart), bind(api.ProcApi.StartProcess, Query)) + processGroup.PUT("", bind(api.ProcApi.StartProcess, Body)) processGroup.PUT("/all", bind(api.ProcApi.StartAllProcess, None)) processGroup.DELETE("/all", bind(api.ProcApi.KillAllProcess, None)) processGroup.POST("/share", middle.RolePermission(eum.RoleAdmin), bind(api.ProcApi.ProcessCreateShare, Body)) diff --git a/resources/package-lock.json b/resources/package-lock.json index 51a9ac7..bb81aed 100644 --- a/resources/package-lock.json +++ b/resources/package-lock.json @@ -16,6 +16,7 @@ "@vueup/vue-quill": "^1.2.0", "@vueuse/core": "^10.11.0", "@vueuse/integrations": "^10.11.0", + "@xterm/addon-canvas": "^0.7.0", "@yeger/vue-masonry-wall": "^5.0.14", "apexcharts": "^3.52.0", "axios": "^1.7.5", @@ -44,7 +45,10 @@ "vue3-perfect-scrollbar": "^2.0.0", "vuedraggable": "^4.1.0", "vuetify": "^3.6.14", - "webfontloader": "^1.6.28" + "webfontloader": "^1.6.28", + "xterm": "^5.3.0", + "xterm-addon-attach": "^0.9.0", + "xterm-addon-fit": "^0.8.0" }, "devDependencies": { "@faker-js/faker": "^8.4.1", @@ -2810,6 +2814,22 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/@xterm/addon-canvas": { + "version": "0.7.0", + "resolved": "https://registry.npmmirror.com/@xterm/addon-canvas/-/addon-canvas-0.7.0.tgz", + "integrity": "sha512-LF5LYcfvefJuJ7QotNRdRSPc9YASAVDeoT5uyXS/nZshZXjYplGXRECBGiznwvhNL2I8bq1Lf5MzRwstsYQ2Iw==", + "license": "MIT", + "peerDependencies": { + "@xterm/xterm": "^5.0.0" + } + }, + "node_modules/@xterm/xterm": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/@xterm/xterm/-/xterm-5.5.0.tgz", + "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==", + "license": "MIT", + "peer": true + }, "node_modules/@yeger/debounce": { "version": "2.0.13", "resolved": "https://registry.npmmirror.com/@yeger/debounce/-/debounce-2.0.13.tgz", @@ -7371,6 +7391,33 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "license": "MIT" }, + "node_modules/xterm": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/xterm/-/xterm-5.3.0.tgz", + "integrity": "sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg==", + "deprecated": "This package is now deprecated. Move to @xterm/xterm instead.", + "license": "MIT" + }, + "node_modules/xterm-addon-attach": { + "version": "0.9.0", + "resolved": "https://registry.npmmirror.com/xterm-addon-attach/-/xterm-addon-attach-0.9.0.tgz", + "integrity": "sha512-NykWWOsobVZPPK3P9eFkItrnBK9Lw0f94uey5zhqIVB1bhswdVBfl+uziEzSOhe2h0rT9wD0wOeAYsdSXeavPw==", + "deprecated": "This package is now deprecated. Move to @xterm/addon-attach instead.", + "license": "MIT", + "peerDependencies": { + "xterm": "^5.0.0" + } + }, + "node_modules/xterm-addon-fit": { + "version": "0.8.0", + "resolved": "https://registry.npmmirror.com/xterm-addon-fit/-/xterm-addon-fit-0.8.0.tgz", + "integrity": "sha512-yj3Np7XlvxxhYF/EJ7p3KHaMt6OdwQ+HDu573Vx1lRXsVxOcnVJs51RgjZOouIZOczTsskaS+CpXspK81/DLqw==", + "deprecated": "This package is now deprecated. Move to @xterm/addon-fit instead.", + "license": "MIT", + "peerDependencies": { + "xterm": "^5.0.0" + } + }, "node_modules/yaml": { "version": "2.8.1", "resolved": "https://registry.npmmirror.com/yaml/-/yaml-2.8.1.tgz", diff --git a/resources/package.json b/resources/package.json index c85bf24..7dce8ed 100644 --- a/resources/package.json +++ b/resources/package.json @@ -18,6 +18,7 @@ "@vueup/vue-quill": "^1.2.0", "@vueuse/core": "^10.11.0", "@vueuse/integrations": "^10.11.0", + "@xterm/addon-canvas": "^0.7.0", "@yeger/vue-masonry-wall": "^5.0.14", "apexcharts": "^3.52.0", "axios": "^1.7.5", @@ -46,7 +47,10 @@ "vue3-perfect-scrollbar": "^2.0.0", "vuedraggable": "^4.1.0", "vuetify": "^3.6.14", - "webfontloader": "^1.6.28" + "webfontloader": "^1.6.28", + "xterm": "^5.3.0", + "xterm-addon-attach": "^0.9.0", + "xterm-addon-fit": "^0.8.0" }, "devDependencies": { "@faker-js/faker": "^8.4.1", diff --git a/resources/src/App.vue b/resources/src/App.vue index 886b563..36fd6c4 100644 --- a/resources/src/App.vue +++ b/resources/src/App.vue @@ -45,7 +45,6 @@ onMounted(() => { - diff --git a/resources/src/api/process.ts b/resources/src/api/process.ts index 748b57f..118bef8 100644 --- a/resources/src/api/process.ts +++ b/resources/src/api/process.ts @@ -1,4 +1,4 @@ -import { ProcessItem } from "../types/process/process"; +import { ProcessConfig, ProcessItem } from "../types/process/process"; import api from "./api"; export function getProcessList() { @@ -30,7 +30,7 @@ export function getContorl(uuid) { } export function getProcessConfig(uuid) { - return api.get("/process/config", { uuid }).then((res) => res); + return api.get("/process/config", { uuid }).then((res) => res); } export function deleteProcessConfig(uuid) { diff --git a/resources/src/api/push.ts b/resources/src/api/push.ts index 321ba44..40787e2 100644 --- a/resources/src/api/push.ts +++ b/resources/src/api/push.ts @@ -1,3 +1,4 @@ +import { PushItem } from "../types/push/push"; import api from "./api"; export function createPush(data) { @@ -5,7 +6,7 @@ export function createPush(data) { } export function getPushList() { - return api.get("/push/list", undefined).then((res) => res); + return api.get("/push/list", undefined).then((res) => res); } export function deletePush(id) { diff --git a/resources/src/components/process/ProcessCard.vue b/resources/src/components/process/ProcessCard.vue index 937bfb9..4ffaa40 100644 --- a/resources/src/components/process/ProcessCard.vue +++ b/resources/src/components/process/ProcessCard.vue @@ -1,6 +1,13 @@ + + diff --git a/resources/src/components/process/TerminalPty.vue b/resources/src/components/process/TerminalPty.vue new file mode 100644 index 0000000..fc181c1 --- /dev/null +++ b/resources/src/components/process/TerminalPty.vue @@ -0,0 +1,167 @@ + + + + + diff --git a/resources/src/types/process/process.ts b/resources/src/types/process/process.ts index 9b38f24..c0100c0 100644 --- a/resources/src/types/process/process.ts +++ b/resources/src/types/process/process.ts @@ -1,34 +1,49 @@ export interface ProcessItem { - name: string; - uuid: number; - startTime: Date; - user: string; - usage: Usage; - state: State; - termType: TermType; - cgroupEnable: boolean; - memoryLimit: number | null; - cpuLimit: number | null; + name: string; + uuid: number; + startTime: Date; + user: string; + usage: Usage; + state: State; + termType: TermType; + cgroupEnable: boolean; + memoryLimit: number | null; + cpuLimit: number | null; } export interface State { - state: number; - info: Info; + state: number; + info: Info; } export enum Info { - Empty = "", - 重启次数异常 = "重启次数异常", + Empty = "", + 重启次数异常 = "重启次数异常", } export enum TermType { - Pty = "pty", + Pty = "pty", } export interface Usage { - cpuCapacity: number; - memCapacity: number; - cpu: number[] | null; - mem: number[] | null; - time: string[] | null; + cpuCapacity: number; + memCapacity: number; + cpu: number[] | null; + mem: number[] | null; + time: string[] | null; +} + +export interface ProcessConfig { + uuid: number; + name: string; + cmd: string; + cwd: string; + autoRestart: boolean; + compulsoryRestart: boolean; + pushIds: number[] | string; + logReport: boolean; + termType: string; + cgroupEnable: boolean; + memoryLimit: null; + cpuLimit: null; } diff --git a/resources/src/types/push/push.ts b/resources/src/types/push/push.ts new file mode 100644 index 0000000..5d42020 --- /dev/null +++ b/resources/src/types/push/push.ts @@ -0,0 +1,8 @@ +export interface PushItem { + id: number; + method: string; + url: string; + body: string; + remark: string; + enable: boolean; +} diff --git a/resources/src/views/process/Process.vue b/resources/src/views/process/Process.vue index 4f2d176..885ec25 100644 --- a/resources/src/views/process/Process.vue +++ b/resources/src/views/process/Process.vue @@ -10,18 +10,41 @@