Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions core/app/api/v2/command.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,102 @@
package v2

import (
"encoding/csv"
"errors"
"fmt"
"io"

"github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/app/repo"
"github.com/gin-gonic/gin"
)

// @Tags Command
// @Summary Export command
// @Success 200 {string} path
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/commands/upload [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"导出快速命令","formatEN":"export quick commands"}
func (b *BaseApi) UploadCommandCsv(c *gin.Context) {
form, err := c.MultipartForm()
if err != nil {
helper.BadRequest(c, err)
return
}
files := form.File["file"]
if len(files) == 0 {
helper.BadRequest(c, errors.New("no such files"))
return
}
uploadFile, _ := files[0].Open()
reader := csv.NewReader(uploadFile)
if _, err := reader.Read(); err != nil {
helper.BadRequest(c, fmt.Errorf("read title failed, err: %v", err))
return
}
groupRepo := repo.NewIGroupRepo()
group, _ := groupRepo.Get(groupRepo.WithByDefault(true))
var commands []dto.CommandInfo
for {
record, err := reader.Read()
if err == io.EOF {
break
}
if err != nil {
helper.BadRequest(c, fmt.Errorf("read content failed, err: %v", err))
return
}
if len(record) >= 2 {
commands = append(commands, dto.CommandInfo{
Name: record[0],
Type: "command",
GroupID: group.ID,
Command: record[1],
GroupBelong: group.Name,
})
}
}

helper.SuccessWithData(c, commands)
}

// @Tags Command
// @Summary Export command
// @Success 200 {string} path
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/commands/export [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"导出快速命令","formatEN":"export quick commands"}
func (b *BaseApi) ExportCommands(c *gin.Context) {
file, err := commandService.Export()
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, file)
}

// @Tags Command
// @Summary Import command
// @Success 200 {string} path
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/commands/import [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"导入快速命令","formatEN":"import quick commands"}
func (b *BaseApi) ImportCommands(c *gin.Context) {
var req dto.CommandImport
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}

for _, item := range req.Items {
_ = commandService.Create(item)
}
helper.Success(c)
}

// @Tags Command
// @Summary Create command
// @Accept json
Expand Down
4 changes: 4 additions & 0 deletions core/app/dto/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ type SearchCommandWithPage struct {
Info string `json:"info"`
}

type CommandImport struct {
Items []CommandOperate `json:"items"`
}

