317 lines
9.5 KiB
Go
317 lines
9.5 KiB
Go
package admin
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/lejianwen/rustdesk-api/v2/global"
|
|
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
|
|
"github.com/lejianwen/rustdesk-api/v2/http/response"
|
|
"github.com/lejianwen/rustdesk-api/v2/service"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type ClientBuild struct {
|
|
}
|
|
|
|
// Generate запускает генерацию/компиляцию клиента
|
|
// @Tags 客户端编译
|
|
// @Summary 生成客户端
|
|
// @Description 启动客户端编译任务
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param body body admin.ClientBuildGenerateForm true "客户端配置"
|
|
// @Success 200 {object} response.Response{data=model.ClientBuild}
|
|
// @Failure 500 {object} response.Response
|
|
// @Router /admin/client_build/generate [post]
|
|
// @Security token
|
|
func (ct *ClientBuild) Generate(c *gin.Context) {
|
|
f := &admin.ClientBuildGenerateForm{}
|
|
if err := c.ShouldBindJSON(f); err != nil {
|
|
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
|
return
|
|
}
|
|
errList := global.Validator.ValidStruct(c, f)
|
|
if len(errList) > 0 {
|
|
response.Fail(c, 101, errList[0])
|
|
return
|
|
}
|
|
|
|
u := service.AllService.UserService.CurUser(c)
|
|
if u.Id == 0 {
|
|
response.Fail(c, 101, response.TranslateMsg(c, "UserNotFound"))
|
|
return
|
|
}
|
|
|
|
// Парсим дополнительные настройки из config
|
|
var customConfig map[string]string
|
|
if f.Config != "" {
|
|
if err := json.Unmarshal([]byte(f.Config), &customConfig); err != nil {
|
|
customConfig = make(map[string]string)
|
|
}
|
|
} else {
|
|
customConfig = make(map[string]string)
|
|
}
|
|
|
|
// Используем настройки сервера из конфигурации, если не указаны (только если не используется кастомный конфиг)
|
|
serverIP := f.ServerIP
|
|
if serverIP == "" && !f.UseCustomConfig {
|
|
serverIP = global.Config.Rustdesk.IdServer
|
|
}
|
|
apiServer := f.ApiServer
|
|
if apiServer == "" && !f.UseCustomConfig {
|
|
apiServer = global.Config.Rustdesk.ApiServer
|
|
}
|
|
key := f.Key
|
|
if key == "" && !f.UseCustomConfig {
|
|
key = global.Config.Rustdesk.Key
|
|
}
|
|
|
|
// Обрабатываем кастомный конфигурационный файл
|
|
// Принимаем любой конфигурационный файл без проверки сертификатов/подписей
|
|
customConfigFile := f.CustomConfigFile
|
|
if f.UseCustomConfig && customConfigFile != "" {
|
|
// Пытаемся декодировать base64, если не получается - используем как есть
|
|
decoded, err := base64.StdEncoding.DecodeString(customConfigFile)
|
|
if err == nil {
|
|
customConfigFile = string(decoded)
|
|
}
|
|
// Если декодирование не удалось, используем исходную строку как текст конфига
|
|
}
|
|
|
|
buildConfig := &service.BuildConfig{
|
|
Platform: f.Platform,
|
|
Version: f.Version,
|
|
AppName: f.AppName,
|
|
FileName: f.FileName,
|
|
ServerIP: serverIP,
|
|
ApiServer: apiServer,
|
|
Key: key,
|
|
Custom: customConfig,
|
|
CustomConfigFile: customConfigFile,
|
|
UseCustomConfig: f.UseCustomConfig,
|
|
}
|
|
|
|
build, err := service.AllService.ClientBuildService.CreateBuild(u.Id, buildConfig)
|
|
if err != nil {
|
|
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
|
return
|
|
}
|
|
|
|
response.Success(c, build)
|
|
}
|
|
|
|
// List получает список задач компиляции
|
|
// @Tags 客户端编译
|
|
// @Summary 编译任务列表
|
|
// @Description 获取当前用户的编译任务列表
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param page query int false "页码"
|
|
// @Param page_size query int false "页大小"
|
|
// @Param platform query string false "平台"
|
|
// @Param status query string false "状态"
|
|
// @Success 200 {object} response.Response{data=model.ClientBuildList}
|
|
// @Failure 500 {object} response.Response
|
|
// @Router /admin/client_build/list [get]
|
|
// @Security token
|
|
func (ct *ClientBuild) List(c *gin.Context) {
|
|
query := &admin.ClientBuildQuery{}
|
|
if err := c.ShouldBindQuery(query); err != nil {
|
|
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
|
return
|
|
}
|
|
|
|
u := service.AllService.UserService.CurUser(c)
|
|
if u.Id == 0 {
|
|
response.Fail(c, 101, response.TranslateMsg(c, "UserNotFound"))
|
|
return
|
|
}
|
|
|
|
res := service.AllService.ClientBuildService.List(uint(query.Page), uint(query.PageSize), func(tx *gorm.DB) *gorm.DB {
|
|
tx = tx.Where("user_id = ?", u.Id)
|
|
if query.Platform != "" {
|
|
tx = tx.Where("platform = ?", query.Platform)
|
|
}
|
|
if query.Status != "" {
|
|
tx = tx.Where("status = ?", query.Status)
|
|
}
|
|
return tx
|
|
})
|
|
|
|
response.Success(c, res)
|
|
}
|
|
|
|
// Status получает статус задачи компиляции
|
|
// @Tags 客户端编译
|
|
// @Summary 编译状态
|
|
// @Description 获取编译任务的状态
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param uuid path string true "Build UUID"
|
|
// @Success 200 {object} response.Response{data=admin.ClientBuildStatusResponse}
|
|
// @Failure 500 {object} response.Response
|
|
// @Router /admin/client_build/status/{uuid} [get]
|
|
// @Security token
|
|
func (ct *ClientBuild) Status(c *gin.Context) {
|
|
uuid := c.Param("uuid")
|
|
if uuid == "" {
|
|
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
|
return
|
|
}
|
|
|
|
u := service.AllService.UserService.CurUser(c)
|
|
if u.Id == 0 {
|
|
response.Fail(c, 101, response.TranslateMsg(c, "UserNotFound"))
|
|
return
|
|
}
|
|
|
|
build := service.AllService.ClientBuildService.GetByUuid(uuid)
|
|
if build.Id == 0 {
|
|
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
|
return
|
|
}
|
|
|
|
// Проверяем, что задача принадлежит пользователю
|
|
if build.UserId != u.Id {
|
|
response.Fail(c, 101, response.TranslateMsg(c, "AccessDenied"))
|
|
return
|
|
}
|
|
|
|
// Вычисляем прогресс
|
|
progress := 0
|
|
if build.Status == "pending" {
|
|
progress = 10
|
|
} else if build.Status == "building" {
|
|
progress = 50
|
|
} else if build.Status == "success" {
|
|
progress = 100
|
|
} else if build.Status == "failed" {
|
|
progress = 0
|
|
}
|
|
|
|
statusResp := &admin.ClientBuildStatusResponse{
|
|
BuildUuid: build.BuildUuid,
|
|
Status: build.Status,
|
|
Progress: progress,
|
|
Log: build.BuildLog,
|
|
ErrorMsg: build.ErrorMsg,
|
|
}
|
|
|
|
// Если сборка успешна, добавляем URL для скачивания
|
|
if build.Status == "success" && build.FilePath != "" {
|
|
statusResp.FileUrl = "/api/admin/client_build/download/" + build.BuildUuid
|
|
}
|
|
|
|
response.Success(c, statusResp)
|
|
}
|
|
|
|
// Download скачивает скомпилированный клиент
|
|
// @Tags 客户端编译
|
|
// @Summary 下载客户端
|
|
// @Description 下载编译完成的客户端文件
|
|
// @Accept json
|
|
// @Produce application/octet-stream
|
|
// @Param uuid path string true "Build UUID"
|
|
// @Success 200 {file} file
|
|
// @Failure 500 {object} response.Response
|
|
// @Router /admin/client_build/download/{uuid} [get]
|
|
// @Security token
|
|
func (ct *ClientBuild) Download(c *gin.Context) {
|
|
uuid := c.Param("uuid")
|
|
if uuid == "" {
|
|
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
|
return
|
|
}
|
|
|
|
u := service.AllService.UserService.CurUser(c)
|
|
if u.Id == 0 {
|
|
response.Fail(c, 101, response.TranslateMsg(c, "UserNotFound"))
|
|
return
|
|
}
|
|
|
|
build := service.AllService.ClientBuildService.GetByUuid(uuid)
|
|
if build.Id == 0 {
|
|
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
|
return
|
|
}
|
|
|
|
// Проверяем, что задача принадлежит пользователю
|
|
if build.UserId != u.Id {
|
|
response.Fail(c, 101, response.TranslateMsg(c, "AccessDenied"))
|
|
return
|
|
}
|
|
|
|
// Проверяем, что сборка успешна
|
|
if build.Status != "success" {
|
|
response.Fail(c, 101, "Build is not completed yet")
|
|
return
|
|
}
|
|
|
|
// Проверяем существование файла
|
|
if _, err := os.Stat(build.FilePath); os.IsNotExist(err) {
|
|
response.Fail(c, 101, response.TranslateMsg(c, "FileNotFound"))
|
|
return
|
|
}
|
|
|
|
// Отправляем файл
|
|
// Определяем Content-Type в зависимости от расширения файла
|
|
contentType := "application/octet-stream"
|
|
fileExt := strings.ToLower(filepath.Ext(build.FilePath))
|
|
switch fileExt {
|
|
case ".exe":
|
|
contentType = "application/x-msdownload"
|
|
case ".deb":
|
|
contentType = "application/vnd.debian.binary-package"
|
|
case ".dmg":
|
|
contentType = "application/x-apple-diskimage"
|
|
case ".apk":
|
|
contentType = "application/vnd.android.package-archive"
|
|
case ".zip":
|
|
contentType = "application/zip"
|
|
}
|
|
c.Header("Content-Disposition", "attachment; filename="+build.FileName)
|
|
c.Header("Content-Type", contentType)
|
|
c.File(build.FilePath)
|
|
}
|
|
|
|
// Delete удаляет задачу компиляции
|
|
// @Tags 客户端编译
|
|
// @Summary 删除编译任务
|
|
// @Description 删除编译任务及其文件
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path int true "Build ID"
|
|
// @Success 200 {object} response.Response
|
|
// @Failure 500 {object} response.Response
|
|
// @Router /admin/client_build/delete/{id} [post]
|
|
// @Security token
|
|
func (ct *ClientBuild) Delete(c *gin.Context) {
|
|
id := c.Param("id")
|
|
idUint, err := strconv.ParseUint(id, 10, 32)
|
|
if err != nil {
|
|
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
|
return
|
|
}
|
|
|
|
u := service.AllService.UserService.CurUser(c)
|
|
if u.Id == 0 {
|
|
response.Fail(c, 101, response.TranslateMsg(c, "UserNotFound"))
|
|
return
|
|
}
|
|
|
|
err = service.AllService.ClientBuildService.Delete(uint(idUint), u.Id)
|
|
if err != nil {
|
|
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
|
return
|
|
}
|
|
|
|
response.Success(c, nil)
|
|
}
|
|
|