feat: Implement comprehensive log viewer with advanced features

- Add new log viewer component in admin tools
- Support dynamic log file browsing and content display
- Implement custom scrollbar for log content
- Add log file refresh, download, and view capabilities
- Update environment variables for log configuration
- Enhance logging system with more flexible configuration options
This commit is contained in:
StarFleetCPTN
2025-03-11 20:50:47 -07:00
parent ed81b2c9be
commit a8b4588ecb
15 changed files with 634 additions and 761 deletions
+7 -121
View File
@@ -1,8 +1,6 @@
package scheduler
import (
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"io"
@@ -102,7 +100,7 @@ func NewLogger() *Logger {
// Ensure logs directory exists
logsDir := filepath.Join(dataDir, "logs")
if envLogsDir := os.Getenv("GOMFT_LOGS_DIR"); envLogsDir != "" {
if envLogsDir := os.Getenv("LOGS_DIR"); envLogsDir != "" {
logsDir = envLogsDir
}
@@ -112,34 +110,34 @@ func NewLogger() *Logger {
// Get log rotation settings from environment or use defaults
maxSize := 10 // Default: 10MB
if envSize := os.Getenv("GOMFT_LOG_MAX_SIZE"); envSize != "" {
if envSize := os.Getenv("LOG_MAX_SIZE"); envSize != "" {
if size, err := strconv.Atoi(envSize); err == nil && size > 0 {
maxSize = size
}
}
maxBackups := 5 // Default: keep 5 backups
if envBackups := os.Getenv("GOMFT_LOG_MAX_BACKUPS"); envBackups != "" {
if envBackups := os.Getenv("LOG_MAX_BACKUPS"); envBackups != "" {
if backups, err := strconv.Atoi(envBackups); err == nil && backups >= 0 {
maxBackups = backups
}
}
maxAge := 30 // Default: 30 days
if envAge := os.Getenv("GOMFT_LOG_MAX_AGE"); envAge != "" {
if envAge := os.Getenv("LOG_MAX_AGE"); envAge != "" {
if age, err := strconv.Atoi(envAge); err == nil && age >= 0 {
maxAge = age
}
}
compress := true // Default: compress logs
if envCompress := os.Getenv("GOMFT_LOG_COMPRESS"); envCompress == "false" {
if envCompress := os.Getenv("LOG_COMPRESS"); envCompress == "false" {
compress = false
}
// Get log level from environment or use default
logLevel := LogLevelInfo // Default to info level
if envLogLevel := os.Getenv("GOMFT_LOG_LEVEL"); envLogLevel != "" {
if envLogLevel := os.Getenv("LOG_LEVEL"); envLogLevel != "" {
logLevel = ParseLogLevel(envLogLevel)
}
@@ -201,7 +199,7 @@ func New(database *db.DB) *Scheduler {
logger := NewLogger()
logger.Info.Println("Initializing scheduler")
c := cron.New(cron.WithSeconds(), cron.WithChain(cron.Recover(cron.DefaultLogger)))
c := cron.New(cron.WithChain(cron.Recover(cron.DefaultLogger)))
c.Start()
s := &Scheduler{
@@ -944,39 +942,6 @@ func (s *Scheduler) RunJobNow(jobID uint) error {
return nil
}
// calculateFileHash computes an MD5 hash for the given file path
func calculateFileHash(filePath string) (string, error) {
file, err := os.Open(filePath)
if err != nil {
return "", fmt.Errorf("error opening file: %v", err)
}
defer file.Close()
hash := md5.New()
if _, err := io.Copy(hash, file); err != nil {
return "", fmt.Errorf("error calculating hash: %v", err)
}
return hex.EncodeToString(hash.Sum(nil)), nil
}
// getFileInfo retrieves file stats like size, creation time, and modification time
func getFileInfo(filePath string) (int64, time.Time, time.Time, error) {
info, err := os.Stat(filePath)
if err != nil {
return 0, time.Time{}, time.Time{}, fmt.Errorf("error getting file info: %v", err)
}
size := info.Size()
modTime := info.ModTime()
// Get creation time (this is platform-specific)
// For simplicity, we'll use modification time as a fallback
createTime := modTime
return size, createTime, modTime, nil
}
// hasFileBeenProcessed checks if a file with the same hash has been processed before
func (s *Scheduler) hasFileBeenProcessed(jobID uint, fileHash string) (bool, *db.FileMetadata, error) {
if fileHash == "" {
@@ -1002,82 +967,3 @@ func (s *Scheduler) checkFileProcessingHistory(jobID uint, fileName string) (*db
return nil, fmt.Errorf("no history found for file %s in job %d", fileName, jobID)
}
// getRemoteFileInfo gets metadata for a remote file using rclone lsjson
func (s *Scheduler) getRemoteFileInfo(config *db.TransferConfig, file string) (int64, time.Time, time.Time, string, error) {
// Get rclone config path
configPath := s.db.GetConfigRclonePath(config)
// Construct the appropriate source path
var sourcePath string
if config.SourceType == "s3" || config.SourceType == "minio" || config.SourceType == "b2" {
sourcePath = fmt.Sprintf("source_%d:%s", config.ID, config.SourceBucket)
if config.SourcePath != "" && config.SourcePath != "/" {
sourcePath = fmt.Sprintf("source_%d:%s/%s", config.ID, config.SourceBucket, config.SourcePath)
}
} else {
sourcePath = fmt.Sprintf("source_%d:%s", config.ID, config.SourcePath)
}
// Use rclone lsjson to get file details
rclonePath := os.Getenv("RCLONE_PATH")
if rclonePath == "" {
rclonePath = "rclone"
}
// Construct the full path to the file
fullPath := fmt.Sprintf("%s/%s", sourcePath, file)
// Run rclone lsjson command
args := []string{
"--config", configPath,
"lsjson",
"--hash",
fullPath,
}
cmd := exec.Command(rclonePath, args...)
output, err := cmd.CombinedOutput()
if err != nil {
return 0, time.Time{}, time.Time{}, "", fmt.Errorf("error getting remote file info: %v", err)
}
// Parse the JSON output
var files []map[string]interface{}
if err := json.Unmarshal(output, &files); err != nil {
return 0, time.Time{}, time.Time{}, "", fmt.Errorf("error parsing lsjson output: %v", err)
}
if len(files) == 0 {
return 0, time.Time{}, time.Time{}, "", fmt.Errorf("file not found: %s", file)
}
fileInfo := files[0]
// Extract file size
var fileSize int64
if size, ok := fileInfo["Size"].(float64); ok {
fileSize = int64(size)
}
// Extract modification time
modTime := time.Now()
if modTimeStr, ok := fileInfo["ModTime"].(string); ok {
if parsedTime, err := time.Parse(time.RFC3339, modTimeStr); err == nil {
modTime = parsedTime
}
}
// Create time is usually not available for remote files, so we'll use modTime
createTime := modTime
// Calculate hash if available
var md5Hash string
if hashes, ok := fileInfo["Hashes"].(map[string]interface{}); ok {
if md5, ok := hashes["md5"].(string); ok {
md5Hash = md5
}
}
return fileSize, createTime, modTime, md5Hash, nil
}