type CommandOperate struct {
ID uint `json:"id"`
Type string `json:"type"`
Expand Down
31 changes: 31 additions & 0 deletions core/app/service/command.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
package service

import (
"fmt"
"os"
"path"
"time"

"github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/app/repo"
"github.com/1Panel-dev/1Panel/core/buserr"
"github.com/1Panel-dev/1Panel/core/constant"
"github.com/1Panel-dev/1Panel/core/global"
"github.com/1Panel-dev/1Panel/core/utils/csv"
"github.com/jinzhu/copier"
)

Expand All @@ -17,6 +24,8 @@ type ICommandService interface {
Create(req dto.CommandOperate) error
Update(req dto.CommandOperate) error
Delete(ids []uint) error

Export() (string, error)
}

func NewICommandService() ICommandService {
Expand Down Expand Up @@ -98,6 +107,28 @@ func (u *CommandService) SearchWithPage(req dto.SearchCommandWithPage) (int64, i
return total, dtoCommands, err
}

func (u *CommandService) Export() (string, error) {
commands, err := commandRepo.List(repo.WithByType("command"))
if err != nil {
return "", err
}
var list []csv.CommandTemplate
for _, item := range commands {
list = append(list, csv.CommandTemplate{
Name: item.Name,
Command: item.Command,
})
}
tmpFileName := path.Join(global.CONF.Base.InstallDir, "1panel/tmp/export/commands", fmt.Sprintf("1panel-commands-%s.csv", time.Now().Format(constant.DateTimeSlimLayout)))
if _, err := os.Stat(path.Dir(tmpFileName)); err != nil {
_ = os.MkdirAll(path.Dir(tmpFileName), constant.DirPerm)
}
if err := csv.ExportCommands(tmpFileName, list); err != nil {
return "", err
}
return tmpFileName, err
}

func (u *CommandService) Create(req dto.CommandOperate) error {
command, _ := commandRepo.Get(repo.WithByName(req.Name), repo.WithByType(req.Type))
if command.ID != 0 {
Expand Down
6 changes: 5 additions & 1 deletion core/i18n/lang/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -230,4 +230,8 @@ MasterNodePortNotAvailable: "Node {{ .name }} port {{ .port }} connectivity veri
ClusterMasterNotExist: "The master node of the cluster is disconnected, please delete the child nodes."

#ssl
ErrReqFailed: "{{.name}} request failed: {{ .err }}"
ErrReqFailed: "{{.name}} request failed: {{ .err }}"

#command
Name: "Name"
Command: "Command"
4 changes: 4 additions & 0 deletions core/i18n/lang/es-ES.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -231,3 +231,7 @@ ClusterMasterNotExist: "El nodo principal del clúster está desconectado, elimi

#ssl
ErrReqFailed: "{{.name}} petición fallida: {{ .err }}"

#command
Name: "Nombre"
Command: "Comando"
6 changes: 5 additions & 1 deletion core/i18n/lang/ja.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -231,4 +231,8 @@ MasterNodePortNotAvailable: "ノード {{ .name }} のポート {{ .port }} の
ClusterMasterNotExist: "クラスタのマスターノードが切断されています。子ノードを削除してください。"

#ssl
ErrReqFailed: "{{.name}} リクエスト失敗: {{ .err }}"
ErrReqFailed: "{{.name}} リクエスト失敗: {{ .err }}"

#command
Name: "名前"
Command: "コマンド"
6 changes: 5 additions & 1 deletion core/i18n/lang/ko.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -230,4 +230,8 @@ MasterNodePortNotAvailable: "노드 {{ .name }} 포트 {{ .port }} 연결성 검
ClusterMasterNotExist: "클러스터의 마스터 노드가 연결이 끊어졌습니다. 자식 노드를 삭제하세요."

#ssl
ErrReqFailed: "{{.name}} 요청 실패: {{ .err }}"
ErrReqFailed: "{{.name}} 요청 실패: {{ .err }}"

#command
Name: "이름"
Command: "명령어"
6 changes: 5 additions & 1 deletion core/i18n/lang/ms.yml
Original file line number Diff line number Diff line change
Expand Up @@ -225,4 +225,8 @@ MasterNodePortNotAvailable: "Pengesahan kesambungan pelabuhan {{ .name }} nod {{
ClusterMasterNotExist: "Node utama kluster terputus, sila padamkan nod anak."

#ssl
ErrReqFailed: "{{.name}} permintaan gagal: {{ .err }}"
ErrReqFailed: "{{.name}} permintaan gagal: {{ .err }}"

#command
Name: "Nama"
Command: "Arahan"
6 changes: 5 additions & 1 deletion core/i18n/lang/pt-BR.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -230,4 +230,8 @@ MasterNodePortNotAvailable: "A verificação de conectividade da porta {{ .port
ClusterMasterNotExist: "O nó mestre do cluster está desconectado, por favor, exclua os nós filhos."

#ssl
ErrReqFailed: "{{.name}} solicitação falhou: {{ .err }}
ErrReqFailed: "{{.name}} solicitação falhou: {{ .err }}"

#command
Name: "Название"
Command: "Команда"
6 changes: 5 additions & 1 deletion core/i18n/lang/ru.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -230,4 +230,8 @@ MasterNodePortNotAvailable: "Проверка подключения порта
ClusterMasterNotExist: "Основной узел кластера отключен, пожалуйста, удалите дочерние узлы."

#ssl
ErrReqFailed: "{{.name}} запрос не удался: {{ .err }}"
ErrReqFailed: "{{.name}} запрос не удался: {{ .err }}"

#command
Name: "Название"
Command: "Команда"
6 changes: 5 additions & 1 deletion core/i18n/lang/tr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -229,4 +229,8 @@ MasterNodePortNotAvailable: "Düğüm {{ .name }} portu {{ .port }} bağlantı d
ClusterMasterNotExist: "Küme ana düğümü bağlantısı kesildi, lütfen alt düğümleri silin."

#ssl
ErrReqFailed: "{{.name}} istek başarısız: {{ .err }}"
ErrReqFailed: "{{.name}} istek başarısız: {{ .err }}"

#command
Name: "Ad"
Command: "Komut"
4 changes: 4 additions & 0 deletions core/i18n/lang/zh-Hant.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -241,3 +241,7 @@ ClusterMasterNotExist: "叢集主節點失聯,請刪除子節點"

#ssl
ErrReqFailed: "{{.name}} 請求失敗: {{ .err }}"

#command
Name: "名稱"
Command: "命令"
6 changes: 5 additions & 1 deletion core/i18n/lang/zh.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -239,4 +239,8 @@ MasterNodePortNotAvailable: "节点 {{ .name }} 端口 {{ .port }} 连通性校
ClusterMasterNotExist: "集群主节点失联,请删除子节点"

#ssl
ErrReqFailed: "{{.name}} 请求失败: {{ .err }}"
ErrReqFailed: "{{.name}} 请求失败: {{ .err }}"

#command
Name: "名称"
Command: "命令"
3 changes: 3 additions & 0 deletions core/router/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,8 @@ func (s *CommandRouter) InitRouter(Router *gin.RouterGroup) {
commandRouter.POST("/search", baseApi.SearchCommand)
commandRouter.POST("/tree", baseApi.SearchCommandTree)
commandRouter.POST("/update", baseApi.UpdateCommand)
commandRouter.POST("/export", baseApi.ExportCommands)
commandRouter.POST("/upload", baseApi.UploadCommandCsv)
commandRouter.POST("/import", baseApi.ImportCommands)
}
}
43 changes: 43 additions & 0 deletions core/utils/csv/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package csv

import (
"encoding/csv"
"os"

"github.com/1Panel-dev/1Panel/core/i18n"
)

type CommandTemplate struct {
Name string `json:"name"`
Command string `json:"command"`
}

func ExportCommands(filename string, commands []CommandTemplate) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()

writer := csv.NewWriter(file)
defer writer.Flush()

if err := writer.Write([]string{
i18n.GetMsgByKey("Name"),
i18n.GetMsgByKey("Command"),
}); err != nil {
return err
}

for _, log := range commands {
record := []string{
log.Name,
log.Command,
}
if err := writer.Write(record); err != nil {
return err
}
}

return nil
}
2 changes: 1 addition & 1 deletion frontend/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ class RequestHttp {
download<BlobPart>(url: string, params?: object, _object = {}): Promise<BlobPart> {
return this.service.post(url, params, _object);
}
upload<T>(url: string, params: object = {}, config?: AxiosRequestConfig): Promise<T> {
upload<T>(url: string, params: object = {}, config?: AxiosRequestConfig): Promise<ResultData<T>> {
return this.service.post(url, params, config);
}
}
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/api/modules/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ import { Command } from '../interface/command';
export const getCommandList = (type: string) => {
return http.post<Array<Command.CommandInfo>>(`/core/commands/list`, { type: type });
};
export const exportCommands = () => {
return http.post<string>(`/core/commands/export`);
};
export const uploadCommands = (params: FormData) => {
return http.upload<Array<Command.CommandInfo>>(`/core/commands/upload`, params);
};
export const importCommands = (list: Array<Command.CommandOperate>) => {
return http.post(`/core/commands/import`, { items: list });
};
export const getCommandPage = (params: SearchWithPage) => {
return http.post<ResPage<Command.CommandInfo>>(`/core/commands/search`, params);
};
Expand Down
1 change: 1 addition & 0 deletions frontend/src/lang/modules/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1184,6 +1184,7 @@ const message = {
fold: 'All contract',
batchInput: 'Batch processing',
quickCommand: 'Quick command | Quick commands',
noSuchCommand: 'No quick command data found in the imported CSV file, please check and try again!',
quickCommandHelper: 'You can use the quick commands at the bottom of the "Terminals -> Terminals".',
groupDeleteHelper:
'After the group is removed, all connections in the group will be migrated to the default group. Do you want to continue?',
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lang/modules/es-es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1185,6 +1185,8 @@ const message = {
fold: 'Contraer todo',
batchInput: 'Procesamiento por lotes',
quickCommand: 'Comando rápido | Comandos rápidos',
noSuchCommand:
'No se encontraron datos de comandos rápidos en el archivo CSV importado, ¡compruebe e inténtelo de nuevo!',
quickCommandHelper: 'Puede usar comandos rápidos en la parte inferior de "Terminales -> Terminales".',
groupDeleteHelper:
'Después de eliminar el grupo, todas las conexiones pasarán al grupo predeterminado. ¿Desea continuar?',
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lang/modules/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1146,6 +1146,8 @@ const message = {
fold: 'すべての契約',
batchInput: 'バッチ処理',
quickCommand: 'クイックコマンド|クイックコマンド',
noSuchCommand:
'インポートしたCSVファイルにクイックコマンドデータが見つかりませんでした。確認して再試行してください!',
quickCommandHelper: '「端末 - >端子」の下部にあるクイックコマンドを使用できます。',
groupDeleteHelper:
'グループが削除された後、グループ内のすべての接続がデフォルトグループに移行されます。続けたいですか?',
Expand Down
1 change: 1 addition & 0 deletions frontend/src/lang/modules/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,7 @@ const message = {
fold: '모두 축소',
batchInput: '배치 처리',
quickCommand: '빠른 명령 | 빠른 명령들',
noSuchCommand: '가져온 CSV 파일에서 빠른 명령어 데이터를 찾을 수 없습니다. 확인 후 다시 시도하세요!',
quickCommandHelper: '"터미널 -> 터미널" 하단에서 빠른 명령을 사용할 수 있습니다.',
groupDeleteHelper: '그룹을 제거하면 해당 그룹의 모든 연결이 기본 그룹으로 이동됩니다. 계속하시겠습니까?',
command: '명령',
Expand Down
Loading
Loading