2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-03 22:22:45 +08:00

feat: 同步修改

This commit is contained in:
耗子
2024-10-12 22:05:58 +08:00
parent d18bf93571
commit 99cd473680
38 changed files with 388 additions and 551 deletions

View File

@@ -3,6 +3,7 @@ package mysql
import (
"fmt"
"net/http"
"os"
"regexp"
"github.com/spf13/cast"
@@ -80,11 +81,16 @@ func (s *Service) Load(w http.ResponseWriter, r *http.Request) {
return
}
raw, err := shell.Execf(`mysqladmin -u root -p "%s" extended-status`, rootPassword)
if err = os.Setenv("MYSQL_PWD", rootPassword); err != nil {
service.Error(w, http.StatusInternalServerError, "设置环境变量失败")
return
}
raw, err := shell.Execf(`mysqladmin -u root extended-status`)
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取负载失败")
return
}
_ = os.Unsetenv("MYSQL_PWD")
var load []map[string]string
expressions := []struct {

View File

@@ -7,7 +7,6 @@ import (
"github.com/spf13/cast"
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/os"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/systemctl"
@@ -61,59 +60,32 @@ func (r *safeRepo) UpdateSSH(port uint, status bool) error {
}
func (r *safeRepo) GetPingStatus() (bool, error) {
if os.IsRHEL() {
out, err := shell.Execf(`firewall-cmd --list-all`)
if err != nil {
return true, errors.New(out)
}
if !strings.Contains(out, `rule protocol value="icmp" drop`) {
return true, nil
} else {
return false, nil
}
} else {
config, err := io.Read("/etc/ufw/before.rules")
if err != nil {
return true, err
}
if strings.Contains(config, "-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT") {
return true, nil
} else {
return false, nil
}
out, err := shell.Execf(`firewall-cmd --list-all`)
if err != nil {
return true, errors.New(out)
}
if !strings.Contains(out, `rule protocol value="icmp" drop`) {
return true, nil
}
return false, nil
}
func (r *safeRepo) UpdatePingStatus(status bool) error {
var out string
var err error
if os.IsRHEL() {
if status {
out, err = shell.Execf(`firewall-cmd --permanent --remove-rich-rule='rule protocol value=icmp drop'`)
} else {
out, err = shell.Execf(`firewall-cmd --permanent --add-rich-rule='rule protocol value=icmp drop'`)
}
if status {
_, err = shell.Execf(`firewall-cmd --permanent --remove-rich-rule='rule protocol value=icmp drop'`)
} else {
if status {
out, err = shell.Execf(`sed -i 's/-A ufw-before-input -p icmp --icmp-type echo-request -j DROP/-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT/g' /etc/ufw/before.rules`)
} else {
out, err = shell.Execf(`sed -i 's/-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT/-A ufw-before-input -p icmp --icmp-type echo-request -j DROP/g' /etc/ufw/before.rules`)
}
_, err = shell.Execf(`firewall-cmd --permanent --add-rich-rule='rule protocol value=icmp drop'`)
}
if err != nil {
return errors.New(out)
}
if os.IsRHEL() {
out, err = shell.Execf(`firewall-cmd --reload`)
} else {
out, err = shell.Execf(`ufw reload`)
return err
}
_, err = shell.Execf(`firewall-cmd --reload`)
if err != nil {
return errors.New(out)
return err
}
return nil

View File

@@ -44,10 +44,13 @@ func (r *settingRepo) Get(key biz.SettingKey, defaultValue ...string) (string, e
func (r *settingRepo) Set(key biz.SettingKey, value string) error {
setting := new(biz.Setting)
if err := app.Orm.Where("key = ?", key).FirstOrInit(setting).Error; err != nil {
return err
if err := app.Orm.Where("key = ?", key).First(setting).Error; err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
}
setting.Key = key
setting.Value = value
return app.Orm.Save(setting).Error
}
@@ -278,6 +281,7 @@ func (r *settingRepo) UpdatePanel(version, url, checksum string) error {
_, _ = shell.Execf("systemctl daemon-reload")
_ = io.Remove("/tmp/panel-storage.zip")
_ = io.Remove(filepath.Join(app.Root, "panel/config.example.yml"))
return nil
}

View File

@@ -40,7 +40,7 @@ func (r *sshRepo) UpdateInfo(req *request.SSHUpdateInfo) error {
if err := r.settingRepo.Set(biz.SettingKeySshHost, req.Host); err != nil {
return err
}
if err := r.settingRepo.Set(biz.SettingKeySshPort, req.Port); err != nil {
if err := r.settingRepo.Set(biz.SettingKeySshPort, cast.ToString(req.Port)); err != nil {
return err
}
if err := r.settingRepo.Set(biz.SettingKeySshUser, req.User); err != nil {

View File

@@ -178,7 +178,7 @@ func (r *websiteRepo) Create(req *request.WebsiteCreate) (*biz.Website, error) {
Name: req.Name,
Status: true,
Path: req.Path,
PHP: cast.ToInt(req.PHP),
PHP: req.PHP,
SSL: false,
}
if err := app.Orm.Create(w).Error; err != nil {
@@ -250,7 +250,7 @@ server
# ssl标记位结束
# php标记位开始
include enable-php-%s.conf;
include enable-php-%d.conf;
# php标记位结束
# 错误页配置,可自行设置

View File

@@ -2,7 +2,7 @@ package request
type SSHUpdateInfo struct {
Host string `json:"host" form:"host"`
Port string `json:"port" form:"port"`
Port int `json:"port" form:"port"`
User string `json:"user" form:"user"`
Password string `json:"password" form:"password"`
}

View File

@@ -10,7 +10,7 @@ type WebsiteCreate struct {
Domains []string `form:"domains" json:"domains"`
Ports []uint `form:"ports" json:"ports"`
Path string `form:"path" json:"path"`
PHP string `form:"php" json:"php"`
PHP int `form:"php" json:"php"`
DB bool `form:"db" json:"db"`
DBType string `form:"db_type" json:"db_type"`
DBName string `form:"db_name" json:"db_name"`

View File

@@ -166,48 +166,48 @@ func Http(r chi.Router) {
container := service.NewContainerService()
r.Get("/", container.List)
r.Get("/search", container.Search)
r.Post("/create", container.Create)
r.Post("/remove", container.Remove)
r.Post("/start", container.Start)
r.Post("/stop", container.Stop)
r.Post("/restart", container.Restart)
r.Post("/pause", container.Pause)
r.Post("/unpause", container.Unpause)
r.Get("/inspect", container.Inspect)
r.Post("/kill", container.Kill)
r.Post("/rename", container.Rename)
r.Get("/stats", container.Stats)
r.Get("/exist", container.Exist)
r.Get("/logs", container.Logs)
r.Post("/", container.Create)
r.Delete("/{id}", container.Remove)
r.Post("/{id}/start", container.Start)
r.Post("/{id}/stop", container.Stop)
r.Post("/{id}/restart", container.Restart)
r.Post("/{id}/pause", container.Pause)
r.Post("/{id}/unpause", container.Unpause)
r.Get("/{id}/inspect", container.Inspect)
r.Post("/{id}/kill", container.Kill)
r.Post("/{id}/rename", container.Rename)
r.Get("/{id}/stats", container.Stats)
r.Get("/{id}/exist", container.Exist)
r.Get("/{id}/logs", container.Logs)
r.Post("/prune", container.Prune)
})
r.Route("/network", func(r chi.Router) {
containerNetwork := service.NewContainerNetworkService()
r.Get("/list", containerNetwork.List)
r.Post("/create", containerNetwork.Create)
r.Post("/remove", containerNetwork.Remove)
r.Get("/exist", containerNetwork.Exist)
r.Get("/inspect", containerNetwork.Inspect)
r.Post("/connect", containerNetwork.Connect)
r.Post("/disconnect", containerNetwork.Disconnect)
r.Get("/", containerNetwork.List)
r.Post("/", containerNetwork.Create)
r.Delete("/{id}", containerNetwork.Remove)
r.Get("/{id}/exist", containerNetwork.Exist)
r.Get("/{id}/inspect", containerNetwork.Inspect)
r.Post("/{network}/connect", containerNetwork.Connect)
r.Post("/{network}/disconnect", containerNetwork.Disconnect)
r.Post("/prune", containerNetwork.Prune)
})
r.Route("/image", func(r chi.Router) {
containerImage := service.NewContainerImageService()
r.Get("/list", containerImage.List)
r.Get("/exist", containerImage.Exist)
r.Post("/pull", containerImage.Pull)
r.Post("/remove", containerImage.Remove)
r.Get("/inspect", containerImage.Inspect)
r.Get("/", containerImage.List)
r.Get("/{id}/exist", containerImage.Exist)
r.Post("/", containerImage.Pull)
r.Delete("/{id}", containerImage.Remove)
r.Get("/{id}", containerImage.Inspect)
r.Post("/prune", containerImage.Prune)
})
r.Route("/volume", func(r chi.Router) {
containerVolume := service.NewContainerVolumeService()
r.Get("/list", containerVolume.List)
r.Post("/create", containerVolume.Create)
r.Get("/exist", containerVolume.Exist)
r.Post("/remove", containerVolume.Remove)
r.Get("/inspect", containerVolume.Inspect)
r.Get("/", containerVolume.List)
r.Post("/", containerVolume.Create)
r.Get("/{id}/exist", containerVolume.Exist)
r.Delete("/{id}", containerVolume.Remove)
r.Get("/{id}", containerVolume.Inspect)
r.Post("/prune", containerVolume.Prune)
})
})
@@ -219,7 +219,7 @@ func Http(r chi.Router) {
r.Get("/content", file.Content)
r.Post("/save", file.Save)
r.Post("/delete", file.Delete)
r.Post("/upload", file.Upload)
r.Post("/upload", file.Upload) // TODO fix
r.Post("/move", file.Move)
r.Post("/copy", file.Copy)
r.Get("/download", file.Download)

View File

@@ -6,7 +6,6 @@ import (
"fmt"
"path/filepath"
"github.com/go-rat/utils/env"
"github.com/go-rat/utils/hash"
"github.com/goccy/go-yaml"
"github.com/gookit/color"
@@ -61,30 +60,11 @@ func (s *CliService) Update(ctx context.Context, cmd *cli.Command) error {
return fmt.Errorf("获取最新版本失败:%v", err)
}
// TODO 需要修改接口直接把arch传过去
var ver, url, checksum string
if env.IsX86() {
for _, v := range panel.Downloads {
if v.Arch == "amd64" {
ver = panel.Version
url = v.URL
checksum = v.Checksum
break
}
}
} else if env.IsArm() {
for _, v := range panel.Downloads {
if v.Arch == "arm64" {
ver = panel.Version
url = v.URL
checksum = v.Checksum
break
}
}
} else {
return errors.New("不支持的架构")
download := str.FirstElement(panel.Downloads)
if download == nil {
return fmt.Errorf("下载地址为空")
}
ver, url, checksum := panel.Version, download.URL, download.Checksum
return s.setting.UpdatePanel(ver, url, checksum)
}

View File

@@ -7,7 +7,6 @@ import (
"strings"
"github.com/go-rat/chix"
"github.com/go-rat/utils/env"
"github.com/hashicorp/go-version"
"github.com/spf13/cast"
@@ -17,6 +16,7 @@ import (
"github.com/TheTNB/panel/pkg/api"
"github.com/TheTNB/panel/pkg/db"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/str"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/types"
)
@@ -285,31 +285,12 @@ func (s *InfoService) Update(w http.ResponseWriter, r *http.Request) {
return
}
// TODO 需要修改接口直接把arch传过去
var ver, url, checksum string
if env.IsX86() {
for _, v := range panel.Downloads {
if v.Arch == "amd64" {
ver = panel.Version
url = v.URL
checksum = v.Checksum
break
}
}
} else if env.IsArm() {
for _, v := range panel.Downloads {
if v.Arch == "arm64" {
ver = panel.Version
url = v.URL
checksum = v.Checksum
break
}
}
} else {
Error(w, http.StatusInternalServerError, "不支持的架构")
download := str.FirstElement(panel.Downloads)
if download == nil {
Error(w, http.StatusInternalServerError, "获取下载链接失败")
return
}
ver, url, checksum := panel.Version, download.URL, download.Checksum
types.Status = types.StatusUpgrade
if err = s.settingRepo.UpdatePanel(ver, url, checksum); err != nil {

View File

@@ -59,9 +59,6 @@ func (s *SSHService) Session(w http.ResponseWriter, r *http.Request) {
upGrader := websocket.Upgrader{
ReadBufferSize: 4096,
WriteBufferSize: 4096,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
ws, err := upGrader.Upgrade(w, r, nil)

View File

@@ -2,20 +2,25 @@ package api
import (
"fmt"
"slices"
"time"
"github.com/go-rat/utils/env"
)
type VersionDownload struct {
URL string `json:"url"`
Arch string `json:"arch"`
Checksum string `json:"checksum"`
}
type Version struct {
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Type string `json:"type"`
Version string `json:"version"`
Description string `json:"description"`
Downloads []struct {
URL string `json:"url"`
Arch string `json:"arch"`
Checksum string `json:"checksum"`
} `json:"downloads"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Type string `json:"type"`
Version string `json:"version"`
Description string `json:"description"`
Downloads []VersionDownload `json:"downloads"`
}
type Versions []Version
@@ -35,6 +40,14 @@ func (r *API) LatestVersion() (*Version, error) {
return nil, err
}
arch := "amd64"
if env.IsArm() {
arch = "arm64"
}
slices.DeleteFunc(version.Downloads, func(item VersionDownload) bool {
return item.Arch != arch
})
return version, nil
}

View File

@@ -1,3 +0,0 @@
# storage
storage 目录存放应用运行时产生的文件,如上传的文件、缓存文件等。

View File

@@ -9,18 +9,18 @@ export default {
dnsProviders: (): Promise<AxiosResponse<any>> => request.get('/cert/dnsProviders'),
// 证书算法列表
algorithms: (): Promise<AxiosResponse<any>> => request.get('/cert/algorithms'),
// ACME 用户列表
users: (page: number, limit: number): Promise<AxiosResponse<any>> =>
request.get('/cert/users', { params: { page, limit } }),
// ACME 用户详情
userInfo: (id: number): Promise<AxiosResponse<any>> => request.get(`/cert/users/${id}`),
// ACME 用户添加
userAdd: (data: any): Promise<AxiosResponse<any>> => request.post('/cert/users', data),
// ACME 用户更新
userUpdate: (id: number, data: any): Promise<AxiosResponse<any>> =>
request.put(`/cert/users/${id}`, data),
// ACME 用户删除
userDelete: (id: number): Promise<AxiosResponse<any>> => request.delete(`/cert/users/${id}`),
// ACME 账号列表
accounts: (page: number, limit: number): Promise<AxiosResponse<any>> =>
request.get('/cert/account', { params: { page, limit } }),
// ACME 账号详情
accountInfo: (id: number): Promise<AxiosResponse<any>> => request.get(`/cert/account/${id}`),
// ACME 账号添加
accountAdd: (data: any): Promise<AxiosResponse<any>> => request.post('/cert/account', data),
// ACME 账号更新
accountUpdate: (id: number, data: any): Promise<AxiosResponse<any>> =>
request.put(`/cert/account/${id}`, data),
// ACME 账号删除
accountDelete: (id: number): Promise<AxiosResponse<any>> => request.delete(`/cert/account/${id}`),
// DNS 记录列表
dns: (page: number, limit: number): Promise<AxiosResponse<any>> =>
request.get('/cert/dns', { params: { page, limit } }),
@@ -35,23 +35,26 @@ export default {
dnsDelete: (id: number): Promise<AxiosResponse<any>> => request.delete(`/cert/dns/${id}`),
// 证书列表
certs: (page: number, limit: number): Promise<AxiosResponse<any>> =>
request.get('/cert/certs', { params: { page, limit } }),
request.get('/cert/cert', { params: { page, limit } }),
// 证书详情
certInfo: (id: number): Promise<AxiosResponse<any>> => request.get(`/cert/certs/${id}`),
certInfo: (id: number): Promise<AxiosResponse<any>> => request.get(`/cert/cert/${id}`),
// 证书添加
certAdd: (data: any): Promise<AxiosResponse<any>> => request.post('/cert/certs', data),
certAdd: (data: any): Promise<AxiosResponse<any>> => request.post('/cert/cert', data),
// 证书更新
certUpdate: (id: number, data: any): Promise<AxiosResponse<any>> =>
request.put(`/cert/certs/${id}`, data),
request.put(`/cert/cert/${id}`, data),
// 证书删除
certDelete: (id: number): Promise<AxiosResponse<any>> => request.delete(`/cert/certs/${id}`),
certDelete: (id: number): Promise<AxiosResponse<any>> => request.delete(`/cert/cert/${id}`),
// 签发
obtain: (id: number): Promise<AxiosResponse<any>> => request.post(`/cert/obtain`, { id }),
obtain: (id: number): Promise<AxiosResponse<any>> =>
request.post(`/cert/cert/${id}/obtain`, { id }),
// 续签
renew: (id: number): Promise<AxiosResponse<any>> => request.post(`/cert/renew`, { id }),
renew: (id: number): Promise<AxiosResponse<any>> =>
request.post(`/cert/cert/${id}/renew`, { id }),
// 获取 DNS 记录
manualDNS: (id: number): Promise<AxiosResponse<any>> => request.post(`/cert/manualDNS`, { id }),
manualDNS: (id: number): Promise<AxiosResponse<any>> =>
request.post(`/cert/cert/${id}/manualDNS`, { id }),
// 部署
deploy: (id: number, website_id: number): Promise<AxiosResponse<any>> =>
request.post(`/cert/deploy`, { id, website_id })
request.post(`/cert/cert/${id}/deploy`, { id, website_id })
}

View File

@@ -5,103 +5,101 @@ import { request } from '@/utils'
export default {
// 获取容器列表
containerList: (page: number, limit: number): Promise<AxiosResponse<any>> =>
request.get('/container/list', { params: { page, limit } }),
request.get('/container/container', { params: { page, limit } }),
// 添加容器
containerCreate: (config: any): Promise<AxiosResponse<any>> =>
request.post('/container/create', config),
request.post('/container/container', config),
// 删除容器
containerRemove: (id: string): Promise<AxiosResponse<any>> =>
request.post(`/container/remove`, { id }),
request.delete(`/container/container/${id}`),
// 启动容器
containerStart: (id: string): Promise<AxiosResponse<any>> =>
request.post(`/container/start`, { id }),
request.post(`/container/container/${id}/start`),
// 停止容器
containerStop: (id: string): Promise<AxiosResponse<any>> =>
request.post(`/container/stop`, { id }),
request.post(`/container/container/${id}/stop`),
// 重启容器
containerRestart: (id: string): Promise<AxiosResponse<any>> =>
request.post(`/container/restart`, { id }),
request.post(`/container/container/${id}/restart`),
// 暂停容器
containerPause: (id: string): Promise<AxiosResponse<any>> =>
request.post(`/container/pause`, { id }),
request.post(`/container/container/${id}/pause`),
// 恢复容器
containerUnpause: (id: string): Promise<AxiosResponse<any>> =>
request.post(`/container/unpause`, { id }),
request.post(`/container/container/${id}/unpause`),
// 获取容器详情
containerDetail: (id: string): Promise<AxiosResponse<any>> =>
request.get(`/container/detail`, { params: { id } }),
request.get(`/container/container/${id}/detail`),
// 杀死容器
containerKill: (id: string): Promise<AxiosResponse<any>> =>
request.post(`/container/kill`, { id }),
request.post(`/container/container/${id}/kill`),
// 重命名容器
containerRename: (id: string, name: string): Promise<AxiosResponse<any>> =>
request.post(`/container/rename`, { id, name }),
request.post(`/container/container/${id}/rename`, { name }),
// 获取容器状态
containerStats: (id: string): Promise<AxiosResponse<any>> =>
request.get(`/container/stats`, { params: { id } }),
request.get(`/container/container/${id}/stats`),
// 检查容器是否存在
containerExist: (id: string): Promise<AxiosResponse<any>> =>
request.get(`/container/exist`, { params: { id } }),
request.get(`/container/container/${id}/exist`),
// 获取容器日志
containerLogs: (id: string): Promise<AxiosResponse<any>> =>
request.get(`/container/logs`, { params: { id } }),
request.get(`/container/container/${id}/logs`),
// 清理容器
containerPrune: (): Promise<AxiosResponse<any>> => request.post(`/container/prune`),
containerPrune: (): Promise<AxiosResponse<any>> => request.post(`/container/container/prune`),
// 获取网络列表
networkList: (page: number, limit: number): Promise<AxiosResponse<any>> =>
request.get(`/container/network/list`, { params: { page, limit } }),
request.get(`/container/network`, { params: { page, limit } }),
// 创建网络
networkCreate: (config: any): Promise<AxiosResponse<any>> =>
request.post(`/container/network/create`, config),
request.post(`/container/network`, config),
// 删除网络
networkRemove: (id: string): Promise<AxiosResponse<any>> =>
request.post(`/container/network/remove`, { id }),
request.delete(`/container/network/${id}`),
// 检查网络是否存在
networkExist: (id: string): Promise<AxiosResponse<any>> =>
request.get(`/container/network/exist`, { params: { id } }),
request.get(`/container/network/${id}/exist`),
// 获取网络详情
networkInspect: (id: string): Promise<AxiosResponse<any>> =>
request.get(`/container/network/inspect`, { params: { id } }),
request.get(`/container/network/${id}/inspect`),
// 连接容器到网络
networkConnect: (config: any): Promise<AxiosResponse<any>> =>
request.post(`/container/network/connect`, config),
networkConnect: (network: string, container: string): Promise<AxiosResponse<any>> =>
request.post(`/container/network/${network}/connect`, container),
// 断开容器到网络的连接
networkDisconnect: (config: any): Promise<AxiosResponse<any>> =>
request.post(`/container/network/disconnect`, config),
networkDisconnect: (network: string, container: string): Promise<AxiosResponse<any>> =>
request.post(`/container/network/${network}/disconnect`, container),
// 清理网络
networkPrune: (): Promise<AxiosResponse<any>> => request.post(`/container/network/prune`),
// 获取镜像列表
imageList: (page: number, limit: number): Promise<AxiosResponse<any>> =>
request.get(`/container/image/list`, { params: { page, limit } }),
request.get(`/container/image`, { params: { page, limit } }),
// 检查镜像是否存在
imageExist: (id: string): Promise<AxiosResponse<any>> =>
request.get(`/container/image/exist`, { params: { id } }),
request.get(`/container/image/${id}/exist`),
// 拉取镜像
imagePull: (config: any): Promise<AxiosResponse<any>> =>
request.post(`/container/image/pull`, config),
imagePull: (config: any): Promise<AxiosResponse<any>> => request.post(`/container/image`, config),
// 删除镜像
imageRemove: (id: string): Promise<AxiosResponse<any>> =>
request.post(`/container/image/remove`, { id }),
request.delete(`/container/image/${id}`),
// 获取镜像详情
imageInspect: (id: string): Promise<AxiosResponse<any>> =>
request.get(`/container/image/inspect`, { params: { id } }),
imageInspect: (id: string): Promise<AxiosResponse<any>> => request.get(`/container/image/${id}`),
// 清理镜像
imagePrune: (): Promise<AxiosResponse<any>> => request.post(`/container/image/prune`),
// 获取卷列表
volumeList: (page: number, limit: number): Promise<AxiosResponse<any>> =>
request.get(`/container/volume/list`, { params: { page, limit } }),
request.get(`/container/volume`, { params: { page, limit } }),
// 创建卷
volumeCreate: (config: any): Promise<AxiosResponse<any>> =>
request.post(`/container/volume/create`, config),
request.post(`/container/volume`, config),
// 检查卷是否存在
volumeExist: (id: string): Promise<AxiosResponse<any>> =>
request.get(`/container/volume/exist`, { params: { id } }),
request.get(`/container/volume/${id}/exist`),
// 删除卷
volumeRemove: (id: string): Promise<AxiosResponse<any>> =>
request.post(`/container/volume/remove`, { id }),
request.delete(`/container/volume/${id}`),
// 获取卷详情
volumeInspect: (id: string): Promise<AxiosResponse<any>> =>
request.get(`/container/volume/inspect`, { params: { id } }),
request.get(`/container/volume/${id}`),
// 清理卷
volumePrune: (): Promise<AxiosResponse<any>> => request.post(`/container/volume/prune`)
}

View File

@@ -44,11 +44,11 @@ export default {
group: string
): Promise<AxiosResponse<any>> => request.post('/file/permission', { path, mode, owner, group }),
// 压缩文件
archive: (paths: string[], file: string): Promise<AxiosResponse<any>> =>
request.post('/file/archive', { paths, file }),
compress: (paths: string[], file: string): Promise<AxiosResponse<any>> =>
request.post('/file/compress', { paths, file }),
// 解压文件
unArchive: (file: string, path: string): Promise<AxiosResponse<any>> =>
request.post('/file/unArchive', { file, path }),
unCompress: (file: string, path: string): Promise<AxiosResponse<any>> =>
request.post('/file/unCompress', { file, path }),
// 搜索文件
search: (keyword: string): Promise<AxiosResponse<any>> =>
request.post('/file/search', { keyword }),

View File

@@ -4,16 +4,13 @@ import { request } from '@/utils'
export default {
// 开关
switch: (monitor: boolean): Promise<AxiosResponse<any>> =>
request.post('/monitor/switch', { monitor }),
setting: (): Promise<AxiosResponse<any>> => request.get('/monitor/setting'),
// 保存天数
saveDays: (days: number): Promise<AxiosResponse<any>> =>
request.post('/monitor/saveDays', { days }),
updateSetting: (enabled: boolean, days: number): Promise<AxiosResponse<any>> =>
request.post('/monitor/setting', { enabled, days }),
// 清空监控记录
clear: (): Promise<AxiosResponse<any>> => request.post('/monitor/clear'),
// 监控记录
list: (start: number, end: number): Promise<AxiosResponse<any>> =>
request.get('/monitor/list', { params: { start, end } }),
// 开关和天数
switchAndDays: (): Promise<AxiosResponse<any>> => request.get('/monitor/switchAndDays')
request.get('/monitor/list', { params: { start, end } })
}

View File

@@ -4,32 +4,27 @@ import { request } from '@/utils'
export default {
// 获取防火墙状态
firewallStatus: (): Promise<AxiosResponse<any>> => request.get('/safe/firewallStatus'),
firewallStatus: (): Promise<AxiosResponse<any>> => request.get('/firewall/status'),
// 设置防火墙状态
setFirewallStatus: (status: boolean): Promise<AxiosResponse<any>> =>
request.post('/safe/firewallStatus', { status }),
request.post('/firewall/status', { status }),
// 获取防火墙规则
firewallRules: (page: number, limit: number): Promise<AxiosResponse<any>> =>
request.get('/safe/firewallRules', { params: { page, limit } }),
request.get('/firewall/rule', { params: { page, limit } }),
// 添加防火墙规则
addFirewallRule: (port: string, protocol: string): Promise<AxiosResponse<any>> =>
request.post('/safe/firewallRules', { port, protocol }),
addFirewallRule: (port: number, protocol: string): Promise<AxiosResponse<any>> =>
request.post('/firewall/rule', { port, protocol }),
// 删除防火墙规则
deleteFirewallRule: (port: string, protocol: string): Promise<AxiosResponse<any>> =>
request.delete('/safe/firewallRules', { data: { port, protocol } }),
// 获取SSH状态
sshStatus: (): Promise<AxiosResponse<any>> => request.get('/safe/sshStatus'),
// 设置SSH状态
setSshStatus: (status: boolean): Promise<AxiosResponse<any>> =>
request.post('/safe/sshStatus', { status }),
// 获取SSH端口
sshPort: (): Promise<AxiosResponse<any>> => request.get('/safe/sshPort'),
// 设置SSH端口
setSshPort: (port: number): Promise<AxiosResponse<any>> =>
request.post('/safe/sshPort', { port }),
deleteFirewallRule: (port: number, protocol: string): Promise<AxiosResponse<any>> =>
request.delete('/firewall/rule', { data: { port, protocol } }),
// 获取SSH
ssh: (): Promise<AxiosResponse<any>> => request.get('/safe/ssh'),
// 设置SSH
setSsh: (status: boolean, port: number): Promise<AxiosResponse<any>> =>
request.post('/safe/ssh', { status, port }),
// 获取Ping状态
pingStatus: (): Promise<AxiosResponse<any>> => request.get('/safe/pingStatus'),
pingStatus: (): Promise<AxiosResponse<any>> => request.get('/safe/ping'),
// 设置Ping状态
setPingStatus: (status: boolean): Promise<AxiosResponse<any>> =>
request.post('/safe/pingStatus', { status })
request.post('/safe/ping', { status })
}

View File

@@ -83,23 +83,6 @@
"success": "Update successful"
}
},
"eggs": {
"count": {
"gt10": "What are you doing, ouch!",
"gt4": "Are you awesome, Brother Kun?",
"gt0": "If you look at it one more time, it will explode."
}
},
"about": {
"title": "About the panel",
"tnb": "The TNB development team wishes everyone a happy Dragon Boat Festival in 2024! Never downtime!",
"specialThanks": "Special thanks to the following supporters and {supporter}!",
"links": {
"group": "group",
"channel": "channel",
"supporter": ""
}
},
"apps": {
"title": "Quick App"
}

View File

@@ -83,23 +83,6 @@
"success": "当前已是最新版本"
}
},
"eggs": {
"count": {
"gt10": "你干嘛,哎呦!",
"gt4": "厉不厉害你坤哥",
"gt0": "在多一眼看一眼就会爆炸"
}
},
"about": {
"title": "关于面板",
"tnb": "树新蜂开发组祝大家 2024 端午安康!永不宕机!",
"specialThanks": "特别感谢以下支持者和 {supporter}",
"links": {
"group": "群",
"channel": "频道",
"supporter": ""
}
},
"apps": {
"title": "快捷应用"
}

View File

@@ -286,7 +286,7 @@ const getFilename = (path: string) => {
return parts.pop()!
}
const isArchive = (name: string) => {
const isCompress = (name: string) => {
const ext = getExt(name)
return ['zip', 'rar', '7z', 'tar', 'gz'].includes(ext)
}
@@ -323,7 +323,7 @@ export {
getExt,
getFilename,
getIconByExt,
isArchive,
isCompress,
languageByPath,
lastDirectory
}

View File

@@ -10,31 +10,31 @@ import {
} from 'naive-ui'
import cert from '@/api/panel/cert'
import type { User } from '@/views/cert/types'
import type { Account } from '@/views/cert/types'
let messageReactive: MessageReactive | null = null
const addUserModel = ref<any>({
const addAccountModel = ref<any>({
hmac_encoded: '',
email: '',
kid: '',
key_type: 'P256',
ca: 'letsencrypt'
})
const updateUserModel = ref<any>({
const updateAccountModel = ref<any>({
hmac_encoded: '',
email: '',
kid: '',
key_type: 'P256',
ca: 'letsencrypt'
})
const addUserModal = ref(false)
const updateUserModal = ref(false)
const updateUser = ref<any>()
const addAccountModal = ref(false)
const updateAccountModal = ref(false)
const updateAccount = ref<any>()
const caProviders = ref<any>([])
const algorithms = ref<any>([])
const userColumns: any = [
const accountColumns: any = [
{ title: '邮箱', key: 'email', resizable: true, ellipsis: { tooltip: true } },
{
title: 'CA',
@@ -86,13 +86,13 @@ const userColumns: any = [
size: 'small',
type: 'primary',
onClick: () => {
updateUser.value = row.id
updateUserModel.value.email = row.email
updateUserModel.value.hmac_encoded = row.hmac_encoded
updateUserModel.value.kid = row.kid
updateUserModel.value.key_type = row.key_type
updateUserModel.value.ca = row.ca
updateUserModal.value = true
updateAccount.value = row.id
updateAccountModel.value.email = row.email
updateAccountModel.value.hmac_encoded = row.hmac_encoded
updateAccountModel.value.kid = row.kid
updateAccountModel.value.key_type = row.key_type
updateAccountModel.value.ca = row.ca
updateAccountModal.value = true
}
},
{
@@ -103,9 +103,9 @@ const userColumns: any = [
NPopconfirm,
{
onPositiveClick: async () => {
await cert.userDelete(row.id)
await cert.accountDelete(row.id)
window.$message.success('删除成功')
onUserPageChange(1)
onAccountPageChange(1)
}
},
{
@@ -131,9 +131,9 @@ const userColumns: any = [
}
}
]
const userData = ref<User[]>([] as User[])
const accountData = ref<Account[]>([] as Account[])
const userPagination = reactive({
const accountPagination = reactive({
page: 1,
pageCount: 1,
pageSize: 10,
@@ -143,51 +143,51 @@ const userPagination = reactive({
pageSizes: [10, 20, 50, 100]
})
const onUserPageChange = (page: number) => {
userPagination.page = page
getUserList(page, userPagination.pageSize).then((res) => {
userData.value = res.items
userPagination.itemCount = res.total
userPagination.pageCount = res.total / userPagination.pageSize + 1
const onAccountPageChange = (page: number) => {
accountPagination.page = page
getAccountList(page, accountPagination.pageSize).then((res) => {
accountData.value = res.items
accountPagination.itemCount = res.total
accountPagination.pageCount = res.total / accountPagination.pageSize + 1
})
}
const onUserPageSizeChange = (pageSize: number) => {
userPagination.pageSize = pageSize
onUserPageChange(1)
const onAccountPageSizeChange = (pageSize: number) => {
accountPagination.pageSize = pageSize
onAccountPageChange(1)
}
const getUserList = async (page: number, limit: number) => {
const { data } = await cert.users(page, limit)
const getAccountList = async (page: number, limit: number) => {
const { data } = await cert.accounts(page, limit)
return data
}
const handleAddUser = async () => {
const handleAddAccount = async () => {
messageReactive = window.$message.loading('正在向 CA 注册账号,请耐心等待', {
duration: 0
})
await cert.userAdd(addUserModel.value)
await cert.accountAdd(addAccountModel.value)
messageReactive.destroy()
window.$message.success('添加成功')
addUserModal.value = false
onUserPageChange(1)
addUserModel.value.email = ''
addUserModel.value.hmac_encoded = ''
addUserModel.value.kid = ''
addAccountModal.value = false
onAccountPageChange(1)
addAccountModel.value.email = ''
addAccountModel.value.hmac_encoded = ''
addAccountModel.value.kid = ''
}
const handleUpdateUser = async () => {
const handleUpdateAccount = async () => {
messageReactive = window.$message.loading('正在向 CA 注册账号,请耐心等待', {
duration: 0
})
await cert.userUpdate(updateUser.value, updateUserModel.value)
await cert.accountUpdate(updateAccount.value, updateAccountModel.value)
messageReactive.destroy()
window.$message.success('更新成功')
updateUserModal.value = false
onUserPageChange(1)
updateUserModel.value.email = ''
updateUserModel.value.hmac_encoded = ''
updateUserModel.value.kid = ''
updateAccountModal.value = false
onAccountPageChange(1)
updateAccountModel.value.email = ''
updateAccountModel.value.hmac_encoded = ''
updateAccountModel.value.kid = ''
}
onMounted(() => {
@@ -207,7 +207,7 @@ onMounted(() => {
})
}
})
onUserPageChange(1)
onAccountPageChange(1)
})
</script>
@@ -215,7 +215,7 @@ onMounted(() => {
<n-space vertical size="large">
<n-card rounded-10>
<n-space>
<n-button type="primary" @click="addUserModal = true"> 添加账号 </n-button>
<n-button type="primary" @click="addAccountModal = true"> 添加账号 </n-button>
</n-space>
</n-card>
<n-data-table
@@ -223,16 +223,16 @@ onMounted(() => {
remote
:loading="false"
:scroll-x="1200"
:columns="userColumns"
:data="userData"
:columns="accountColumns"
:data="accountData"
:row-key="(row: any) => row.id"
:pagination="userPagination"
@update:page="onUserPageChange"
@update:page-size="onUserPageSizeChange"
:pagination="accountPagination"
@update:page="onAccountPageChange"
@update:page-size="onAccountPageSizeChange"
/>
</n-space>
<n-modal
v-model:show="addUserModal"
v-model:show="addAccountModal"
preset="card"
title="添加账号"
style="width: 60vw"
@@ -245,10 +245,10 @@ onMounted(() => {
<n-alert type="warning">
境内无法使用 Google CA其他 CA 视网络情况而定建议使用 Let's Encrypt
</n-alert>
<n-form :model="addUserModel">
<n-form :model="addAccountModel">
<n-form-item path="ca" label="CA">
<n-select
v-model:value="addUserModel.ca"
v-model:value="addAccountModel.ca"
placeholder="选择 CA"
clearable
:options="caProviders"
@@ -256,7 +256,7 @@ onMounted(() => {
</n-form-item>
<n-form-item path="key_type" label="密钥类型">
<n-select
v-model:value="addUserModel.key_type"
v-model:value="addAccountModel.key_type"
placeholder="选择密钥类型"
clearable
:options="algorithms"
@@ -264,7 +264,7 @@ onMounted(() => {
</n-form-item>
<n-form-item path="email" label="邮箱">
<n-input
v-model:value="addUserModel.email"
v-model:value="addAccountModel.email"
type="text"
@keydown.enter.prevent
placeholder="输入邮箱地址"
@@ -272,7 +272,7 @@ onMounted(() => {
</n-form-item>
<n-form-item path="kid" label="KID">
<n-input
v-model:value="addUserModel.kid"
v-model:value="addAccountModel.kid"
type="text"
@keydown.enter.prevent
placeholder="输入 KID"
@@ -280,18 +280,18 @@ onMounted(() => {
</n-form-item>
<n-form-item path="hmac_encoded" label="HMAC">
<n-input
v-model:value="addUserModel.hmac_encoded"
v-model:value="addAccountModel.hmac_encoded"
type="text"
@keydown.enter.prevent
placeholder="输入 HMAC"
/>
</n-form-item>
</n-form>
<n-button type="info" block @click="handleAddUser">提交</n-button>
<n-button type="info" block @click="handleAddAccount">提交</n-button>
</n-space>
</n-modal>
<n-modal
v-model:show="updateUserModal"
v-model:show="updateAccountModal"
preset="card"
title="修改账号"
style="width: 60vw"
@@ -304,10 +304,10 @@ onMounted(() => {
<n-alert type="warning">
境内无法使用 Google CA其他 CA 视网络情况而定建议使用 Let's Encrypt
</n-alert>
<n-form :model="updateUserModel">
<n-form :model="updateAccountModel">
<n-form-item path="ca" label="CA">
<n-select
v-model:value="updateUserModel.ca"
v-model:value="updateAccountModel.ca"
placeholder="选择 CA"
clearable
:options="caProviders"
@@ -315,7 +315,7 @@ onMounted(() => {
</n-form-item>
<n-form-item path="key_type" label="密钥类型">
<n-select
v-model:value="updateUserModel.key_type"
v-model:value="updateAccountModel.key_type"
placeholder="选择密钥类型"
clearable
:options="algorithms"
@@ -323,7 +323,7 @@ onMounted(() => {
</n-form-item>
<n-form-item path="email" label="邮箱">
<n-input
v-model:value="updateUserModel.email"
v-model:value="updateAccountModel.email"
type="text"
@keydown.enter.prevent
placeholder="输入邮箱地址"
@@ -331,7 +331,7 @@ onMounted(() => {
</n-form-item>
<n-form-item path="kid" label="KID">
<n-input
v-model:value="updateUserModel.kid"
v-model:value="updateAccountModel.kid"
type="text"
@keydown.enter.prevent
placeholder="输入 KID"
@@ -339,14 +339,14 @@ onMounted(() => {
</n-form-item>
<n-form-item path="hmac_encoded" label="HMAC">
<n-input
v-model:value="updateUserModel.hmac_encoded"
v-model:value="updateAccountModel.hmac_encoded"
type="text"
@keydown.enter.prevent
placeholder="输入 HMAC"
/>
</n-form-item>
</n-form>
<n-button type="info" block @click="handleUpdateUser">提交</n-button>
<n-button type="info" block @click="handleUpdateAccount">提交</n-button>
</n-space>
</n-modal>
</template>

View File

@@ -11,7 +11,7 @@ const addCertModel = ref<any>({
domains: [],
dns_id: 0,
type: 'P256',
user_id: null,
account_id: null,
website_id: 0,
auto_renew: true
})
@@ -19,7 +19,7 @@ const updateCertModel = ref<any>({
domains: [],
dns_id: 0,
type: 'P256',
user_id: null,
account_id: null,
website_id: 0,
auto_renew: true
})
@@ -40,7 +40,7 @@ const deployCertModel = ref<any>({
const algorithms = ref<any>([])
const websites = ref<any>([])
const dns = ref<any>([])
const users = ref<any>([])
const accounts = ref<any>([])
const certColumns: any = [
{
@@ -94,7 +94,7 @@ const certColumns: any = [
},
{
title: '关联账号',
key: 'user_id',
key: 'account_id',
width: 200,
resizable: true,
ellipsis: { tooltip: true },
@@ -102,11 +102,11 @@ const certColumns: any = [
return h(
NTag,
{
type: row.user == null ? 'error' : 'success',
type: row.account == null ? 'error' : 'success',
bordered: false
},
{
default: () => (row.user?.email == null ? '无' : row.user.email)
default: () => (row.account?.email == null ? '无' : row.account.email)
}
)
}
@@ -314,7 +314,7 @@ const certColumns: any = [
updateCertModel.value.domains = row.domains
updateCertModel.value.dns_id = row.dns_id
updateCertModel.value.type = row.type
updateCertModel.value.user_id = row.user_id
updateCertModel.value.account_id = row.account_id
updateCertModel.value.website_id = row.website_id
updateCertModel.value.auto_renew = row.auto_renew
updateCertModal.value = true
@@ -395,7 +395,7 @@ const handleAddCert = async () => {
addCertModel.value.domains = []
addCertModel.value.dns_id = 0
addCertModel.value.type = 'P256'
addCertModel.value.user_id = 0
addCertModel.value.account_id = 0
addCertModel.value.website_id = 0
addCertModel.value.auto_renew = true
await getAsyncData()
@@ -409,7 +409,7 @@ const handleUpdateCert = async () => {
updateCertModel.value.domains = []
updateCertModel.value.dns_id = 0
updateCertModel.value.type = 'P256'
updateCertModel.value.user_id = 0
updateCertModel.value.account_id = 0
updateCertModel.value.website_id = 0
updateCertModel.value.auto_renew = true
await getAsyncData()
@@ -459,10 +459,10 @@ const getAsyncData = async () => {
})
}
const { data: userData } = await cert.users(1, 10000)
users.value = []
for (const item of userData.items) {
users.value.push({
const { data: accountData } = await cert.accounts(1, 10000)
accounts.value = []
for (const item of accountData.items) {
accounts.value.push({
label: item.email,
value: item.id
})
@@ -539,15 +539,15 @@ onMounted(() => {
:options="websites"
/>
</n-form-item>
<n-form-item path="user_id" label="账号">
<n-form-item path="account_id" label="账号">
<n-select
v-model:value="addCertModel.user_id"
v-model:value="addCertModel.account_id"
placeholder="选择用于签发证书的账号"
clearable
:options="users"
:options="accounts"
/>
</n-form-item>
<n-form-item path="user_id" label="DNS">
<n-form-item path="account_id" label="DNS">
<n-select
v-model:value="addCertModel.dns_id"
placeholder="选择用于签发证书的DNS"
@@ -598,15 +598,15 @@ onMounted(() => {
:options="websites"
/>
</n-form-item>
<n-form-item path="user_id" label="账号">
<n-form-item path="account_id" label="账号">
<n-select
v-model:value="updateCertModel.user_id"
v-model:value="updateCertModel.account_id"
placeholder="选择用于签发证书的账号"
clearable
:options="users"
:options="accounts"
/>
</n-form-item>
<n-form-item path="user_id" label="DNS">
<n-form-item path="account_id" label="DNS">
<n-select
v-model:value="updateCertModel.dns_id"
placeholder="选择用于签发证书的DNS"

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import AccountView from '@/views/cert/AccountView.vue'
import CertView from '@/views/cert/CertView.vue'
import DNSView from '@/views/cert/DNSView.vue'
import UserView from '@/views/cert/UserView.vue'
import DnsView from '@/views/cert/DnsView.vue'
const currentTab = ref('cert')
</script>
@@ -10,13 +10,13 @@ const currentTab = ref('cert')
<common-page show-footer>
<n-tabs v-model:value="currentTab" type="line" animated>
<n-tab-pane name="cert" tab="证书列表">
<CertView />
<cert-view />
</n-tab-pane>
<n-tab-pane name="user" tab="账号列表">
<UserView />
<account-view />
</n-tab-pane>
<n-tab-pane name="dns" tab="DNS 列表">
<DNSView />
<dns-view />
</n-tab-pane>
</n-tabs>
</common-page>

View File

@@ -1,6 +1,6 @@
export interface Cert {
id: number
user_id: number
account_id: number
website_id: number
dns_id: number
type: string
@@ -13,7 +13,7 @@ export interface Cert {
updated_at: string
website: Website
dns: DNS
user: User
account: Account
}
export interface Website {
@@ -43,7 +43,7 @@ export interface DNS {
updated_at: string
}
export interface User {
export interface Account {
id: number
email: string
ca: string

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import { NButton, NInput } from 'naive-ui'
import * as api from '@/api/panel/file'
import api from '@/api/panel/file'
import { generateRandomString, getBase } from '@/utils'
import EventBus from '@/utils/event'
@@ -31,8 +31,8 @@ const handleArchive = async () => {
const message = window.$message.loading('正在压缩中...', {
duration: 0
})
await api.default
.archive(selected.value, file.value)
await api
.compress(selected.value, file.value)
.then(() => {
window.$message.success('压缩成功')
show.value = false

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import ArchiveModal from '@/views/file/ArchiveModal.vue'
import CompressModal from '@/views/file/CompressModal.vue'
import ListTable from '@/views/file/ListTable.vue'
import PathInput from '@/views/file/PathInput.vue'
import PermissionModal from '@/views/file/PermissionModal.vue'
@@ -10,7 +10,7 @@ const path = ref('/www')
const selected = ref<string[]>([])
const marked = ref<Marked[]>([])
const archive = ref(false)
const compress = ref(false)
const permission = ref(false)
</script>
@@ -22,17 +22,17 @@ const permission = ref(false)
v-model:path="path"
v-model:selected="selected"
v-model:marked="marked"
v-model:archive="archive"
v-model:compress="compress"
v-model:permission="permission"
/>
<list-table
v-model:path="path"
v-model:selected="selected"
v-model:marked="marked"
v-model:archive="archive"
v-model:compress="compress"
v-model:permission="permission"
/>
<archive-modal v-model:show="archive" v-model:path="path" v-model:selected="selected" />
<compress-modal v-model:show="compress" v-model:path="path" v-model:selected="selected" />
<permission-modal v-model:show="permission" v-model:selected="selected" />
</n-flex>
</common-page>

View File

@@ -6,7 +6,7 @@ import type { RowData } from 'naive-ui/es/data-table/src/interface'
import file from '@/api/panel/file'
import TheIcon from '@/components/custom/TheIcon.vue'
import EventBus from '@/utils/event'
import { checkName, checkPath, getExt, getFilename, getIconByExt, isArchive } from '@/utils/file'
import { checkName, checkPath, getExt, getFilename, getIconByExt, isCompress } from '@/utils/file'
import EditModal from '@/views/file/EditModal.vue'
import type { Marked } from '@/views/file/types'
@@ -14,7 +14,7 @@ const loading = ref(false)
const path = defineModel<string>('path', { type: String, required: true })
const selected = defineModel<any[]>('selected', { type: Array, default: () => [] })
const marked = defineModel<Marked[]>('marked', { type: Array, default: () => [] })
const archive = defineModel<boolean>('archive', { type: Boolean, required: true })
const compress = defineModel<boolean>('compress', { type: Boolean, required: true })
const permission = defineModel<boolean>('permission', { type: Boolean, required: true })
const editorModal = ref(false)
const editorFile = ref('')
@@ -24,8 +24,8 @@ const renameModel = ref({
source: '',
target: ''
})
const unArchiveModal = ref(false)
const unArchiveModel = ref({
const unCompressModal = ref(false)
const unCompressModel = ref({
path: '',
file: ''
})
@@ -125,7 +125,7 @@ const columns: DataTableColumns<RowData> = [
onClick: () => {
if (row.dir) {
selected.value = [row.full]
archive.value = true
compress.value = true
} else {
window.open('/api/panel/file/download?path=' + encodeURIComponent(row.full))
}
@@ -189,8 +189,8 @@ const columns: DataTableColumns<RowData> = [
{ label: '复制', value: 'copy' },
{ label: '移动', value: 'move' },
{ label: '权限', value: 'permission' },
{ label: '压缩', value: 'archive' },
{ label: '解压', value: 'unarchive', disabled: !isArchive(row.name) }
{ label: '压缩', value: 'compress' },
{ label: '解压', value: 'uncompress', disabled: !isCompress(row.name) }
],
onUpdateValue: (value) => {
switch (value) {
@@ -218,14 +218,14 @@ const columns: DataTableColumns<RowData> = [
selected.value = [row.full]
permission.value = true
break
case 'archive':
case 'compress':
selected.value = [row.full]
archive.value = true
compress.value = true
break
case 'unarchive':
unArchiveModel.value.file = row.full
unArchiveModel.value.path = path.value
unArchiveModal.value = true
case 'uncompress':
unCompressModel.value.file = row.full
unCompressModel.value.path = path.value
unCompressModal.value = true
break
}
}
@@ -306,11 +306,11 @@ const handleRename = () => {
})
}
const handleUnArchive = () => {
const handleUnCompress = () => {
// 移除首位的 / 去检测
if (
!unArchiveModel.value.path.startsWith('/') ||
!checkPath(unArchiveModel.value.path.slice(1))
!unCompressModel.value.path.startsWith('/') ||
!checkPath(unCompressModel.value.path.slice(1))
) {
window.$message.error('路径不合法')
return
@@ -319,11 +319,11 @@ const handleUnArchive = () => {
duration: 0
})
file
.unArchive(unArchiveModel.value.file, unArchiveModel.value.path)
.unCompress(unCompressModel.value.file, unCompressModel.value.path)
.then(() => {
message?.destroy()
window.$message.success('解压成功')
unArchiveModal.value = false
unCompressModal.value = false
EventBus.emit('file:refresh')
})
.catch(() => {
@@ -391,9 +391,9 @@ onUnmounted(() => {
</n-flex>
</n-modal>
<n-modal
v-model:show="unArchiveModal"
v-model:show="unCompressModal"
preset="card"
:title="'解压缩 - ' + unArchiveModel.file"
:title="'解压缩 - ' + unCompressModel.file"
style="width: 60vw"
size="huge"
:bordered="false"
@@ -402,10 +402,10 @@ onUnmounted(() => {
<n-flex vertical>
<n-form>
<n-form-item label="解压到">
<n-input v-model:value="unArchiveModel.path" />
<n-input v-model:value="unCompressModel.path" />
</n-form-item>
</n-form>
<n-button type="primary" @click="handleUnArchive">解压</n-button>
<n-button type="primary" @click="handleUnCompress">解压</n-button>
</n-flex>
</n-modal>
</template>

View File

@@ -10,7 +10,7 @@ import type { Marked } from '@/views/file/types'
const path = defineModel<string>('path', { type: String, required: true })
const selected = defineModel<string[]>('selected', { type: Array, default: () => [] })
const marked = defineModel<Marked[]>('marked', { type: Array, default: () => [] })
const archive = defineModel<boolean>('archive', { type: Boolean, required: true })
const compress = defineModel<boolean>('compress', { type: Boolean, required: true })
const permission = defineModel<boolean>('permission', { type: Boolean, required: true })
const upload = ref(false)
@@ -126,7 +126,7 @@ const bulkDelete = () => {
<n-button-group v-if="selected.length">
<n-button @click="handleCopy"> 复制 </n-button>
<n-button @click="handleMove"> 移动 </n-button>
<n-button @click="archive = true"> 压缩 </n-button>
<n-button @click="compress = true"> 压缩 </n-button>
<n-button @click="permission = true"> 权限 </n-button>
<n-popconfirm @positive-click="bulkDelete">
<template #trigger>

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import type { UploadCustomRequestOptions } from 'naive-ui'
import * as api from '@/api/panel/file'
import api from '@/api/panel/file'
import EventBus from '@/utils/event'
const show = defineModel<boolean>('show', { type: Boolean, required: true })
@@ -11,7 +11,7 @@ const upload = ref<any>(null)
const uploadRequest = ({ file, onFinish, onError, onProgress }: UploadCustomRequestOptions) => {
const formData = new FormData()
formData.append('file', file.file as File)
api.default
api
.upload(`${path.value}/${file.name}`, formData, onProgress)
.then(() => {
window.$message.success(`上传 ${file.name} 成功`)

View File

@@ -112,18 +112,6 @@ const handleUpdate = () => {
})
}
let eggCount = 0
const getEgg = () => {
eggCount++
if (eggCount > 10) {
return t('homeIndex.eggs.count.gt10')
} else if (eggCount > 4) {
return t('homeIndex.eggs.count.gt4')
} else {
return t('homeIndex.eggs.count.gt0')
}
}
const toSponsor = () => {
if (locale.value === 'en') {
window.open('https://opencollective.com/tnb')
@@ -540,101 +528,49 @@ onUnmounted(() => {
</div>
</n-gi>
</n-grid>
<n-grid
x-gap="12"
y-gap="12"
cols="1 s:1 m:2 l:3 xl:3 2xl:3"
item-responsive
responsive="screen"
>
<n-gi span="2 s:1 m:1 l:2">
<div min-w-375 flex-1>
<n-card :segmented="true" rounded-10 size="small" :title="$t('homeIndex.apps.title')">
<n-grid
v-if="homeApps"
x-gap="12"
y-gap="12"
cols="3 s:1 m:2 l:3"
item-responsive
responsive="screen"
<div min-w-375 flex-1>
<n-card :segmented="true" rounded-10 size="small" :title="$t('homeIndex.apps.title')">
<n-grid
v-if="homeApps"
x-gap="12"
y-gap="12"
cols="3 s:1 m:2 l:3"
item-responsive
responsive="screen"
>
<n-gi v-for="item in homeApps" :key="item.name">
<n-card
:segmented="true"
size="small"
cursor-pointer
rounded-10
hover:card-shadow
@click="handleManageApp(item.slug)"
>
<n-gi v-for="item in homeApps" :key="item.name">
<n-card
:segmented="true"
size="small"
cursor-pointer
rounded-10
hover:card-shadow
@click="handleManageApp(item.slug)"
>
<n-space>
<n-thing>
<template #avatar>
<n-avatar class="mt-4">
<n-icon>
<icon-mdi:package-variant-closed />
</n-icon>
</n-avatar>
</template>
<template #header>
{{ item.name }}
</template>
<template #description>
{{ item.version }}
</template>
</n-thing>
</n-space>
</n-card>
</n-gi>
</n-grid>
<n-skeleton v-else text :repeat="9" />
</n-card>
</div>
</n-gi>
<n-gi>
<div min-w-375 flex-1>
<n-card
:segmented="true"
rounded-10
size="small"
:title="$t('homeIndex.about.title')"
>
<template #header-extra>
<n-popover trigger="hover">
<template #trigger>
<n-icon size="20">
<icon-mdi:about-circle-outline />
</n-icon>
</template>
<span>{{ getEgg() }}</span>
</n-popover>
</template>
<n-space vertical :size="12">
<n-alert type="success">
{{ $t('homeIndex.about.tnb') }}
</n-alert>
<n-alert type="info">
<span
v-html="
$t('homeIndex.about.specialThanks', {
supporter: `<a target='_blank' href='https://www.weixiaoduo.com/'>「薇晓朵」<\/a>`
})
"
>
</span>
</n-alert>
<n-image
src="https://mirror.ghproxy.com/https://raw.githubusercontent.com/TheTNB/sponsor/main/sponsors.svg"
width="100%"
preview-disabled
lazy
@click="toSponsor"
/>
</n-space>
</n-card>
</div>
</n-gi>
</n-grid>
<n-space>
<n-thing>
<template #avatar>
<n-avatar class="mt-4">
<n-icon>
<icon-mdi:package-variant-closed />
</n-icon>
</n-avatar>
</template>
<template #header>
{{ item.name }}
</template>
<template #description>
{{ item.version }}
</template>
</n-thing>
</n-space>
</n-card>
</n-gi>
</n-grid>
<n-skeleton v-else text :repeat="9" />
</n-card>
</div>
</n-space>
</div>
</AppPage>

View File

@@ -6,10 +6,10 @@ import { useI18n } from 'vue-i18n'
import info from '@/api/panel/info'
import { router } from '@/router'
import { formatDateTime } from '@/utils'
import type { PanelInfo } from '@/views/home/types'
import type { Version } from '@/views/home/types'
const { t } = useI18n()
const versions = ref<PanelInfo[] | null>(null)
const versions = ref<Version[] | null>(null)
let messageReactive: MessageReactive | null = null
const getVersions = () => {
@@ -75,9 +75,9 @@ onMounted(() => {
:type="Number(index) == 0 ? 'info' : 'default'"
:key="index"
:title="item.version"
:time="formatDateTime(item.date)"
:time="formatDateTime(item.updated_at)"
>
<pre v-html="item.body" />
<pre v-html="item.description" />
</n-timeline-item>
</n-timeline>
<div v-else pt-40>

View File

@@ -175,13 +175,17 @@ export interface HomeApp {
updated_at: string
}
export interface PanelInfo {
name: string
version: string
download_name: string
download_url: string
body: string
date: string
checksums: string
checksums_url: string
export interface VersionDownload {
url: string
arch: string
checksum: string
}
export interface Version {
created_at: string
updated_at: string
type: string
version: string
description: string
downloads: VersionDownload[]
}

View File

@@ -414,21 +414,15 @@ const getData = async () => {
})
}
const getSwitchAndDays = async () => {
monitor.switchAndDays().then((res) => {
monitorSwitch.value = res.data.switch
const getSetting = async () => {
monitor.setting().then((res) => {
monitorSwitch.value = res.data.enabled
saveDay.value = res.data.days
})
}
const handleSwitch = async () => {
monitor.switch(monitorSwitch.value).then(() => {
window.$message.success('操作成功')
})
}
const handleDays = async () => {
monitor.saveDays(saveDay.value).then(() => {
const handleUpdate = async () => {
monitor.updateSetting(monitorSwitch.value, saveDay.value).then(() => {
window.$message.success('操作成功')
})
}
@@ -469,7 +463,7 @@ watch([start, end], () => {
})
onMounted(() => {
getSwitchAndDays()
getSetting()
getData()
})
</script>
@@ -485,7 +479,7 @@ onMounted(() => {
>
<n-grid cols="1 s:1 m:1 l:24 xl:24 2xl:24" item-responsive responsive="screen">
<n-form-item-gi :span="3" label="开启监控">
<n-switch v-model:value="monitorSwitch" @update-value="handleSwitch" />
<n-switch v-model:value="monitorSwitch" @update-value="handleUpdate" />
</n-form-item-gi>
<n-form-item-gi :span="6" label="保存天数">
<n-input-number v-model:value="saveDay">
@@ -493,7 +487,7 @@ onMounted(() => {
</n-input-number>
</n-form-item-gi>
<n-form-item-gi :span="2">
<n-button type="primary" @click="handleDays">确定</n-button>
<n-button type="primary" @click="handleUpdate">确定</n-button>
</n-form-item-gi>
<n-form-item-gi :span="9" label="时间选择">
<n-date-picker v-model:value="start" type="datetime" />

View File

@@ -87,7 +87,7 @@ const pagination = reactive({
const selectedRowKeys = ref<any>([])
const addModel = ref({
port: '',
port: 80,
protocol: 'tcp'
})
@@ -105,7 +105,7 @@ const handleDelete = async (row: any) => {
const handleAdd = async () => {
await safe.addFirewallRule(addModel.value.port, addModel.value.protocol).then(() => {
window.$message.success(t('safeIndex.alerts.add'))
addModel.value.port = ''
addModel.value.port = 80
addModel.value.protocol = 'tcp'
})
getFirewallRules(pagination.page, pagination.pageSize).then((res) => {
@@ -124,15 +124,13 @@ const getSetting = async () => {
safe.firewallStatus().then((res) => {
model.value.firewallStatus = res.data
})
safe.sshStatus().then((res) => {
model.value.sshStatus = res.data
safe.ssh().then((res) => {
model.value.sshStatus = res.data.status
model.value.sshPort = res.data.port
})
safe.pingStatus().then((res) => {
model.value.pingStatus = res.data
})
safe.sshPort().then((res) => {
model.value.sshPort = res.data
})
}
const handleFirewallStatus = () => {
@@ -141,8 +139,8 @@ const handleFirewallStatus = () => {
})
}
const handleSshStatus = () => {
safe.setSshStatus(model.value.sshStatus).then(() => {
const handleSsh = () => {
safe.setSsh(model.value.sshStatus, model.value.sshPort).then(() => {
window.$message.success(t('safeIndex.alerts.setup'))
})
}
@@ -153,12 +151,6 @@ const handlePingStatus = () => {
})
}
const handleSshPort = () => {
safe.setSshPort(model.value.sshPort).then(() => {
window.$message.success(t('safeIndex.alerts.setup'))
})
}
const batchDelete = async () => {
if (selectedRowKeys.value.length === 0) {
window.$message.info(t('safeIndex.alerts.select'))
@@ -231,7 +223,7 @@ onMounted(() => {
<n-form-item :label="$t('safeIndex.filter.fields.ssh.label')">
<n-switch
v-model:value="model.sshStatus"
@update:value="handleSshStatus"
@update:value="handleSsh"
:checkedChildren="$t('safeIndex.filter.fields.ssh.checked')"
:unCheckedChildren="$t('safeIndex.filter.fields.ssh.unchecked')"
/>
@@ -245,7 +237,7 @@ onMounted(() => {
/>
</n-form-item>
<n-form-item :label="$t('safeIndex.filter.fields.port.label')">
<n-input-number v-model:value="model.sshPort" @blur="handleSshPort" />
<n-input-number v-model:value="model.sshPort" @blur="handleSsh" />
</n-form-item>
</n-form>
</n-card>
@@ -257,9 +249,11 @@ onMounted(() => {
{{ $t('safeIndex.confirm.batchDelete') }}
</n-popconfirm>
<n-text>{{ $t('safeIndex.portControl.title') }}</n-text>
<n-input
<n-input-number
v-model:value="addModel.port"
:placeholder="$t('safeIndex.portControl.fields.port.placeholder')"
:min="1"
:max="65535"
/>
<n-select
v-model:value="addModel.protocol"

View File

@@ -191,7 +191,7 @@ const addModel = ref({
name: '',
domains: [] as Array<string>,
ports: [] as Array<number>,
php: '0',
php: 0,
db: false,
db_type: '0',
db_name: '',
@@ -326,7 +326,7 @@ const handleAdd = async () => {
name: '',
domains: [] as Array<string>,
ports: [] as Array<number>,
php: '0',
php: 0,
db: false,
db_type: '0',
db_name: '',