diff --git a/conf/sample.config.toml b/conf/sample.config.toml index 7fde3d1..7b4fa38 100644 --- a/conf/sample.config.toml +++ b/conf/sample.config.toml @@ -8,6 +8,8 @@ # more than one running on the same server, this ID will be shown in the WebUI. # could be set also with SNMPCOL_GENERAL_INSTANCE_ID env var instanceID = "WAN Communicactions" + location = "my_location" + description = "SNMP Instance running at localhost" # datadir set the directory where the data will be placed , also sqlite db if set as db engine # if not set the default datadir will be placed in the configuration directory diff --git a/conf/snmpcollector_schema.sql b/conf/snmpcollector_schema.sql index abf820c..45abd29 100644 --- a/conf/snmpcollector_schema.sql +++ b/conf/snmpcollector_schema.sql @@ -1,6 +1,6 @@ CREATE TABLE `influx_cfg` (`id` TEXT NULL, `host` TEXT NULL, `port` INTEGER NULL, `db` TEXT NULL, `user` TEXT NULL, `password` TEXT NULL, `retention` TEXT NULL); CREATE UNIQUE INDEX `UQE_influx_cfg_id` ON `influx_cfg` (`id`); -CREATE TABLE `snmp_device_cfg` (`id` TEXT NULL, `host` TEXT NULL, `port` INTEGER NULL, `retries` INTEGER NULL, `timeout` INTEGER NULL, `repeat` INTEGER NULL, `snmpversion` TEXT NULL, `community` TEXT NULL, `v3seclevel` TEXT NULL, `v3authuser` TEXT NULL, `v3authpass` TEXT NULL, `v3authprot` TEXT NULL, `v3privpass` TEXT NULL, `v3privprot` TEXT NULL, `freq` INTEGER NULL, `outdb` TEXT NULL, `loglevel` TEXT NULL, `logfile` TEXT NULL, `snmpdebug` INTEGER NULL, `devicetagname` TEXT NULL, `devicetagvalue` TEXT NULL, `extra-tags` TEXT NULL); +CREATE TABLE `snmp_device_cfg` (`id` TEXT NULL, `host` TEXT NULL, `port` INTEGER NULL, `retries` INTEGER NULL, `timeout` INTEGER NULL, `repeat` INTEGER NULL, `snmpversion` TEXT NULL, `community` TEXT NULL, `v3seclevel` TEXT NULL, `v3authuser` TEXT NULL, `v3authpass` TEXT NULL, `v3authprot` TEXT NULL, `v3privpass` TEXT NULL, `v3privprot` TEXT NULL, `freq` INTEGER NULL, `outdb` TEXT NULL, `loglevel` TEXT NULL, `logfile` TEXT NULL, `snmpdebug` INTEGER NULL, `devicetagname` TEXT NULL, `devicetagvalue` TEXT NULL, `extra-tags` TEXT NULL, `location` TEXT NULL); CREATE UNIQUE INDEX `UQE_snmp_device_cfg_id` ON `snmp_device_cfg` (`id`); CREATE TABLE `snmp_metric_cfg` (`id` TEXT NULL, `field_name` TEXT NULL, `description` TEXT NULL, `baseoid` TEXT NULL, `datasrctype` TEXT NULL, `getrate` INTEGER NULL, `scale` REAL NULL, `shift` REAL NULL, `istag` INTEGER NULL DEFAULT 0); CREATE UNIQUE INDEX `UQE_snmp_metric_cfg_id` ON `snmp_metric_cfg` (`id`); diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index ce95152..ca1eb98 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -289,7 +289,7 @@ func AddDeviceInRuntime(k string, cfg *config.SnmpDeviceCfg) { // LoadConf loads the DB conf and initializes the device metric config. func LoadConf() { - MainConfig.Database.LoadDbConfig(&DBConfig) + MainConfig.Database.LoadDbConfig(&DBConfig, MainConfig.General.Location) influxdb = PrepareInfluxDBs() // begin self monitoring process if needed, before all goroutines diff --git a/pkg/config/database.go b/pkg/config/database.go index ee59dad..c63eead 100644 --- a/pkg/config/database.go +++ b/pkg/config/database.go @@ -2,6 +2,7 @@ package config import ( "fmt" + "net" "os" "strconv" "strings" @@ -43,7 +44,7 @@ type DbObjAction struct { } //InitDB initialize de BD configuration -func (dbc *DatabaseCfg) InitDB() error { +func (dbc *DatabaseCfg) InitDB(cfg *GeneralConfig) error { // Create ORM engine and database var err error var dbtype string @@ -159,6 +160,103 @@ func (dbc *DatabaseCfg) InitDB() error { if err = dbc.x.Sync(new(OidConditionCfg)); err != nil { log.Fatalf("Fail to sync database OidConditionCfg: %v\n", err) } + if err = dbc.x.Sync(new(PollerLocationCfg)); err != nil { + log.Fatalf("Fail to sync database PollerLocationCfg: %v\n", err) + } + //Lookup if PollerLocation if got Instance_ID else create one + var locationsFound []*PollerLocationCfg + if err = dbc.x.Where("Instance_ID = '" + cfg.InstanceID + "'").Find(&locationsFound); err != nil { + log.Fatalf("There were an error when looking for %s in PollerLocationCfg. Error: %+v", cfg.InstanceID, err) + } else { + if len(locationsFound) == 0 { + //Create Location + + //Get Hostname + hostname, err := os.Hostname() + if err != nil { + hostname = cfg.InstanceID + } + //Get external IP + ip := getExternalIp() + //Get Location or assign InstanceID + loc := cfg.Location + if len(loc) == 0 { + loc = cfg.InstanceID + } + + var location = PollerLocationCfg{ + ID: cfg.InstanceID, + Location: loc, + Instance_ID: cfg.InstanceID, + Active: true, + Hostname: hostname, + IP: ip.String(), + Description: cfg.Description, + } + + var affected int64 + session := dbc.x.NewSession() + defer session.Close() + affected, err = session.Insert(&location) + if err != nil { + log.Fatalf("There were an error when creating default PollerLocationCfg Error: %+v", err) + session.Rollback() + } else { + log.Infof("PollerLocationCfg created succefully. %+v", affected) + } + } else { + log.Debug("Instance_ID founded on PollerLocationCfg. Checking if any param was changed") + for _, instance := range locationsFound { + log.Debugf("--> %+v\n", instance) + } + //Check if instance params was changed + instance := locationsFound[0] + hasChanges := false + //IP + if instance.IP != getExternalIp().String() { + instance.IP = getExternalIp().String() + hasChanges = true + } + //Description + if instance.Description != cfg.Description { + instance.Description = cfg.Description + hasChanges = true + } + //Hostname + hostname, err := os.Hostname() + if err != nil { + hostname = cfg.InstanceID + } + if instance.Hostname != hostname { + instance.Description = hostname + hasChanges = true + } + //Location + loc := cfg.Location + if len(loc) == 0 { + loc = cfg.InstanceID + } + if instance.Location != loc { + instance.Location = loc + hasChanges = true + } + + if hasChanges { + log.Debug("Updating with newer values...") + var affected int64 + session := dbc.x.NewSession() + defer session.Close() + //affected, err := session.ID(instance.ID).AllCols().Update() + affected, err = session.Where("Instance_ID ='" + instance.Instance_ID + "'").AllCols().Update(instance) + if err != nil { + log.Fatalf("There were an error when updating PollerLocationCfg Error: %+v", err) + session.Rollback() + } else { + log.Infof("PollerLocationCfg updated succefully. %+v", affected) + } + } + } + } return nil } @@ -189,7 +287,7 @@ func CatalogVar2Map(cv map[string]*VarCatalogCfg) map[string]interface{} { } //LoadDbConfig get data from database -func (dbc *DatabaseCfg) LoadDbConfig(cfg *DBConfig) { +func (dbc *DatabaseCfg) LoadDbConfig(cfg *DBConfig, Location string) { var err error //Load Global Variables VarCatalog := make(map[string]*VarCatalogCfg) @@ -232,10 +330,23 @@ func (dbc *DatabaseCfg) LoadDbConfig(cfg *DBConfig) { } //Device - - cfg.SnmpDevice, err = dbc.GetSnmpDeviceCfgMap("") + //Parameters inside GetSnmpDeviceCfgMap are filter to get devices. All devices who match with location will be returned + cfg.SnmpDevice, err = dbc.GetSnmpDeviceCfgMap("location = '" + Location + "'") if err != nil { log.Warningf("Some errors on get SnmpDeviceConf :%v", err) } dbc.resetChanges() } + +//Utils +func getExternalIp() net.IP { + conn, err := net.Dial("udp", "8.8.8.8:80") + if err != nil { + log.Fatal(err) + } + defer conn.Close() + + localAddr := conn.LocalAddr().(*net.UDPAddr) + + return localAddr.IP +} diff --git a/pkg/config/dbconfig.go b/pkg/config/dbconfig.go index b3bc327..7f0188f 100644 --- a/pkg/config/dbconfig.go +++ b/pkg/config/dbconfig.go @@ -45,6 +45,8 @@ type SnmpDeviceCfg struct { //Filters for measurements MeasurementGroups []string `xorm:"-"` MeasFilters []string `xorm:"-"` + //Federated SNMPC + Location string `xorm:"'location' default 'Collector1'"` } // InfluxCfg is the main configuration for any InfluxDB TSDB @@ -131,6 +133,17 @@ type SnmpDevMGroups struct { IDMGroupCfg string `xorm:"id_mgroup_cfg"` } +// SnmpDevMGroups Mgroups defined on each SnmpDevice +type PollerLocationCfg struct { + ID string `xorm:"'id' unique" binding:"Required"` + Location string `xorm:"location"` + Instance_ID string `xorm:"instance_id"` + Active bool `xorm:"active"` + Hostname string `xorm:"hostname"` + IP string `xorm:"ip"` + Description string `xorm:"description"` +} + // DBConfig read from DB type DBConfig struct { Metrics map[string]*SnmpMetricCfg @@ -138,6 +151,7 @@ type DBConfig struct { MFilters map[string]*MeasFilterCfg GetGroups map[string]*MGroupsCfg SnmpDevice map[string]*SnmpDeviceCfg + Location map[string]*PollerLocationCfg Influxdb map[string]*InfluxCfg VarCatalog map[string]interface{} } diff --git a/pkg/config/mainconfig.go b/pkg/config/mainconfig.go index b4a728d..7ac70de 100644 --- a/pkg/config/mainconfig.go +++ b/pkg/config/mainconfig.go @@ -6,12 +6,14 @@ import ( // GeneralConfig has miscellaneous configuration options type GeneralConfig struct { - InstanceID string `mapstructure:"instanceID" envconfig:"SNMPCOL_GENERAL_INSTANCE_ID"` - LogDir string `mapstructure:"logdir" envconfig:"SNMPCOL_GENERAL_LOG_DIR"` - HomeDir string `mapstructure:"homedir" envconfig:"SNMPCOL_GENERAL_HOME_DIR"` - DataDir string `mapstructure:"datadir" envconfig:"SNMPCOL_GENERAL_DATA_DIR" ` - LogLevel string `mapstructure:"loglevel" envconfig:"SNMPCOL_GENERAL_LOG_LEVEL"` - LogMode string `mapstructure:"log_mode" envconfig:"SNMPCOL_GENERAL_LOG_MODE"` + InstanceID string `mapstructure:"instanceID" envconfig:"SNMPCOL_GENERAL_INSTANCE_ID"` + LogDir string `mapstructure:"logdir" envconfig:"SNMPCOL_GENERAL_LOG_DIR"` + HomeDir string `mapstructure:"homedir" envconfig:"SNMPCOL_GENERAL_HOME_DIR"` + DataDir string `mapstructure:"datadir" envconfig:"SNMPCOL_GENERAL_DATA_DIR" ` + LogLevel string `mapstructure:"loglevel" envconfig:"SNMPCOL_GENERAL_LOG_LEVEL"` + LogMode string `mapstructure:"log_mode" envconfig:"SNMPCOL_GENERAL_LOG_MODE"` + Location string `mapstructure:"location" envconfig:"SNMPCOL_GENERAL_LOCATION"` + Description string `mapstructure:"description" envconfig:"SNMPCOL_GENERAL_DESCRIPTION"` } //DatabaseCfg de configuration for the database diff --git a/pkg/config/pollerlocationcfg.go b/pkg/config/pollerlocationcfg.go new file mode 100644 index 0000000..44efff3 --- /dev/null +++ b/pkg/config/pollerlocationcfg.go @@ -0,0 +1,171 @@ +package config + +import ( + "fmt" + "strings" +) + +/*************************** +Global Var + -GetPollerLocationCfgCfgByLocation(struct) + -GetPollerLocationCfgMap (map - for interna config use + -GetPollerLocationCfgArray(Array - for web ui use ) + -AddPollerLocationCfg + -DelPollerLocationCfg + -UpdatePollerLocationCfg + -GetPollerLocationCfgAffectOnDel +***********************************/ + +/*GetPollerLocationCfgByID get metric data by id*/ +func (dbc *DatabaseCfg) GetPollerLocationCfgByID(id string) (PollerLocationCfg, error) { + cfgarray, err := dbc.GetPollerLocationCfgArray("id='" + id + "'") + if err != nil { + return PollerLocationCfg{}, err + } + if len(cfgarray) > 1 { + return PollerLocationCfg{}, fmt.Errorf("Error %d results on get PollerLocationCfg by id %s", len(cfgarray), id) + } + if len(cfgarray) == 0 { + return PollerLocationCfg{}, fmt.Errorf("Error no values have been returned with this id %s in the Global Var config table", id) + } + return *cfgarray[0], nil +} + +/*GetPollerLocationCfgMap return data in map format*/ +func (dbc *DatabaseCfg) GetPollerLocationCfgMap(filter string) (map[string]*PollerLocationCfg, error) { + cfgarray, err := dbc.GetPollerLocationCfgArray(filter) + cfgmap := make(map[string]*PollerLocationCfg) + for _, val := range cfgarray { + cfgmap[val.ID] = val + //log.Debugf("%+v", *val) + } + return cfgmap, err +} + +/*GetPollerLocationCfgArray generate an array of metrics with all its information */ +func (dbc *DatabaseCfg) GetPollerLocationCfgArray(filter string) ([]*PollerLocationCfg, error) { + var err error + var devices []*PollerLocationCfg + //Get Only data for selected metrics + if len(filter) > 0 { + if err = dbc.x.Where(filter).Find(&devices); err != nil { + log.Warnf("Fail to get PollerLocationCfg data filteter with %s : %v\n", filter, err) + return nil, err + } + } else { + if err = dbc.x.Find(&devices); err != nil { + log.Warnf("Fail to get PollerLocationCfg data: %v\n", err) + return nil, err + } + } + return devices, nil +} + +/*AddPollerLocationCfg for adding new Global Variable*/ +func (dbc *DatabaseCfg) AddPollerLocationCfg(dev PollerLocationCfg) (int64, error) { + var err error + var affected int64 + + // initialize data persistence + session := dbc.x.NewSession() + defer session.Close() + + affected, err = session.Insert(dev) + if err != nil { + session.Rollback() + return 0, err + } + //no other relation + err = session.Commit() + if err != nil { + return 0, err + } + log.Infof("Added new Global Variable Successfully with id %s ", dev.ID) + dbc.addChanges(affected) + return affected, nil +} + +/*DelPollerLocationCfg for deleting influx databases from ID*/ +func (dbc *DatabaseCfg) DelPollerLocationCfg(id string) (int64, error) { + var affecteddev, affected int64 + var err error + + session := dbc.x.NewSession() + defer session.Close() + // deleting references in Measurements + + affected, err = session.Where("id='" + id + "'").Delete(&PollerLocationCfg{}) + if err != nil { + session.Rollback() + return 0, err + } + + err = session.Commit() + if err != nil { + return 0, err + } + log.Infof("Deleted Successfully Global Var with ID %s [ %d Measurements Affected ]", id, affecteddev) + dbc.addChanges(affecteddev) + return affected, nil +} + +/*UpdatePollerLocationCfg for adding new influxdb*/ +func (dbc *DatabaseCfg) UpdatePollerLocationCfg(id string, dev PollerLocationCfg) (int64, error) { + var affecteddev, affected int64 + var err error + // create PollerLocationCfg to check if any configuration issue found before persist to database. + + session := dbc.x.NewSession() + defer session.Close() + + if id != dev.ID { //ID has been changed + var metrics []*SnmpMetricCfg + session.Where("datasrctype = 'STRINGEVAL' and extradata like '%" + id + "%'").Find(&metrics) + for _, v := range metrics { + v.ExtraData = strings.Replace(v.ExtraData, id, dev.ID, -1) + _, err = session.Where("id='" + v.ID + "'").UseBool().AllCols().Update(v) + if err != nil { + session.Rollback() + return 0, err + } + log.Infof("Updated STRING EVAL Metric %s devices old variable name %s new %s", v.ID, dev.ID, id) + } + log.Infof("Updated PollerLocationiableConfig to %d devices ", affecteddev) + } + + affected, err = session.Where("id='" + id + "'").UseBool().AllCols().Update(dev) + if err != nil { + session.Rollback() + return 0, err + } + err = session.Commit() + if err != nil { + return 0, err + } + + log.Infof("Updated PollerLocationiableConfig Successfully with id %s and data:%+v, affected", id, dev) + dbc.addChanges(affected + affecteddev) + return affected, nil +} + +/*GetPollerLocationCfgAffectOnDel for deleting devices from ID*/ +func (dbc *DatabaseCfg) GetPollerLocationCfgAffectOnDel(id string) ([]*DbObjAction, error) { + //var devices []*MeasurementFieldCfg + var obj []*DbObjAction + /* + if err := dbc.x.Where("id_metric_cfg='" + id + "'").Find(&devices); err != nil { + log.Warnf("Error on Get Snmp Metric Cfg id %d for devices , error: %s", id, err) + return nil, err + } + + for _, val := range devices { + obj = append(obj, &DbObjAction{ + Type: "measurementcfg", + TypeDesc: "Measurements", + ObID: val.IDMeasurementCfg, + Action: "Delete PollerLocationiablefield from Measurement relation", + }) + + }*/ + return obj, nil +} diff --git a/pkg/main.go b/pkg/main.go index 55a0c63..b23a115 100644 --- a/pkg/main.go +++ b/pkg/main.go @@ -224,7 +224,7 @@ func main() { }() - agent.MainConfig.Database.InitDB() + agent.MainConfig.Database.InitDB(&agent.MainConfig.General) measurement.SetDB(&agent.MainConfig.Database) impexp.SetDB(&agent.MainConfig.Database) diff --git a/pkg/webui/apicfg-pollerlocations.go b/pkg/webui/apicfg-pollerlocations.go new file mode 100644 index 0000000..737b46a --- /dev/null +++ b/pkg/webui/apicfg-pollerlocations.go @@ -0,0 +1,100 @@ +package webui + +import ( + "github.com/go-macaron/binding" + "github.com/toni-moreno/snmpcollector/pkg/agent" + "github.com/toni-moreno/snmpcollector/pkg/config" + "gopkg.in/macaron.v1" +) + +// NewAPICfgPollerLocation PollerLocation API REST creator +func NewAPICfgPollerLocation(m *macaron.Macaron) error { + + bind := binding.Bind + + m.Group("/api/cfg/pollerlocations", func() { + m.Get("/", reqSignedIn, GetPollerLocation) + m.Post("/", reqSignedIn, bind(config.PollerLocationCfg{}), AddPollerLocation) + m.Put("/:id", reqSignedIn, bind(config.PollerLocationCfg{}), UpdatePollerLocation) + m.Delete("/:id", reqSignedIn, DeletePollerLocation) + m.Get("/:id", reqSignedIn, GetPollerLocationByID) + m.Get("/checkondel/:id", reqSignedIn, GetInfluxAffectOnDel) + }) + + return nil +} + +// GetPollerLocation Return Server Array +func GetPollerLocation(ctx *Context) { + cfgarray, err := agent.MainConfig.Database.GetPollerLocationCfgArray("") + if err != nil { + ctx.JSON(404, err.Error()) + log.Errorf("Error on get PollerLocationiable :%+s", err) + return + } + ctx.JSON(200, &cfgarray) + log.Debugf("Getting Gloval Variable s %+v", &cfgarray) +} + +// AddPollerLocation Insert new global var into the database +func AddPollerLocation(ctx *Context, dev config.PollerLocationCfg) { + log.Printf("ADDING Global Variable %+v", dev) + affected, err := agent.MainConfig.Database.AddPollerLocationCfg(dev) + if err != nil { + log.Warningf("Error on insert new Global Variable %s , affected : %+v , error: %s", dev.ID, affected, err) + ctx.JSON(404, err.Error()) + } else { + //TODO: review if needed return data or affected + ctx.JSON(200, &dev) + } +} + +// UpdatePollerLocation --pending-- +func UpdatePollerLocation(ctx *Context, dev config.PollerLocationCfg) { + id := ctx.Params(":id") + log.Debugf("Tying to update: %+v", dev) + affected, err := agent.MainConfig.Database.UpdatePollerLocationCfg(id, dev) + if err != nil { + log.Warningf("Error on update Global Variable %s , affected : %+v , error: %s", dev.ID, affected, err) + } else { + //TODO: review if needed return device data + ctx.JSON(200, &dev) + } +} + +//DeletePollerLocation --pending-- +func DeletePollerLocation(ctx *Context) { + id := ctx.Params(":id") + log.Debugf("Trying to delete: %+v", id) + affected, err := agent.MainConfig.Database.DelPollerLocationCfg(id) + if err != nil { + log.Warningf("Error on delete Global Variable %s , affected : %+v , error: %s", id, affected, err) + ctx.JSON(404, err.Error()) + } else { + ctx.JSON(200, "deleted") + } +} + +//GetPollerLocationByID --pending-- +func GetPollerLocationByID(ctx *Context) { + id := ctx.Params(":id") + dev, err := agent.MainConfig.Database.GetPollerLocationCfgByID(id) + if err != nil { + log.Warningf("Error on get gloval variable %s , error: %s", id, err) + ctx.JSON(404, err.Error()) + } else { + ctx.JSON(200, &dev) + } +} + +//GetPollerLocationAffectOnDel --pending-- +func GetPollerLocationAffectOnDel(ctx *Context) { + id := ctx.Params(":id") + obarray, err := agent.MainConfig.Database.GetPollerLocationCfgAffectOnDel(id) + if err != nil { + log.Warningf("Error on get object array for influx device %s , error: %s", id, err) + ctx.JSON(404, err.Error()) + } else { + ctx.JSON(200, &obarray) + } +} diff --git a/pkg/webui/webserver.go b/pkg/webui/webserver.go index 9ef395f..6cf5541 100644 --- a/pkg/webui/webserver.go +++ b/pkg/webui/webserver.go @@ -180,6 +180,8 @@ func WebServer(publicPath string, httpListen string, cfg *config.HTTPConfig, id NewAPICfgVarCatalog(m) + NewAPICfgPollerLocation(m) + NewAPICfgOidCondition(m) NewAPICfgSnmpMetric(m) diff --git a/src/main.module.ts b/src/main.module.ts index 654e61c..76934e5 100644 --- a/src/main.module.ts +++ b/src/main.module.ts @@ -22,6 +22,7 @@ import { PasswordToggleDirective } from './common/custom-directives' import { TableActions } from './common/table-actions'; //snmpcollector components +import { PollerLocationCfgComponent } from './pollerlocations/pollerlocations.component'; import { VarCatalogCfgComponent } from './varcatalog/varcatalogcfg.component'; import { SnmpDeviceCfgComponent } from './snmpdevice/snmpdevicecfg.component'; import { OidConditionCfgComponent } from './oidcondition/oidconditioncfg.component'; @@ -82,6 +83,7 @@ import { SpinnerComponent } from './common/spinner'; InfluxServerCfgComponent, CustomFilterCfgComponent, VarCatalogCfgComponent, + PollerLocationCfgComponent, TableListComponent, RuntimeComponent, GenericModal, diff --git a/src/pollerlocations/pollerlocationeditor.html b/src/pollerlocations/pollerlocationeditor.html new file mode 100644 index 0000000..3a6776d --- /dev/null +++ b/src/pollerlocations/pollerlocationeditor.html @@ -0,0 +1,2 @@ +

