mirror of
https://github.com/acepanel/panel.git
synced 2026-02-05 03:22:32 +08:00
* refactor: 重构部分完成 * fix: 添加.gitkeep * fix: build * fix: lint * fix: lint * chore(deps): Update module github.com/go-playground/validator/v10 to v10.22.1 (#162) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): Update module gorm.io/gorm to v1.25.12 (#161) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): Update module golang.org/x/net to v0.29.0 (#159) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * workflow: 更新工作流 * workflow: test new download * feat: merge frontend project * workflow: fix frontend build * workflow: fix frontend build * workflow: fix frontend build * workflow: fix frontend build * workflow: fix frontend build * workflow: fix frontend build * workflow: fix frontend build * workflow: fix frontend build * workflow: fix frontend build * workflow: fix frontend build * workflow: fix frontend build * workflow: fix frontend build * workflow: fix frontend build * workflow: update to ubuntu-24.04 * workflow: rename build-* * workflow: 修改fetch-depth * chore(deps): Update dependency eslint to v9 (#164) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(frontend): update dependences * chore(frontend): fix lint * chore(frontend): fix lint * workflow: add govulncheck * workflow: disable nilaway * feat: 使用新的压缩解压库 * fix: 测试 * fix: 测试 * fix: 测试 * feat: 添加ntp包 * chore(deps): Lock file maintenance (#168) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): Update module github.com/go-resty/resty/v2 to v2.15.0 (#167) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): Update dependency @iconify/json to v2.2.249 (#169) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * feat: 添加限流器 * feat: 调整登录限流 * feat: 证书 * fix: lint * feat: 证书dns * feat: 证书acme账号 * fix: 修改UserID导致的一系列问题 * feat: 低配版任务队列 * feat: 队列完成 * fix: lint * fix: lint * fix: swagger和前端路由 * fix: 去掉ntp测试 * feat: 完成插件接口 * feat: 完成cron * feat: 完成safe * chore(deps): Update dependency vue to v3.5.6 (#170) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): Update dependency @vueuse/core to v11.1.0 (#171) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): Update dependency vite to v5.4.6 (#173) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): Update unocss monorepo to v0.62.4 (#172) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore: update renovate config * feat: 新的firewall客户端 * fix: lint * feat: firewall完成 * feat: ssh完成 * feat: 容器完成1/2 * feat: 容器完成 * feat: 文件完成 * feat: systemctl及设置 * fix: windows编译 * fix: session not work * fix: migrate not work * feat: 前端路由 * feat: 初步支持cli --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
356 lines
8.3 KiB
Go
356 lines
8.3 KiB
Go
//go:build linux
|
||
|
||
package service
|
||
|
||
import (
|
||
"fmt"
|
||
"net/http"
|
||
stdos "os"
|
||
"path/filepath"
|
||
"strconv"
|
||
"strings"
|
||
"syscall"
|
||
|
||
"github.com/go-rat/chix"
|
||
"github.com/golang-module/carbon/v2"
|
||
|
||
"github.com/TheTNB/panel/internal/http/request"
|
||
"github.com/TheTNB/panel/pkg/io"
|
||
"github.com/TheTNB/panel/pkg/os"
|
||
"github.com/TheTNB/panel/pkg/shell"
|
||
"github.com/TheTNB/panel/pkg/str"
|
||
)
|
||
|
||
type FileService struct {
|
||
}
|
||
|
||
func NewFileService() *FileService {
|
||
return &FileService{}
|
||
}
|
||
|
||
func (s *FileService) Create(w http.ResponseWriter, r *http.Request) {
|
||
req, err := Bind[request.FileCreate](r)
|
||
if err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
|
||
if !req.Dir {
|
||
if out, err := shell.Execf("touch %s", req.Path); err != nil {
|
||
Error(w, http.StatusInternalServerError, out)
|
||
return
|
||
}
|
||
} else {
|
||
if err = io.Mkdir(req.Path, 0755); err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
}
|
||
|
||
s.setPermission(req.Path, 0755, "www", "www")
|
||
Success(w, nil)
|
||
}
|
||
|
||
func (s *FileService) Content(w http.ResponseWriter, r *http.Request) {
|
||
req, err := Bind[request.FilePath](r)
|
||
if err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
|
||
fileInfo, err := io.FileInfo(req.Path)
|
||
if err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
if fileInfo.IsDir() {
|
||
Error(w, http.StatusInternalServerError, "目标路径不是文件")
|
||
return
|
||
}
|
||
if fileInfo.Size() > 10*1024*1024 {
|
||
Error(w, http.StatusInternalServerError, "文件大小超过 10 M,不支持在线编辑")
|
||
return
|
||
}
|
||
|
||
content, err := io.Read(req.Path)
|
||
if err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
|
||
Success(w, content)
|
||
}
|
||
|
||
func (s *FileService) Save(w http.ResponseWriter, r *http.Request) {
|
||
req, err := Bind[request.FileSave](r)
|
||
if err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
|
||
fileInfo, err := io.FileInfo(req.Path)
|
||
if err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
|
||
if err = io.Write(req.Path, req.Content, fileInfo.Mode()); err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
|
||
s.setPermission(req.Path, 0755, "www", "www")
|
||
Success(w, nil)
|
||
}
|
||
|
||
func (s *FileService) Delete(w http.ResponseWriter, r *http.Request) {
|
||
req, err := Bind[request.FilePath](r)
|
||
if err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
|
||
if err = io.Remove(req.Path); err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
|
||
Success(w, nil)
|
||
}
|
||
|
||
func (s *FileService) Upload(w http.ResponseWriter, r *http.Request) {
|
||
req, err := Bind[request.FileUpload](r)
|
||
if err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
|
||
if err = io.Write(req.Path, string(req.File), 0755); err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
|
||
s.setPermission(req.Path, 0755, "www", "www")
|
||
Success(w, nil)
|
||
}
|
||
|
||
func (s *FileService) Move(w http.ResponseWriter, r *http.Request) {
|
||
req, err := Bind[request.FileMove](r)
|
||
if err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
|
||
if io.Exists(req.Target) && !req.Force {
|
||
Error(w, http.StatusForbidden, "目标路径"+req.Target+"已存在")
|
||
}
|
||
|
||
if err = io.Mv(req.Source, req.Target); err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
|
||
Success(w, nil)
|
||
}
|
||
|
||
func (s *FileService) Copy(w http.ResponseWriter, r *http.Request) {
|
||
req, err := Bind[request.FileCopy](r)
|
||
if err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
|
||
if io.Exists(req.Target) && !req.Force {
|
||
Error(w, http.StatusForbidden, "目标路径"+req.Target+"已存在")
|
||
}
|
||
|
||
if err = io.Cp(req.Source, req.Target); err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
|
||
Success(w, nil)
|
||
}
|
||
|
||
func (s *FileService) Download(w http.ResponseWriter, r *http.Request) {
|
||
req, err := Bind[request.FilePath](r)
|
||
if err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
|
||
info, err := io.FileInfo(req.Path)
|
||
if err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
if info.IsDir() {
|
||
Error(w, http.StatusInternalServerError, "不能下载目录")
|
||
return
|
||
}
|
||
|
||
render := chix.NewRender(w, r)
|
||
defer render.Release()
|
||
render.Download(req.Path, info.Name())
|
||
}
|
||
|
||
func (s *FileService) RemoteDownload(w http.ResponseWriter, r *http.Request) {
|
||
// TODO: 未实现
|
||
}
|
||
|
||
func (s *FileService) Info(w http.ResponseWriter, r *http.Request) {
|
||
req, err := Bind[request.FilePath](r)
|
||
if err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
|
||
info, err := io.FileInfo(req.Path)
|
||
if err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
|
||
Success(w, chix.M{
|
||
"name": info.Name(),
|
||
"size": str.FormatBytes(float64(info.Size())),
|
||
"mode_str": info.Mode().String(),
|
||
"mode": fmt.Sprintf("%04o", info.Mode().Perm()),
|
||
"dir": info.IsDir(),
|
||
"modify": carbon.CreateFromStdTime(info.ModTime()).ToDateTimeString(),
|
||
})
|
||
}
|
||
|
||
func (s *FileService) Permission(w http.ResponseWriter, r *http.Request) {
|
||
req, err := Bind[request.FilePermission](r)
|
||
if err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
|
||
// 解析成8进制
|
||
mode, err := strconv.ParseUint(req.Mode, 8, 64)
|
||
if err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
|
||
if err = io.Chmod(req.Path, stdos.FileMode(mode)); err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
if err = io.Chown(req.Path, req.Owner, req.Group); err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
|
||
Success(w, nil)
|
||
}
|
||
|
||
func (s *FileService) Compress(w http.ResponseWriter, r *http.Request) {
|
||
req, err := Bind[request.FileCompress](r)
|
||
if err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
|
||
if err = io.Compress(req.Paths, req.File, io.Zip); err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
|
||
s.setPermission(req.File, 0755, "www", "www")
|
||
Success(w, nil)
|
||
}
|
||
|
||
func (s *FileService) UnCompress(w http.ResponseWriter, r *http.Request) {
|
||
req, err := Bind[request.FileUnCompress](r)
|
||
if err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
|
||
if err = io.UnCompress(req.File, req.Path, io.Zip); err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
|
||
s.setPermission(req.Path, 0755, "www", "www")
|
||
Success(w, nil)
|
||
}
|
||
|
||
func (s *FileService) Search(w http.ResponseWriter, r *http.Request) {
|
||
req, err := Bind[request.FileSearch](r)
|
||
if err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
|
||
paths := make(map[string]stdos.FileInfo)
|
||
err = filepath.Walk(req.Path, func(path string, info stdos.FileInfo, err error) error {
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if strings.Contains(info.Name(), req.KeyWord) {
|
||
paths[path] = info
|
||
}
|
||
return nil
|
||
})
|
||
if err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
|
||
Success(w, paths)
|
||
}
|
||
|
||
func (s *FileService) List(w http.ResponseWriter, r *http.Request) {
|
||
req, err := Bind[request.FilePath](r)
|
||
if err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
|
||
fileInfoList, err := io.ReadDir(req.Path)
|
||
if err != nil {
|
||
Error(w, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
|
||
var paths []any
|
||
for _, fileInfo := range fileInfoList {
|
||
info, _ := fileInfo.Info()
|
||
stat := info.Sys().(*syscall.Stat_t)
|
||
|
||
paths = append(paths, map[string]any{
|
||
"name": info.Name(),
|
||
"full": filepath.Join(req.Path, info.Name()),
|
||
"size": str.FormatBytes(float64(info.Size())),
|
||
"mode_str": info.Mode().String(),
|
||
"mode": fmt.Sprintf("%04o", info.Mode().Perm()),
|
||
"owner": os.GetUser(stat.Uid),
|
||
"group": os.GetGroup(stat.Gid),
|
||
"uid": stat.Uid,
|
||
"gid": stat.Gid,
|
||
"hidden": io.IsHidden(info.Name()),
|
||
"symlink": io.IsSymlink(info.Mode()),
|
||
"link": io.GetSymlink(filepath.Join(req.Path, info.Name())),
|
||
"dir": info.IsDir(),
|
||
"modify": carbon.CreateFromStdTime(info.ModTime()).ToDateTimeString(),
|
||
})
|
||
}
|
||
|
||
paged, total := Paginate(r, paths)
|
||
|
||
Success(w, chix.M{
|
||
"total": total,
|
||
"items": paged,
|
||
})
|
||
}
|
||
|
||
// setPermission
|
||
func (s *FileService) setPermission(path string, mode stdos.FileMode, owner, group string) {
|
||
_ = io.Chmod(path, mode)
|
||
_ = io.Chown(path, owner, group)
|
||
}
|