From fab2db09365ec8e2c1195e2780c139f8c0646dfc Mon Sep 17 00:00:00 2001 From: Azareal Date: Sun, 7 May 2017 09:31:41 +0100 Subject: [PATCH] Added the Control Panel Dashboard. Bumped the version requirements upto Go 1.8 Added gopsutil as a dependency. Added the minify_templates and super_debug config.go options. Cleaned up the console. config.go is a little more organised now. --- README.md | 18 ++- config.go | 37 +++--- files.go | 5 +- install-gosora-linux | 2 + install.bat | 12 +- install/install.go | 38 +++--- main.go | 34 +++--- mysql.go | 27 +++-- pages.go | 19 +++ panel_routes.go | 160 ++++++++++++++++++++++++-- setting.go | 6 +- templates.go | 113 +++++++++--------- templates/panel-dashboard.html | 10 +- templates/panel-modlogs.html | 2 +- themes.go | 13 ++- themes/cosmo-conflux/public/main.css | 37 +++++- themes/cosmo/public/main.css | 47 ++++++-- themes/tempra-conflux/public/main.css | 35 +++++- themes/tempra-cursive/public/main.css | 37 +++++- themes/tempra-simple/public/main.css | 37 +++++- update-deps-linux | 4 +- update-deps.bat | 14 +++ utils.go | 47 ++++++++ 23 files changed, 592 insertions(+), 162 deletions(-) diff --git a/README.md b/README.md index bfa338e7..3f497a68 100644 --- a/README.md +++ b/README.md @@ -110,13 +110,17 @@ We're looking for ways to clean-up the plugin system so that all of them (except # Dependencies -* Go 1.7 +* Go 1.8 -* MariaDB +* MariaDB (or any other MySQL compatible database engine) -* github.com/go-sql-driver/mysql +* github.com/go-sql-driver/mysql For interfacing with MariaDB. -* golang.org/x/crypto/bcrypt +* golang.org/x/crypto/bcrypt For hashing passwords. + +* github.com/shirou/gopsutil For pulling information on CPU and memory usage. + + * github.com/StackExchange/wmi Dependency for gopsutil on Windows. # Bundled Plugins @@ -127,3 +131,9 @@ There are several plugins which are bundled with the software by default. These * BBCode - A plugin in early development for converting BBCode Tags into HTML. * Markdown - An extremely simple plugin for converting Markdown into HTML. + +# Developers + +There are a few things you'll need to know before running the more developer oriented features like the tests or the benchmarks. + +The benchmarks are currently being rewritten as they're currently extremely serial which can lead to severe slow-downs when run on a home computer due to the benchmarks being run on the one core everything else is being run on (Browser, OS, etc.) and the tests not taking parallelism into account. diff --git a/config.go b/config.go index 25346213..79aae651 100644 --- a/config.go +++ b/config.go @@ -1,5 +1,13 @@ package main +// Site Info +var site_name = "Test Install" // Should be a setting in the database +var site_url = "localhost:8080" +var server_port = "8080" +var enable_ssl = false +var ssl_privkey = "" +var ssl_fullchain = "" + // Database details var dbhost = "localhost" var dbuser = "root" @@ -10,35 +18,32 @@ var dbport = "3306" // You probably won't need to change this // Limiters var max_request_size = 5 * megabyte -// Misc +// Caching var cache_topicuser = CACHE_STATIC var user_cache_capacity = 100 // The max number of users held in memory var topic_cache_capacity = 100 // The max number of topics held in memory -var default_route = route_topics -var default_group = 3 // Should be a setting -var activation_group = 5 // Should be a setting -var staff_css = " background-color: #ffeaff;" -var uncategorised_forum_visible = true -var enable_emails = false -var site_name = "Test Install" // Should be a setting -var site_email = "" // Should be a setting +// Email +var site_email = "" // Should be a setting in the database var smtp_server = "" var smtp_username = "" var smtp_password = "" var smtp_port = "25" +var enable_emails = false + +// Misc +var default_route = route_topics +var default_group = 3 // Should be a setting in the database +var activation_group = 5 // Should be a setting in the database +var staff_css = " background-color: #ffeaff;" +var uncategorised_forum_visible = true +var minify_templates = true //var noavatar = "https://api.adorable.io/avatars/{width}/{id}@{site_url}.png" var noavatar = "https://api.adorable.io/avatars/285/{id}@" + site_url + ".png" var items_per_page = 25 -var site_url = "localhost:8080" -var server_port = "8080" -var enable_ssl = false -var ssl_privkey = "" -var ssl_fullchain = "" - // Developer flags var debug = false +var super_debug = false var profiling = false - diff --git a/files.go b/files.go index f96fa63b..dda32554 100644 --- a/files.go +++ b/files.go @@ -75,9 +75,10 @@ func add_static_file(path string, prefix string) error { return err } - log.Print("Adding the '" + path + "' static file") path = strings.TrimPrefix(path, prefix) - log.Print("Added the '" + path + "' static file") + if debug { + log.Print("Added the '" + path + "' static file") + } gzip_data := compress_bytes_gzip(data) static_files["/static" + path] = SFile{data,gzip_data,0,int64(len(data)),int64(len(gzip_data)),mime.TypeByExtension(filepath.Ext(prefix + path)),f,f.ModTime().UTC().Format(http.TimeFormat)} diff --git a/install-gosora-linux b/install-gosora-linux index 5a2ada69..9da702d0 100644 --- a/install-gosora-linux +++ b/install-gosora-linux @@ -2,6 +2,8 @@ echo "Installing the MySQL Driver" go get -u github.com/go-sql-driver/mysql echo "Installing bcrypt" go get -u golang.org/x/crypto/bcrypt +echo "Installing gopsutil" +go get -u github.com/shirou/gopsutil echo "Preparing the installer" go generate diff --git a/install.bat b/install.bat index be08c151..8b6bcbc2 100644 --- a/install.bat +++ b/install.bat @@ -1,5 +1,5 @@ @echo off -echo Installing the dependencies +echo Installing dependencies go get -u github.com/go-sql-driver/mysql if %errorlevel% neq 0 ( pause @@ -10,6 +10,16 @@ if %errorlevel% neq 0 ( pause exit /b %errorlevel% ) +go get -u github.com/StackExchange/wmi +if %errorlevel% neq 0 ( + pause + exit /b %errorlevel% +) +go get -u github.com/shirou/gopsutil +if %errorlevel% neq 0 ( + pause + exit /b %errorlevel% +) echo Preparing the installer go generate diff --git a/install/install.go b/install/install.go index 12da223f..ebfd1248 100644 --- a/install/install.go +++ b/install/install.go @@ -138,6 +138,14 @@ func main() { configContents := []byte(`package main +// Site Info +var site_name = "` + site_name + `" // Should be a setting in the database +var site_url = "` + site_url + `" +var server_port = "` + server_port + `" +var enable_ssl = false +var ssl_privkey = "" +var ssl_fullchain = "" + // Database details var dbhost = "` + db_host + `" var dbuser = "` + db_username + `" @@ -148,36 +156,34 @@ var dbport = "` + db_port + `" // You probably won't need to change this // Limiters var max_request_size = 5 * megabyte -// Misc +// Caching var cache_topicuser = CACHE_STATIC var user_cache_capacity = 100 // The max number of users held in memory var topic_cache_capacity = 100 // The max number of topics held in memory -var default_route = route_topics -var default_group = 3 // Should be a setting -var activation_group = 5 // Should be a setting -var staff_css = " background-color: #ffeaff;" -var uncategorised_forum_visible = true -var enable_emails = false -var site_name = "` + site_name + `" // Should be a setting -var site_email = "" // Should be a setting +// Email +var site_email = "" // Should be a setting in the database var smtp_server = "" var smtp_username = "" var smtp_password = "" var smtp_port = "25" +var enable_emails = false + +// Misc +var default_route = route_topics +var default_group = 3 // Should be a setting in the database +var activation_group = 5 // Should be a setting in the database +var staff_css = " background-color: #ffeaff;" +var uncategorised_forum_visible = true +var minify_templates = true //var noavatar = "https://api.adorable.io/avatars/{width}/{id}@{site_url}.png" var noavatar = "https://api.adorable.io/avatars/285/{id}@" + site_url + ".png" var items_per_page = 25 -var site_url = "` + site_url + `" -var server_port = "` + server_port + `" -var enable_ssl = false -var ssl_privkey = "" -var ssl_fullchain = "" - // Developer flag -var debug = false +var debug = true +var super_debug = false var profiling = false `) diff --git a/main.go b/main.go index 87441339..bca4c986 100644 --- a/main.go +++ b/main.go @@ -1,8 +1,9 @@ -/* Copyright Azareal 2016 - 2017 */ +/* Copyright Azareal 2016 - 2018 */ package main import ( "net/http" + "fmt" "log" "mime" "strings" @@ -14,15 +15,18 @@ import ( //"runtime/pprof" ) +var version Version = Version{Major:0,Minor:1,Patch:0,Tag:"dev"} + const hour int = 60 * 60 const day int = hour * 24 const month int = day * 30 const year int = day * 365 const kilobyte int = 1024 const megabyte int = kilobyte * 1024 +const gigabyte int = megabyte * 1024 +const terabyte int = gigabyte * 1024 const saltLength int = 32 const sessionLength int = 80 -var nogrouplog bool = false // This is mainly for benchmarks, as we don't want a lot of information getting in the way of the results var templates = template.New("") var no_css_tmpl = template.CSS("") @@ -106,19 +110,14 @@ func init_templates() { compile_templates() // Filler functions for now... + filler_func := func(in interface{}, in2 interface{})interface{} { + return 1 + } fmap := make(map[string]interface{}) - fmap["add"] = func(in interface{}, in2 interface{})interface{} { - return 1 - } - fmap["subtract"] = func(in interface{}, in2 interface{})interface{} { - return 1 - } - fmap["multiply"] = func(in interface{}, in2 interface{})interface{} { - return 1 - } - fmap["divide"] = func(in interface{}, in2 interface{})interface{} { - return 1 - } + fmap["add"] = filler_func + fmap["subtract"] = filler_func + fmap["multiply"] = filler_func + fmap["divide"] = filler_func // The interpreted templates... templates.Funcs(fmap) @@ -140,7 +139,9 @@ func init_static_files() { } path = strings.TrimPrefix(path,"public/") - log.Print("Added the '" + path + "' static file.") + if debug { + log.Print("Added the '" + path + "' static file.") + } gzip_data := compress_bytes_gzip(data) static_files["/static/" + path] = SFile{data,gzip_data,0,int64(len(data)),int64(len(gzip_data)),mime.TypeByExtension(filepath.Ext("/public/" + path)),f,f.ModTime().UTC().Format(http.TimeFormat)} @@ -160,6 +161,9 @@ func main(){ // pprof.StartCPUProfile(f) //} + log.Print("Running Gosora v" + version.String()) + fmt.Println("") + init_themes() err := init_database() if err != nil { diff --git a/mysql.go b/mysql.go index 5b637c5c..d9f5570d 100644 --- a/mysql.go +++ b/mysql.go @@ -633,7 +633,7 @@ func init_database() (err error) { // Ugh, you really shouldn't physically delete these items, it makes a big mess of things if group.ID != i { - fmt.Println("Stop physically deleting groups. You are messing up the IDs. Use the Group Manager or delete_group() instead x.x") + log.Print("Stop physically deleting groups. You are messing up the IDs. Use the Group Manager or delete_group() instead x.x") fill_group_id_gap(i, group.ID) } @@ -641,8 +641,8 @@ func init_database() (err error) { if err != nil { return err } - if !nogrouplog { - fmt.Println(group.Name + ": ") + if debug { + log.Print(group.Name + ": ") fmt.Printf("%+v\n", group.Perms) } @@ -679,7 +679,7 @@ func init_database() (err error) { // Ugh, you really shouldn't physically delete these items, it makes a big mess of things if forum.ID != i { - fmt.Println("Stop physically deleting forums. You are messing up the IDs. Use the Forum Manager or delete_forum() instead x.x") + log.Print("Stop physically deleting forums. You are messing up the IDs. Use the Forum Manager or delete_forum() instead x.x") fill_forum_id_gap(i, forum.ID) } @@ -693,7 +693,13 @@ func init_database() (err error) { forum.LastTopicTime = "" }*/ - log.Print("Adding the " + forum.Name + " forum") + if forum.Name == "" { + if debug { + log.Print("Adding a placeholder forum") + } + } else { + log.Print("Adding the " + forum.Name + " forum") + } forums = append(forums,forum) } err = rows.Err() @@ -712,12 +718,13 @@ func init_database() (err error) { } defer rows.Close() + if debug { + log.Print("Adding the forum permissions") + } // Temporarily store the forum perms in a map before transferring it to a much faster slice - log.Print("Adding the forum permissions") forum_perms = make(map[int]map[int]ForumPerms) for rows.Next() { - var gid int - var fid int + var gid, fid int var perms []byte var pperms ForumPerms err := rows.Scan(&gid, &fid, &perms) @@ -737,7 +744,9 @@ func init_database() (err error) { forum_perms[gid][fid] = pperms } for gid, _ := range groups { - log.Print("Adding the forum permissions for Group #" + strconv.Itoa(gid) + " - " + groups[gid].Name) + if debug { + log.Print("Adding the forum permissions for Group #" + strconv.Itoa(gid) + " - " + groups[gid].Name) + } //groups[gid].Forums = append(groups[gid].Forums,BlankForumPerms) // GID 0. I sometimes wish MySQL's AUTO_INCREMENT would start at zero for fid, _ := range forums { forum_perm, ok := forum_perms[gid][fid] diff --git a/pages.go b/pages.go index 96a4b546..a70f6db2 100644 --- a/pages.go +++ b/pages.go @@ -77,6 +77,25 @@ type CreateTopicPage struct ExtData interface{} } +type GridElement struct +{ + Body string + Order int // For future use + Class string + Background string + TextColour string + Note string +} + +type PanelDashboardPage struct +{ + Title string + CurrentUser User + NoticeList []string + GridItems []GridElement + ExtData interface{} +} + type ThemesPage struct { Title string diff --git a/panel_routes.go b/panel_routes.go index 3f67420f..557390e9 100644 --- a/panel_routes.go +++ b/panel_routes.go @@ -1,15 +1,23 @@ package main -import "log" -import "fmt" -import "strings" -import "strconv" -import "html" -import "encoding/json" -import "net/http" -import "html/template" -import "database/sql" +import ( + "log" + "fmt" + "errors" + "strings" + "strconv" + "html" + "time" + "runtime" + "encoding/json" + "net/http" + "html/template" + "database/sql" +) + import _ "github.com/go-sql-driver/mysql" +import "github.com/shirou/gopsutil/cpu" +import "github.com/shirou/gopsutil/mem" func route_panel(w http.ResponseWriter, r *http.Request){ user, noticeList, ok := SessionCheck(w,r) @@ -20,7 +28,137 @@ func route_panel(w http.ResponseWriter, r *http.Request){ NoPermissions(w,r,user) return } - pi := Page{"Control Panel Dashboard",user,noticeList,tList,nil} + + var cpustr, cpuColour string + perc2, err := cpu.Percent(time.Duration(time.Second),true) + if err != nil { + cpustr = "Unknown" + } else { + /*cpures, _ := cpu.Times(true) + totcpu := cpures[0].Idle + cpures[0].System + cpures[0].User + fmt.Println("System",cpures[0].System) + fmt.Println("User",cpures[0].User) + fmt.Println("Usage",cpures[0].System + cpures[0].User) + fmt.Println("Idle",cpures[0].Idle) + fmt.Println("Gap",totcpu - (cpures[0].System + cpures[0].User)) + perc := ((cpures[0].System + + cpures[0].User) * 100) / totcpu + fmt.Println("Perc",perc) + fmt.Println("Perc2",perc2)*/ + calcperc := int(perc2[0]) / runtime.NumCPU() + cpustr = strconv.Itoa(calcperc) + if calcperc < 25 { + cpuColour = "stat_green" + } else if calcperc < 75 { + cpuColour = "stat_orange" + } else { + cpuColour = "stat_red" + } + } + + var ramstr, ramColour string + memres, err := mem.VirtualMemory() + if err != nil { + ramstr = "Unknown" + } else { + total_count, total_unit := convert_byte_unit(float64(memres.Total)) + used_count := convert_byte_in_unit(float64(memres.Total - memres.Available),total_unit) + + // Round totals with .9s up, it's how most people see it anyway. Floats are notoriously imprecise, so do it off 0.85 + //fmt.Println(used_count) + var totstr string + if (total_count - float64(int(total_count))) > 0.85 { + used_count += 1.0 - (total_count - float64(int(total_count))) + totstr = strconv.Itoa(int(total_count) + 1) + } else { + totstr = fmt.Sprintf("%.1f",total_count) + } + //fmt.Println(used_count) + + if used_count > total_count { + used_count = total_count + } + ramstr = fmt.Sprintf("%.1f",used_count) + " / " + totstr + total_unit + + ramperc := ((memres.Total - memres.Available) * 100) / memres.Total + //fmt.Println(ramperc) + if ramperc < 50 { + ramColour = "stat_green" + } else if ramperc < 75 { + ramColour = "stat_orange" + } else { + ramColour = "stat_red" + } + } + + var postCount int + err = db.QueryRow("select count(*) from replies where createdAt BETWEEN (now() - interval 1 day) and now()").Scan(&postCount) + if err != nil && err != sql.ErrNoRows { + InternalError(err,w,r) + return + } + var postInterval string = "day" + + var postColour string + if postCount > 10 { + postColour = "stat_green" + } else if postCount > 0 { + postColour = "stat_orange" + } else { + postColour = "stat_red" + } + + var topicCount int + err = db.QueryRow("select count(*) from topics where createdAt BETWEEN (now() - interval 1 day) and now()").Scan(&topicCount) + if err != nil && err != sql.ErrNoRows { + InternalError(err,w,r) + return + } + var topicInterval string = "day" + + var topicColour string + if topicCount > 10 { + topicColour = "stat_green" + } else if topicCount > 0 { + topicColour = "stat_orange" + } else { + topicColour = "stat_red" + } + + var reportCount int + err = db.QueryRow("select count(*) from topics where createdAt BETWEEN (now() - interval 1 day) and now() and parentID = 1").Scan(&reportCount) + if err != nil && err != sql.ErrNoRows { + InternalError(err,w,r) + return + } + var reportInterval string = "week" + + var newUserCount int + err = db.QueryRow("select count(*) from users where createdAt BETWEEN (now() - interval 1 day) and now()").Scan(&newUserCount) + if err != nil && err != sql.ErrNoRows { + InternalError(err,w,r) + return + } + var newUserInterval string = "week" + + var gridElements []GridElement = []GridElement{ + GridElement{"v" + version.String(),0,"grid_istat stat_green","","","Gosora is up-to-date :)"}, + GridElement{"CPU: " + cpustr + "%",1,"grid_istat " + cpuColour,"","","The global CPU usage of this server"}, + GridElement{"RAM: " + ramstr,2,"grid_istat " + ramColour,"","","The global RAM usage of this server"}, + + GridElement{strconv.Itoa(postCount) + " posts / " + postInterval,3,"grid_stat " + postColour,"","","The number of new posts over the last 24 hours"}, + GridElement{strconv.Itoa(topicCount) + " topics / " + topicInterval,4,"grid_stat " + topicColour,"","","The number of new topics over the last 24 hours"}, + GridElement{"20 online / day",5,"grid_stat stat_disabled","","","Coming Soon!"/*"The people online over the last 24 hours"*/}, + + GridElement{"8 searches / week",6,"grid_stat stat_disabled","","","Coming Soon!"/*"The number of searches over the last 7 days"*/}, + GridElement{strconv.Itoa(newUserCount) + " new users / " + newUserInterval,7,"grid_stat","","","The number of new users over the last 7 days"}, + GridElement{strconv.Itoa(reportCount) + " reports / " + reportInterval,8,"grid_stat","","","The number of reports over the last 7 days"}, + + GridElement{"2 minutes / user / week",9,"grid_stat stat_disabled","","","Coming Soon!"/*"The average number of number of minutes spent by each active user over the last 7 days"*/}, + GridElement{"2 visitors / week",10,"grid_stat stat_disabled","","","Coming Soon!"/*"The number of unique visitors we've had over the last 7 days"*/}, + GridElement{"5 posts / user / week",11,"grid_stat stat_disabled","","","Coming Soon!"/*"The average number of posts made by each active user over the past week"*/}, + } + + pi := PanelDashboardPage{"Control Panel Dashboard",user,noticeList,gridElements,nil} templates.ExecuteTemplate(w,"panel-dashboard.html",pi) } @@ -1261,7 +1399,7 @@ func route_panel_themes_default(w http.ResponseWriter, r *http.Request, uname st dTheme, ok := themes[defaultTheme] if !ok { - log.Fatal("The default theme is missing") + InternalError(errors.New("The default theme is missing"),w,r) return } dTheme.Active = false diff --git a/setting.go b/setting.go index 7ce7211a..a318d8fa 100644 --- a/setting.go +++ b/setting.go @@ -27,11 +27,7 @@ func init() { func parseSetting(sname string, scontent string, stype string, constraint string) string { var err error if stype == "bool" { - if scontent == "1" { - settings[sname] = true - } else { - settings[sname] = false - } + settings[sname] = (scontent == "1") } else if stype == "int" { settings[sname], err = strconv.Atoi(scontent) if err != nil { diff --git a/templates.go b/templates.go index fad3cb83..aea7349f 100644 --- a/templates.go +++ b/templates.go @@ -1,4 +1,5 @@ package main + import "log" import "fmt" import "bytes" @@ -90,7 +91,7 @@ func (c *CTemplateSet) compile_template(name string, dir string, expects string, } content := string(res) - if !debug { + if minify_templates { content = minify(content) } @@ -100,7 +101,7 @@ func (c *CTemplateSet) compile_template(name string, dir string, expects string, if err != nil { log.Fatal(err) } - if debug { + if super_debug { fmt.Println(name) } @@ -110,7 +111,7 @@ func (c *CTemplateSet) compile_template(name string, dir string, expects string, c.tlist[fname] = tree varholder := "tmpl_" + fname + "_vars" - if debug { + if super_debug { fmt.Println(c.tlist) } c.localVars = make(map[string]map[string]VarItemReflect) @@ -123,13 +124,13 @@ func (c *CTemplateSet) compile_template(name string, dir string, expects string, c.FragmentCursor[fname] = 0 subtree := c.tlist[fname] - if debug { + if super_debug { fmt.Println(subtree.Root) } treeLength := len(subtree.Root.Nodes) for index, node := range subtree.Root.Nodes { - if debug { + if super_debug { fmt.Println("Node: " + node.String()) } @@ -165,12 +166,14 @@ w.Write([]byte(`," + ",-1) //whitespace_writes := regexp.MustCompile(`(?s)w.Write\(\[\]byte\(`+spstr+`\)\)`) //fout = whitespace_writes.ReplaceAllString(fout,"") - for index, count := range c.stats { - fmt.Println(index + ": " + strconv.Itoa(count)) - } - fmt.Println(" ") - if debug { + for index, count := range c.stats { + fmt.Println(index + ": " + strconv.Itoa(count)) + } + fmt.Println(" ") + } + + if super_debug { fmt.Println("Output!") fmt.Println(fout) } @@ -180,7 +183,7 @@ w.Write([]byte(`," + ",-1) func (c *CTemplateSet) compile_switch(varholder string, holdreflect reflect.Value, template_name string, node interface{}) (out string) { switch node := node.(type) { case *parse.ActionNode: - if debug { + if super_debug { fmt.Println("Action Node") } @@ -192,14 +195,14 @@ func (c *CTemplateSet) compile_switch(varholder string, holdreflect reflect.Valu } return out case *parse.IfNode: - if debug { + if super_debug { fmt.Println("If Node: ") fmt.Println(node.Pipe) } var expr string for _, cmd := range node.Pipe.Cmds { - if debug { + if super_debug { fmt.Println("If Node Bit: ") fmt.Println(cmd) fmt.Println(reflect.ValueOf(cmd).Type().Name()) @@ -211,18 +214,18 @@ func (c *CTemplateSet) compile_switch(varholder string, holdreflect reflect.Valu c.currentNode = parse.NodeList c.nextNode = -1 if node.ElseList == nil { - if debug { + if super_debug { fmt.Println("Branch 1") } return "if " + expr + " {\n" + c.compile_switch(varholder, holdreflect, template_name, node.List) + "}\n" } else { - if debug { + if super_debug { fmt.Println("Branch 2") } return "if " + expr + " {\n" + c.compile_switch(varholder, holdreflect, template_name, node.List) + "} else {\n" + c.compile_switch(varholder, holdreflect, template_name, node.ElseList) + "}\n" } case *parse.ListNode: - if debug { + if super_debug { fmt.Println("List Node") } for _, subnode := range node.Nodes { @@ -230,21 +233,21 @@ func (c *CTemplateSet) compile_switch(varholder string, holdreflect reflect.Valu } return out case *parse.RangeNode: - if debug { + if super_debug { fmt.Println("Range Node!") fmt.Println(node.Pipe) } var outVal reflect.Value for _, cmd := range node.Pipe.Cmds { - if debug { + if super_debug { fmt.Println("Range Bit: ") fmt.Println(cmd) } out, outVal = c.compile_reflectswitch(varholder, holdreflect, template_name, cmd) } - if debug { + if super_debug { fmt.Println("Returned: ") fmt.Println(out) fmt.Println("Range Kind Switch!") @@ -277,7 +280,7 @@ func (c *CTemplateSet) compile_switch(varholder string, holdreflect reflect.Valu } return out case *parse.TemplateNode: - if debug { + if super_debug { fmt.Println("Template Node") } return c.compile_subtemplate(varholder, holdreflect, node) @@ -309,7 +312,7 @@ func (c *CTemplateSet) compile_subswitch(varholder string, holdreflect reflect.V firstWord := node.Args[0] switch n := firstWord.(type) { case *parse.FieldNode: - if debug { + if super_debug { fmt.Println("Field Node: ") fmt.Println(n.Ident) } @@ -324,7 +327,7 @@ func (c *CTemplateSet) compile_subswitch(varholder string, holdreflect reflect.V } for _, id := range n.Ident { - if debug { + if super_debug { fmt.Println("Data Kind: ") fmt.Println(cur.Kind().String()) fmt.Println("Field Bit: ") @@ -346,7 +349,7 @@ func (c *CTemplateSet) compile_subswitch(varholder string, holdreflect reflect.V } else { varbit += "." + id } - if debug { + if super_debug { fmt.Println("End Cycle") } } @@ -359,7 +362,7 @@ func (c *CTemplateSet) compile_subswitch(varholder string, holdreflect reflect.V } return out case *parse.DotNode: - if debug { + if super_debug { fmt.Println("Dot Node") fmt.Println(node.String()) } @@ -367,7 +370,7 @@ func (c *CTemplateSet) compile_subswitch(varholder string, holdreflect reflect.V case *parse.NilNode: panic("Nil is not a command x.x") case *parse.VariableNode: - if debug { + if super_debug { fmt.Println("Variable Node") fmt.Println(n.String()) fmt.Println(n.Ident) @@ -377,7 +380,7 @@ func (c *CTemplateSet) compile_subswitch(varholder string, holdreflect reflect.V case *parse.StringNode: return n.Quoted case *parse.IdentifierNode: - if debug { + if super_debug { fmt.Println("Identifier Node: ") fmt.Println(node) fmt.Println(node.Args) @@ -397,7 +400,7 @@ func (c *CTemplateSet) compile_varswitch(varholder string, holdreflect reflect.V firstWord := node.Args[0] switch n := firstWord.(type) { case *parse.FieldNode: - if debug { + if super_debug { fmt.Println("Field Node: ") fmt.Println(n.Ident) @@ -410,14 +413,14 @@ func (c *CTemplateSet) compile_varswitch(varholder string, holdreflect reflect.V /* Use reflect to determine if the field is for a method, otherwise assume it's a variable. Coming Soon. */ return c.compile_boolsub(n.String(), varholder, template_name, holdreflect) case *parse.ChainNode: - if debug { + if super_debug { fmt.Println("Chain Node: ") fmt.Println(n.Node) fmt.Println(node.Args) } break case *parse.IdentifierNode: - if debug { + if super_debug { fmt.Println("Identifier Node: ") fmt.Println(node) fmt.Println(node.Args) @@ -426,7 +429,7 @@ func (c *CTemplateSet) compile_varswitch(varholder string, holdreflect reflect.V case *parse.DotNode: return varholder case *parse.VariableNode: - if debug { + if super_debug { fmt.Println("Variable Node") fmt.Println(n.String()) fmt.Println(n.Ident) @@ -436,7 +439,7 @@ func (c *CTemplateSet) compile_varswitch(varholder string, holdreflect reflect.V case *parse.NilNode: panic("Nil is not a command x.x") case *parse.PipeNode: - if debug { + if super_debug { fmt.Println("Pipe Node!") fmt.Println(n) fmt.Println("Args: ") @@ -444,7 +447,7 @@ func (c *CTemplateSet) compile_varswitch(varholder string, holdreflect reflect.V } out += c.compile_identswitch_n(varholder, holdreflect, template_name, node) - if debug { + if super_debug { fmt.Println("Out: ") fmt.Println(out) } @@ -467,7 +470,7 @@ func (c *CTemplateSet) compile_identswitch_n(varholder string, holdreflect refle func (c *CTemplateSet) compile_identswitch(varholder string, holdreflect reflect.Value, template_name string, node *parse.CommandNode) (out string, val reflect.Value) { ArgLoop: for pos, id := range node.Args { - if debug { + if super_debug { fmt.Println(id) } @@ -480,42 +483,42 @@ func (c *CTemplateSet) compile_identswitch(varholder string, holdreflect reflect out += " && " case "le": out += c.compile_if_varsub_n(node.Args[pos + 1].String(), varholder, template_name, holdreflect) + " <= " + c.compile_if_varsub_n(node.Args[pos + 2].String(), varholder, template_name, holdreflect) - if debug { + if super_debug { fmt.Println(node.Args[pos + 1]) fmt.Println(node.Args[pos + 2]) } break ArgLoop case "lt": out += c.compile_if_varsub_n(node.Args[pos + 1].String(), varholder, template_name, holdreflect) + " < " + c.compile_if_varsub_n(node.Args[pos + 2].String(), varholder, template_name, holdreflect) - if debug { + if super_debug { fmt.Println(node.Args[pos + 1]) fmt.Println(node.Args[pos + 2]) } break ArgLoop case "gt": out += c.compile_if_varsub_n(node.Args[pos + 1].String(), varholder, template_name, holdreflect) + " > " + c.compile_if_varsub_n(node.Args[pos + 2].String(), varholder, template_name, holdreflect) - if debug { + if super_debug { fmt.Println(node.Args[pos + 1]) fmt.Println(node.Args[pos + 2]) } break ArgLoop case "ge": out += c.compile_if_varsub_n(node.Args[pos + 1].String(), varholder, template_name, holdreflect) + " >= " + c.compile_if_varsub_n(node.Args[pos + 2].String(), varholder, template_name, holdreflect) - if debug { + if super_debug { fmt.Println(node.Args[pos + 1]) fmt.Println(node.Args[pos + 2]) } break ArgLoop case "eq": out += c.compile_if_varsub_n(node.Args[pos + 1].String(), varholder, template_name, holdreflect) + " == " + c.compile_if_varsub_n(node.Args[pos + 2].String(), varholder, template_name, holdreflect) - if debug { + if super_debug { fmt.Println(node.Args[pos + 1]) fmt.Println(node.Args[pos + 2]) } break ArgLoop case "ne": out += c.compile_if_varsub_n(node.Args[pos + 1].String(), varholder, template_name, holdreflect) + " != " + c.compile_if_varsub_n(node.Args[pos + 2].String(), varholder, template_name, holdreflect) - if debug { + if super_debug { fmt.Println(node.Args[pos + 1]) fmt.Println(node.Args[pos + 2]) } @@ -534,7 +537,7 @@ func (c *CTemplateSet) compile_identswitch(varholder string, holdreflect reflect } out += param1 + " + " + param2 - if debug { + if super_debug { fmt.Println("add") fmt.Println(node.Args[pos + 1]) fmt.Println(node.Args[pos + 2]) @@ -554,7 +557,7 @@ func (c *CTemplateSet) compile_identswitch(varholder string, holdreflect reflect } out += param1 + " - " + param2 - if debug { + if super_debug { fmt.Println("subtract") fmt.Println(node.Args[pos + 1]) fmt.Println(node.Args[pos + 2]) @@ -574,7 +577,7 @@ func (c *CTemplateSet) compile_identswitch(varholder string, holdreflect reflect } out += param1 + " / " + param2 - if debug { + if super_debug { fmt.Println("divide") fmt.Println(node.Args[pos + 1]) fmt.Println(node.Args[pos + 2]) @@ -594,14 +597,14 @@ func (c *CTemplateSet) compile_identswitch(varholder string, holdreflect reflect } out += param1 + " * " + param2 - if debug { + if super_debug { fmt.Println("multiply") fmt.Println(node.Args[pos + 1]) fmt.Println(node.Args[pos + 2]) } break ArgLoop default: - if debug { + if super_debug { fmt.Println("Variable!") } out += c.compile_if_varsub_n(id.String(), varholder, template_name, holdreflect) @@ -614,7 +617,7 @@ func (c *CTemplateSet) compile_reflectswitch(varholder string, holdreflect refle firstWord := node.Args[0] switch n := firstWord.(type) { case *parse.FieldNode: - if debug { + if super_debug { fmt.Println("Field Node: ") fmt.Println(n.Ident) @@ -626,7 +629,7 @@ func (c *CTemplateSet) compile_reflectswitch(varholder string, holdreflect refle /* Use reflect to determine if the field is for a method, otherwise assume it's a variable. Coming Soon. */ return c.compile_if_varsub(n.String(), varholder, template_name, holdreflect) case *parse.ChainNode: - if debug { + if super_debug { fmt.Println("Chain Node: ") fmt.Println(n.Node) fmt.Println(node.Args) @@ -676,7 +679,7 @@ func (c *CTemplateSet) compile_if_varsub(varname string, varholder string, templ } bits[0] = strings.TrimPrefix(bits[0],"$") - if debug { + if super_debug { fmt.Println("Cur Kind: ") fmt.Println(cur.Kind()) fmt.Println("Cur Type: ") @@ -684,7 +687,7 @@ func (c *CTemplateSet) compile_if_varsub(varname string, varholder string, templ } for _, bit := range bits { - if debug { + if super_debug { fmt.Println("Variable Field!") fmt.Println(bit) } @@ -701,7 +704,7 @@ func (c *CTemplateSet) compile_if_varsub(varname string, varholder string, templ out += "." + bit } - if debug { + if super_debug { fmt.Println("Data Kind: ") fmt.Println(cur.Kind()) fmt.Println("Data Type: ") @@ -709,7 +712,7 @@ func (c *CTemplateSet) compile_if_varsub(varname string, varholder string, templ } } - if debug { + if super_debug { fmt.Println("Out Value: ") fmt.Println(out) fmt.Println("Out Kind: ") @@ -724,7 +727,7 @@ func (c *CTemplateSet) compile_if_varsub(varname string, varholder string, templ } } - if debug { + if super_debug { fmt.Println("Out Value: ") fmt.Println(out) fmt.Println("Out Kind: ") @@ -802,7 +805,7 @@ func (c *CTemplateSet) compile_varsub(varname string, val reflect.Value) string } func (c *CTemplateSet) compile_subtemplate(pvarholder string, pholdreflect reflect.Value, node *parse.TemplateNode) (out string) { - if debug { + if super_debug { fmt.Println("Template Node: " + node.Name) } @@ -832,7 +835,7 @@ func (c *CTemplateSet) compile_subtemplate(pvarholder string, pholdreflect refle } content := string(res) - if !debug { + if minify_templates { content = minify(content) } @@ -845,7 +848,7 @@ func (c *CTemplateSet) compile_subtemplate(pvarholder string, pholdreflect refle c.tlist[fname] = tree subtree := c.tlist[fname] - if debug { + if super_debug { fmt.Println(subtree.Root) } @@ -855,7 +858,7 @@ func (c *CTemplateSet) compile_subtemplate(pvarholder string, pholdreflect refle treeLength := len(subtree.Root.Nodes) for index, node := range subtree.Root.Nodes { - if debug { + if super_debug { fmt.Println("Node: " + node.String()) } diff --git a/templates/panel-dashboard.html b/templates/panel-dashboard.html index 89c6bc22..58ddfa20 100644 --- a/templates/panel-dashboard.html +++ b/templates/panel-dashboard.html @@ -1,11 +1,11 @@ {{template "header.html" . }} {{template "panel-menu.html" . }}
-
- -
-
-
Coming Soon...
+
+{{range .GridItems}} +
{{.Body}}
+{{end}}
{{template "footer.html" . }} \ No newline at end of file diff --git a/templates/panel-modlogs.html b/templates/panel-modlogs.html index 756b2f66..fa7ba114 100644 --- a/templates/panel-modlogs.html +++ b/templates/panel-modlogs.html @@ -20,7 +20,7 @@ {{.Action}}
IP: {{.IPAddress}} - {{.DoneAt}} + {{.DoneAt}}
{{end}} diff --git a/themes.go b/themes.go index 4563dcd7..669fd1d1 100644 --- a/themes.go +++ b/themes.go @@ -77,7 +77,9 @@ func init_themes() { theme.Active = false // Set this to false, just in case someone explicitly overrode this value in the JSON file if theme.FullImage != "" { - log.Print("Adding theme image") + if debug { + log.Print("Adding theme image") + } err = add_static_file("./themes/" + themeName + "/" + theme.FullImage, "./themes/" + themeName) if err != nil { log.Fatal(err) @@ -98,14 +100,19 @@ func add_theme_static_files(themeName string) { } path = strings.Replace(path,"\\","/",-1) - log.Print("Attempting to add static file '" + path + "' for default theme '" + themeName + "'") + if debug { + log.Print("Attempting to add static file '" + path + "' for default theme '" + themeName + "'") + } + data, err := ioutil.ReadFile(path) if err != nil { return err } path = strings.TrimPrefix(path,"themes/" + themeName + "/public") - log.Print("Added the '" + path + "' static file for default theme " + themeName + ".") + if debug { + log.Print("Added the '" + path + "' static file for default theme " + themeName + ".") + } gzip_data := compress_bytes_gzip(data) static_files["/static" + path] = SFile{data,gzip_data,0,int64(len(data)),int64(len(gzip_data)),mime.TypeByExtension(filepath.Ext("/themes/" + themeName + "/public" + path)),f,f.ModTime().UTC().Format(http.TimeFormat)} diff --git a/themes/cosmo-conflux/public/main.css b/themes/cosmo-conflux/public/main.css index c5f26670..d2f91c77 100644 --- a/themes/cosmo-conflux/public/main.css +++ b/themes/cosmo-conflux/public/main.css @@ -1,4 +1,4 @@ -/* AtomBB Cosmo Port. Copyright Azareal 2017 */ +/* AtomBB Cosmo Port. Copyright Azareal 2017 - 2018 */ /* I'm currently converting the CSS over. Don't use this yet! */ * { @@ -261,7 +261,7 @@ hr { color: silver; border: 1px solid silver; } .colblock_left:first-of-type { margin-top: 8px; } .colblock_right:first-of-type { margin-top: 8px; } -/* The new method of doing columns layouts, colblock is now deprecated */ +/* The new method of doing columns layouts, colblock is now deprecated :( */ .colstack_left { float: left; @@ -289,6 +289,39 @@ hr { color: silver; border: 1px solid silver; } .colstack_left:empty { display: none; } .colstack_right:empty { display: none; } +.colstack_grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + /*grid-gap: 15px;*/ + grid-gap: 12px; + margin-left: 5px; +} +.grid_item { + border: 1px solid #ccc; + word-wrap: break-word; + background-color: white; + width: 100%; + overflow: hidden; +} +.grid_stat, .grid_istat { + /*padding-top: 15px;*/ + text-align: center; + /*padding-bottom: 15px; + font-size: 20px;*/ + padding-top: 12px; + padding-bottom: 12px; + font-size: 16px; +} +.grid_istat { + /*margin-bottom: 10px;*/ + margin-bottom: 5px; +} + +.stat_green { background-color: lightgreen; border-color: green; } +.stat_orange { background-color: #ffe4b3; border-color: orange; } +.stat_red { background-color: #ffb2b2; border-color: red; } +.stat_disabled { background-color: lightgray; border-color: gray; } + .colitem { padding-left: 8px; diff --git a/themes/cosmo/public/main.css b/themes/cosmo/public/main.css index 57b715ed..67b8f48b 100644 --- a/themes/cosmo/public/main.css +++ b/themes/cosmo/public/main.css @@ -196,13 +196,8 @@ hr { color: silver; border: 1px solid silver; } border-left: 1px solid black; border-right: 1px solid black; } -.rowblock:first-of-type { - margin-top: 8px; -} -.rowitem:not(:last-child) -{ - border-bottom: 1px dotted #ccc; -} +.rowblock:first-of-type { margin-top: 8px; } +.rowitem:not(:last-child) { border-bottom: 1px dotted #ccc; } .rowhead, .colhead { background: #ce2424; @@ -283,6 +278,39 @@ hr { color: silver; border: 1px solid silver; } .colstack_left:empty { display: none; } .colstack_right:empty { display: none; } +.colstack_grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + /*grid-gap: 15px;*/ + grid-gap: 12px; + margin-left: 5px; +} +.grid_item { + border: 1px solid #ccc; + word-wrap: break-word; + background-color: white; + width: 100%; + overflow: hidden; +} +.grid_stat, .grid_istat { + /*padding-top: 15px;*/ + text-align: center; + /*padding-bottom: 15px; + font-size: 20px;*/ + padding-top: 12px; + padding-bottom: 12px; + font-size: 16px; +} +.grid_istat { + /*margin-bottom: 10px;*/ + margin-bottom: 5px; +} + +.stat_green { background-color: lightgreen; border-color: green; } +.stat_orange { background-color: #ffe4b3; border-color: orange; } +.stat_red { background-color: #ffb2b2; border-color: red; } +.stat_disabled { background-color: lightgray; border-color: gray; } + .colitem { padding-left: 8px; @@ -302,10 +330,7 @@ hr { color: silver; border: 1px solid silver; } text-decoration: none; color: black; } -.colitem a:hover -{ - color: silver; -} +.colitem a:hover { color: silver; } .col_left { diff --git a/themes/tempra-conflux/public/main.css b/themes/tempra-conflux/public/main.css index 80db6346..7384eef8 100644 --- a/themes/tempra-conflux/public/main.css +++ b/themes/tempra-conflux/public/main.css @@ -178,7 +178,7 @@ li a .colblock_left:empty { display: none; } .colblock_right:empty { display: none; } -/* The new method of doing columns layouts, colblock is now deprecated */ +/* The new method of doing columns layouts, colblock is now deprecated :( */ .colstack_left { float: left; @@ -205,6 +205,39 @@ li a .colstack_left:empty { display: none; } .colstack_right:empty { display: none; } +.colstack_grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + /*grid-gap: 15px;*/ + grid-gap: 12px; + margin-left: 5px; +} +.grid_item { + border: 1px solid #ccc; + word-wrap: break-word; + background-color: white; + width: 100%; + overflow: hidden; +} +.grid_stat, .grid_istat { + /*padding-top: 15px;*/ + text-align: center; + /*padding-bottom: 15px; + font-size: 20px;*/ + padding-top: 12px; + padding-bottom: 12px; + font-size: 16px; +} +.grid_istat { + /*margin-bottom: 10px;*/ + margin-bottom: 5px; +} + +.stat_green { background-color: lightgreen; border-color: green; } +.stat_orange { background-color: #ffe4b3; border-color: orange; } +.stat_red { background-color: #ffb2b2; border-color: red; } +.stat_disabled { background-color: lightgray; border-color: gray; } + .rowitem { width: 100%; diff --git a/themes/tempra-cursive/public/main.css b/themes/tempra-cursive/public/main.css index eae4c190..1fdcbdf9 100644 --- a/themes/tempra-cursive/public/main.css +++ b/themes/tempra-cursive/public/main.css @@ -12,7 +12,7 @@ body /* Patch for Edge */ @supports (-ms-ime-align:auto) { - .user_content { font-family: Segoe UI Emoji, arial; } +.user_content { font-family: Segoe UI Emoji, arial; } } ul @@ -167,7 +167,7 @@ li a .colblock_left:empty { display: none; } .colblock_right:empty { display: none; } -/* The new method of doing columns layouts, colblock is now deprecated */ +/* The new method of doing columns layouts, colblock is now deprecated :( */ .colstack_left { float: left; @@ -194,6 +194,39 @@ li a .colstack_left:empty { display: none; } .colstack_right:empty { display: none; } +.colstack_grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + /*grid-gap: 15px;*/ + grid-gap: 12px; + margin-left: 5px; +} +.grid_item { + border: 1px solid #ccc; + word-wrap: break-word; + background-color: white; + width: 100%; + overflow: hidden; +} +.grid_stat, .grid_istat { + /*padding-top: 15px;*/ + text-align: center; + /*padding-bottom: 15px; + font-size: 20px;*/ + padding-top: 12px; + padding-bottom: 12px; + font-size: 16px; +} +.grid_istat { + /*margin-bottom: 10px;*/ + margin-bottom: 5px; +} + +.stat_green { background-color: lightgreen; border-color: lightgreen; } +.stat_orange { background-color: #ffe4b3; border-color: #ffe4b3; } +.stat_red { background-color: #ffb2b2; border-color: #ffb2b2; } +.stat_disabled { background-color: lightgray; border-color: lightgray; } + .rowhead { font-family: cursive; } .rowitem { diff --git a/themes/tempra-simple/public/main.css b/themes/tempra-simple/public/main.css index b20191ee..be93abfe 100644 --- a/themes/tempra-simple/public/main.css +++ b/themes/tempra-simple/public/main.css @@ -12,7 +12,7 @@ body /* Patch for Edge */ @supports (-ms-ime-align:auto) { - .user_content { font-family: Segoe UI Emoji, arial; } +.user_content { font-family: Segoe UI Emoji, arial; } } /*.move_left{float: left;position: relative;left: 50%;} @@ -167,7 +167,7 @@ li a .colblock_left:empty { display: none; } .colblock_right:empty { display: none; } -/* The new method of doing columns layouts, colblock is now deprecated */ +/* The new method of doing columns layouts, colblock is now deprecated :( */ .colstack_left { float: left; @@ -194,6 +194,39 @@ li a .colstack_left:empty { display: none; } .colstack_right:empty { display: none; } +.colstack_grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + /*grid-gap: 15px;*/ + grid-gap: 12px; + margin-left: 5px; +} +.grid_item { + border: 1px solid #ccc; + word-wrap: break-word; + background-color: white; + width: 100%; + overflow: hidden; +} +.grid_stat, .grid_istat { + /*padding-top: 15px;*/ + text-align: center; + /*padding-bottom: 15px; + font-size: 20px;*/ + padding-top: 12px; + padding-bottom: 12px; + font-size: 16px; +} +.grid_istat { + /*margin-bottom: 10px;*/ + margin-bottom: 5px; +} + +.stat_green { background-color: lightgreen; border-color: lightgreen; } +.stat_orange { background-color: #ffe4b3; border-color: #ffe4b3; } +.stat_red { background-color: #ffb2b2; border-color: #ffb2b2; } +.stat_disabled { background-color: lightgray; border-color: lightgray; } + .rowitem { width: 100%; diff --git a/update-deps-linux b/update-deps-linux index a37ebee0..a24e4160 100644 --- a/update-deps-linux +++ b/update-deps-linux @@ -1,4 +1,6 @@ echo "Updating the MySQL Driver" go get -u github.com/go-sql-driver/mysql echo "Updating bcrypt" -go get -u golang.org/x/crypto/bcrypt \ No newline at end of file +go get -u golang.org/x/crypto/bcrypt +echo "Updating gopsutil" +go get -u github.com/shirou/gopsutil diff --git a/update-deps.bat b/update-deps.bat index 4b54097a..38ef0adb 100644 --- a/update-deps.bat +++ b/update-deps.bat @@ -13,5 +13,19 @@ if %errorlevel% neq 0 ( exit /b %errorlevel% ) +echo Updating wmi (dependency for gopsutil) +go get -u github.com/StackExchange/wmi +if %errorlevel% neq 0 ( + pause + exit /b %errorlevel% +) + +echo Updating gopsutil +go get -u github.com/shirou/gopsutil +if %errorlevel% neq 0 ( + pause + exit /b %errorlevel% +) + echo The dependencies were successfully updated pause \ No newline at end of file diff --git a/utils.go b/utils.go index 163750a1..b2d83ef6 100644 --- a/utils.go +++ b/utils.go @@ -6,10 +6,31 @@ import "os" import "math" import "strings" import "unicode" +import "strconv" import "encoding/base64" import "crypto/rand" import "net/smtp" +type Version struct +{ + Major int + Minor int + Patch int + Tag string + TagID int +} + +func (version *Version) String() (out string) { + out = strconv.Itoa(version.Major) + "." + strconv.Itoa(version.Minor) + "." + strconv.Itoa(version.Patch) + if version.Tag != "" { + out += "-" + version.Tag + if version.TagID != 0 { + out += strconv.Itoa(version.TagID) + } + } + return +} + // Generate a cryptographically secure set of random bytes.. func GenerateSafeString(length int) (string, error) { rb := make([]byte,length) @@ -66,6 +87,32 @@ func relative_time(in string) (string, error) { } } +func convert_byte_unit(bytes float64) (float64,string) { + switch + { + case bytes >= float64(terabyte): return bytes / float64(terabyte), "TB" + case bytes >= float64(gigabyte): return bytes / float64(gigabyte), "GB" + case bytes >= float64(megabyte): return bytes / float64(megabyte), "MB" + case bytes >= float64(kilobyte): return bytes / float64(kilobyte), "KB" + default: return bytes, " bytes" + } +} + +func convert_byte_in_unit(bytes float64,unit string) (count float64) { + switch(unit) { + case "TB": count = bytes / float64(terabyte) + case "GB": count = bytes / float64(gigabyte) + case "MB": count = bytes / float64(megabyte) + case "KB": count = bytes / float64(kilobyte) + default: count = 0.1 + } + + if count < 0.1 { + count = 0.1 + } + return +} + func SendEmail(email string, subject string, msg string) (res bool) { // This hook is useful for plugin_sendmail or for testing tools. Possibly to hook it into some sort of mail server? if vhooks["email_send_intercept"] != nil {