diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..8a9c223 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +DISK_FETCH_FREQUENCY=5 +MEMORY_DUMP_FREQUENCY=60 +MAX_HISTORY_AGE=2592000 \ No newline at end of file diff --git a/.gitignore b/.gitignore index c2e6ef3..9c0e28e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ snapshots.dat +.env diff --git a/go.mod b/go.mod index 1356e71..fe69c41 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.17.0 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/joho/godotenv v1.5.1 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/leodido/go-urn v1.2.4 // indirect diff --git a/go.sum b/go.sum index 3c1fda2..70d5549 100644 --- a/go.sum +++ b/go.sum @@ -30,6 +30,8 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= diff --git a/lib/config/config.go b/lib/config/config.go index 3e21da4..79009ff 100644 --- a/lib/config/config.go +++ b/lib/config/config.go @@ -1,19 +1,48 @@ package config +import ( + "log" + "os" + "strconv" + + "github.com/joho/godotenv" +) + type DHConfig struct { - DiskFetchFrequency int `json:"diskFetchFrequency" comment:"How often should a snapshot be taken of the current state of the disks"` - MemoryDumpFrequency int `json:"memoryDumpFrequency" comment:"How often should we save the snapshots from memory to disk"` - MaxHistoryAge int + DiskFetchFrequency int `json:"diskFetchFrequency"` + MemoryDumpFrequency int `json:"memoryDumpFrequency"` + MaxHistoryAge int `json:"maxHistoryAge"` } func GetConfiguration() DHConfig { - - // TODO: Read from os.environment or simply load the defaults - - return DHConfig{ - DiskFetchFrequency: 5, - MemoryDumpFrequency: 16, - MaxHistoryAge: 2592000, + // Load .env file if it exists + if err := godotenv.Load(); err != nil { + log.Println("No .env file found") } + config := DHConfig{ + DiskFetchFrequency: 5, // default value + MemoryDumpFrequency: 60, // default value + MaxHistoryAge: 2592000, // default value + } + + if val, exists := os.LookupEnv("DISK_FETCH_FREQUENCY"); exists { + if intValue, err := strconv.Atoi(val); err == nil { + config.DiskFetchFrequency = intValue + } + } + + if val, exists := os.LookupEnv("MEMORY_DUMP_FREQUENCY"); exists { + if intValue, err := strconv.Atoi(val); err == nil { + config.MemoryDumpFrequency = intValue + } + } + + if val, exists := os.LookupEnv("MAX_HISTORY_AGE"); exists { + if intValue, err := strconv.Atoi(val); err == nil { + config.MaxHistoryAge = intValue + } + } + + return config } diff --git a/lib/svc/service.go b/lib/svc/service.go index 5017e46..d92556c 100644 --- a/lib/svc/service.go +++ b/lib/svc/service.go @@ -104,16 +104,12 @@ func RunService() { // Snapshot taking routine go func() { + waitTime := time.Duration(config.GetConfiguration().DiskFetchFrequency) * time.Second for { - time.Sleep(time.Duration(config.GetConfiguration().DiskFetchFrequency) * time.Second) - data, err := TakeHardwareSnapshot() + time.Sleep(waitTime) + _, err := TakeHardwareSnapshot() if err != nil { fmt.Printf("Hardware Fetch Error: %s", err) - } else { - fmt.Println("Got Snapshot for " + data.TimeStamp.Format("02/01/2006")) - for _, hdd := range data.HDD { - fmt.Printf("%s[%s]: %v\n", hdd.Model, hdd.Size, hdd.Temperature) - } } } }() @@ -121,13 +117,12 @@ func RunService() { // Periodic saving routine go func() { for { - time.Sleep(time.Duration(config.GetConfiguration().MemoryDumpFrequency) * time.Second) + waitTime := time.Duration(config.GetConfiguration().MemoryDumpFrequency) * time.Second + time.Sleep(waitTime) err := SaveSnapshotsToFile() if err != nil { fmt.Printf("Memory Dump Error: %s", err) } - - fmt.Println("Saved Snapshots to file") } }() } diff --git a/static/main.js b/static/main.js new file mode 100644 index 0000000..63b829b --- /dev/null +++ b/static/main.js @@ -0,0 +1,134 @@ +function stringToColor(str) { + let hash = 0; + for (let i = 0; i < str.length; i++) { + hash = str.charCodeAt(i) + ((hash << 5) - hash); + } + let color = '#'; + for (let i = 0; i < 3; i++) { + const value = (hash >> (i * 8)) & 0xFF; + color += ('00' + value.toString(16)).substr(-2); + } + return color; +} + +document.addEventListener('DOMContentLoaded', function() { + const diskTableBody = document.getElementById('disk-table-body'); + const ctx = document.getElementById('temperatureChart').getContext('2d'); + let temperatureChart = new Chart(ctx, { + type: 'line', + data: { + datasets: [] + }, + options: { + scales: { + x: { + type: 'time', + time: { + unit: 'second', + displayFormats: { + second: 'HH:mm:ss' + } + }, + title: { + display: true, + text: 'Time' + } + }, + y: { + beginAtZero: true, + title: { + display: true, + text: 'Temperature (°C)' + } + } + } + } + }); + + function fetchAndUpdateDisks() { + fetch('/v1/api/disks?temp=true') + .then(response => response.json()) + .then(data => { + updateDiskTable(data.disks); + }) + .catch(error => console.error('Error fetching disk data:', error)); + } + + function updateDiskTable(disks) { + let tableHTML = ''; + disks.forEach(disk => { + tableHTML += ` + + ${disk.Name} + ${disk.Transport} + ${disk.Size} + ${disk.Model} + ${disk.Serial} + ${disk.Type} + ${disk.Temperature} + + `; + }); + diskTableBody.innerHTML = tableHTML; + } + + function fetchAndUpdateTemperatureChart() { + fetch('/v1/api/snapshots') + .then(response => response.json()) + .then(snapshots => { + updateTemperatureChart(snapshots); + }) + .catch(error => console.error('Error fetching temperature data:', error)); + } + + function updateTemperatureChart(snapshots) { + // Clear existing datasets + temperatureChart.data.datasets = []; + + snapshots.forEach(snapshot => { + const time = new Date(snapshot.TimeStamp); + snapshot.HDD.forEach(disk => { + let dataset = temperatureChart.data.datasets.find(d => d.label === disk.Name); + if (!dataset) { + dataset = { + label: disk.Name, + data: [], + fill: false, + borderColor: stringToColor(disk.Name), + borderWidth: 1 + }; + temperatureChart.data.datasets.push(dataset); + } + + dataset.data.push({ + x: time, + y: disk.Temperature + }); + }); + }); + + temperatureChart.update(); + } + + // Chart.js zoom and pan configuration + temperatureChart.options.plugins.zoom = { + zoom: { + wheel: { + enabled: true, + }, + pinch: { + enabled: true + }, + mode: 'x', + }, + pan: { + enabled: true, + mode: 'x', + } + }; + + fetchAndUpdateDisks(); + fetchAndUpdateTemperatureChart(); + setInterval(fetchAndUpdateDisks, 5000); + setInterval(fetchAndUpdateTemperatureChart, 5000); +}); diff --git a/templates/index.html b/templates/index.html index d2430d8..4c85b59 100644 --- a/templates/index.html +++ b/templates/index.html @@ -11,30 +11,26 @@

Drive Health Dashboard

- - - - - - - - - + - - {{range .drives}} - - - - - - - - - - {{end}} + +
NameTransportSizeModelSerialTypeTemperature
{{.Name}}{{.Transport}}{{.Size}}{{.Model}}{{.Serial}}{{.Type}}{{.Temperature}}
+ +
+ +
+ +
+ + + + + + + +