From e33db4aed4bf31930ceb3b3f82d49dd99d9dfc83 Mon Sep 17 00:00:00 2001 From: Jacques Beets Date: Sun, 6 Oct 2024 09:45:40 +0200 Subject: [PATCH 1/6] wip new setup --- .gitignore | 1 + backend/db.go | 110 ++++++++++++++++++++++++++++++++ backend/models.go | 36 +++++++++++ frontend/components/Sidebar.vue | 2 +- frontend/pages/config/mqtt.vue | 51 ++++++++++++++- go.mod | 1 + go.sum | 2 + 7 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 backend/db.go create mode 100644 backend/models.go diff --git a/.gitignore b/.gitignore index ca22a49..f4fe94c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # /frontend /scripts/*.ps1 !scripts/test_notification.ps1 +/data/** */ *.exe *.log *.txt diff --git a/backend/db.go b/backend/db.go new file mode 100644 index 0000000..d4eb2f3 --- /dev/null +++ b/backend/db.go @@ -0,0 +1,110 @@ +package main + +import ( + "database/sql" + "time" + + _ "github.com/mattn/go-sqlite3" +) + +type DB struct { + *sql.DB +} + +func NewDB() (*DB, error) { + db, err := sql.Open("sqlite3", "../data/store.db") + if err != nil { + return nil, err + } + if err = db.Ping(); err != nil { + return nil, err + } + return &DB{db}, nil +} + +func (db *DB) InitSchema() error { + _, err := db.Exec(` + CREATE TABLE IF NOT EXISTS configs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + broker_address TEXT NOT NULL, + username TEXT, + password TEXT, + client_id TEXT, + topic TEXT, + log_level TEXT, + script_timeout INTEGER, + created_at DATETIME, + updated_at DATETIME + ); + + CREATE TABLE IF NOT EXISTS commands ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + script_path TEXT NOT NULL, + run_as_user BOOLEAN, + created_at DATETIME, + updated_at DATETIME + ); + + CREATE TABLE IF NOT EXISTS sensor_configs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + enabled BOOLEAN, + interval INTEGER, + sensor_topic TEXT, + created_at DATETIME, + updated_at DATETIME + ); + `) + return err +} + +func (db *DB) GetConfig() (*Config, error) { + var config Config + err := db.QueryRow("SELECT * FROM configs ORDER BY id DESC LIMIT 1").Scan( + &config.ID, &config.BrokerAddress, &config.Username, &config.Password, + &config.ClientID, &config.Topic, &config.LogLevel, &config.ScriptTimeout, + &config.CreatedAt, &config.UpdatedAt, + ) + if err != nil { + return nil, err + } + return &config, nil +} + +func (db *DB) SaveConfig(config *Config) error { + now := time.Now() + _, err := db.Exec(` + INSERT INTO configs ( + broker_address, username, password, client_id, topic, + log_level, script_timeout, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, + config.BrokerAddress, config.Username, config.Password, + config.ClientID, config.Topic, config.LogLevel, + config.ScriptTimeout, now, now, + ) + return err +} + +func (db *DB) GetSensorConfig() (*SensorConfig, error) { + var sensorConfig SensorConfig + err := db.QueryRow("SELECT * FROM sensor_configs ORDER BY id DESC LIMIT 1").Scan( + &sensorConfig.ID, &sensorConfig.Enabled, &sensorConfig.Interval, + &sensorConfig.SensorTopic, &sensorConfig.CreatedAt, &sensorConfig.UpdatedAt, + ) + if err != nil { + return nil, err + } + return &sensorConfig, nil +} + +func (db *DB) SaveSensorConfig(sensorConfig *SensorConfig) error { + now := time.Now() + _, err := db.Exec(` + INSERT INTO sensor_configs ( + enabled, interval, sensor_topic, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?)`, + sensorConfig.Enabled, sensorConfig.Interval, + sensorConfig.SensorTopic, now, now, + ) + return err +} diff --git a/backend/models.go b/backend/models.go new file mode 100644 index 0000000..d1683bb --- /dev/null +++ b/backend/models.go @@ -0,0 +1,36 @@ +package main + +import ( + "time" +) + +type Config struct { + ID int64 `db:"id"` + BrokerAddress string `db:"broker_address"` + Username string `db:"username"` + Password string `db:"password"` + ClientID string `db:"client_id"` + Topic string `db:"topic"` + LogLevel string `db:"log_level"` + ScriptTimeout int `db:"script_timeout"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` +} + +type Command struct { + ID int64 `db:"id"` + Name string `db:"name"` + ScriptPath string `db:"script_path"` + RunAsUser bool `db:"run_as_user"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` +} + +type SensorConfig struct { + ID int64 `db:"id"` + Enabled bool `db:"enabled"` + Interval int `db:"interval"` + SensorTopic string `db:"sensor_topic"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` +} diff --git a/frontend/components/Sidebar.vue b/frontend/components/Sidebar.vue index 40360be..2f9ab9b 100644 --- a/frontend/components/Sidebar.vue +++ b/frontend/components/Sidebar.vue @@ -9,7 +9,7 @@ Scripts - + MQTT diff --git a/frontend/pages/config/mqtt.vue b/frontend/pages/config/mqtt.vue index 41a40c8..5c33962 100644 --- a/frontend/pages/config/mqtt.vue +++ b/frontend/pages/config/mqtt.vue @@ -1 +1,50 @@ - \ No newline at end of file + + + \ No newline at end of file diff --git a/go.mod b/go.mod index e24f445..f4cfefd 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/gorilla/websocket v1.5.3 // indirect github.com/kardianos/service v1.2.2 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/mattn/go-sqlite3 v1.14.24 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/rs/cors v1.11.1 // indirect github.com/shirou/gopsutil/v4 v4.24.9 // indirect diff --git a/go.sum b/go.sum index ee174a4..b620c6a 100644 --- a/go.sum +++ b/go.sum @@ -13,6 +13,8 @@ github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= +github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= From bac605a919d1172897805c8792b74e0a6207b39e Mon Sep 17 00:00:00 2001 From: Jacques Beets Date: Sun, 6 Oct 2024 13:00:02 +0200 Subject: [PATCH 2/6] wip db config --- backend/config.go | 58 ++++++++-------------------- backend/db.go | 96 +++++++++++++++++++++++++++++++++++++++++----- backend/models.go | 21 +++++----- backend/mqtt.go | 52 +++++++++++++------------ backend/service.go | 15 ++++++++ 5 files changed, 156 insertions(+), 86 deletions(-) diff --git a/backend/config.go b/backend/config.go index f837621..6d67399 100644 --- a/backend/config.go +++ b/backend/config.go @@ -1,59 +1,31 @@ package main import ( - "encoding/json" "fmt" - "os" - "path/filepath" ) type Config struct { - BrokerAddress string `json:"broker_address"` - Username string `json:"username"` - Password string `json:"password"` - ClientID string `json:"client_id"` - Topic string `json:"topic"` - LogLevel string `json:"log_level"` - ScriptTimeout int `json:"script_timeout"` - Commands map[string]ScriptConfig `json:"commands"` - SensorConfig SensorConfig `json:"sensor_config"` -} - -type ScriptConfig struct { - ScriptPath string `json:"script_path"` - RunAsUser bool `json:"run_as_user"` -} -type SensorConfig struct { - Enabled bool `json:"enabled"` - Interval int `json:"interval"` - SensorTopic string `json:"sensor_topic"` + ID int64 `json:"id"` + BrokerAddress string `json:"broker_address"` + Username string `json:"username"` + Password string `json:"password"` + ClientID string `json:"client_id"` + Topic string `json:"topic"` + LogLevel string `json:"log_level"` + ScriptTimeout int `json:"script_timeout"` + SensorConfigEnabled bool `json:"sensor_config_enabled"` + Commands map[string]ScriptConfig `json:"commands"` + Sensors map[string]SensorConfig `json:"sensors"` } func (p *program) loadConfig() error { p.logger.Debug("Starting to load config...") - exePath, err := os.Executable() - if err != nil { - p.logger.Error(fmt.Sprintf("Failed to get executable path: %v", err)) - return fmt.Errorf("failed to get executable path: %v", err) - } - p.logger.Debug(fmt.Sprintf("Executable path: %s", exePath)) - - configPath := filepath.Join(filepath.Dir(exePath), "config.json") - p.logger.Debug(fmt.Sprintf("Config path: %s", configPath)) - - file, err := os.Open(configPath) + conf, err := p.db.GetConfig() if err != nil { - p.logger.Error(fmt.Sprintf("Failed to open config file: %v", err)) - return fmt.Errorf("failed to open config file: %v", err) + p.logger.Error(fmt.Sprintf("Failed to get config: %v", err)) + return err } - defer file.Close() - - decoder := json.NewDecoder(file) - if err := decoder.Decode(&p.config); err != nil { - p.logger.Error(fmt.Sprintf("Failed to decode config: %v", err)) - return fmt.Errorf("failed to decode config: %v", err) - } - + p.config = *conf p.logger.Debug("Config loaded successfully") return nil } diff --git a/backend/db.go b/backend/db.go index d4eb2f3..095197c 100644 --- a/backend/db.go +++ b/backend/db.go @@ -37,7 +37,7 @@ func (db *DB) InitSchema() error { updated_at DATETIME ); - CREATE TABLE IF NOT EXISTS commands ( + CREATE TABLE IF NOT EXISTS script_configs ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, script_path TEXT NOT NULL, @@ -59,18 +59,69 @@ func (db *DB) InitSchema() error { } func (db *DB) GetConfig() (*Config, error) { - var config Config - err := db.QueryRow("SELECT * FROM configs ORDER BY id DESC LIMIT 1").Scan( - &config.ID, &config.BrokerAddress, &config.Username, &config.Password, - &config.ClientID, &config.Topic, &config.LogLevel, &config.ScriptTimeout, - &config.CreatedAt, &config.UpdatedAt, + var ( + configModel ConfigModel + config Config ) + + err := db.QueryRow("SELECT * FROM configs ORDER BY id DESC LIMIT 1").Scan(&configModel) + if err != nil { + return nil, err + } + + configsScript, err := db.GetScriptConfigs() + if err != nil { + return nil, err + } + configsScriptArray := make(map[string]ScriptConfig) + for _, config := range *configsScript { + configsScriptArray[config.ScriptPath] = config + } + + configsSensor, err := db.GetSensorConfigs() if err != nil { return nil, err } + configsSensorArray := make(map[string]SensorConfig) + for _, config := range *configsSensor { + configsSensorArray[config.SensorTopic] = config + } + + config = Config{ + ID: configModel.ID, + BrokerAddress: configModel.BrokerAddress, + Username: configModel.Username, + Password: configModel.Password, + ClientID: configModel.ClientID, + Topic: configModel.Topic, + LogLevel: configModel.LogLevel, + ScriptTimeout: 300, + SensorConfigEnabled: false, + Commands: configsScriptArray, + Sensors: configsSensorArray, + } + return &config, nil } +func (db *DB) GetScriptConfigs() (*ScriptConfigs, error) { + var scriptConfigs ScriptConfigs + err := db.QueryRow("SELECT * FROM script_configs ORDER BY id DESC").Scan(&scriptConfigs) + if err != nil { + return nil, err + } + return &scriptConfigs, nil +} + +func (db *DB) GetSensorConfigs() (*SensorConfigs, error) { + var sensorConfigs SensorConfigs + err := db.QueryRow("SELECT * FROM sensor_configs ORDER BY id DESC").Scan(&sensorConfigs) + if err != nil { + return nil, err + } + return &sensorConfigs, nil +} + func (db *DB) SaveConfig(config *Config) error { now := time.Now() _, err := db.Exec(` @@ -85,9 +136,24 @@ func (db *DB) SaveConfig(config *Config) error { return err } -func (db *DB) GetSensorConfig() (*SensorConfig, error) { +func (db *DB) UpdateConfig(config *Config) error { + now := time.Now() + _, err := db.Exec(` + UPDATE configs SET + broker_address = ?, username = ?, password = ?, client_id = ?, topic = ?, + log_level = ?, script_timeout = ?, updated_at = ? + WHERE id = ?`, + config.BrokerAddress, config.Username, config.Password, + config.ClientID, config.Topic, config.LogLevel, + config.ScriptTimeout, now, + config.ID, + ) + return err +} + +func (db *DB) GetSensorConfig(id int64) (*SensorConfig, error) { var sensorConfig SensorConfig - err := db.QueryRow("SELECT * FROM sensor_configs ORDER BY id DESC LIMIT 1").Scan( + err := db.QueryRow("SELECT * FROM sensor_configs WHERE id = ? ORDER BY id DESC LIMIT 1", id).Scan( &sensorConfig.ID, &sensorConfig.Enabled, &sensorConfig.Interval, &sensorConfig.SensorTopic, &sensorConfig.CreatedAt, &sensorConfig.UpdatedAt, ) @@ -97,7 +163,19 @@ func (db *DB) GetSensorConfig() (*SensorConfig, error) { return &sensorConfig, nil } -func (db *DB) SaveSensorConfig(sensorConfig *SensorConfig) error { +func (db *DB) UpdateSensorConfig(sensorConfig *SensorConfig) error { + _, err := db.Exec(` + UPDATE sensor_configs SET + enabled = ?, interval = ?, sensor_topic = ?, updated_at = ? + WHERE id = ?`, + sensorConfig.Enabled, sensorConfig.Interval, + sensorConfig.SensorTopic, time.Now(), + sensorConfig.ID, + ) + return err +} + +func (db *DB) CreateSensorConfig(sensorConfig *SensorConfig) error { now := time.Now() _, err := db.Exec(` INSERT INTO sensor_configs ( diff --git a/backend/models.go b/backend/models.go index d1683bb..189e696 100644 --- a/backend/models.go +++ b/backend/models.go @@ -4,7 +4,7 @@ import ( "time" ) -type Config struct { +type ConfigModel struct { ID int64 `db:"id"` BrokerAddress string `db:"broker_address"` Username string `db:"username"` @@ -12,25 +12,28 @@ type Config struct { ClientID string `db:"client_id"` Topic string `db:"topic"` LogLevel string `db:"log_level"` - ScriptTimeout int `db:"script_timeout"` CreatedAt time.Time `db:"created_at"` UpdatedAt time.Time `db:"updated_at"` } -type Command struct { - ID int64 `db:"id"` - Name string `db:"name"` - ScriptPath string `db:"script_path"` - RunAsUser bool `db:"run_as_user"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` +type ScriptConfig struct { + ID int64 `db:"id"` + Name string `db:"name"` + ScriptPath string `db:"script_path"` + ScriptTimeout int `db:"script_timeout"` + RunAsUser bool `db:"run_as_user"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` } +type ScriptConfigs []ScriptConfig type SensorConfig struct { ID int64 `db:"id"` + Name string `db:"name"` Enabled bool `db:"enabled"` Interval int `db:"interval"` SensorTopic string `db:"sensor_topic"` CreatedAt time.Time `db:"created_at"` UpdatedAt time.Time `db:"updated_at"` } +type SensorConfigs []SensorConfig diff --git a/backend/mqtt.go b/backend/mqtt.go index 4461142..0476b9b 100644 --- a/backend/mqtt.go +++ b/backend/mqtt.go @@ -1,7 +1,6 @@ package main import ( - "encoding/json" "fmt" "path/filepath" "runtime/debug" @@ -83,29 +82,32 @@ func (p *program) publishResponse(client mqtt.Client, message string) { } func (p *program) publishSensorData() { - ticker := time.NewTicker(time.Duration(p.config.SensorConfig.Interval) * time.Second) - defer ticker.Stop() - - for range ticker.C { - sensorData, err := collectSensorData() - if err != nil { - p.logger.Error(fmt.Sprintf("Failed to collect sensor data: %v", err)) - continue - } - - jsonData, err := json.Marshal(sensorData) - if err != nil { - p.logger.Error(fmt.Sprintf("Failed to marshal sensor data: %v", err)) - continue - } - - token := p.mqttClient.Publish(p.config.SensorConfig.SensorTopic, 0, false, jsonData) - if token.Wait() && token.Error() != nil { - p.logger.Error(fmt.Sprintf("Failed to publish sensor data: %v", token.Error())) - } else { - p.logger.Debug("Successfully published sensor data") - } - } + // TODO: Uncomment this when sensors are implemented + // Must be able to manage multiple sensors independently + + // ticker := time.NewTicker(time.Duration(p.config.SensorConfig.Interval) * time.Second) + // defer ticker.Stop() + + // for range ticker.C { + // sensorData, err := collectSensorData() + // if err != nil { + // p.logger.Error(fmt.Sprintf("Failed to collect sensor data: %v", err)) + // continue + // } + + // jsonData, err := json.Marshal(sensorData) + // if err != nil { + // p.logger.Error(fmt.Sprintf("Failed to marshal sensor data: %v", err)) + // continue + // } + + // token := p.mqttClient.Publish(p.config.SensorConfig.SensorTopic, 0, false, jsonData) + // if token.Wait() && token.Error() != nil { + // p.logger.Error(fmt.Sprintf("Failed to publish sensor data: %v", token.Error())) + // } else { + // p.logger.Debug("Successfully published sensor data") + // } + // } } func (p *program) setupMQTTClient() { @@ -123,7 +125,7 @@ func (p *program) setupMQTTClient() { p.mqttClient = mqtt.NewClient(opts) - if p.config.SensorConfig.Enabled { + if p.config.SensorConfigEnabled { go p.publishSensorData() } } diff --git a/backend/service.go b/backend/service.go index 20cdd59..d11b085 100644 --- a/backend/service.go +++ b/backend/service.go @@ -21,6 +21,7 @@ type program struct { logger *Logger scriptDir string router *mux.Router + db *DB } func newProgram() (*program, error) { @@ -36,6 +37,20 @@ func newProgram() (*program, error) { return nil, fmt.Errorf("failed to get executable path: %v", err) } + // Init DB + p.db, err = NewDB() + if err != nil { + p.logger.Error(fmt.Sprintf("Failed to create database: %v", err)) + return nil, err + } + + // Init Schema + err = p.db.InitSchema() + if err != nil { + p.logger.Error(fmt.Sprintf("Failed to initialize database schema: %v", err)) + return nil, err + } + // Set scripts directory p.scriptDir = filepath.Join(filepath.Dir(exePath), "scripts") From f7bf635c87875aef6f0ec373fbfdf1a3393914aa Mon Sep 17 00:00:00 2001 From: Jacques Beets Date: Sun, 6 Oct 2024 14:27:44 +0200 Subject: [PATCH 3/6] script updates and bug fixes --- .github/workflows/build_and_release.yml | 8 +- .gitignore | 2 +- backend/db.go | 166 +++++++++++++++++++----- backend/models.go | 1 + build_and_deploy.ps1 | 2 +- 5 files changed, 147 insertions(+), 32 deletions(-) diff --git a/.github/workflows/build_and_release.yml b/.github/workflows/build_and_release.yml index 89b5ae4..f98980f 100644 --- a/.github/workflows/build_and_release.yml +++ b/.github/workflows/build_and_release.yml @@ -18,10 +18,16 @@ jobs: uses: actions/setup-go@v2 with: go-version: 1.21 - + + - name: Install MinGW-w64 + run: | + choco install mingw -y + echo "C:\ProgramData\chocolatey\lib\mingw\tools\install\mingw64\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + - name: Build Go binary run: | cd backend + $env:CGO_ENABLED=1 go build -o ../WinSenseConnect.exe - name: Set up Node.js diff --git a/.gitignore b/.gitignore index f4fe94c..c71fa95 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # /frontend /scripts/*.ps1 !scripts/test_notification.ps1 -/data/** */ +/data/** *.exe *.log *.txt diff --git a/backend/db.go b/backend/db.go index 095197c..e8e68f1 100644 --- a/backend/db.go +++ b/backend/db.go @@ -2,6 +2,9 @@ package main import ( "database/sql" + "fmt" + "os" + "path/filepath" "time" _ "github.com/mattn/go-sqlite3" @@ -12,7 +15,22 @@ type DB struct { } func NewDB() (*DB, error) { - db, err := sql.Open("sqlite3", "../data/store.db") + exePath, err := os.Executable() + if err != nil { + return nil, fmt.Errorf("failed to get executable path: %v", err) + } + + dbpath := filepath.Join(filepath.Dir(exePath), "data", "store.db") + _, err = os.Stat(dbpath) + if os.IsNotExist(err) { + // If it doesn't exist, create it + _, err := os.Create(dbpath) + if err != nil { + return nil, err + } + } + + db, err := sql.Open("sqlite3", dbpath) if err != nil { return nil, err } @@ -23,7 +41,22 @@ func NewDB() (*DB, error) { } func (db *DB) InitSchema() error { - _, err := db.Exec(` + // Drop tables if they exist + _, err := db.Exec(`DROP TABLE IF EXISTS configs`) + if err != nil { + return err + } + _, err = db.Exec(`DROP TABLE IF EXISTS script_configs`) + if err != nil { + return err + } + _, err = db.Exec(`DROP TABLE IF EXISTS sensor_configs`) + if err != nil { + return err + } + + // Create tables + _, err = db.Exec(` CREATE TABLE IF NOT EXISTS configs ( id INTEGER PRIMARY KEY AUTOINCREMENT, broker_address TEXT NOT NULL, @@ -42,12 +75,14 @@ func (db *DB) InitSchema() error { name TEXT NOT NULL, script_path TEXT NOT NULL, run_as_user BOOLEAN, + script_timeout INTEGER, created_at DATETIME, updated_at DATETIME ); CREATE TABLE IF NOT EXISTS sensor_configs ( id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, enabled BOOLEAN, interval INTEGER, sensor_topic TEXT, @@ -55,23 +90,64 @@ func (db *DB) InitSchema() error { updated_at DATETIME ); `) + + if err != nil { + return err + } + + // Add default data if it doesn't exist + _, err = db.Exec(` + INSERT INTO configs (id, broker_address, username, password, client_id, topic, log_level, script_timeout, created_at, updated_at) + VALUES (1, 'tcp://0.0.0.0:1883', 'your_username', 'your_password', 'my-windows-automation-service', 'windows/commands', 'debug', 300, '2023-07-01 12:00:00', '2023-07-01 12:00:00'); + `) + + if err != nil { + return err + } + + // Add test script if it doesn't exist + _, err = db.Exec(` + INSERT INTO script_configs (id, name, script_path, script_timeout, run_as_user, created_at, updated_at) + VALUES (1, 'test_notification', 'test_notification.ps1', 300, true, '2023-07-01 12:00:00', '2023-07-01 12:00:00') + `) + if err != nil { + return err + } + + // Add default sensor config if it doesn't exist + _, err = db.Exec(` + INSERT INTO sensor_configs (id, name, enabled, interval, sensor_topic, created_at, updated_at) + VALUES (1, 'cpu_usage', false, 60, 'windows/sensors/cpu_usage', '2023-07-01 12:00:00', '2023-07-01 12:00:00'); + `) + if err != nil { + return err + } + return err } func (db *DB) GetConfig() (*Config, error) { - var ( - configModel ConfigModel - config Config - ) + var configModel ConfigModel - err := db.QueryRow("SELECT * FROM configs ORDER BY id DESC LIMIT 1").Scan(&configModel) + err := db.QueryRow("SELECT id, broker_address, username, password, client_id, topic, log_level, script_timeout, created_at, updated_at FROM configs ORDER BY id DESC LIMIT 1").Scan( + &configModel.ID, + &configModel.BrokerAddress, + &configModel.Username, + &configModel.Password, + &configModel.ClientID, + &configModel.Topic, + &configModel.LogLevel, + &configModel.ScriptTimeout, + &configModel.CreatedAt, + &configModel.UpdatedAt, + ) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to get config: %v", err) } configsScript, err := db.GetScriptConfigs() if err != nil { - return nil, err + return nil, fmt.Errorf("failed to get script configs: %v", err) } configsScriptArray := make(map[string]ScriptConfig) for _, config := range *configsScript { @@ -80,14 +156,14 @@ func (db *DB) GetConfig() (*Config, error) { configsSensor, err := db.GetSensorConfigs() if err != nil { - return nil, err + return nil, fmt.Errorf("failed to get sensor configs: %v", err) } configsSensorArray := make(map[string]SensorConfig) for _, config := range *configsSensor { configsSensorArray[config.SensorTopic] = config } - config = Config{ + config := Config{ ID: configModel.ID, BrokerAddress: configModel.BrokerAddress, Username: configModel.Username, @@ -95,7 +171,7 @@ func (db *DB) GetConfig() (*Config, error) { ClientID: configModel.ClientID, Topic: configModel.Topic, LogLevel: configModel.LogLevel, - ScriptTimeout: 300, + ScriptTimeout: configModel.ScriptTimeout, SensorConfigEnabled: false, Commands: configsScriptArray, Sensors: configsSensorArray, @@ -105,19 +181,39 @@ func (db *DB) GetConfig() (*Config, error) { } func (db *DB) GetScriptConfigs() (*ScriptConfigs, error) { - var scriptConfigs ScriptConfigs - err := db.QueryRow("SELECT * FROM script_configs ORDER BY id DESC").Scan(&scriptConfigs) + rows, err := db.Query("SELECT id, name, script_path, run_as_user, script_timeout, created_at, updated_at FROM script_configs ORDER BY id DESC") if err != nil { - return nil, err + return nil, fmt.Errorf("failed to query script configs: %v", err) + } + defer rows.Close() + + var scriptConfigs ScriptConfigs + for rows.Next() { + var sc ScriptConfig + err := rows.Scan(&sc.ID, &sc.Name, &sc.ScriptPath, &sc.RunAsUser, &sc.ScriptTimeout, &sc.CreatedAt, &sc.UpdatedAt) + if err != nil { + return nil, fmt.Errorf("failed to scan script config: %v", err) + } + scriptConfigs = append(scriptConfigs, sc) } return &scriptConfigs, nil } func (db *DB) GetSensorConfigs() (*SensorConfigs, error) { - var sensorConfigs SensorConfigs - err := db.QueryRow("SELECT * FROM sensor_configs ORDER BY id DESC").Scan(&sensorConfigs) + rows, err := db.Query("SELECT id, name, enabled, interval, sensor_topic, created_at, updated_at FROM sensor_configs ORDER BY id DESC") if err != nil { - return nil, err + return nil, fmt.Errorf("failed to query sensor configs: %v", err) + } + defer rows.Close() + + var sensorConfigs SensorConfigs + for rows.Next() { + var sc SensorConfig + err := rows.Scan(&sc.ID, &sc.Name, &sc.Enabled, &sc.Interval, &sc.SensorTopic, &sc.CreatedAt, &sc.UpdatedAt) + if err != nil { + return nil, fmt.Errorf("failed to scan sensor config: %v", err) + } + sensorConfigs = append(sensorConfigs, sc) } return &sensorConfigs, nil } @@ -153,12 +249,17 @@ func (db *DB) UpdateConfig(config *Config) error { func (db *DB) GetSensorConfig(id int64) (*SensorConfig, error) { var sensorConfig SensorConfig - err := db.QueryRow("SELECT * FROM sensor_configs WHERE id = ? ORDER BY id DESC LIMIT 1", id).Scan( - &sensorConfig.ID, &sensorConfig.Enabled, &sensorConfig.Interval, - &sensorConfig.SensorTopic, &sensorConfig.CreatedAt, &sensorConfig.UpdatedAt, + err := db.QueryRow("SELECT id, name, enabled, interval, sensor_topic, created_at, updated_at FROM sensor_configs WHERE id = ? ORDER BY id DESC LIMIT 1", id).Scan( + &sensorConfig.ID, + &sensorConfig.Name, + &sensorConfig.Enabled, + &sensorConfig.Interval, + &sensorConfig.SensorTopic, + &sensorConfig.CreatedAt, + &sensorConfig.UpdatedAt, ) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to get sensor config: %v", err) } return &sensorConfig, nil } @@ -166,10 +267,13 @@ func (db *DB) GetSensorConfig(id int64) (*SensorConfig, error) { func (db *DB) UpdateSensorConfig(sensorConfig *SensorConfig) error { _, err := db.Exec(` UPDATE sensor_configs SET - enabled = ?, interval = ?, sensor_topic = ?, updated_at = ? + name = ?, enabled = ?, interval = ?, sensor_topic = ?, updated_at = ? WHERE id = ?`, - sensorConfig.Enabled, sensorConfig.Interval, - sensorConfig.SensorTopic, time.Now(), + sensorConfig.Name, + sensorConfig.Enabled, + sensorConfig.Interval, + sensorConfig.SensorTopic, + time.Now(), sensorConfig.ID, ) return err @@ -179,10 +283,14 @@ func (db *DB) CreateSensorConfig(sensorConfig *SensorConfig) error { now := time.Now() _, err := db.Exec(` INSERT INTO sensor_configs ( - enabled, interval, sensor_topic, created_at, updated_at - ) VALUES (?, ?, ?, ?, ?)`, - sensorConfig.Enabled, sensorConfig.Interval, - sensorConfig.SensorTopic, now, now, + name, enabled, interval, sensor_topic, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?)`, + sensorConfig.Name, + sensorConfig.Enabled, + sensorConfig.Interval, + sensorConfig.SensorTopic, + now, + now, ) return err } diff --git a/backend/models.go b/backend/models.go index 189e696..1731b50 100644 --- a/backend/models.go +++ b/backend/models.go @@ -12,6 +12,7 @@ type ConfigModel struct { ClientID string `db:"client_id"` Topic string `db:"topic"` LogLevel string `db:"log_level"` + ScriptTimeout int `db:"script_timeout"` CreatedAt time.Time `db:"created_at"` UpdatedAt time.Time `db:"updated_at"` } diff --git a/build_and_deploy.ps1 b/build_and_deploy.ps1 index 4220566..3de32c2 100644 --- a/build_and_deploy.ps1 +++ b/build_and_deploy.ps1 @@ -6,7 +6,7 @@ Stop-Service -Name "WinSenseConnect" -ErrorAction SilentlyContinue # Build the Go program Write-Host "Building the Go program..." Set-Location .\backend -go build -o ..\WinSenseConnect.exe +$env:CGO_ENABLED=1; go build -o ..\WinSenseConnect.exe if ($LASTEXITCODE -ne 0) { Write-Host "Build failed. Exiting." From e3fd3ed21f7cce65b77d8d95879e342727209306 Mon Sep 17 00:00:00 2001 From: Jacques Beets Date: Sun, 6 Oct 2024 14:31:11 +0200 Subject: [PATCH 4/6] better default data --- backend/db.go | 44 ++++++++++++++------------------------------ 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/backend/db.go b/backend/db.go index e8e68f1..e59a104 100644 --- a/backend/db.go +++ b/backend/db.go @@ -42,21 +42,21 @@ func NewDB() (*DB, error) { func (db *DB) InitSchema() error { // Drop tables if they exist - _, err := db.Exec(`DROP TABLE IF EXISTS configs`) - if err != nil { - return err - } - _, err = db.Exec(`DROP TABLE IF EXISTS script_configs`) - if err != nil { - return err - } - _, err = db.Exec(`DROP TABLE IF EXISTS sensor_configs`) - if err != nil { - return err - } + // _, err := db.Exec(`DROP TABLE IF EXISTS configs`) + // if err != nil { + // return err + // } + // _, err = db.Exec(`DROP TABLE IF EXISTS script_configs`) + // if err != nil { + // return err + // } + // _, err = db.Exec(`DROP TABLE IF EXISTS sensor_configs`) + // if err != nil { + // return err + // } // Create tables - _, err = db.Exec(` + _, err := db.Exec(` CREATE TABLE IF NOT EXISTS configs ( id INTEGER PRIMARY KEY AUTOINCREMENT, broker_address TEXT NOT NULL, @@ -99,29 +99,13 @@ func (db *DB) InitSchema() error { _, err = db.Exec(` INSERT INTO configs (id, broker_address, username, password, client_id, topic, log_level, script_timeout, created_at, updated_at) VALUES (1, 'tcp://0.0.0.0:1883', 'your_username', 'your_password', 'my-windows-automation-service', 'windows/commands', 'debug', 300, '2023-07-01 12:00:00', '2023-07-01 12:00:00'); - `) - if err != nil { - return err - } - - // Add test script if it doesn't exist - _, err = db.Exec(` INSERT INTO script_configs (id, name, script_path, script_timeout, run_as_user, created_at, updated_at) - VALUES (1, 'test_notification', 'test_notification.ps1', 300, true, '2023-07-01 12:00:00', '2023-07-01 12:00:00') - `) - if err != nil { - return err - } + VALUES (1, 'test_notification', 'test_notification.ps1', 300, true, '2023-07-01 12:00:00', '2023-07-01 12:00:00'); - // Add default sensor config if it doesn't exist - _, err = db.Exec(` INSERT INTO sensor_configs (id, name, enabled, interval, sensor_topic, created_at, updated_at) VALUES (1, 'cpu_usage', false, 60, 'windows/sensors/cpu_usage', '2023-07-01 12:00:00', '2023-07-01 12:00:00'); `) - if err != nil { - return err - } return err } From 81073b137f7db7f2f54c1818d9bdcf7950ab4ac8 Mon Sep 17 00:00:00 2001 From: Jacques Beets Date: Sun, 6 Oct 2024 15:52:27 +0200 Subject: [PATCH 5/6] more bugs, styling and api hookup --- backend/db.go | 18 ++++++++++---- backend/http_server.go | 17 ++++++++++--- backend/models.go | 28 +++++++++++----------- backend/service.go | 2 +- frontend/assets/css/tailwind.css | 41 ++++++++++++++++++++++++++++++++ frontend/layouts/default.vue | 2 +- frontend/pages/config/mqtt.vue | 27 +++++++++++++-------- 7 files changed, 102 insertions(+), 33 deletions(-) diff --git a/backend/db.go b/backend/db.go index e59a104..e6348fa 100644 --- a/backend/db.go +++ b/backend/db.go @@ -40,8 +40,9 @@ func NewDB() (*DB, error) { return &DB{db}, nil } -func (db *DB) InitSchema() error { +func (db *DB) InitSchema(logger *Logger) error { // Drop tables if they exist + // logger.Debug("Dropping tables if they exist...") // _, err := db.Exec(`DROP TABLE IF EXISTS configs`) // if err != nil { // return err @@ -94,9 +95,17 @@ func (db *DB) InitSchema() error { if err != nil { return err } - - // Add default data if it doesn't exist - _, err = db.Exec(` + // Check if the default data already exists + var defaultDataExists bool + err = db.QueryRow("SELECT id FROM configs LIMIT 1").Scan(&defaultDataExists) + logger.Debug(fmt.Sprintf("Default data exists: %v", defaultDataExists)) + if err != nil && err != sql.ErrNoRows { + logger.Error(fmt.Sprintf("Failed to check if default data exists: %v", err)) + return err + } + if !defaultDataExists { + // Add default data if it doesn't exist + _, err = db.Exec(` INSERT INTO configs (id, broker_address, username, password, client_id, topic, log_level, script_timeout, created_at, updated_at) VALUES (1, 'tcp://0.0.0.0:1883', 'your_username', 'your_password', 'my-windows-automation-service', 'windows/commands', 'debug', 300, '2023-07-01 12:00:00', '2023-07-01 12:00:00'); @@ -106,6 +115,7 @@ func (db *DB) InitSchema() error { INSERT INTO sensor_configs (id, name, enabled, interval, sensor_topic, created_at, updated_at) VALUES (1, 'cpu_usage', false, 60, 'windows/sensors/cpu_usage', '2023-07-01 12:00:00', '2023-07-01 12:00:00'); `) + } return err } diff --git a/backend/http_server.go b/backend/http_server.go index a5618d2..6463f85 100644 --- a/backend/http_server.go +++ b/backend/http_server.go @@ -55,9 +55,20 @@ func (p *program) handleGetConfig(w http.ResponseWriter, r *http.Request) { func (p *program) handleUpdateConfig(w http.ResponseWriter, r *http.Request) { p.logger.Debug("Handling /api/config POST request") var newConfig Config - json.NewDecoder(r.Body).Decode(&newConfig) - // Write new config to file - // This will later be saved in a db + err := json.NewDecoder(r.Body).Decode(&newConfig) + if err != nil { + p.logger.Error(fmt.Sprintf("Failed to decode config: %v", err)) + http.Error(w, "Bad Request", http.StatusBadRequest) + return + } + p.config = newConfig + err = p.db.UpdateConfig(&p.config) + if err != nil { + p.logger.Error(fmt.Sprintf("Failed to save config: %v", err)) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusOK) } func (p *program) handleListScripts(w http.ResponseWriter, r *http.Request) { diff --git a/backend/models.go b/backend/models.go index 1731b50..292945b 100644 --- a/backend/models.go +++ b/backend/models.go @@ -18,23 +18,23 @@ type ConfigModel struct { } type ScriptConfig struct { - ID int64 `db:"id"` - Name string `db:"name"` - ScriptPath string `db:"script_path"` - ScriptTimeout int `db:"script_timeout"` - RunAsUser bool `db:"run_as_user"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` + ID int64 `db:"id" json:"id"` + Name string `db:"name" json:"name"` + ScriptPath string `db:"script_path" json:"script_path"` + RunAsUser bool `db:"run_as_user" json:"run_as_user"` + ScriptTimeout int `db:"script_timeout" json:"script_timeout"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at"` } type ScriptConfigs []ScriptConfig type SensorConfig struct { - ID int64 `db:"id"` - Name string `db:"name"` - Enabled bool `db:"enabled"` - Interval int `db:"interval"` - SensorTopic string `db:"sensor_topic"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` + ID int64 `db:"id" json:"id"` + Name string `db:"name" json:"name"` + Enabled bool `db:"enabled" json:"enabled"` + Interval int `db:"interval" json:"interval"` + SensorTopic string `db:"sensor_topic" json:"sensor_topic"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at"` } type SensorConfigs []SensorConfig diff --git a/backend/service.go b/backend/service.go index d11b085..51f107b 100644 --- a/backend/service.go +++ b/backend/service.go @@ -45,7 +45,7 @@ func newProgram() (*program, error) { } // Init Schema - err = p.db.InitSchema() + err = p.db.InitSchema(p.logger) if err != nil { p.logger.Error(fmt.Sprintf("Failed to initialize database schema: %v", err)) return nil, err diff --git a/frontend/assets/css/tailwind.css b/frontend/assets/css/tailwind.css index 8377154..829389c 100644 --- a/frontend/assets/css/tailwind.css +++ b/frontend/assets/css/tailwind.css @@ -5,4 +5,45 @@ html, body, #__nuxt { @apply text-slate-50 font-sans h-full; background: rgb(15, 23, 42); +} + +/* Input Styles */ +input, select { + @apply bg-slate-800 text-white rounded-md py-2 px-4 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 focus:ring-offset-slate-50; +} + +.btn-primary { + @apply bg-primary-500 text-white font-semibold rounded-md py-2 px-8 focus:outline-none focus:ring-2 focus:ring-primary-400 focus:ring-offset-2 focus:ring-offset-primary-50; +} + +.btn-secondary { + @apply bg-slate-200 text-slate-600 rounded-md py-2 px-4 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 focus:ring-offset-slate-50; +} + +/* Form Control */ +.form-control { + @apply mb-4 flex flex-col gap-2; +} + +/* Table */ +.table { + @apply table-auto w-full text-left text-sm text-slate-500; +} +.table thead { + @apply text-xs font-semibold text-slate-400 uppercase bg-slate-800; +} +.table tbody tr { + @apply border-b border-slate-700; +} +.table tbody tr:last-child { + @apply border-b-0; +} +.table tbody td { + @apply py-2 px-4; +} +.table tbody td:first-child { + @apply pl-0; +} +.table tbody td:last-child { + @apply pr-0; } \ No newline at end of file diff --git a/frontend/layouts/default.vue b/frontend/layouts/default.vue index 3b492c0..bbc0345 100644 --- a/frontend/layouts/default.vue +++ b/frontend/layouts/default.vue @@ -5,7 +5,7 @@
-
+
diff --git a/frontend/pages/config/mqtt.vue b/frontend/pages/config/mqtt.vue index 5c33962..9b26e2f 100644 --- a/frontend/pages/config/mqtt.vue +++ b/frontend/pages/config/mqtt.vue @@ -1,9 +1,9 @@ \ No newline at end of file From 1719ea9cc2b6718afdc9d550a4a2b605406bd8af Mon Sep 17 00:00:00 2001 From: Jacques Beets Date: Sun, 6 Oct 2024 17:39:38 +0200 Subject: [PATCH 6/6] mqtt config updated. toasts, restart after update, small bugs --- backend/db.go | 85 ++++++++++++++++++++++++++------ backend/http_server.go | 12 +++++ backend/service.go | 17 +++++++ frontend/package-lock.json | 29 +++++++++-- frontend/package.json | 3 +- frontend/pages/config/mqtt.vue | 54 ++++++++++++++++---- frontend/plugins/toast.client.js | 10 ++++ 7 files changed, 182 insertions(+), 28 deletions(-) create mode 100644 frontend/plugins/toast.client.js diff --git a/backend/db.go b/backend/db.go index e6348fa..ef3acd0 100644 --- a/backend/db.go +++ b/backend/db.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "time" _ "github.com/mattn/go-sqlite3" @@ -43,17 +44,17 @@ func NewDB() (*DB, error) { func (db *DB) InitSchema(logger *Logger) error { // Drop tables if they exist // logger.Debug("Dropping tables if they exist...") - // _, err := db.Exec(`DROP TABLE IF EXISTS configs`) - // if err != nil { - // return err + // _, dropErr := db.Exec(`DROP TABLE IF EXISTS configs`) + // if dropErr != nil { + // return dropErr // } - // _, err = db.Exec(`DROP TABLE IF EXISTS script_configs`) - // if err != nil { - // return err + // _, dropErr = db.Exec(`DROP TABLE IF EXISTS script_configs`) + // if dropErr != nil { + // return dropErr // } - // _, err = db.Exec(`DROP TABLE IF EXISTS sensor_configs`) - // if err != nil { - // return err + // _, dropErr = db.Exec(`DROP TABLE IF EXISTS sensor_configs`) + // if dropErr != nil { + // return dropErr // } // Create tables @@ -109,17 +110,57 @@ func (db *DB) InitSchema(logger *Logger) error { INSERT INTO configs (id, broker_address, username, password, client_id, topic, log_level, script_timeout, created_at, updated_at) VALUES (1, 'tcp://0.0.0.0:1883', 'your_username', 'your_password', 'my-windows-automation-service', 'windows/commands', 'debug', 300, '2023-07-01 12:00:00', '2023-07-01 12:00:00'); - INSERT INTO script_configs (id, name, script_path, script_timeout, run_as_user, created_at, updated_at) - VALUES (1, 'test_notification', 'test_notification.ps1', 300, true, '2023-07-01 12:00:00', '2023-07-01 12:00:00'); - INSERT INTO sensor_configs (id, name, enabled, interval, sensor_topic, created_at, updated_at) VALUES (1, 'cpu_usage', false, 60, 'windows/sensors/cpu_usage', '2023-07-01 12:00:00', '2023-07-01 12:00:00'); `) + if err != nil { + return err + } } - + err = db.AddScriptsFromDir() return err } +func (db *DB) AddScriptsFromDir() error { + exePath, err := os.Executable() + if err != nil { + return fmt.Errorf("failed to get executable path: %v", err) + } + dir := filepath.Join(filepath.Dir(exePath), "scripts") + + files, err := os.ReadDir(dir) + if err != nil { + return fmt.Errorf("failed to read directory: %v", err) + } + + for _, file := range files { + if file.IsDir() { + continue + } + + // Check if db has script with same script path + var script ScriptConfig + err := db.QueryRow("SELECT id, name, script_path, run_as_user, script_timeout, created_at, updated_at FROM script_configs WHERE script_path = ?", file.Name()).Scan(&script) + // split file name into name and extension + filename := strings.Split(file.Name(), ".")[0] + + if err == sql.ErrNoRows { + // If not, add it + script := ScriptConfig{ + Name: filename, + ScriptPath: file.Name(), + RunAsUser: true, + ScriptTimeout: 300, + } + err = db.CreateScriptConfig(&script) + if err != nil { + return fmt.Errorf("failed to create script config: %v", err) + } + } + } + return nil +} + func (db *DB) GetConfig() (*Config, error) { var configModel ConfigModel @@ -145,7 +186,7 @@ func (db *DB) GetConfig() (*Config, error) { } configsScriptArray := make(map[string]ScriptConfig) for _, config := range *configsScript { - configsScriptArray[config.ScriptPath] = config + configsScriptArray[config.Name] = config } configsSensor, err := db.GetSensorConfigs() @@ -288,3 +329,19 @@ func (db *DB) CreateSensorConfig(sensorConfig *SensorConfig) error { ) return err } + +func (db *DB) CreateScriptConfig(scriptConf *ScriptConfig) error { + now := time.Now() + _, err := db.Exec(` + INSERT INTO script_configs ( + name, script_path, run_as_user, script_timeout, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?)`, + scriptConf.Name, + scriptConf.ScriptPath, + scriptConf.RunAsUser, + scriptConf.ScriptTimeout, + now, + now, + ) + return err +} diff --git a/backend/http_server.go b/backend/http_server.go index 6463f85..32ce3a5 100644 --- a/backend/http_server.go +++ b/backend/http_server.go @@ -25,6 +25,7 @@ func (p *program) startHTTPServer() { r.HandleFunc("/api/config", p.handleUpdateConfig).Methods("POST") r.HandleFunc("/api/scripts", p.handleListScripts).Methods("GET") r.HandleFunc("/api/scripts", p.handleAddScript).Methods("POST") + r.HandleFunc("/api/restart", p.handleRestartService).Methods("POST") // Serve static files (our UI) - this will be added at build time from our Nuxt frontend r.PathPrefix("/").Handler(http.FileServer(http.Dir(staticPath))) @@ -81,3 +82,14 @@ func (p *program) handleAddScript(w http.ResponseWriter, r *http.Request) { p.logger.Debug("Handling /api/scripts POST request") // Logic to add new powershell scripts } + +func (p *program) handleRestartService(w http.ResponseWriter, r *http.Request) { + p.logger.Debug("Handling /api/restart POST request") + err := p.restartService() + if err != nil { + p.logger.Error(fmt.Sprintf("Failed to restart service: %v", err)) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusOK) +} diff --git a/backend/service.go b/backend/service.go index 51f107b..c4da773 100644 --- a/backend/service.go +++ b/backend/service.go @@ -167,3 +167,20 @@ func (p *program) executeScript(scriptPath string, runAsUser bool) (string, erro return p.runAsLocalSystem(scriptPath) } } + +func (p *program) restartService() error { + p.logger.Debug("Restarting service") + err := p.Stop(nil) + if err != nil { + return fmt.Errorf("failed to stop service: %v", err) + } + // clear mqtt client + p.mqttClient = nil + time.Sleep(time.Second * 5) + p.logger.Debug("Service stopped, restarting...") + err = p.Start(nil) + if err != nil { + return fmt.Errorf("failed to start service: %v", err) + } + return nil +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 33e23f8..a4ed710 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,16 +1,17 @@ { - "name": "nuxt-app", + "name": "mqtt-win-control", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "nuxt-app", + "name": "mqtt-win-control", "hasInstallScript": true, "dependencies": { "@nuxtjs/tailwindcss": "^6.12.1", "nuxt": "^3.13.0", "vue": "latest", - "vue-router": "latest" + "vue-router": "latest", + "vue3-toastify": "^0.2.2" } }, "node_modules/@alloc/quick-lru": { @@ -10725,6 +10726,28 @@ "vue": "^3.2.0" } }, + "node_modules/vue3-toastify": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/vue3-toastify/-/vue3-toastify-0.2.2.tgz", + "integrity": "sha512-D8pmIp2UeU8MU1OY7GktA70HviZ38b1RagN82P7tFu3abUD86w+PjfmbdRch4QVtjVxK+eqKLvi5cXJRndwJfw==", + "license": "MIT", + "workspaces": [ + "docs", + "playground" + ], + "engines": { + "node": ">=18.18.0", + "npm": ">=9.0.0" + }, + "peerDependencies": { + "vue": ">=3.2.0" + }, + "peerDependenciesMeta": { + "vue": { + "optional": true + } + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 27b1250..f3368e5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,6 +13,7 @@ "@nuxtjs/tailwindcss": "^6.12.1", "nuxt": "^3.13.0", "vue": "latest", - "vue-router": "latest" + "vue-router": "latest", + "vue3-toastify": "^0.2.2" } } diff --git a/frontend/pages/config/mqtt.vue b/frontend/pages/config/mqtt.vue index 9b26e2f..a3511b6 100644 --- a/frontend/pages/config/mqtt.vue +++ b/frontend/pages/config/mqtt.vue @@ -1,5 +1,5 @@ \ No newline at end of file diff --git a/frontend/plugins/toast.client.js b/frontend/plugins/toast.client.js new file mode 100644 index 0000000..cc0bc6f --- /dev/null +++ b/frontend/plugins/toast.client.js @@ -0,0 +1,10 @@ +import Vue3Toastify, { toast } from 'vue3-toastify'; +import 'vue3-toastify/dist/index.css'; + +export default defineNuxtPlugin((nuxtApp) => { + nuxtApp.vueApp.use(Vue3Toastify, { autoClose: 2000 }); + + return { + provide: { toast }, + }; +}); \ No newline at end of file