mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-11-27 20:49:03 +08:00
feat: 完成操作日志功能 (#6870)
This commit is contained in:
parent
4bb0974c4a
commit
4b84124801
@ -352,7 +352,7 @@ func (b *BaseApi) UpdatePHPConfig(c *gin.Context) {
|
||||
// @Summary Update php conf file
|
||||
// @Description 更新 php 配置文件
|
||||
// @Accept json
|
||||
// @Param request body request.WebsitePHPFileUpdate true "request"
|
||||
// @Param request body request.PHPFileUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /runtimes/php/update [post]
|
||||
@ -415,7 +415,7 @@ func (b *BaseApi) UpdateFPMConfig(c *gin.Context) {
|
||||
// @Description 获取 fpm 配置
|
||||
// @Accept json
|
||||
// @Param id path integer true "request"
|
||||
// @Success 200 {object} response.FPMConfig
|
||||
// @Success 200 {object} request.FPMConfig
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /runtimes/php/fpm/config/:id [get]
|
||||
func (b *BaseApi) GetFPMConfig(c *gin.Context) {
|
||||
@ -437,7 +437,7 @@ func (b *BaseApi) GetFPMConfig(c *gin.Context) {
|
||||
// @Description 获取 supervisor 进程
|
||||
// @Accept json
|
||||
// @Param id path integer true "request"
|
||||
// @Success 200 {object} response.SupervisorProcess
|
||||
// @Success 200 {object} response.SupervisorProcessConfig
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /runtimes/supervisor/process/:id [get]
|
||||
func (b *BaseApi) GetSupervisorProcess(c *gin.Context) {
|
||||
|
@ -52,7 +52,7 @@ func (b *BaseApi) CreateSnapshot(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/snapshot/recrete [post]
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"snapshots","output_column":"name","output_value":"name"}],"formatZH":"重试创建快照 [name]","formatEN":recrete the snapshot [name]"}
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"snapshots","output_column":"name","output_value":"name"}],"formatZH":"重试创建快照 [name]","formatEN":"recrete the snapshot [name]"}
|
||||
func (b *BaseApi) RecreateSnapshot(c *gin.Context) {
|
||||
var req dto.OperateByID
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
|
@ -995,7 +995,7 @@ func (b *BaseApi) GetRealIPConfig(c *gin.Context) {
|
||||
// @Description 获取网站资源
|
||||
// @Accept json
|
||||
// @Param id path int true "id"
|
||||
// @Success 200 {object} response.WebsiteResource
|
||||
// @Success 200 {object} response.Resource
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /websites/resource/{id} [get]
|
||||
func (b *BaseApi) GetWebsiteResource(c *gin.Context) {
|
||||
@ -1016,7 +1016,7 @@ func (b *BaseApi) GetWebsiteResource(c *gin.Context) {
|
||||
// @Summary Get databases
|
||||
// @Description 获取数据库列表
|
||||
// @Accept json
|
||||
// @Success 200 {object} response.WebsiteDatabase
|
||||
// @Success 200 {object} response.Database
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /websites/databases [get]
|
||||
func (b *BaseApi) GetWebsiteDatabase(c *gin.Context) {
|
||||
|
@ -100,6 +100,9 @@ func (u *CronjobService) handleShell(cronjob model.Cronjob, logPath string) erro
|
||||
scriptFile, _ := os.ReadFile(cronjob.Script)
|
||||
return cmd.ExecShell(logPath, 24*time.Hour, "docker", "exec", cronjob.ContainerName, command, "-c", strings.ReplaceAll(string(scriptFile), "\"", "\\\""))
|
||||
}
|
||||
if len(cronjob.Executor) == 0 {
|
||||
cronjob.Executor = "bash"
|
||||
}
|
||||
if cronjob.ScriptMode == "input" {
|
||||
fileItem := path.Join(global.CONF.System.BaseDir, "1panel", "task", "shell", cronjob.Name, cronjob.Name+".sh")
|
||||
_ = os.MkdirAll(path.Dir(fileItem), os.ModePerm)
|
||||
@ -111,8 +114,14 @@ func (u *CronjobService) handleShell(cronjob model.Cronjob, logPath string) erro
|
||||
if _, err := shellFile.WriteString(cronjob.Script); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(cronjob.User) == 0 {
|
||||
return cmd.ExecShell(logPath, 24*time.Hour, cronjob.Executor, fileItem)
|
||||
}
|
||||
return cmd.ExecShell(logPath, 24*time.Hour, "sudo", "-u", cronjob.User, cronjob.Executor, fileItem)
|
||||
}
|
||||
if len(cronjob.User) == 0 {
|
||||
return cmd.ExecShell(logPath, 24*time.Hour, cronjob.Executor, cronjob.Script)
|
||||
}
|
||||
return cmd.ExecShell(logPath, 24*time.Hour, "sudo", "-u", cronjob.User, cronjob.Executor, cronjob.Script)
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -3,15 +3,76 @@ package docs
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGenerateXlog(t *testing.T) {
|
||||
fset := token.NewFileSet()
|
||||
|
||||
apiDirs := []string{"../../../agent/app/api/v2", "../../../core/app/api/v2", "../../../agent/xpack/app/api/v2", "../../../core/xpack/app/api/v2"}
|
||||
|
||||
xlogMap := make(map[string]operationJson)
|
||||
for _, dir := range apiDirs {
|
||||
if err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
fileItem, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, decl := range fileItem.Decls {
|
||||
switch d := decl.(type) {
|
||||
case *ast.FuncDecl:
|
||||
if d.Doc != nil {
|
||||
routerContent := ""
|
||||
logContent := ""
|
||||
for _, comment := range d.Doc.List {
|
||||
if strings.HasPrefix(comment.Text, "// @Router") {
|
||||
routerContent = replaceStr(comment.Text, "// @Router", "[post]", "[get]", " ")
|
||||
}
|
||||
if strings.HasPrefix(comment.Text, "// @x-panel-log") {
|
||||
logContent = replaceStr(comment.Text, "// @x-panel-log", " ")
|
||||
}
|
||||
}
|
||||
if len(routerContent) != 0 && len(logContent) != 0 {
|
||||
var item operationJson
|
||||
if err := json.Unmarshal([]byte(logContent), &item); err != nil {
|
||||
panic(fmt.Sprintf("json unamrshal failed, err: %v", err))
|
||||
}
|
||||
xlogMap[routerContent] = item
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
newJson, err := json.MarshalIndent(xlogMap, "", "\t")
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("json marshal for new file failed, err: %v", err))
|
||||
}
|
||||
if err := os.WriteFile("x-log.json", newJson, 0640); err != nil {
|
||||
panic(fmt.Sprintf("write new swagger.json failed, err: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateSwaggerDoc(t *testing.T) {
|
||||
workDir := "/Users/slooop/Documents/mycode/1Panel"
|
||||
swagBin := "/Users/slooop/.gvm/pkgsets/go1.22.4/global/bin/swag"
|
||||
workDir := "/usr/songliu/1Panel"
|
||||
swagBin := "/root/go/bin/swag"
|
||||
|
||||
cmd1 := exec.Command(swagBin, "init", "-o", workDir+"/cmd/server/docs/docs_agent", "-d", workDir+"/agent", "-g", "./cmd/server/main.go")
|
||||
cmd1.Dir = workDir
|
||||
@ -68,26 +129,6 @@ func TestGenerateSwaggerDoc(t *testing.T) {
|
||||
newSwagger.Paths[key] = val
|
||||
}
|
||||
|
||||
newXLog := make(map[string]interface{})
|
||||
for key, val := range newSwagger.Paths {
|
||||
methodMap, isMethodMap := val.(map[string]interface{})
|
||||
if !isMethodMap {
|
||||
continue
|
||||
}
|
||||
dataMap, hasPost := methodMap["post"]
|
||||
if !hasPost {
|
||||
continue
|
||||
}
|
||||
data, isDataMap := dataMap.(map[string]interface{})
|
||||
if !isDataMap {
|
||||
continue
|
||||
}
|
||||
xLog, hasXLog := data["x-panel-log"]
|
||||
if !hasXLog {
|
||||
continue
|
||||
}
|
||||
newXLog[key] = xLog
|
||||
}
|
||||
newJson, err := json.MarshalIndent(newSwagger, "", "\t")
|
||||
if err != nil {
|
||||
fmt.Printf("json marshal for new file failed, err: %v", err)
|
||||
@ -103,16 +144,6 @@ func TestGenerateSwaggerDoc(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
newXLogFile, err := json.MarshalIndent(newXLog, "", "\t")
|
||||
if err != nil {
|
||||
fmt.Printf("json marshal for new x-log file failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
if err := os.WriteFile("x-log.json", newXLogFile, 0640); err != nil {
|
||||
fmt.Printf("write new x-log.json failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
_ = os.RemoveAll(workDir + "/cmd/server/docs/docs_agent")
|
||||
_ = os.RemoveAll(workDir + "/cmd/server/docs/docs_core")
|
||||
}
|
||||
@ -150,3 +181,26 @@ func init() {
|
||||
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
|
||||
}`
|
||||
}
|
||||
|
||||
func replaceStr(val string, rep ...string) string {
|
||||
for _, item := range rep {
|
||||
val = strings.ReplaceAll(val, item, "")
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
type operationJson struct {
|
||||
BodyKeys []string `json:"bodyKeys"`
|
||||
ParamKeys []string `json:"paramKeys"`
|
||||
BeforeFunctions []functionInfo `json:"beforeFunctions"`
|
||||
FormatZH string `json:"formatZH"`
|
||||
FormatEN string `json:"formatEN"`
|
||||
}
|
||||
type functionInfo struct {
|
||||
InputColumn string `json:"input_column"`
|
||||
InputValue string `json:"input_value"`
|
||||
IsList bool `json:"isList"`
|
||||
DB string `json:"db"`
|
||||
OutputColumn string `json:"output_column"`
|
||||
OutputValue string `json:"output_value"`
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -138,7 +138,7 @@ func (b *BaseApi) SearchHost(c *gin.Context) {
|
||||
// @Summary Delete host
|
||||
// @Description 删除主机
|
||||
// @Accept json
|
||||
// @Param request body dto.BatchDeleteReq true "request"
|
||||
// @Param request body dto.OperateByIDs true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /core/hosts/del [post]
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -15,8 +16,9 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/core/app/service"
|
||||
"github.com/1Panel-dev/1Panel/core/constant"
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/copier"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func OperationLog() gin.HandlerFunc {
|
||||
@ -36,43 +38,16 @@ func OperationLog() gin.HandlerFunc {
|
||||
Path: pathItem,
|
||||
UserAgent: c.Request.UserAgent(),
|
||||
}
|
||||
var (
|
||||
swagger swaggerJson
|
||||
operationDic operationJson
|
||||
)
|
||||
swagger := make(map[string]operationJson)
|
||||
if err := json.Unmarshal(docs.XLogJson, &swagger); err != nil {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
path, hasPath := swagger.Paths[record.Path]
|
||||
operationDic, hasPath := swagger[record.Path]
|
||||
if !hasPath {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
methodMap, isMethodMap := path.(map[string]interface{})
|
||||
if !isMethodMap {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
dataMap, hasPost := methodMap["post"]
|
||||
if !hasPost {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
data, isDataMap := dataMap.(map[string]interface{})
|
||||
if !isDataMap {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
xlog, hasXlog := data["x-panel-log"]
|
||||
if !hasXlog {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
if err := copier.Copy(&operationDic, xlog); err != nil {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
if len(operationDic.FormatZH) == 0 {
|
||||
c.Next()
|
||||
return
|
||||
@ -93,21 +68,27 @@ func OperationLog() gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
if len(operationDic.BeforeFunctions) != 0 {
|
||||
dbItem, err := newDB(record.Path)
|
||||
if err != nil {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
for _, funcs := range operationDic.BeforeFunctions {
|
||||
for key, value := range formatMap {
|
||||
if funcs.InputValue == key {
|
||||
var names []string
|
||||
if funcs.IsList {
|
||||
sql := fmt.Sprintf("SELECT %s FROM %s where %s in (?);", funcs.OutputColumn, funcs.DB, funcs.InputColumn)
|
||||
_ = global.DB.Raw(sql, value).Scan(&names)
|
||||
_ = dbItem.Raw(sql, value).Scan(&names)
|
||||
} else {
|
||||
_ = global.DB.Raw(fmt.Sprintf("select %s from %s where %s = ?;", funcs.OutputColumn, funcs.DB, funcs.InputColumn), value).Scan(&names)
|
||||
_ = dbItem.Raw(fmt.Sprintf("select %s from %s where %s = ?;", funcs.OutputColumn, funcs.DB, funcs.InputColumn), value).Scan(&names)
|
||||
}
|
||||
formatMap[funcs.OutputValue] = strings.Join(names, ",")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
closeDB(dbItem)
|
||||
}
|
||||
for key, value := range formatMap {
|
||||
if strings.Contains(operationDic.FormatEN, "["+key+"]") {
|
||||
@ -168,10 +149,6 @@ func OperationLog() gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
type swaggerJson struct {
|
||||
Paths map[string]interface{} `json:"paths"`
|
||||
}
|
||||
|
||||
type operationJson struct {
|
||||
API string `json:"api"`
|
||||
Method string `json:"method"`
|
||||
@ -206,7 +183,7 @@ func (r responseBodyWriter) Write(b []byte) (int, error) {
|
||||
}
|
||||
|
||||
func loadLogInfo(path string) string {
|
||||
path = strings.ReplaceAll(path, "/api/v2", "")
|
||||
path = replaceStr(path, "/api/v2", "/core", "/xpack")
|
||||
if !strings.Contains(path, "/") {
|
||||
return ""
|
||||
}
|
||||
@ -216,3 +193,42 @@ func loadLogInfo(path string) string {
|
||||
}
|
||||
return pathArrays[1]
|
||||
}
|
||||
|
||||
func newDB(pathItem string) (*gorm.DB, error) {
|
||||
dbFile := ""
|
||||
switch {
|
||||
case strings.HasPrefix(pathItem, "/core"):
|
||||
dbFile = path.Join(global.CONF.System.BaseDir, "1panel/db/core.db")
|
||||
case strings.HasPrefix(pathItem, "/xpack"):
|
||||
dbFile = path.Join(global.CONF.System.BaseDir, "1panel/db/xpack/xpack.db")
|
||||
default:
|
||||
dbFile = path.Join(global.CONF.System.BaseDir, "1panel/db/agent.db")
|
||||
}
|
||||
|
||||
db, _ := gorm.Open(sqlite.Open(dbFile), &gorm.Config{
|
||||
DisableForeignKeyConstraintWhenMigrating: true,
|
||||
})
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sqlDB.SetConnMaxIdleTime(10)
|
||||
sqlDB.SetMaxOpenConns(100)
|
||||
sqlDB.SetConnMaxLifetime(time.Hour)
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func closeDB(db *gorm.DB) {
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_ = sqlDB.Close()
|
||||
}
|
||||
|
||||
func replaceStr(val string, rep ...string) string {
|
||||
for _, item := range rep {
|
||||
val = strings.ReplaceAll(val, item, "")
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ export const getSystemLogs = (name: string) => {
|
||||
};
|
||||
|
||||
export const cleanLogs = (param: Log.CleanLog) => {
|
||||
return http.post(`/logs/clean`, param);
|
||||
return http.post(`/core/logs/clean`, param);
|
||||
};
|
||||
|
||||
export const searchTasks = (req: Log.SearchTaskReq) => {
|
||||
|
@ -6,19 +6,19 @@ import { Setting } from '../interface/setting';
|
||||
|
||||
// license
|
||||
export const UploadFileData = (params: FormData) => {
|
||||
return http.upload('/licenses/upload', params);
|
||||
return http.upload('/xpack/licenses/upload', params);
|
||||
};
|
||||
export const getLicense = () => {
|
||||
return http.get<Setting.License>(`/licenses/get`);
|
||||
return http.get<Setting.License>(`/xpack/licenses/get`);
|
||||
};
|
||||
export const getLicenseStatus = () => {
|
||||
return http.get<Setting.LicenseStatus>(`/licenses/get/status`);
|
||||
return http.get<Setting.LicenseStatus>(`/xpack/licenses/get/status`);
|
||||
};
|
||||
export const syncLicense = () => {
|
||||
return http.post(`/licenses/sync`);
|
||||
return http.post(`/xpack/licenses/sync`);
|
||||
};
|
||||
export const unbindLicense = () => {
|
||||
return http.post(`/licenses/unbind`);
|
||||
return http.post(`/xpack/licenses/unbind`);
|
||||
};
|
||||
|
||||
// agent
|
||||
|
@ -1180,6 +1180,8 @@ const message = {
|
||||
runtimes: 'Runtime',
|
||||
process: 'Process',
|
||||
toolbox: 'Toolbox',
|
||||
tampers: 'Tamper',
|
||||
xsetting: 'Interface Settings',
|
||||
logs: 'Panel Logs',
|
||||
settings: 'Panel Setting',
|
||||
cronjobs: 'Cronjob',
|
||||
|
@ -1118,6 +1118,8 @@ const message = {
|
||||
runtimes: '運行環境',
|
||||
process: '進程管理',
|
||||
toolbox: '工具箱',
|
||||
tampers: '防篡改',
|
||||
xsetting: '界面設定',
|
||||
logs: '日誌審計',
|
||||
settings: '面板設置',
|
||||
cronjobs: '計劃任務',
|
||||
|
@ -1120,6 +1120,8 @@ const message = {
|
||||
runtimes: '运行环境',
|
||||
process: '进程管理',
|
||||
toolbox: '工具箱',
|
||||
tampers: '防篡改',
|
||||
xsetting: '界面设置',
|
||||
logs: '日志审计',
|
||||
settings: '面板设置',
|
||||
cronjobs: '计划任务',
|
||||
|
@ -237,11 +237,7 @@
|
||||
<el-card>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
:label="$t('commons.table.user')"
|
||||
prop="user"
|
||||
:rules="Rules.requiredSelect"
|
||||
>
|
||||
<el-form-item :label="$t('commons.table.user')" prop="user">
|
||||
<el-select filterable v-model="dialogData.rowData!.user">
|
||||
<div v-for="item in userOptions" :key="item">
|
||||
<el-option :value="item" :label="item" />
|
||||
@ -250,11 +246,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
:label="$t('cronjob.executor')"
|
||||
prop="executor"
|
||||
:rules="Rules.requiredSelect"
|
||||
>
|
||||
<el-form-item :label="$t('cronjob.executor')" prop="executor">
|
||||
<el-checkbox border v-model="dialogData.rowData!.isExecutorCustom">
|
||||
{{ $t('container.custom') }}
|
||||
</el-checkbox>
|
||||
|
@ -150,9 +150,15 @@ const loadDetail = (log: string) => {
|
||||
if (log.indexOf('[enable]') !== -1) {
|
||||
log = log.replace('[enable]', '[' + i18n.global.t('commons.button.enable') + ']');
|
||||
}
|
||||
if (log.indexOf('[Enable]') !== -1) {
|
||||
log = log.replace('[Enable]', '[' + i18n.global.t('commons.button.enable') + ']');
|
||||
}
|
||||
if (log.indexOf('[disable]') !== -1) {
|
||||
log = log.replace('[disable]', '[' + i18n.global.t('commons.button.disable') + ']');
|
||||
}
|
||||
if (log.indexOf('[Disable]') !== -1) {
|
||||
log = log.replace('[Disable]', '[' + i18n.global.t('commons.button.disable') + ']');
|
||||
}
|
||||
if (log.indexOf('[light]') !== -1) {
|
||||
log = log.replace('[light]', '[' + i18n.global.t('setting.light') + ']');
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user