{{defaultConfig.name}}

+ \ No newline at end of file diff --git a/src/pollerlocations/pollerlocations.component.ts b/src/pollerlocations/pollerlocations.component.ts new file mode 100644 index 0000000..f9ec966 --- /dev/null +++ b/src/pollerlocations/pollerlocations.component.ts @@ -0,0 +1,310 @@ + +import { Component, ChangeDetectionStrategy, ViewChild } from '@angular/core'; +import { FormBuilder, Validators} from '@angular/forms'; +import { FormArray, FormGroup, FormControl} from '@angular/forms'; + +import { PollerLocationService } from './pollerlocations.service'; +import { ValidationService } from '../common/validation.service' +import { ExportServiceCfg } from '../common/dataservice/export.service' + +import { GenericModal } from '../common/generic-modal'; +import { ExportFileModal } from '../common/dataservice/export-file-modal'; +import { Observable } from 'rxjs/Rx'; + +import { ItemsPerPageOptions } from '../common/global-constants'; +import { TableActions } from '../common/table-actions'; +import { AvailableTableActions } from '../common/table-available-actions'; + +import { TableListComponent } from '../common/table-list.component'; +import { PollerLocationCfgComponentConfig, TableRole, OverrideRoleActions } from './pollerlocations.data'; + +declare var _:any; + +@Component({ + selector: 'pollerlocation', + providers: [PollerLocationService, ValidationService], + templateUrl: './pollerlocationeditor.html', + styleUrls: ['../css/component-styles.css'] +}) + +export class PollerLocationCfgComponent { + + @ViewChild('viewModal') public viewModal: GenericModal; + @ViewChild('viewModalDelete') public viewModalDelete: GenericModal; + @ViewChild('exportFileModal') public exportFileModal : ExportFileModal; + + itemsPerPageOptions : any = ItemsPerPageOptions; + editmode: string; //list , create, modify + pollerlocations: Array; + filter: string; + pollerlocationForm: any; + myFilterValue: any; + alertHandler : any = null; + + + //Initialization data, rows, colunms for Table + private data: Array = []; + public rows: Array = []; + + public defaultConfig : any = PollerLocationCfgComponentConfig; + public tableRole : any = TableRole; + public overrideRoleActions: any = OverrideRoleActions; + + public tableAvailableActions : any; + + editEnabled : boolean = false; + selectedArray : any = []; + + public isRequesting : boolean; + public counterItems : number = null; + public counterErrors: any = []; + + public page: number = 1; + public itemsPerPage: number = 20; + public maxSize: number = 5; + public numPages: number = 1; + public length: number = 0; + private builder; + private oldID : string; + //Set config + public config: any = { + paging: true, + sorting: { columns: this.defaultConfig['table-columns'] }, + filtering: { filterString: '' }, + className: ['table-striped', 'table-bordered'] + }; + + constructor(public PollerLocationService: PollerLocationService, public exportServiceCfg : ExportServiceCfg, builder: FormBuilder) { + this.editmode = 'list'; + this.reloadData(); + this.builder = builder; + } + + enableEdit() { + this.editEnabled = !this.editEnabled; + let obsArray = []; + this.tableAvailableActions = new AvailableTableActions('pollerlocation').availableOptions; + } + + createStaticForm() { + this.pollerlocationForm = this.builder.group({ + ID: [this.pollerlocationForm ? this.pollerlocationForm.value.ID : '', Validators.required], + Type: [this.pollerlocationForm ? this.pollerlocationForm.value.Type : '', Validators.required], + Value: [this.pollerlocationForm ? this.pollerlocationForm.value.Value : ''], + Description: [this.pollerlocationForm ? this.pollerlocationForm.value.Description : ''] + }); + } + + reloadData() { + // now it's a simple subscription to the observable + this.alertHandler = null; + this.PollerLocationService.getPollerLocation(null) + .subscribe( + data => { + this.isRequesting = false; + this.pollerlocations = data + this.data = data; + }, + err => console.error(err), + () => console.log('DONE') + ); + } + + applyAction(test : any, data? : Array) : void { + this.selectedArray = data || []; + switch(test.action) { + case "RemoveAllSelected": { + this.removeAllSelectedItems(this.selectedArray); + break; + } + case "ChangeProperty": { + this.updateAllSelectedItems(this.selectedArray,test.field,test.value) + break; + } + case "AppendProperty": { + this.updateAllSelectedItems(this.selectedArray,test.field,test.value,true); + } + default: { + break; + } + } + } + + customActions(action : any) { + switch (action.option) { + case 'export' : + this.exportItem(action.event); + break; + case 'new' : + this.newPollerLocation() + case 'view': + this.viewItem(action.event); + break; + case 'edit': + this.editPollerLocation(action.event); + break; + case 'remove': + this.removeItem(action.event); + break; + case 'tableaction': + this.applyAction(action.event, action.data); + break; + } + } + + + viewItem(id) { + console.log('view', id); + this.viewModal.parseObject(id); + } + + exportItem(item : any) : void { + this.exportFileModal.initExportModal(item); + } + + removeAllSelectedItems(myArray) { + let obsArray = []; + this.counterItems = 0; + this.isRequesting = true; + for (let i in myArray) { + console.log("Removing ",myArray[i].ID) + this.deletePollerLocation(myArray[i].ID,true); + obsArray.push(this.deletePollerLocation(myArray[i].ID,true)); + } + this.genericForkJoin(obsArray); + } + + removeItem(row) { + let id = row.ID; + console.log('remove', id); + this.PollerLocationService.checkOnDeletePollerLocation(id) + .subscribe( + data => { + console.log(data); + let temp = data; + this.viewModalDelete.parseObject(temp) + }, + err => console.error(err), + () => { } + ); + } + newPollerLocation() { + //No hidden fields, so create fixed Form + this.createStaticForm(); + this.editmode = "create"; + } + + editPollerLocation(row) { + let id = row.ID; + this.PollerLocationService.getPollerLocationById(id) + .subscribe(data => { + this.pollerlocationForm = {}; + this.pollerlocationForm.value = data; + this.oldID = data.ID + this.createStaticForm(); + this.editmode = "modify"; + }, + err => console.error(err) + ); + } + + deletePollerLocation(id, recursive?) { + if (!recursive) { + this.PollerLocationService.deletePollerLocation(id) + .subscribe(data => { }, + err => console.error(err), + () => { this.viewModalDelete.hide(); this.editmode = "list"; this.reloadData() } + ); + } else { + return this.PollerLocationService.deletePollerLocation(id, true) + .do( + (test) => { this.counterItems++}, + (err) => { this.counterErrors.push({'ID': id, 'error' : err})} + ); + } + } + + cancelEdit() { + this.editmode = "list"; + this.reloadData(); + } + + savePollerLocation() { + if (this.pollerlocationForm.valid) { + this.PollerLocationService.addPollerLocation(this.pollerlocationForm.value) + .subscribe(data => { console.log(data) }, + err => { + console.log(err); + }, + () => { this.editmode = "list"; this.reloadData() } + ); + } + } + + updateAllSelectedItems(mySelectedArray,field,value, append?) { + let obsArray = []; + this.counterItems = 0; + this.isRequesting = true; + if (!append) + for (let component of mySelectedArray) { + component[field] = value; + obsArray.push(this.updatePollerLocation(true,component)); + } else { + let tmpArray = []; + if(!Array.isArray(value)) value = value.split(','); + console.log(value); + for (let component of mySelectedArray) { + console.log(value); + //check if there is some new object to append + let newEntries = _.differenceWith(value,component[field],_.isEqual); + tmpArray = newEntries.concat(component[field]) + console.log(tmpArray); + component[field] = tmpArray; + obsArray.push(this.updatePollerLocation(true,component)); + } + } + this.genericForkJoin(obsArray); + //Make sync calls and wait the result + this.counterErrors = []; + } + + updatePollerLocation(recursive?, component?) { + if(!recursive) { + if (this.pollerlocationForm.valid) { + var r = true; + if (this.pollerlocationForm.value.ID != this.oldID) { + r = confirm("Changing variable identifier " + this.oldID + " to " + this.pollerlocationForm.value.ID + ". Proceed?"); + } + if (r == true) { + this.PollerLocationService.editPollerLocation(this.pollerlocationForm.value, this.oldID, true) + .subscribe(data => { console.log(data) }, + err => console.error(err), + () => { this.editmode = "list"; this.reloadData() } + ); + } + } + } else { + return this.PollerLocationService.editPollerLocation(component, component.ID) + .do( + (test) => { this.counterItems++ }, + (err) => { this.counterErrors.push({'ID': component['ID'], 'error' : err['_body']})} + ) + .catch((err) => { + return Observable.of({'ID': component.ID , 'error': err['_body']}) + }) + } + } + + + genericForkJoin(obsArray: any) { + Observable.forkJoin(obsArray) + .subscribe( + data => { + this.selectedArray = []; + this.reloadData() + }, + err => console.error(err), + ); + } + +} diff --git a/src/pollerlocations/pollerlocations.data.ts b/src/pollerlocations/pollerlocations.data.ts new file mode 100644 index 0000000..2dc39e7 --- /dev/null +++ b/src/pollerlocations/pollerlocations.data.ts @@ -0,0 +1,21 @@ +export const PollerLocationCfgComponentConfig: any = + { + 'name' : 'Locations', + 'table-columns' : [ + { title: 'Instance ID', name: 'Instance_ID' }, + { title: 'Location', name: 'Location' }, + { title: 'Active', name: 'Active' }, + { title: 'Hostname', name: 'Hostname' }, + { title: 'IP', name: 'IP' }, + { title: 'Description', name: 'Description' } + ], + 'slug' : 'pollerlocationcfg' + }; + + export const TableRole : string = 'fulledit'; + export const OverrideRoleActions : Array = [ + {'name':'export', 'type':'icon', 'icon' : 'glyphicon glyphicon-download-alt text-default', 'tooltip': 'Export item'}, + {'name':'view', 'type':'icon', 'icon' : 'glyphicon glyphicon-eye-open text-success', 'tooltip': 'View item'}, + {'name':'edit', 'type':'icon', 'icon' : 'glyphicon glyphicon-edit text-warning', 'tooltip': 'Edit item'}, + {'name':'remove', 'type':'icon', 'icon' : 'glyphicon glyphicon glyphicon-remove text-danger', 'tooltip': 'Remove item'} + ] \ No newline at end of file diff --git a/src/pollerlocations/pollerlocations.service.ts b/src/pollerlocations/pollerlocations.service.ts new file mode 100644 index 0000000..e476bc6 --- /dev/null +++ b/src/pollerlocations/pollerlocations.service.ts @@ -0,0 +1,96 @@ +import { Injectable } from '@angular/core'; +import { HttpService } from '../core/http.service'; +import { Observable } from 'rxjs/Observable'; + +declare var _:any; + +@Injectable() +export class PollerLocationService { + + constructor(public httpAPI: HttpService) { + } + + parseJSON(key,value) { + if ( key == 'Port' || + key == 'Timeout' ) { + return parseInt(value); + } + return value; + } + + addPollerLocation(dev) { + return this.httpAPI.post('/api/cfg/pollerlocations',JSON.stringify(dev,this.parseJSON)) + .map( (responseData) => responseData.json()); + + } + + editPollerLocation(dev, id, hideAlert?) { + return this.httpAPI.put('/api/cfg/pollerlocations/'+id,JSON.stringify(dev,this.parseJSON),null,hideAlert) + .map( (responseData) => responseData.json()); + } + + getPollerLocation(filter_s: string) { + // return an observable + return this.httpAPI.get('/api/cfg/pollerlocations') + .map( (responseData) => { + console.log("Poller locations Volvio ---> ",responseData) + return responseData.json(); + }) + .map((pollerlocations) => { + console.log("MAP SERVICE PollerLocations",pollerlocations); + let result = []; + if (pollerlocations) { + _.forEach(pollerlocations,function(value,key){ + console.log("FOREACH LOOP",value,value.ID); + if(filter_s && filter_s.length > 0 ) { + console.log("maching: "+value.ID+ "filter: "+filter_s); + var re = new RegExp(filter_s, 'gi'); + if (value.ID.match(re)){ + result.push(value); + } + console.log(value.ID.match(re)); + } else { + result.push(value); + } + }); + } + return result; + }); + } + + getPollerLocationById(id : string) { + // return an observable + console.log("ID: ",id); + return this.httpAPI.get('/api/cfg/pollerlocations/'+id) + .map( (responseData) => + responseData.json() + )}; + + checkOnDeletePollerLocation(id : string){ + return this.httpAPI.get('/api/cfg/pollerlocations/checkondel/'+id) + .map( (responseData) => + responseData.json() + ).map((deleteobject) => { + console.log("MAP SERVICE",deleteobject); + let result : any = {'ID' : id}; + _.forEach(deleteobject,function(value,key){ + result[value.TypeDesc] = []; + }); + _.forEach(deleteobject,function(value,key){ + result[value.TypeDesc].Description=value.Action; + result[value.TypeDesc].push(value.ObID); + }); + return result; + }); + }; + + deletePollerLocation(id : string, hideAlert?) { + // return an observable + console.log("ID: ",id); + console.log("DELETING"); + return this.httpAPI.delete('/api/cfg/pollerlocations/'+id, null, hideAlert) + .map( (responseData) => + responseData.json() + ); + }; +} diff --git a/src/snmpdevice/snmpdevicecfg.component.ts b/src/snmpdevice/snmpdevicecfg.component.ts index 222cf0b..d000b60 100644 --- a/src/snmpdevice/snmpdevicecfg.component.ts +++ b/src/snmpdevice/snmpdevicecfg.component.ts @@ -7,6 +7,7 @@ import { MeasGroupService } from '../measgroup/measgroupcfg.service'; import { MeasFilterService } from '../measfilter/measfiltercfg.service'; import { VarCatalogService } from '../varcatalog/varcatalogcfg.service'; import { ValidationService } from '../common/validation.service'; +import { PollerLocationService } from '../pollerlocations/pollerlocations.service'; import { Observable } from 'rxjs/Rx'; import { FormArray, FormGroup, FormControl} from '@angular/forms'; import { BlockUIService } from '../common/blockui/blockui-service'; @@ -30,7 +31,7 @@ declare var _:any; @Component({ selector: 'snmpdevs', - providers: [SnmpDeviceService, InfluxServerService, MeasGroupService, MeasFilterService, VarCatalogService,BlockUIService], + providers: [SnmpDeviceService, InfluxServerService, MeasGroupService, MeasFilterService, PollerLocationService, VarCatalogService, BlockUIService], templateUrl: './snmpdeviceeditor.html', styleUrls: ['../css/component-styles.css'] }) @@ -60,11 +61,13 @@ export class SnmpDeviceCfgComponent { measfilters: Array; measgroups: Array; varcatalogs: Array; + pollerlocations: Array; filteroptions: any; selectgroups: IMultiSelectOption[] = []; selectfilters: IMultiSelectOption[] = []; selectinfluxservers: IMultiSelectOption[] = []; selectvarcatalogs: IMultiSelectOption[] = []; + selectpollerlocations: IMultiSelectOption[] = []; private mySettingsInflux: IMultiSelectSettings = { singleSelect: true, }; @@ -98,7 +101,16 @@ export class SnmpDeviceCfgComponent { selectedVars: Array = []; public extraActions: any = ExtraActions; - constructor(public snmpDeviceService: SnmpDeviceService, public varCatalogService: VarCatalogService, public influxserverDeviceService: InfluxServerService, public measgroupsDeviceService: MeasGroupService, public measfiltersDeviceService: MeasFilterService, public exportServiceCfg : ExportServiceCfg, builder: FormBuilder, private _blocker: BlockUIService) { + constructor( + public snmpDeviceService: SnmpDeviceService, + public varCatalogService: VarCatalogService, + public influxserverDeviceService: InfluxServerService, + public measgroupsDeviceService: MeasGroupService, + public measfiltersDeviceService: MeasFilterService, + public pollerlocationsService: PollerLocationService, + public exportServiceCfg: ExportServiceCfg, + builder: FormBuilder, private _blocker: BlockUIService + ) { this.editmode = 'list'; this.reloadData(); this.builder = builder; @@ -132,7 +144,8 @@ export class SnmpDeviceCfgComponent { Freq: [this.snmpdevForm ? this.snmpdevForm.value.Freq : 60, Validators.compose([Validators.required, ValidationService.uintegerNotZeroValidator])], UpdateFltFreq: [this.snmpdevForm ? this.snmpdevForm.value.UpdateFltFreq : 60, Validators.compose([Validators.required, ValidationService.uintegerAndLessOneValidator])], ConcurrentGather: [this.snmpdevForm ? this.snmpdevForm.value.ConcurrentGather : 'true', Validators.required], - OutDB: [this.snmpdevForm ? this.snmpdevForm.value.OutDB : '', Validators.required], + Location: [this.snmpdevForm ? this.snmpdevForm.value.Location : '', Validators.required], + OutDB: [this.snmpdevForm ? this.snmpdevForm.value.OutDB : '', Validators.required], LogLevel: [this.snmpdevForm ? this.snmpdevForm.value.LogLevel : 'info', Validators.required], SnmpDebug: [this.snmpdevForm ? this.snmpdevForm.value.SnmpDebug : 'false', Validators.required], DeviceTagName: [this.snmpdevForm ? this.snmpdevForm.value.DeviceTagName : '', Validators.required], @@ -200,6 +213,8 @@ export class SnmpDeviceCfgComponent { case '2c': controlArray.push({'ID': 'Community', 'defVal' : 'public', 'Validators' : Validators.required }); break; + case 'Location': + controlArray.push({ 'ID': 'Location', 'defVal': '', 'Validators': Validators.required }); default: //Gauge32 controlArray.push({'ID': 'SnmpVersion', 'defVal' : '2c', 'Validators' : Validators.required }); controlArray.push({'ID': 'Community', 'defVal' : 'public', 'Validators' : Validators.required }); @@ -383,6 +398,7 @@ export class SnmpDeviceCfgComponent { this.getMeasGroupsforDevices(); this.getMeasFiltersforDevices(); this.getVarCatalogsforDevices(); + this.getLocationsforDevices(); this.editmode = "create"; } @@ -393,6 +409,7 @@ export class SnmpDeviceCfgComponent { this.getMeasGroupsforDevices(); this.getMeasFiltersforDevices(); this.getVarCatalogsforDevices(); + this.getLocationsforDevices(); this.snmpDeviceService.getDevicesById(id) .subscribe(data => { @@ -609,4 +626,20 @@ updateAllSelectedItems(mySelectedArray,field,value, append?) { () => console.log('DONE') ); } + + + getLocationsforDevices() { + this.pollerlocationsService.getPollerLocation(null) + .subscribe( + data => { + this.pollerlocations = data + this.selectpollerlocations = []; + for (let entry of data) { + this.selectpollerlocations.push({ 'id': entry.ID, 'name': entry.Instance_ID + ' - ' + entry.Location + ' (' + entry.IP + ')'}); + } + }, + err => console.error(err), + () => console.log('DONE') + ); + } } diff --git a/src/snmpdevice/snmpdevicecfg.data.ts b/src/snmpdevice/snmpdevicecfg.data.ts index 0b78302..1b0dacb 100644 --- a/src/snmpdevice/snmpdevicecfg.data.ts +++ b/src/snmpdevice/snmpdevicecfg.data.ts @@ -12,6 +12,7 @@ export const SnmpDeviceCfgComponentConfig: any = { title: 'Polling Period (sec)', name: 'Freq' }, { title: 'Update Filter (Cycles)', name: 'UpdateFltFreq' }, { title: 'Concurrent Gather', name: 'ConcurrentGather' }, + { title: 'Location', name: 'Location' }, { title: 'Influx DB', name: 'OutDB' }, { title: 'Log Level', name: 'LogLevel' }, { title: 'Disable Snmp Bulk Queries', name: 'DisableBulk' }, diff --git a/src/snmpdevice/snmpdeviceeditor.html b/src/snmpdevice/snmpdeviceeditor.html index c717de7..0a6a0be 100644 --- a/src/snmpdevice/snmpdeviceeditor.html +++ b/src/snmpdevice/snmpdeviceeditor.html @@ -320,6 +320,16 @@

+
+ + +
+
+ + +
+
+