diff --git a/.air.toml b/.air.toml new file mode 100644 index 0000000..93fd403 --- /dev/null +++ b/.air.toml @@ -0,0 +1,46 @@ +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] + args_bin = [] + bin = "./tmp/main" + cmd = "go build -o ./tmp/main ." + delay = 1000 + exclude_dir = ["assets", "tmp", "vendor", "testdata"] + exclude_file = [] + exclude_regex = ["_test.go", "_templ.go"] + exclude_unchanged = false + follow_symlink = false + full_bin = "" + include_dir = [] + include_ext = ["go", "tpl", "templ", "html"] + include_file = [] + kill_delay = "0s" + log = "build-errors.log" + poll = false + poll_interval = 0 + post_cmd = ["rm tmp/main"] + pre_cmd = ["templ generate"] + rerun = false + rerun_delay = 500 + send_interrupt = false + stop_on_error = false + +[color] + app = "" + build = "yellow" + main = "magenta" + runner = "green" + watcher = "cyan" + +[log] + main_only = false + time = false + +[misc] + clean_on_exit = false + +[screen] + clear_on_rebuild = false + keep_scroll = true diff --git a/.gitignore b/.gitignore index 291135f..bd25a2a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ .null-ls_* a.out *.sqlite +tmp/ diff --git a/Dockerfile b/Dockerfile index 5b9acba..910d7d7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,11 @@ FROM golang:alpine AS builder RUN apk update && apk add --no-cache git +RUN go install github.com/a-h/templ/cmd/templ@latest WORKDIR $GOPATH/src/goipam COPY . . +RUN rm web/templates/*_templ.go +RUN templ generate RUN go get -d -v RUN go build -a -installsuffix cgo -ldflags="-w -s" -o /go/bin/goipam diff --git a/Makefile b/Makefile index d6d888e..5133baa 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,7 @@ release: deps templ GIN_MODE=release go build -v -x -buildvcs=true . dev: templ - go build . - ./webframework -p=false + air clean: rm -f webframework @@ -12,9 +11,10 @@ clean: rm -f db.sqlite templ: + rm -f web/templates/*_templ.go templ fmt . templ generate deps: go mod download - go mod tidy \ No newline at end of file + go mod tidy diff --git a/config.go b/config.go index e63ce85..f83d961 100644 --- a/config.go +++ b/config.go @@ -1,9 +1,12 @@ package main import ( + "fmt" "log" "os" + "path" "path/filepath" + "strings" "github.com/spf13/viper" ) @@ -23,9 +26,28 @@ func readConfig() { viper.SetDefault("web.host", "0.0.0.0") viper.SetDefault("web.port", 8080) viper.SetDefault("web.prod", true) + // Database Config + viper.SetDefault("db.type", "sqlite") + viper.SetDefault("db.host", "localhost") + viper.SetDefault("db.user", "dbuser") + viper.SetDefault("db.path", "./db.sqlite") + viper.SetDefault("db.password", "dbpw") + viper.SetDefault("db.port", 5432) + viper.SetDefault("db.sslmode", "disable") + + viper.AutomaticEnv() err = viper.ReadInConfig() - if err != nil { - log.Fatal("err") + + // If a config file is found, read it in. + if err == nil { + fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) + } else { + log.Println(err) + errString := err.Error() + if strings.Contains(errString, "Not Found") { + log.Println("could not find config file, creating...") + viper.WriteConfigAs(path.Join(executableDir, "goipam.yaml")) + } } diff --git a/db/addresses.go b/db/addresses.go new file mode 100644 index 0000000..09e8e6d --- /dev/null +++ b/db/addresses.go @@ -0,0 +1,31 @@ +package db + +import ( + "log" +) + +func (address *Address) Save() error { + if err := conn.Save(address).Error; err != nil { + return err + } + return nil + +} + +func (address *Address) Delete() error { + if err := conn.Delete(address).Error; err != nil { + return err + } + return nil + +} +func CountAddresss() int { + var count int64 + err := conn.Model(&Address{}).Count(&count) + if err != nil { + count = 0 + log.Println("Error counting addresss: ") + } + + return int(count) +} diff --git a/db/database.go b/db/database.go index 68b952d..2d557b9 100644 --- a/db/database.go +++ b/db/database.go @@ -1,9 +1,10 @@ package db import ( + "log" + "github.com/spf13/viper" "gorm.io/gorm" - "log" ) var conn *gorm.DB diff --git a/db/devices.go b/db/devices.go new file mode 100644 index 0000000..bb27cba --- /dev/null +++ b/db/devices.go @@ -0,0 +1,31 @@ +package db + +import ( + "log" +) + +func (device *Device) Save() error { + if err := conn.Save(device).Error; err != nil { + return err + } + return nil + +} + +func (device *Device) Delete() error { + if err := conn.Delete(device).Error; err != nil { + return err + } + return nil + +} +func CountDevices() int { + var count int64 + err := conn.Model(&Device{}).Count(&count) + if err != nil { + count = 0 + log.Println("Error counting devices: ") + } + + return int(count) +} diff --git a/db/locations.go b/db/locations.go new file mode 100644 index 0000000..a74d574 --- /dev/null +++ b/db/locations.go @@ -0,0 +1,30 @@ +package db + +import "log" + +func (location *Location) Save() error { + if err := conn.Save(location).Error; err != nil { + return err + } + return nil + +} + +func (location *Location) Delete() error { + if err := conn.Delete(location).Error; err != nil { + return err + } + return nil + +} + +func CountLocations() int { + var count int64 + err := conn.Model(&Location{}).Count(&count) + if err != nil { + count = 0 + log.Println("Error counting locations: ") + } + + return int(count) +} diff --git a/db/models.go b/db/models.go index c9d03bb..5f65786 100644 --- a/db/models.go +++ b/db/models.go @@ -1,7 +1,9 @@ // Define all Database Models here package db -import "gorm.io/gorm" +import ( + "gorm.io/gorm" +) type User struct { gorm.Model @@ -19,3 +21,39 @@ type Group struct { DisplayName string Users []User `gorm:"many2many:user_groups"` } + +type Subnet struct { + gorm.Model + Name string `grom:"unique;index"` + DisplayName string + VLAN int + IPv4Net string + IPv6Net string + LocationID uint +} + +type Location struct { + gorm.Model + Name string + Comment string + Subnets []Subnet +} + +type Address struct { + gorm.Model + Name string + Comment string + IPv4Address string + IPv6Address string + MACAddress string + DNSName string + DeviceID uint + SubnetID uint +} + +type Device struct { + gorm.Model + Name string + Comment string + Addresses []Address +} diff --git a/db/setup.go b/db/setup.go index 572784b..1182d03 100644 --- a/db/setup.go +++ b/db/setup.go @@ -3,6 +3,7 @@ package db import ( "log" + "github.com/spf13/viper" "gorm.io/gorm" "git.jmbit.de/jmb/goipam/utils" @@ -13,11 +14,18 @@ func SetupDB() { ConnectDB() var users []User var count int64 + var err error + var adminPW string conn.Find(&users).Count(&count) if count >= 1 { return } - adminPW, err := utils.RandomString(32) + if viper.GetString("web.adminpw") != "" { + adminPW = viper.GetString("web.adminpw") + + } else { + adminPW, err = utils.RandomString(32) + } if err != nil { log.Fatalf("Could not generate admin PW: %v", err) } else { diff --git a/db/subnets.go b/db/subnets.go new file mode 100644 index 0000000..c434c42 --- /dev/null +++ b/db/subnets.go @@ -0,0 +1,31 @@ +package db + +import ( + "log" +) + +func (subnet *Subnet) Save() error { + if err := conn.Save(subnet).Error; err != nil { + return err + } + return nil + +} + +func (subnet *Subnet) Delete() error { + if err := conn.Delete(subnet).Error; err != nil { + return err + } + return nil + +} +func CountSubnets() int { + var count int64 + err := conn.Model(&Subnet{}).Count(&count) + if err != nil { + count = 0 + log.Println("Error counting subnets: ") + } + + return int(count) +} diff --git a/goipam.yaml b/goipam.yaml new file mode 100644 index 0000000..0d27f90 --- /dev/null +++ b/goipam.yaml @@ -0,0 +1,12 @@ +db: + host: localhost + password: dbpw + path: ./db.sqlite + port: 5432 + sslmode: disable + type: sqlite + user: dbuser +web: + host: 0.0.0.0 + port: 8080 + prod: true diff --git a/main.go b/main.go index f8ee5c4..289a9e7 100644 --- a/main.go +++ b/main.go @@ -1,5 +1,18 @@ package main -func main { - readC +import ( + "log" + + "git.jmbit.de/jmb/goipam/db" + "git.jmbit.de/jmb/goipam/web" +) + +func main() { + readConfig() + db.SetupDB() + db.ConnectDB() + err := web.Run() + if err != nil { + log.Fatal(err) + } } diff --git a/web/auth/middleware.go b/web/auth/middleware.go index 8925e9c..a361e03 100644 --- a/web/auth/middleware.go +++ b/web/auth/middleware.go @@ -27,7 +27,7 @@ func AuthMiddleware(requiredLevel int) gin.HandlerFunc { metaContent := utils.GenMetaContent(c) metaContent.ErrorTitle = "Not Authorized" metaContent.ErrorText = "You are not authorized to do this Action" - c.HTML(http.StatusUnauthorized, "", templates.Index(metaContent)) + c.HTML(http.StatusUnauthorized, "", templates.Login(metaContent, "Login", nil)) c.Abort() return } diff --git a/web/router.go b/web/router.go index e9aeb85..e7d2479 100644 --- a/web/router.go +++ b/web/router.go @@ -5,11 +5,13 @@ import ( "log" "net/http" - "git.jmbit.de/jmb/goipam/db" - "git.jmbit.de/jmb/goipam/web/static" "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" "github.com/spf13/viper" + + "git.jmbit.de/jmb/goipam/db" + "git.jmbit.de/jmb/goipam/web/static" + "git.jmbit.de/jmb/goipam/web/ui" ) func Run() error { @@ -38,6 +40,7 @@ func setupRouter() *gin.Engine { } router := gin.New() router.Use(gin.Recovery()) + router.Use(urlLog()) router.Use(sessions.Sessions("session", db.CreateStore())) err := router.SetTrustedProxies(viper.GetStringSlice("web.trustedProxies")) router.HTMLRender = &TemplRender{} @@ -50,6 +53,7 @@ func setupRouter() *gin.Engine { } else { router.StaticFS("/static", http.FS(static.StaticFS)) } + router = ui.GroupWeb(router) return router } diff --git a/web/static/bulmaScripts.js b/web/static/bulmaScripts.js new file mode 100644 index 0000000..4b964c4 --- /dev/null +++ b/web/static/bulmaScripts.js @@ -0,0 +1,2 @@ +// NavBar Hamburger Script + diff --git a/web/templates/formatting.templ b/web/templates/formatting.templ index e8d6f4a..6c4fa56 100644 --- a/web/templates/formatting.templ +++ b/web/templates/formatting.templ @@ -1,7 +1,6 @@ package templates // columns takes components and arranges them in columns - templ columns(columns ...templ.Component) {
Subnets
+{ fmt.Sprint(counters.SubnetCount) }
+Addresses
+{ fmt.Sprint(counters.AddressCount) }
+Devices
+{ fmt.Sprint(counters.DeviceCount) }
+Locations
+{ fmt.Sprint(counters.LocationCount) }
+Subnets
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(counters.SubnetCount)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/index.templ`, Line: 19, Col: 55} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
Addresses
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(counters.AddressCount)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/index.templ`, Line: 25, Col: 56} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
Devices
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(counters.DeviceCount)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/index.templ`, Line: 31, Col: 55} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
Locations
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(counters.LocationCount)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/index.templ`, Line: 37, Col: 57} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
Please login to GoIPAM to proceed
+Please login to GoIPAM to proceed