Browse Source

Dev (#2)

* Pointer instead var

* Write logs to localhost folder if host not found

* Theme scripts file inside template folder

* Theme assets files to non root folder

* Theme scripts file

* Theme styles and JS file to template variables

* Better CP default text color

* Settings module, robots txt edit page

* Auto code formating on debug

* Settings, robots.txt update action

* Update README.md

* Left/right sidebars in template

* Change in CP robots.txt icon

* Fix CP left sidebar icons margin

* Red delete icon in data table

* The same red color

* Update README.md

* Security: mysql database install action

* Security: first user action

* Rename error message

* CMS version to template vars

* Release++
Vova Tkach 6 years ago
parent
commit
a30f7ec01b

+ 2 - 2
Dockerfile

@@ -2,8 +2,8 @@ FROM debian:latest
 
 ENV FAVE_HOST=0.0.0.0 FAVE_PORT=8080 FAVE_DIR=/app/hosts FAVE_DEBUG=false FAVE_KEEPALIVE=false
 
-ADD https://github.com/vladimirok5959/golang-fave/releases/download/v1.0.1/fave.linux-amd64.tar.gz /app/fave.linux-amd64.tar.gz
-ADD https://github.com/vladimirok5959/golang-fave/releases/download/v1.0.1/localhost.tar.gz /app/hosts/localhost.tar.gz
+ADD https://github.com/vladimirok5959/golang-fave/releases/download/v1.0.2/fave.linux-amd64.tar.gz /app/fave.linux-amd64.tar.gz
+ADD https://github.com/vladimirok5959/golang-fave/releases/download/v1.0.2/localhost.tar.gz /app/hosts/localhost.tar.gz
 
 RUN tar -zxf /app/fave.linux-amd64.tar.gz -C /app && \
  tar -zxf /app/hosts/localhost.tar.gz -C /app/hosts && \

+ 2 - 1
Makefile

@@ -1,10 +1,11 @@
-VERSION="1.0.1"
+VERSION="1.0.2"
 
 default: debug test run
 
 debug:
 	go vet ./...
 	gofmt -d ./
+	gofmt -w ./
 	go build -mod vendor -o ./fave
 
 test:

+ 21 - 15
README.md

@@ -12,6 +12,8 @@ Usage of ./fave:
     virtual hosts directory
   -host string
     server host (default "0.0.0.0")
+  -keepalive
+    enable/disable server keep alive
   -port int
     server port (default 8080)
 ```
@@ -19,21 +21,25 @@ Usage of ./fave:
 ## Hosts structure
 ```
 hosts
-├── localhost        # Main host directory
-├──── config         # Config directory
-├────── mysql.json   # MySQL config file
-├──── htdocs         # Public http files
-├──── logs           # Logs dir
-├────── access.log   # Access log file
-├────── error.log    # Error log file
-├──── template       # Engine templates
-├────── 404.html     # Template for 404 page
-├────── footer.html  # Footer
-├────── header.html  # Header
-├────── index.html   # Template for index page
-├────── page.html    # Template for any other pages
-├────── sidebar.html # Can be included in templates
-└──── tmp            # Temporary dir for session files
+├── localhost              # Main host directory
+├──── config               # Config directory
+├────── mysql.json         # MySQL config file
+├──── htdocs               # Public http files
+├──── logs                 # Logs dir
+├────── access.log         # Access log file
+├────── error.log          # Error log file
+├──── template             # Engine templates
+├────── 404.html           # Template for 404 page
+├────── footer.html        # Footer
+├────── header.html        # Header
+├────── index.html         # Template for index page
+├────── page.html          # Template for any other pages
+├────── robots.txt         # Host robots.txt file
+├────── scripts.js         # Theme scripts file
+├────── sidebar-left.html  # Can be included in templates
+├────── sidebar-right.html # Can be included in templates
+├────── styles.css         # Theme styles file
+└──── tmp                  # Temporary dir for session files
 ```
 Unlimited hosts count. Template variables in [Wiki](https://github.com/vladimirok5959/golang-fave/wiki) or [here](https://github.com/vladimirok5959/golang-fave/wiki/Variables-for-template-($.Data)) and [here](https://github.com/vladimirok5959/golang-fave/wiki/Variables-for-template-($.System)).
 

+ 11 - 2
assets/cp.styles.css

@@ -94,7 +94,7 @@ body.cp {
 	font-size: 1rem;
 	font-weight: 400;
 	line-height: 1.5;
-	color: #212529;
+	color: #444;
 	width: 100%;
 	height: 100%;
 	overflow: hidden;
@@ -214,6 +214,7 @@ body.cp .wrap .sidebar.sidebar-left ul.nav li.nav-item:last-child ul {
 body.cp .wrap .sidebar.sidebar-left ul.nav li.nav-item svg.sicon {
 	fill: currentColor;
 	margin-right: 0.3rem;
+	margin-bottom: 1px;
 }
 
 /* SVG colors */
@@ -224,7 +225,7 @@ body.cp .wrap .sidebar.sidebar-left ul.nav li.nav-item svg.sicon {
 
 .svg-red svg {
 	fill: currentColor;
-	color: #cb2431;
+	color: #d9534f;
 }
 
 /* Pagination */
@@ -250,6 +251,14 @@ ul.pagination {
 	color: #0056b3;
 }
 
+.data-table a.ico.delete svg {
+	color: #d9534f;
+}
+
+.data-table a.ico.delete:hover svg {
+	color: #c9302c;
+}
+
 .data-table td.col_action a.ico {
 	display: inline-block;
 	width: 1rem;

File diff suppressed because it is too large
+ 0 - 0
assets/cp.styles.css.go


+ 1 - 0
assets/sys.svg.icon.go

@@ -12,3 +12,4 @@ var SysSvgIconDot = `<svg viewBox="0 0 16 16" width="16" height="16" class="sico
 var SysSvgIconAlert = `<svg viewBox="0 0 16 16" width="16" height="16" class="sicon" version="1.1"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg>`
 var SysSvgIconError = `<svg viewBox="0 0 16 16" width="16" height="16" class="sicon" version="1.1"><path fill-rule="evenodd" d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48L7.48 8z"></path></svg>`
 var SysSvgIconView = `<svg viewBox="0 0 16 16" width="16" height="16" class="sicon" version="1.1"><path fill-rule="evenodd" d="M8.06 2C3 2 0 8 0 8s3 6 8.06 6C13 14 16 8 16 8s-3-6-7.94-6zM8 12c-2.2 0-4-1.78-4-4 0-2.2 1.8-4 4-4 2.22 0 4 1.8 4 4 0 2.22-1.78 4-4 4zm2-4c0 1.11-.89 2-2 2-1.11 0-2-.89-2-2 0-1.11.89-2 2-2 1.11 0 2 .89 2 2z"></path></svg>`
+var SysSvgIconBug = `<svg viewBox="4 4 18 18" width="16" height="16" class="sicon" version="1.1"><path fill-rule="evenodd" d="M20 8h-2.81c-.45-.78-1.07-1.45-1.82-1.96L17 4.41 15.59 3l-2.17 2.17C12.96 5.06 12.49 5 12 5c-.49 0-.96.06-1.41.17L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8zm-6 8h-4v-2h4v2zm0-4h-4v-2h4v2z"></path></svg>`

+ 5 - 2
consts/consts.go

@@ -4,8 +4,8 @@ import (
 	"html/template"
 )
 
-const ServerVersion = "1.0.1"
-const AssetsVersion = "15"
+const ServerVersion = "1.0.2"
+const AssetsVersion = "16"
 const AssetsPath = "assets"
 const DirIndexFile = "index.html"
 
@@ -48,6 +48,9 @@ type TmplSystem struct {
 	PathJsPopper     string
 	PathJsBootstrap  string
 	PathJsCpScripts  string
+	PathThemeStyles  string
+	PathThemeScripts string
+	InfoVersion      string
 }
 
 type TmplError struct {

+ 12 - 5
engine/builder/data_table_action.go

@@ -1,10 +1,11 @@
 package builder
 
 type DataTableActionRow struct {
-	Icon   string
-	Href   string
-	Hint   string
-	Target string
+	Icon    string
+	Href    string
+	Hint    string
+	Target  string
+	Classes string
 }
 
 func DataTableAction(data *[]DataTableActionRow) string {
@@ -14,7 +15,13 @@ func DataTableAction(data *[]DataTableActionRow) string {
 		if row.Target != "" {
 			target = ` target="` + row.Target + `"`
 		}
-		result += `<a class="ico" title="` + row.Hint + `" href="` +
+
+		classes := row.Classes
+		if classes != "" {
+			classes = " " + classes
+		}
+
+		result += `<a class="ico` + classes + `" title="` + row.Hint + `" href="` +
 			row.Href + `"` + target + `>` + row.Icon + `</a>`
 	}
 	return result

+ 2 - 1
engine/wrapper/wrapper.go

@@ -168,7 +168,8 @@ func (this *Wrapper) RenderFrontEnd(tname string, data interface{}) {
 	tmpl, err := template.ParseFiles(
 		this.DTemplate+string(os.PathSeparator)+tname+".html",
 		this.DTemplate+string(os.PathSeparator)+"header.html",
-		this.DTemplate+string(os.PathSeparator)+"sidebar.html",
+		this.DTemplate+string(os.PathSeparator)+"sidebar-left.html",
+		this.DTemplate+string(os.PathSeparator)+"sidebar-right.html",
 		this.DTemplate+string(os.PathSeparator)+"footer.html",
 	)
 	if err != nil {

+ 1 - 1
hosts/localhost/template/footer.html

@@ -1,6 +1,6 @@
 						</div>
 						<div class="col-md-4">
-							{{template "sidebar.html" .}}
+							{{template "sidebar-right.html" .}}
 						</div>
 					</div>
 				</div>

+ 6 - 3
hosts/localhost/template/header.html

@@ -14,14 +14,17 @@
 		<meta name="description" content="{{$.Data.MetaDescription}}" />
 		<link rel="shortcut icon" href="{{$.System.PathIcoFav}}" type="image/x-icon" />
 
-		<!-- Template CSS file from public folder -->
-		<link rel="stylesheet" href="/styles.css">
+		<!-- Template CSS file from template folder -->
+		<link rel="stylesheet" href="{{$.System.PathThemeStyles}}?v=1">
+
+		<!-- Template JavaScript file from template folder -->
+		<script src="{{$.System.PathThemeScripts}}?v=1"></script>
 	</head>
 	<body class="fixed-top-bar1">
 		<div id="wrap">
 			<nav class="navbar navbar-expand-lg navbar-light bg-light">
 				<div class="container">
-					<a class="navbar-brand" href="/">Fave 1.0.0</a>
+					<a class="navbar-brand" href="/">Fave {{$.System.InfoVersion}}</a>
 					<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
 						<span class="navbar-toggler-icon"></span>
 					</button>

+ 2 - 2
hosts/localhost/template/robots.txt

@@ -1,2 +1,2 @@
-User-agent: *
-Disallow: /
+User-agent: *
+Disallow: /

+ 0 - 0
hosts/localhost/template/scripts.js


+ 0 - 0
hosts/localhost/template/sidebar-left.html


+ 0 - 0
hosts/localhost/template/sidebar.html → hosts/localhost/template/sidebar-right.html


+ 8 - 3
logger/logger.go

@@ -22,7 +22,7 @@ type Logger struct {
 	cclose chan bool
 }
 
-func (this *Logger) console(msg logMsg) {
+func (this *Logger) console(msg *logMsg) {
 	if consts.ParamDebug {
 		if !msg.isError {
 			if consts.IS_WIN {
@@ -47,7 +47,7 @@ func (this *Logger) console(msg logMsg) {
 	}
 }
 
-func (this *Logger) write(msg logMsg) {
+func (this *Logger) write(msg *logMsg) {
 	// Ignore file if debug
 	if consts.ParamDebug {
 		this.console(msg)
@@ -70,6 +70,11 @@ func (this *Logger) write(msg logMsg) {
 	host, _ := utils.ExtractHostPort(msg.host, false)
 	logs_dir := this.wwwDir + string(os.PathSeparator) + host + string(os.PathSeparator) + "logs"
 
+	// Try use localhost folder for logs
+	if !utils.IsDirExists(logs_dir) {
+		logs_dir = this.wwwDir + string(os.PathSeparator) + "localhost" + string(os.PathSeparator) + "logs"
+	}
+
 	// Ignore file if logs dir is not exists
 	if !utils.IsDirExists(logs_dir) {
 		this.console(msg)
@@ -109,7 +114,7 @@ func New() *Logger {
 		for {
 			select {
 			case msg := <-cdata:
-				lg.write(msg)
+				lg.write(&msg)
 			case <-cclose:
 				cclose <- true
 				return

+ 7 - 4
main.go

@@ -142,10 +142,13 @@ func main() {
 		}
 
 		// Robots.txt and styles.css from template dir
-		if ServeTemplateFile(w, r, "robots.txt", vhost_dir_template) {
+		if ServeTemplateFile(w, r, "robots.txt", "", vhost_dir_template) {
 			return
 		}
-		if ServeTemplateFile(w, r, "styles.css", vhost_dir_template) {
+		if ServeTemplateFile(w, r, "styles.css", "assets/theme/", vhost_dir_template) {
+			return
+		}
+		if ServeTemplateFile(w, r, "scripts.js", "assets/theme/", vhost_dir_template) {
 			return
 		}
 
@@ -165,8 +168,8 @@ func main() {
 	})
 }
 
-func ServeTemplateFile(w http.ResponseWriter, r *http.Request, file string, dir string) bool {
-	if r.URL.Path == "/"+file {
+func ServeTemplateFile(w http.ResponseWriter, r *http.Request, file string, path string, dir string) bool {
+	if r.URL.Path == "/"+path+file {
 		f, err := os.Open(dir + string(os.PathSeparator) + file)
 		if err == nil {
 			defer f.Close()

+ 29 - 2
modules/module_index.go

@@ -147,7 +147,8 @@ func (this *Modules) RegisterModule_Index() *Module {
 						Icon: assets.SysSvgIconRemove,
 						Href: "javascript:fave.ActionDataTableDelete(this,'index-delete','" +
 							(*values)[0] + "','Are you sure want to delete page?');",
-						Hint: "Delete",
+						Hint:    "Delete",
+						Classes: "delete",
 					},
 				})
 			}, "/cp/"+wrap.CurrModule+"/")
@@ -458,6 +459,12 @@ func (this *Modules) RegisterAction_IndexMysqlSetup() *Action {
 			return
 		}
 
+		// Security, check if still need to run this action
+		if wrap.ConfMysqlExists {
+			wrap.MsgError(`CMS is already configured`)
+			return
+		}
+
 		// Try connect to mysql
 		db, err := sql.Open("mysql", pf_user+":"+pf_password+"@tcp("+pf_host+":"+pf_port+")/"+pf_name)
 		if err != nil {
@@ -559,7 +566,27 @@ func (this *Modules) RegisterAction_IndexFirstUser() *Action {
 			return
 		}
 
-		_, err := wrap.DB.Query(
+		// Security, check if still need to run this action
+		var count int
+		err := wrap.DB.QueryRow(`
+			SELECT
+				COUNT(*)
+			FROM
+				users
+			;`,
+		).Scan(
+			&count,
+		)
+		if err != nil {
+			wrap.MsgError(err.Error())
+			return
+		}
+		if count > 0 {
+			wrap.MsgError(`CMS is already configured`)
+			return
+		}
+
+		_, err = wrap.DB.Query(
 			`INSERT INTO users SET
 				first_name = ?,
 				last_name = ?,

+ 87 - 0
modules/module_settings.go

@@ -0,0 +1,87 @@
+package modules
+
+import (
+	"html"
+	"io/ioutil"
+	"os"
+
+	"golang-fave/assets"
+	"golang-fave/consts"
+	"golang-fave/engine/builder"
+	"golang-fave/engine/wrapper"
+)
+
+func (this *Modules) RegisterModule_Settings() *Module {
+	return this.newModule(MInfo{
+		WantDB: false,
+		Mount:  "settings",
+		Name:   "Settings",
+		Order:  801,
+		System: true,
+		Icon:   assets.SysSvgIconGear,
+		Sub: &[]MISub{
+			{Mount: "default", Name: "Robots.txt", Show: true, Icon: assets.SysSvgIconBug},
+		},
+	}, nil, func(wrap *wrapper.Wrapper) (string, string, string) {
+		content := ""
+		sidebar := ""
+
+		if wrap.CurrSubModule == "" || wrap.CurrSubModule == "default" {
+			content += this.getBreadCrumbs(wrap, &[]consts.BreadCrumb{
+				{Name: "Robots.txt"},
+			})
+
+			fcont := []byte(``)
+			fcont, _ = ioutil.ReadFile(wrap.DTemplate + string(os.PathSeparator) + "robots.txt")
+
+			content += builder.DataForm(wrap, []builder.DataFormField{
+				{
+					Kind:  builder.DFKHidden,
+					Name:  "action",
+					Value: "settings-robots-txt",
+				},
+				{
+					Kind: builder.DFKText,
+					CallBack: func(field *builder.DataFormField) string {
+						return `<div class="form-group"><div class="row"><div class="col-12"><textarea class="form-control autosize" id="lbl_content" name="content" placeholder="" autocomplete="off">` + html.EscapeString(string(fcont)) + `</textarea></div></div></div>`
+					},
+				},
+				{
+					Kind: builder.DFKMessage,
+					CallBack: func(field *builder.DataFormField) string {
+						return `<div class="row"><div class="col-12"><div class="sys-messages"></div></div></div>`
+					},
+				},
+				{
+					Kind: builder.DFKSubmit,
+					CallBack: func(field *builder.DataFormField) string {
+						return `<div class="row d-lg-none"><div class="col-12"><button type="submit" class="btn btn-primary" data-target="add-edit-button">Save</button></div></div>`
+					},
+				},
+			})
+
+			sidebar += `<button class="btn btn-primary btn-sidebar" id="add-edit-button">Save</button>`
+		}
+		return this.getSidebarModules(wrap), content, sidebar
+	})
+}
+
+func (this *Modules) RegisterAction_SettingsRobotsTxt() *Action {
+	return this.newAction(AInfo{
+		WantDB:    true,
+		Mount:     "settings-robots-txt",
+		WantAdmin: true,
+	}, func(wrap *wrapper.Wrapper) {
+		pf_content := wrap.R.FormValue("content")
+
+		// Save robots.txt content
+		err := ioutil.WriteFile(wrap.DTemplate+string(os.PathSeparator)+"robots.txt", []byte(pf_content), 0664)
+		if err != nil {
+			wrap.MsgError(err.Error())
+			return
+		}
+
+		// Reload current page
+		wrap.Write(`window.location.reload(false);`)
+	})
+}

+ 2 - 1
modules/module_users.go

@@ -82,7 +82,8 @@ func (this *Modules) RegisterModule_Users() *Module {
 						Icon: assets.SysSvgIconRemove,
 						Href: "javascript:fave.ActionDataTableDelete(this,'users-delete','" +
 							(*values)[0] + "','Are you sure want to delete user?');",
-						Hint: "Delete",
+						Hint:    "Delete",
+						Classes: "delete",
 					},
 				})
 			}, "/cp/"+wrap.CurrModule+"/")

+ 3 - 0
utils/utils.go

@@ -102,6 +102,9 @@ func GetTmplSystemData() consts.TmplSystem {
 		PathJsPopper:     GetAssetsUrl(consts.AssetsPopperJs),
 		PathJsBootstrap:  GetAssetsUrl(consts.AssetsBootstrapJs),
 		PathJsCpScripts:  GetAssetsUrl(consts.AssetsCpScriptsJs),
+		PathThemeStyles:  "/assets/theme/styles.css",
+		PathThemeScripts: "/assets/theme/scripts.js",
+		InfoVersion:      consts.ServerVersion,
 	}
 }
 

+ 3 - 0
utils/utils_test.go

@@ -96,6 +96,9 @@ func TestGetTmplSystemData(t *testing.T) {
 		PathJsPopper:     "/assets/popper.js?v=" + consts.AssetsVersion,
 		PathJsBootstrap:  "/assets/bootstrap.js?v=" + consts.AssetsVersion,
 		PathJsCpScripts:  "/assets/cp/scripts.js?v=" + consts.AssetsVersion,
+		PathThemeStyles:  "/assets/theme/styles.css",
+		PathThemeScripts: "/assets/theme/scripts.js",
+		InfoVersion:      consts.ServerVersion,
 	})
 }
 

Some files were not shown because too many files changed in this diff