From d70ecda454bdb95c9b20d1160e8b6e343102f482 Mon Sep 17 00:00:00 2001 From: ssongliu Date: Mon, 22 Sep 2025 16:09:06 +0800 Subject: [PATCH] feat: Support import and export operations for quick commands --- core/app/api/v2/command.go | 91 +++++++++ core/app/dto/command.go | 4 + core/app/service/command.go | 31 ++++ core/i18n/lang/en.yaml | 6 +- core/i18n/lang/es-ES.yaml | 4 + core/i18n/lang/ja.yaml | 6 +- core/i18n/lang/ko.yaml | 6 +- core/i18n/lang/ms.yml | 6 +- core/i18n/lang/pt-BR.yaml | 6 +- core/i18n/lang/ru.yaml | 6 +- core/i18n/lang/tr.yaml | 6 +- core/i18n/lang/zh-Hant.yaml | 4 + core/i18n/lang/zh.yaml | 6 +- core/router/command.go | 3 + core/utils/csv/command.go | 43 +++++ frontend/src/api/index.ts | 2 +- frontend/src/api/modules/command.ts | 9 + frontend/src/lang/modules/en.ts | 1 + frontend/src/lang/modules/es-es.ts | 2 + frontend/src/lang/modules/ja.ts | 2 + frontend/src/lang/modules/ko.ts | 1 + frontend/src/lang/modules/ms.ts | 1 + frontend/src/lang/modules/pt-br.ts | 2 + frontend/src/lang/modules/ru.ts | 1 + frontend/src/lang/modules/tr.ts | 2 + frontend/src/lang/modules/zh-Hant.ts | 1 + frontend/src/lang/modules/zh.ts | 1 + .../views/terminal/command/import/index.vue | 175 ++++++++++++++++++ frontend/src/views/terminal/command/index.vue | 33 +++- 29 files changed, 451 insertions(+), 10 deletions(-) create mode 100644 core/utils/csv/command.go create mode 100644 frontend/src/views/terminal/command/import/index.vue diff --git a/core/app/api/v2/command.go b/core/app/api/v2/command.go index 0e2f7ae036ca..c749b9b22312 100644 --- a/core/app/api/v2/command.go +++ b/core/app/api/v2/command.go @@ -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 diff --git a/core/app/dto/command.go b/core/app/dto/command.go index 934664b16bdc..59d40704f517 100644 --- a/core/app/dto/command.go +++ b/core/app/dto/command.go @@ -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"` diff --git a/core/app/service/command.go b/core/app/service/command.go index 1f931d98aa67..5231688b9929 100644 --- a/core/app/service/command.go +++ b/core/app/service/command.go @@ -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" ) @@ -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 { @@ -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 { diff --git a/core/i18n/lang/en.yaml b/core/i18n/lang/en.yaml index 12f8c4ef48a3..0d4248920564 100644 --- a/core/i18n/lang/en.yaml +++ b/core/i18n/lang/en.yaml @@ -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 }}" \ No newline at end of file +ErrReqFailed: "{{.name}} request failed: {{ .err }}" + +#command +Name: "Name" +Command: "Command" \ No newline at end of file diff --git a/core/i18n/lang/es-ES.yaml b/core/i18n/lang/es-ES.yaml index d491153ebd63..c2410f3e5c54 100644 --- a/core/i18n/lang/es-ES.yaml +++ b/core/i18n/lang/es-ES.yaml @@ -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" \ No newline at end of file diff --git a/core/i18n/lang/ja.yaml b/core/i18n/lang/ja.yaml index c8c744f39d86..1da18ad60457 100644 --- a/core/i18n/lang/ja.yaml +++ b/core/i18n/lang/ja.yaml @@ -231,4 +231,8 @@ MasterNodePortNotAvailable: "ノード {{ .name }} のポート {{ .port }} の ClusterMasterNotExist: "クラスタのマスターノードが切断されています。子ノードを削除してください。" #ssl -ErrReqFailed: "{{.name}} リクエスト失敗: {{ .err }}" \ No newline at end of file +ErrReqFailed: "{{.name}} リクエスト失敗: {{ .err }}" + +#command +Name: "名前" +Command: "コマンド" \ No newline at end of file diff --git a/core/i18n/lang/ko.yaml b/core/i18n/lang/ko.yaml index d9053e45ce46..6028dad06436 100644 --- a/core/i18n/lang/ko.yaml +++ b/core/i18n/lang/ko.yaml @@ -230,4 +230,8 @@ MasterNodePortNotAvailable: "노드 {{ .name }} 포트 {{ .port }} 연결성 검 ClusterMasterNotExist: "클러스터의 마스터 노드가 연결이 끊어졌습니다. 자식 노드를 삭제하세요." #ssl -ErrReqFailed: "{{.name}} 요청 실패: {{ .err }}" \ No newline at end of file +ErrReqFailed: "{{.name}} 요청 실패: {{ .err }}" + +#command +Name: "이름" +Command: "명령어" \ No newline at end of file diff --git a/core/i18n/lang/ms.yml b/core/i18n/lang/ms.yml index cee732e03cbc..979e7688fcfe 100644 --- a/core/i18n/lang/ms.yml +++ b/core/i18n/lang/ms.yml @@ -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 }}" \ No newline at end of file +ErrReqFailed: "{{.name}} permintaan gagal: {{ .err }}" + +#command +Name: "Nama" +Command: "Arahan" \ No newline at end of file diff --git a/core/i18n/lang/pt-BR.yaml b/core/i18n/lang/pt-BR.yaml index ee5dd6d94030..26b6e86aeabd 100644 --- a/core/i18n/lang/pt-BR.yaml +++ b/core/i18n/lang/pt-BR.yaml @@ -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 }} \ No newline at end of file +ErrReqFailed: "{{.name}} solicitação falhou: {{ .err }}" + +#command +Name: "Название" +Command: "Команда" \ No newline at end of file diff --git a/core/i18n/lang/ru.yaml b/core/i18n/lang/ru.yaml index ae43df935874..9e01640aa088 100644 --- a/core/i18n/lang/ru.yaml +++ b/core/i18n/lang/ru.yaml @@ -230,4 +230,8 @@ MasterNodePortNotAvailable: "Проверка подключения порта ClusterMasterNotExist: "Основной узел кластера отключен, пожалуйста, удалите дочерние узлы." #ssl -ErrReqFailed: "{{.name}} запрос не удался: {{ .err }}" \ No newline at end of file +ErrReqFailed: "{{.name}} запрос не удался: {{ .err }}" + +#command +Name: "Название" +Command: "Команда" \ No newline at end of file diff --git a/core/i18n/lang/tr.yaml b/core/i18n/lang/tr.yaml index cdc1406453d9..bcc424723f80 100644 --- a/core/i18n/lang/tr.yaml +++ b/core/i18n/lang/tr.yaml @@ -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 }}" \ No newline at end of file +ErrReqFailed: "{{.name}} istek başarısız: {{ .err }}" + +#command +Name: "Ad" +Command: "Komut" \ No newline at end of file diff --git a/core/i18n/lang/zh-Hant.yaml b/core/i18n/lang/zh-Hant.yaml index b560e2738c98..a6b8a14fcf4a 100644 --- a/core/i18n/lang/zh-Hant.yaml +++ b/core/i18n/lang/zh-Hant.yaml @@ -241,3 +241,7 @@ ClusterMasterNotExist: "叢集主節點失聯,請刪除子節點" #ssl ErrReqFailed: "{{.name}} 請求失敗: {{ .err }}" + +#command +Name: "名稱" +Command: "命令" \ No newline at end of file diff --git a/core/i18n/lang/zh.yaml b/core/i18n/lang/zh.yaml index a35aff9b8aae..1e5f1882e1b7 100644 --- a/core/i18n/lang/zh.yaml +++ b/core/i18n/lang/zh.yaml @@ -239,4 +239,8 @@ MasterNodePortNotAvailable: "节点 {{ .name }} 端口 {{ .port }} 连通性校 ClusterMasterNotExist: "集群主节点失联,请删除子节点" #ssl -ErrReqFailed: "{{.name}} 请求失败: {{ .err }}" \ No newline at end of file +ErrReqFailed: "{{.name}} 请求失败: {{ .err }}" + +#command +Name: "名称" +Command: "命令" \ No newline at end of file diff --git a/core/router/command.go b/core/router/command.go index 862cc0c74154..2c6660754194 100644 --- a/core/router/command.go +++ b/core/router/command.go @@ -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) } } diff --git a/core/utils/csv/command.go b/core/utils/csv/command.go new file mode 100644 index 000000000000..863d0f72bf97 --- /dev/null +++ b/core/utils/csv/command.go @@ -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 +} diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 4f558b71a065..7c2a956e8e0b 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -153,7 +153,7 @@ class RequestHttp { download(url: string, params?: object, _object = {}): Promise { return this.service.post(url, params, _object); } - upload(url: string, params: object = {}, config?: AxiosRequestConfig): Promise { + upload(url: string, params: object = {}, config?: AxiosRequestConfig): Promise> { return this.service.post(url, params, config); } } diff --git a/frontend/src/api/modules/command.ts b/frontend/src/api/modules/command.ts index 32847127dc48..9c72c7bc3b94 100644 --- a/frontend/src/api/modules/command.ts +++ b/frontend/src/api/modules/command.ts @@ -5,6 +5,15 @@ import { Command } from '../interface/command'; export const getCommandList = (type: string) => { return http.post>(`/core/commands/list`, { type: type }); }; +export const exportCommands = () => { + return http.post(`/core/commands/export`); +}; +export const uploadCommands = (params: FormData) => { + return http.upload>(`/core/commands/upload`, params); +}; +export const importCommands = (list: Array) => { + return http.post(`/core/commands/import`, { items: list }); +}; export const getCommandPage = (params: SearchWithPage) => { return http.post>(`/core/commands/search`, params); }; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index ce171aadf002..749322d30145 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -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?', diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index 0452c593ebfa..9f424dada9c0 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -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?', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index 401d3f2e45fe..9c4817c4fba4 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -1146,6 +1146,8 @@ const message = { fold: 'すべての契約', batchInput: 'バッチ処理', quickCommand: 'クイックコマンド|クイックコマンド', + noSuchCommand: + 'インポートしたCSVファイルにクイックコマンドデータが見つかりませんでした。確認して再試行してください!', quickCommandHelper: '「端末 - >端子」の下部にあるクイックコマンドを使用できます。', groupDeleteHelper: 'グループが削除された後、グループ内のすべての接続がデフォルトグループに移行されます。続けたいですか?', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index cfecba991394..7be5d3924f6d 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -1138,6 +1138,7 @@ const message = { fold: '모두 축소', batchInput: '배치 처리', quickCommand: '빠른 명령 | 빠른 명령들', + noSuchCommand: '가져온 CSV 파일에서 빠른 명령어 데이터를 찾을 수 없습니다. 확인 후 다시 시도하세요!', quickCommandHelper: '"터미널 -> 터미널" 하단에서 빠른 명령을 사용할 수 있습니다.', groupDeleteHelper: '그룹을 제거하면 해당 그룹의 모든 연결이 기본 그룹으로 이동됩니다. 계속하시겠습니까?', command: '명령', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index 98443c2c4fa7..3a04830fe644 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -1174,6 +1174,7 @@ const message = { fold: 'Kontrak semua', batchInput: 'Pemprosesan kelompok', quickCommand: 'Arahan pantas | Arahan pantas', + noSuchCommand: 'Tiada data arahan pantas ditemui dalam fail CSV yang diimport, sila periksa dan cuba lagi!', quickCommandHelper: 'Anda boleh menggunakan arahan pantas di bahagian bawah "Terminal -> Terminal".', groupDeleteHelper: 'Selepas kumpulan dikeluarkan, semua sambungan dalam kumpulan akan dipindahkan ke kumpulan lalai. Adakah anda mahu meneruskan?', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index a95e4d8cd929..935c252bda5a 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -1167,6 +1167,8 @@ const message = { fold: 'Contrair tudo', batchInput: 'Processamento em lote', quickCommand: 'Comando rápido | Comandos rápidos', + noSuchCommand: + 'Nenhum dado de comando rápido encontrado no arquivo CSV importado, verifique e tente novamente!', quickCommandHelper: 'Você pode usar os comandos rápidos na parte inferior de "Terminais -> Terminais".', groupDeleteHelper: 'Após o grupo ser removido, todas as conexões no grupo serão migradas para o grupo padrão. Você deseja continuar?', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index b1a02194f10f..3585f8a2b713 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -1170,6 +1170,7 @@ const message = { fold: 'Свернуть все', batchInput: 'Пакетная обработка', quickCommand: 'Быстрая команда | Быстрые команды', + noSuchCommand: 'В импортированном CSV-файле не найдены данные быстрых команд, проверьте и повторите попытку!', quickCommandHelper: 'Вы можете использовать быстрые команды внизу страницы "Терминалы -> Терминалы".', groupDeleteHelper: 'После удаления группы все подключения в группе будут перемещены в группу по умолчанию. Хотите продолжить?', diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index 859ae2339a95..db072574d0d6 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -1196,6 +1196,8 @@ const message = { fold: 'Tümünü daralt', batchInput: 'Toplu işleme', quickCommand: 'Hızlı komut | Hızlı komutlar', + noSuchCommand: + 'İçe aktarılan CSV dosyasında hızlı komut verisi bulunamadı, lütfen kontrol edip tekrar deneyin!', quickCommandHelper: '"Terminaller -> Terminaller" altındaki hızlı komutları kullanabilirsiniz.', groupDeleteHelper: 'Grup kaldırıldıktan sonra, gruptaki tüm bağlantılar varsayılan gruba taşınacaktır. Devam etmek istiyor musunuz?', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index f4a4de022660..fc7b1b450bea 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -1128,6 +1128,7 @@ const message = { fold: '全部收縮', batchInput: '批次輸入', quickCommand: '快速指令', + noSuchCommand: '導入的CSV文件中未能發現快速命令數據,請檢查後重試!', quickCommandHelper: '常用命令列表,用於在終端介面底部快速選擇', groupDeleteHelper: '移除組後,組內所有連接將遷移到 default 組內,是否繼續?', command: '指令', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 24343e01433d..2767b07652f2 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -1128,6 +1128,7 @@ const message = { fold: '全部收缩', batchInput: '批量输入', quickCommand: '快速命令', + noSuchCommand: '导入的 csv 文件中未能发现快速命令数据,请检查后重试!', quickCommandHelper: '常用命令列表,用于在终端界面底部快速选择', groupDeleteHelper: '移除组后,组内所有连接将迁移到 default 组内,是否继续?', command: '命令', diff --git a/frontend/src/views/terminal/command/import/index.vue b/frontend/src/views/terminal/command/import/index.vue new file mode 100644 index 000000000000..5a9f0e4cfbb6 --- /dev/null +++ b/frontend/src/views/terminal/command/import/index.vue @@ -0,0 +1,175 @@ + + + diff --git a/frontend/src/views/terminal/command/index.vue b/frontend/src/views/terminal/command/index.vue index 3b9cf43f8afe..606c5c78a4d4 100644 --- a/frontend/src/views/terminal/command/index.vue +++ b/frontend/src/views/terminal/command/index.vue @@ -14,6 +14,15 @@ {{ $t('commons.button.delete') }} + + + + {{ $t('commons.button.import') }} + + + {{ $t('commons.button.export') }} + + @@ -93,11 +103,13 @@ import { Command } from '@/api/interface/command'; import GroupDialog from '@/components/group/index.vue'; import OperateDialog from '@/views/terminal/command/operate/index.vue'; -import { editCommand, deleteCommand, getCommandPage } from '@/api/modules/command'; +import ImportDialog from '@/views/terminal/command/import/index.vue'; +import { editCommand, deleteCommand, getCommandPage, exportCommands } from '@/api/modules/command'; import { reactive, ref } from 'vue'; import i18n from '@/lang'; import { MsgSuccess } from '@/utils/message'; import { getGroupList } from '@/api/modules/group'; +import { downloadFile } from '@/utils/util'; const loading = ref(); const data = ref(); @@ -115,6 +127,7 @@ const info = ref(); const group = ref(''); const dialogRef = ref(); const opRef = ref(); +const importDialogRef = ref(); const acceptParams = () => { search(); @@ -150,6 +163,24 @@ const updateGroup = async (row: any) => { MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); }; +const onExport = async () => { + loading.value = true; + await exportCommands() + .then((res) => { + if (res.data) { + loading.value = false; + downloadFile(res.data, 'local'); + } + }) + .catch(() => { + loading.value = false; + }); +}; + +const onImport = () => { + importDialogRef.value.acceptParams(); +}; + const batchDelete = async (row: Command.CommandInfo | null) => { let names = []; let ids = [];