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
23 changes: 23 additions & 0 deletions agent/app/api/v2/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,29 @@ func (b *BaseApi) GetContent(c *gin.Context) {
}
}

// @Tags File
// @Summary Preview file content
// @Accept json
// @Param request body request.FileContentReq true "request"
// @Success 200 {object} response.FileInfo
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /files/preview [post]
// @x-panel-log {"bodyKeys":["path"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"预览文件内容 [path]","formatEN":"Preview file content [path]"}
func (b *BaseApi) PreviewContent(c *gin.Context) {
var req request.FileContentReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
info, err := fileService.GetPreviewContent(req)
if err != nil {
helper.InternalServer(c, err)
return
}

helper.SuccessWithData(c, info)
}

// @Tags File
// @Summary Update file content
// @Accept json
Expand Down
1 change: 0 additions & 1 deletion agent/app/dto/request/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ type FileContentReq struct {
Path string `json:"path" validate:"required"`
IsDetail bool `json:"isDetail"`
}

type SearchUploadWithPage struct {
dto.PageInfo
Path string `json:"path" validate:"required"`
Expand Down
77 changes: 77 additions & 0 deletions agent/app/service/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type IFileService interface {
Compress(c request.FileCompress) error
DeCompress(c request.FileDeCompress) error
GetContent(op request.FileContentReq) (response.FileInfo, error)
GetPreviewContent(op request.FileContentReq) (response.FileInfo, error)
SaveContent(edit request.FileEdit) error
FileDownload(d request.FileDownload) (string, error)
DirSize(req request.DirSizeReq) (response.DirSizeRes, error)
Expand Down Expand Up @@ -374,6 +375,82 @@ func (f *FileService) GetContent(op request.FileContentReq) (response.FileInfo,
return response.FileInfo{FileInfo: *info}, nil
}

func (f *FileService) GetPreviewContent(op request.FileContentReq) (response.FileInfo, error) {
info, err := files.NewFileInfo(files.FileOption{
Path: op.Path,
Expand: false,
IsDetail: op.IsDetail,
})
if err != nil {
return response.FileInfo{}, err
}

if files.IsBlockDevice(info.FileMode) {
return response.FileInfo{FileInfo: *info}, nil
}

file, err := os.Open(op.Path)
if err != nil {
return response.FileInfo{}, err
}
defer file.Close()

headBuf := make([]byte, 1024)
n, err := file.Read(headBuf)
if err != nil && err != io.EOF {
return response.FileInfo{}, err
}
headBuf = headBuf[:n]

if len(headBuf) > 0 && files.DetectBinary(headBuf) {
return response.FileInfo{FileInfo: *info}, nil
}

const maxSize = 10 * 1024 * 1024
if info.Size <= maxSize {
if _, err := file.Seek(0, 0); err != nil {
return response.FileInfo{}, err
}
content, err := io.ReadAll(file)
if err != nil {
return response.FileInfo{}, err
}
info.Content = string(content)
} else {
lines, err := files.TailFromEnd(op.Path, 300)
if err != nil {
return response.FileInfo{}, err
}
info.Content = strings.Join(lines, "\n")
}

content := []byte(info.Content)
if len(content) > 1024 {
content = content[:1024]
}
if !utf8.Valid(content) {
_, decodeName, _ := charset.DetermineEncoding(content, "")
decoder := files.GetDecoderByName(decodeName)
if decoder != nil {
reader := strings.NewReader(info.Content)
var dec *encoding.Decoder
if decodeName == "windows-1252" {
dec = simplifiedchinese.GBK.NewDecoder()
} else {
dec = decoder.NewDecoder()
}
decodedReader := transform.NewReader(reader, dec)
contents, err := io.ReadAll(decodedReader)
if err != nil {
return response.FileInfo{}, err
}
info.Content = string(contents)
}
}

return response.FileInfo{FileInfo: *info}, nil
}

func (f *FileService) SaveContent(edit request.FileEdit) error {
info, err := files.NewFileInfo(files.FileOption{
Path: edit.Path,
Expand Down
1 change: 1 addition & 0 deletions agent/router/ro_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func (f *FileRouter) InitRouter(Router *gin.RouterGroup) {
fileRouter.POST("/compress", baseApi.CompressFile)
fileRouter.POST("/decompress", baseApi.DeCompressFile)
fileRouter.POST("/content", baseApi.GetContent)
fileRouter.POST("/preview", baseApi.PreviewContent)
fileRouter.POST("/save", baseApi.SaveContent)
fileRouter.POST("/check", baseApi.CheckFile)
fileRouter.POST("/batch/check", baseApi.BatchCheckFiles)
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/api/interface/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ export namespace File {
node: string;
}

export interface PreviewContentReq {
path: string;
isDetail?: boolean;
}

export interface SearchUploadInfo extends ReqPage {
path: string;
}
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/api/modules/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ export const getFileContent = (params: File.ReqFile) => {
return http.post<File.File>('files/content', params);
};

export const getPreviewContent = (params: File.PreviewContentReq) => {
return http.post<File.File>('files/preview', params, TimeoutEnum.T_5M);
};

export const saveFileContent = (params: File.FileEdit) => {
return http.post<File.File>('files/save', params);
};
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/lang/modules/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1576,6 +1576,9 @@ const message = {
noNameFile: 'Untitled file',
minimap: 'Code mini map',
fileCanNotRead: 'File can not read',
previewTruncated: 'File is too large, only showing the last part',
previewEmpty: 'File is empty or not a text file',
previewLargeFile: 'Preview',
panelInstallDir: `1Panel installation directory can't be deleted`,
wgetTask: 'Download Task',
existFileTitle: 'Same name file prompt',
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/lang/modules/es-es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1578,6 +1578,9 @@ const message = {
noNameFile: 'Archivo sin nombre',
minimap: 'Mapa de código',
fileCanNotRead: 'No se puede leer el archivo',
previewTruncated: 'El archivo es demasiado grande, solo se muestra la última parte',
previewEmpty: 'El archivo está vacío o no es un archivo de texto',
previewLargeFile: 'Vista previa',
panelInstallDir: 'El directorio de instalación de 1Panel no puede eliminarse',
wgetTask: 'Tarea de descarga',
existFileTitle: 'Archivo con el mismo nombre',
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/lang/modules/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1527,6 +1527,9 @@ const message = {
noNameFile: '無題のファイル',
minimap: 'コードミニマップ',
fileCanNotRead: 'ファイルは読み取れません',
previewTruncated: 'ファイルが大きすぎるため、末尾の内容のみ表示しています',
previewEmpty: 'ファイルが空であるか、テキストファイルではありません',
previewLargeFile: 'プレビュー',
panelInstallDir: `1Panelインストールディレクトリは削除できません`,
wgetTask: 'ダウンロードタスク',
existFileTitle: '同名ファイルの警告',
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/lang/modules/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1509,6 +1509,9 @@ const message = {
noNameFile: '제목 없는 파일',
minimap: '코드 미니맵',
fileCanNotRead: '파일을 읽을 수 없습니다.',
previewTruncated: '파일이 너무 커서 마지막 부분만 표시됩니다',
previewEmpty: '파일이 비어 있거나 텍스트 파일이 아닙니다',
previewLargeFile: '미리보기',
panelInstallDir: `1Panel 설치 디렉터리는 삭제할 수 없습니다.`,
wgetTask: '다운로드 작업',
existFileTitle: '동일한 이름의 파일 경고',
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/lang/modules/ms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1568,6 +1568,9 @@ const message = {
noNameFile: 'Fail tanpa nama',
minimap: 'Peta mini kod',
fileCanNotRead: 'Fail tidak dapat dibaca',
previewTruncated: 'Fail terlalu besar, hanya menunjukkan bahagian terakhir',
previewEmpty: 'Fail kosong atau bukan fail teks',
previewLargeFile: 'Pratonton',
panelInstallDir: 'Direktori pemasangan 1Panel tidak boleh dipadamkan',
wgetTask: 'Tugas Muat Turun',
existFileTitle: 'Amaran fail dengan nama yang sama',
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/lang/modules/pt-br.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1558,6 +1558,9 @@ const message = {
noNameFile: 'Arquivo sem nome',
minimap: 'Mini mapa de código',
fileCanNotRead: 'O arquivo não pode ser lido',
previewTruncated: 'O arquivo é muito grande, mostrando apenas a última parte',
previewEmpty: 'O arquivo está vazio ou não é um arquivo de texto',
previewLargeFile: 'Visualizar',
panelInstallDir: 'O diretório de instalação do 1Panel não pode ser excluído',
wgetTask: 'Tarefa de Download',
existFileTitle: 'Aviso de arquivo com o mesmo nome',
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/lang/modules/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1559,6 +1559,9 @@ const message = {
noNameFile: 'Безымянный файл',
minimap: 'Мини-карта кода',
fileCanNotRead: 'Файл не может быть прочитан',
previewTruncated: 'Файл слишком большой, отображается только последняя часть',
previewEmpty: 'Файл пуст или не является текстовым файлом',
previewLargeFile: 'Предпросмотр',
panelInstallDir: 'Директорию установки 1Panel нельзя удалить',
wgetTask: 'Задача загрузки',
existFileTitle: 'Предупреждение о файле с тем же именем',
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/lang/modules/tr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1593,6 +1593,9 @@ const message = {
noNameFile: 'İsimsiz dosya',
minimap: 'Kod mini haritası',
fileCanNotRead: 'Dosya okunamıyor',
previewTruncated: 'Dosya çok büyük, yalnızca son kısım gösteriliyor',
previewEmpty: 'Dosya boş veya metin dosyası değil',
previewLargeFile: 'Önizleme',
panelInstallDir: '1Panel kurulum dizini silinemez',
wgetTask: 'İndirme Görevi',
existFileTitle: 'Aynı ada sahip dosya uyarısı',
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/lang/modules/zh-Hant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1494,6 +1494,9 @@ const message = {
noNameFile: '未命名檔案',
minimap: '縮圖',
fileCanNotRead: '此文件不支援預覽',
previewTruncated: '檔案過大,僅顯示末尾內容',
previewEmpty: '檔案內容為空或不是文字檔案',
previewLargeFile: '預覽',
panelInstallDir: '1Panel 安裝目錄不能刪除',
wgetTask: '下載任務',
existFileTitle: '同名檔案提示',
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/lang/modules/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1496,6 +1496,9 @@ const message = {
noNameFile: '未命名文件',
minimap: '缩略图',
fileCanNotRead: '此文件不支持预览',
previewTruncated: '文件过大,仅显示末尾内容',
previewEmpty: '文件内容为空或不是文本文件',
previewLargeFile: '预览',
panelInstallDir: '1Panel 安装目录不能删除',
wgetTask: '下载任务',
existFileTitle: '同名文件提示',
Expand Down
28 changes: 27 additions & 1 deletion frontend/src/views/host/file-management/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,7 @@
<BatchRole ref="batchRoleRef" @close="search" />
<VscodeOpenDialog ref="dialogVscodeOpenRef" />
<Preview ref="previewRef" />
<TextPreview ref="textPreviewRef" />
<TerminalDialog ref="dialogTerminalRef" />
<Convert ref="convertRef" @close="search" />
</div>
Expand Down Expand Up @@ -684,6 +685,7 @@ import RecycleBin from './recycle-bin/index.vue';
import Favorite from './favorite/index.vue';
import BatchRole from './batch-role/index.vue';
import Preview from './preview/index.vue';
import TextPreview from './text-preview/index.vue';
import VscodeOpenDialog from '@/components/vscode-open/index.vue';
import Convert from './convert/index.vue';
import { debounce } from 'lodash-es';
Expand Down Expand Up @@ -786,7 +788,10 @@ const favorites = ref([]);
const batchRoleRef = ref();
const dialogVscodeOpenRef = ref();
const previewRef = ref();
const textPreviewRef = ref();
const processRef = ref();

const MAX_OPEN_SIZE = 10 * 1024 * 1024;
const hostMount = ref<Dashboard.DiskInfo[]>([]);
let resizeObserver: ResizeObserver;
const dirTotalSize = ref(-1);
Expand Down Expand Up @@ -1247,12 +1252,16 @@ const openView = (item: File.File) => {
return openPreview(item, fileType);
}

const path = item.isSymlink ? item.linkPath : item.path;
if (item.size > MAX_OPEN_SIZE) {
return openTextPreview(path, item.name);
}

const actionMap = {
compress: openDeCompress,
text: () => openCodeEditor(item.path, item.extension),
};

const path = item.isSymlink ? item.linkPath : item.path;
return actionMap[fileType] ? actionMap[fileType](item) : openCodeEditor(path, item.extension);
};

Expand Down Expand Up @@ -1296,6 +1305,10 @@ const openCodeEditor = (path: string, extension: string) => {
.catch(() => {});
};

const openTextPreview = (path: string, name: string) => {
textPreviewRef.value.acceptParams({ path, name });
};

const openUpload = () => {
fileUpload.path = req.path;
uploadRef.value.acceptParams(fileUpload);
Expand Down Expand Up @@ -1544,6 +1557,19 @@ const beforeButtons = [
{
label: i18n.global.t('commons.button.open'),
click: open,
show: (row: File.File) => {
return row?.isDir || row?.size <= MAX_OPEN_SIZE;
},
},
{
label: i18n.global.t('file.previewLargeFile'),
click: (row: File.File) => {
const path = row.isSymlink ? row.linkPath : row.path;
openTextPreview(path, row.name);
},
show: (row: File.File) => {
return !row?.isDir && row?.size > MAX_OPEN_SIZE;
},
},
{
label: i18n.global.t('commons.button.download'),
Expand Down
Loading
Loading