Beispiel #1
0
func logout(ctx *macaron.Context) string {
	var tpl vision.New
	tpl.TemplateFile("template/login.tpl")

	user, auth := util.Auth(ctx, "any")

	if user.Sudo {
		ctx.SetCookie("sudo", "", -1)
		set_error("No longer logged in as "+user.System_username+".", ctx)
		ctx.Redirect("/dashboard", 302)
		return "success"
	}

	if auth {
		new_token := util.MkToken()
		db, _ := util.MySQL()
		defer db.Close()

		ustmt, _ := db.Prepare("update hostcontrol_users set login_token=? where system_username=?")
		ustmt.Exec(new_token, user.System_username)
		ustmt.Close()
	}

	ctx.SetCookie("hostcontrol_id", "", -1)
	ctx.SetCookie("login_token", "", -1)

	tpl.Parse("login")
	tpl.Parse("login/logged_out")
	return tpl.Out()

}
Beispiel #2
0
func websites(ctx *macaron.Context) string {
	_, auth := util.Auth(ctx, "websites")
	if !auth {
		ctx.Redirect("/", 302)
		return ""
	}

	var tpl vision.New
	tpl.TemplateFile("template/websites.tpl")
	tpl.Parse("websites")

	websites := API("/api/web/domain/list", ctx)

	domains := make(map[string]map[string]string)
	json.Unmarshal([]byte(websites), &domains)

	for _, domain := range domains {
		tpl.Assign("vhost_id", domain["vhost_id"])
		tpl.Assign("system_username", domain["system_username"])
		tpl.Assign("domain", domain["domain"])
		tpl.Assign("documentroot", domain["documentroot"])
		tpl.Assign("ipaddr", domain["ipaddr"])
		tpl.Assign("ssl_enabled", domain["ssl_enabled"])

		determin_fm_page := strings.Split(domain["documentroot"], "www")
		filemanager_path := "www" + determin_fm_page[1]

		tpl.Assign("filemanager_path", filemanager_path)
		tpl.Parse("websites/domain")
	}

	return header(ctx) + tpl.Out() + footer(ctx)
}
Beispiel #3
0
func ftpusers(ctx *macaron.Context) string {
	hcuser, auth := util.Auth(ctx, "ftpusers")
	if !auth {
		ctx.Redirect("/", 302)
		return ""
	}

	var tpl vision.New
	tpl.TemplateFile("template/ftpusers.tpl")

	tpl.Assign("homedir", hcuser.HomeDir)
	tpl.Parse("ftpusers")

	userdata := API("/api/ftpusers/list", ctx)

	users := make(map[string]map[string]string)
	json.Unmarshal([]byte(userdata), &users)

	for _, user := range users {
		tpl.Assign("username", user["username"])
		tpl.Assign("homedir", user["homedir"])

		tpl.Parse("ftpusers/user")
	}

	return header(ctx) + tpl.Out() + footer(ctx)
}
Beispiel #4
0
func header(ctx *macaron.Context) string {
	var tpl vision.New
	tpl.TemplateFile("template/overall.tpl")

	user, auth := util.Auth(ctx, "any")
	if auth {
		tpl.Assign("username", user.System_username)
	}

	tpl.Parse("header")

	err_str := ctx.GetCookie("err_str")
	if err_str != "" {
		tpl.Assign("message", err_str)
		tpl.Parse("header/error")
		ctx.SetCookie("err_str", "")
	}

	info_str := ctx.GetCookie("info_str")
	if info_str != "" {
		tpl.Assign("message", info_str)
		tpl.Parse("header/info")
		ctx.SetCookie("info_str", "")
	}

	return tpl.Out()
}
Beispiel #5
0
func die(ctx *macaron.Context, msg string) string {
	var tpl vision.New
	tpl.TemplateFile("template/error.tpl")
	tpl.Assign("message", msg)
	tpl.Parse("error")
	return header(ctx) + tpl.Out() + footer(ctx)

}
Beispiel #6
0
func login(ctx *macaron.Context) string {
	var tpl vision.New
	tpl.TemplateFile("template/login.tpl")
	tpl.Assign("x", "y")
	tpl.Parse("login")
	return tpl.Out()

}
Beispiel #7
0
func users(ctx *macaron.Context) string {
	hcuser, auth := util.Auth(ctx, "sysusers")
	if !auth {
		ctx.Redirect("/", 302)
		return ""
	}

	var tpl vision.New
	tpl.TemplateFile("template/users.tpl")
	tpl.Parse("users")

	if strings.Contains(hcuser.Privileges, "all") {
		tpl.Parse("users/perms_all")
	}
	if strings.Contains(hcuser.Privileges, "websites") || strings.Contains(hcuser.Privileges, "all") {
		tpl.Parse("users/perms_websites")
	}
	if strings.Contains(hcuser.Privileges, "mail") || strings.Contains(hcuser.Privileges, "all") {
		tpl.Parse("users/perms_mail")
	}
	if strings.Contains(hcuser.Privileges, "databases") || strings.Contains(hcuser.Privileges, "all") {
		tpl.Parse("users/perms_databases")
	}
	if strings.Contains(hcuser.Privileges, "ftpusers") || strings.Contains(hcuser.Privileges, "all") {
		tpl.Parse("users/perms_ftpusers")
	}
	if strings.Contains(hcuser.Privileges, "dns") || strings.Contains(hcuser.Privileges, "all") {
		tpl.Parse("users/perms_dns")
	}
	if strings.Contains(hcuser.Privileges, "sysusers") || strings.Contains(hcuser.Privileges, "all") {
		tpl.Parse("users/perms_sysusers")
	}

	userdata := API("/api/users/list", ctx)

	users := make(map[string]map[string]string)
	json.Unmarshal([]byte(userdata), &users)

	for _, user := range users {
		tpl.Assign("hostcontrol_id", user["hostcontrol_id"])
		tpl.Assign("system_username", user["system_username"])
		tpl.Assign("privileges", user["privileges"])
		tpl.Assign("owned_by", user["owned_by"])
		tpl.Assign("login_token", user["login_token"])
		tpl.Assign("email_address", user["email_address"])

		tpl.Parse("users/user")
	}

	return header(ctx) + tpl.Out() + footer(ctx)
}
Beispiel #8
0
func dashboard(ctx *macaron.Context) string {
	//hcuser, auth := util.Auth(ctx, "any")
	_, auth := util.Auth(ctx, "any")
	if !auth {
		ctx.Redirect("/", 302)
		return ""
	}

	var tpl vision.New
	tpl.TemplateFile("template/dashboard.tpl")

	tpl.Parse("dashboard")

	return header(ctx) + tpl.Out() + footer(ctx)
}
func file_editor(ctx *macaron.Context) string {
	hcuser, auth := util.Auth(ctx, "any")
	if !auth {
		ctx.Redirect("/", 302)
		return ""
	}

	suser, err := user.Lookup(hcuser.System_username)

	if err != nil {
		return die(ctx, string(err.Error()))
	}

	selected_object := path.Clean(util.Query(ctx, "path"))
	full_object := path.Clean(suser.HomeDir + "/" + selected_object)

	// check ownership...
	uid, _ := strconv.Atoi(suser.Uid)
	gid, _ := strconv.Atoi(suser.Gid)
	if !util.ChkPerms(full_object, uid, gid) {
		return die(ctx, "You do not have access to object "+full_object)
	}

	filecontents := util.Query(ctx, "filecontents")
	if filecontents != "" {
		filecontents = strings.Replace(filecontents, "\r\n", "\n", -1)
		ioutil.WriteFile(full_object, []byte(filecontents), 0644)
	}

	rawcontents, err := ioutil.ReadFile(full_object)
	if err != nil {
		return die(ctx, string(err.Error()))
	}

	content := html.EscapeString(string(rawcontents))

	var tpl vision.New
	tpl.TemplateFile("template/file-editor.tpl")

	tpl.Assign("path_up", path.Dir(selected_object))
	tpl.Assign("selected_path", selected_object)
	tpl.Assign("current_path", full_object)
	tpl.Assign("filedata", content)

	tpl.Parse("file-editor")

	return header(ctx) + tpl.Out() + footer(ctx)
}
Beispiel #10
0
func sslmanager(ctx *macaron.Context) string {
	_, auth := util.Auth(ctx, "websites")
	if !auth {
		ctx.Redirect("/", 302)
		return ""
	}

	vhost_id := util.Query(ctx, "vhost_id")

	var tpl vision.New
	tpl.TemplateFile("template/websites.sslmanager.tpl")

	websites := API("/api/web/domain/list", ctx)

	domains := make(map[string]map[string]string)
	json.Unmarshal([]byte(websites), &domains)

	found := false
	for _, domain := range domains {
		if domain["vhost_id"] == vhost_id {
			tpl.Assign("vhost_id", domain["vhost_id"])
			tpl.Assign("system_username", domain["system_username"])
			tpl.Assign("domain", domain["domain"])
			tpl.Assign("documentroot", domain["documentroot"])
			tpl.Assign("ipaddr", domain["ipaddr"])
			tpl.Assign("ssl_certificate", domain["ssl_certificate"])
			tpl.Assign("ssl_key", domain["ssl_key"])
			tpl.Assign("ssl_ca_certificate", domain["ssl_ca_certificate"])

			if domain["ssl_enabled"] == "Y" {
				tpl.Assign("ssl_enabled", "checked")
			} else {
				tpl.Assign("ssl_enabled", "")
			}
			found = true
		}
	}

	if !found {
		set_error("Failed to find requested domain.", ctx)
		ctx.Redirect("/websites", 302)
		return ""
	}
	tpl.Parse("sslmanager")

	return header(ctx) + tpl.Out() + footer(ctx)
}
Beispiel #11
0
func dashboard(ctx *macaron.Context) string {
	hcuser, auth := util.Auth(ctx, "any")
	if !auth {
		ctx.Redirect("/", 302)
		return ""
	}

	var tpl vision.New
	tpl.TemplateFile("template/dashboard.tpl")

	hostname := string(ctx.Req.Header.Get("X-FORWARDED-HOST"))
	if hostname == "" {
		hostname = string(ctx.Req.Host)
	}
	hostname = strings.Split(hostname, ":")[0]
	tpl.Assign("console_url", "https://"+hostname+"/shellinabox")

	tpl.Parse("dashboard")

	if (strings.Contains(hcuser.Privileges, "websites") || strings.Contains(hcuser.Privileges, "all")) && hcuser.System_username != "root" {
		tpl.Parse("dashboard/websitesbtn")
	}
	if strings.Contains(hcuser.Privileges, "databases") || strings.Contains(hcuser.Privileges, "all") {
		tpl.Parse("dashboard/databasesbtn")
	}
	if strings.Contains(hcuser.Privileges, "dns") || strings.Contains(hcuser.Privileges, "all") {
		tpl.Parse("dashboard/dnsbtn")
	}
	if (strings.Contains(hcuser.Privileges, "mail") || strings.Contains(hcuser.Privileges, "all")) && hcuser.System_username != "root" {
		tpl.Parse("dashboard/mailbtn")
	}
	if (strings.Contains(hcuser.Privileges, "ftpusers") || strings.Contains(hcuser.Privileges, "all")) && hcuser.System_username != "root" {
		tpl.Parse("dashboard/ftpusersbtn")
	}
	if strings.Contains(hcuser.Privileges, "all") {
		tpl.Parse("dashboard/firewallbtn")
	}
	if strings.Contains(hcuser.Privileges, "all") {
		tpl.Parse("dashboard/servicesbtn")
	}
	if strings.Contains(hcuser.Privileges, "sysusers") || strings.Contains(hcuser.Privileges, "all") {
		tpl.Parse("dashboard/usersbtn")
	}
	return header(ctx) + tpl.Out() + footer(ctx)
}
Beispiel #12
0
func mail(ctx *macaron.Context) string {
	_, auth := util.Auth(ctx, "mail")
	if !auth {
		ctx.Redirect("/", 302)
		return ""
	}

	var tpl vision.New
	tpl.TemplateFile("template/mail.tpl")

	hostname := string(ctx.Req.Header.Get("X-FORWARDED-HOST"))
	if hostname == "" {
		hostname = string(ctx.Req.Host)
	}
	hostname = strings.Split(hostname, ":")[0]

	tpl.Assign("webmail_url", "https://"+hostname+"/roundcubemail")

	tpl.Parse("mail")

	// list domains and records
	dns_data := API("/api/mail/list", ctx)

	// map[domain]map[record_id]map[key]value
	data := make(map[string]map[string]map[string]string)
	json.Unmarshal([]byte(dns_data), &data)

	for domain, email_accounts := range data {
		tpl.Assign("domain_name", domain)
		tpl.Parse("mail/domain")

		for key, email := range email_accounts {
			if key == "placebo" {
				continue
			}
			tpl.Assign("email", email["email"])
			tpl.Assign("email_id", email["email_id"])
			tpl.Parse("mail/domain/email")
		}
	}

	return header(ctx) + tpl.Out() + footer(ctx)
}
Beispiel #13
0
func dns(ctx *macaron.Context) string {
	_, auth := util.Auth(ctx, "dns")
	if !auth {
		ctx.Redirect("/", 302)
		return ""
	}

	var tpl vision.New
	tpl.TemplateFile("template/dns.tpl")
	tpl.Parse("dns")

	// list domains and records
	dns_data := API("/api/dns/list", ctx)

	// map[domain]map[record_id]map[key]value
	data := make(map[string]map[string]map[string]string)
	json.Unmarshal([]byte(dns_data), &data)

	for domain, records := range data {
		tpl.Assign("domain_name", domain)
		tpl.Parse("dns/domain")

		for key, record := range records {
			if key == "placebo" {
				continue
			}
			tpl.Assign("record_change_date", record["record_change_date"])
			tpl.Assign("record_content", record["record_content"])
			tpl.Assign("record_disabled", record["record_disabled"])
			tpl.Assign("record_domain_id", record["record_domain_id"])
			tpl.Assign("record_id", record["record_id"])
			tpl.Assign("record_name", record["record_name"])
			tpl.Assign("record_ordername", record["record_ordername"])
			tpl.Assign("record_prio", record["record_prio"])
			tpl.Assign("record_ttl", record["record_ttl"])
			tpl.Assign("record_type", record["record_type"])
			tpl.Parse("dns/domain/record")
		}
	}

	return header(ctx) + tpl.Out() + footer(ctx)
}
Beispiel #14
0
func main() {

	var tpl vision.New
	tpl.TemplateFile("tpl/hello.tpl")

	tpl.Assign("testvar", "Foobar")

	tpl.Parse("main")
	tpl.Parse("main/row")
	tpl.Parse("main/row")
	tpl.Parse("main/row")

	tpl.Assign("foovar", "Hello World")
	tpl.Parse("main/vrow")
	tpl.Assign("foovar", "Hello Dog")
	tpl.Parse("main/vrow")
	tpl.Assign("foovar", "Hello Cat")
	tpl.Parse("main/vrow")

	fmt.Println(tpl.Out())
}
Beispiel #15
0
func settings(ctx *macaron.Context) string {
	hcuser, auth := util.Auth(ctx, "any")
	if !auth {
		ctx.Redirect("/", 302)
		return ""
	}

	var tpl vision.New
	tpl.TemplateFile("template/settings.tpl")
	tpl.Assign("username", hcuser.System_username)
	tpl.Parse("settings")

	db, _ := util.MySQL()
	defer db.Close()

	// tokens
	stmt, _ := db.Prepare("select * from hostcontrol_user_tokens where hostcontrol_id=?")
	rows, _ := stmt.Query(hcuser.Hostcontrol_id)
	stmt.Close()

	for rows.Next() {
		var token string
		var hostcontrol_id string
		var description string
		var token_id string

		rows.Scan(&token, &hostcontrol_id, &description, &token_id)

		tpl.Assign("raw_token", token)
		tpl.Assign("token", hostcontrol_id+"/"+token)
		tpl.Assign("description", description)

		tpl.Parse("settings/token")
	}

	return header(ctx) + tpl.Out() + footer(ctx)
}
Beispiel #16
0
func filemanager(ctx *macaron.Context) string {
	hcuser, auth := util.Auth(ctx, "any")
	if !auth {
		ctx.Redirect("/", 302)
		return ""
	}

	var tpl vision.New
	tpl.TemplateFile("template/filemanager.tpl")

	suser, err := user.Lookup(hcuser.System_username)

	if err != nil {
		return die(ctx, string(err.Error()))
	}

	uid, err := strconv.Atoi(suser.Uid)
	if err != nil {
		return die(ctx, string(err.Error()))
	}

	gid, err := strconv.Atoi(suser.Gid)
	if err != nil {
		return die(ctx, string(err.Error()))
	}

	selected_object := path.Clean(util.Query(ctx, "path"))

	full_object := path.Clean(suser.HomeDir + "/" + selected_object)

	// check ownership...
	if !util.ChkPerms(full_object, uid, gid) {
		return die(ctx, "You do not have access to object "+full_object)
	}

	delete_objectin := util.Query(ctx, "delete")
	delete_object := path.Clean(util.Query(ctx, "delete"))
	delete_object = path.Clean(suser.HomeDir + "/" + delete_object)

	if delete_objectin != "" {
		os.RemoveAll(delete_object)
	}

	newdirin := util.Query(ctx, "dirname")
	newdir := path.Clean(util.Query(ctx, "dirname"))
	newdir = path.Clean(full_object + "/" + newdir)

	newfilein := util.Query(ctx, "filename")
	newfile := path.Clean(util.Query(ctx, "filename"))
	newfile = path.Clean(full_object + "/" + newfile)

	if newdirin != "" {
		os.Mkdir(newdir, 0755)
		os.Chown(newdir, uid, gid)
	}

	if newfilein != "" {
		f, _ := os.Create(newfile)
		f.Close()
		os.Chown(newfile, uid, gid)
		os.Chmod(newfile, 0644)
	}

	tpl.GAssign("path_up", path.Dir(selected_object))
	tpl.GAssign("current_path", full_object)
	tpl.GAssign("selected_path", selected_object)

	objects, err := ioutil.ReadDir(full_object)

	if err != nil {
		return die(ctx, string(err.Error()))
	}

	tpl.Parse("filemanager")

	for _, file := range objects {
		tpl.Assign("filename", file.Name())

		mode := string(fmt.Sprintf("%s", file.Mode()))
		tpl.Assign("mode", mode)
		if file.IsDir() {
			tpl.Parse("filemanager/directory")
		} else {
			tpl.Parse("filemanager/file")
		}
	}

	return header(ctx) + tpl.Out() + footer(ctx)
}
Beispiel #17
0
func login_post(ctx *macaron.Context) string {
	db, err := util.MySQL()
	defer db.Close()
	if err != nil {
		return "Problem opening MySQL"
	}

	new_token := util.MkToken()

	username := util.Query(ctx, "username")
	password := util.Query(ctx, "password")
	rememberme := util.Query(ctx, "rememberme")

	login_failed := false

	if chklogin(username, password) {
		stmt, _ := db.Prepare("SELECT * from hostcontrol_users WHERE system_username = ?")
		rows, _ := stmt.Query(username)
		stmt.Close()

		var hostcontrol_id int
		var system_username string
		var privileges string
		var owned_by string
		var login_token string
		var email_address string

		// check if we have a row returned...
		if rows.Next() {
			rows.Scan(&hostcontrol_id, &system_username, &privileges, &owned_by, &login_token, &email_address)
			ustmt, _ := db.Prepare("update hostcontrol_users set login_token=? where system_username=?")
			ustmt.Exec(new_token, username)
			ustmt.Close()

			// insert root if login worked and he doesn't exist!
		} else if username == "root" {
			istmt, _ := db.Prepare("insert hostcontrol_users set hostcontrol_id=null, system_username=?, privileges=?, owned_by=?, login_token=?, email_address=?")
			istmt.Exec("root", "all", "root", new_token, "")
			istmt.Close()

			// fallback to failure.
		} else {
			login_failed = true
		}

		if !login_failed {
			// set cookies
			if rememberme == "checked" {
				ctx.SetCookie("hostcontrol_id", strconv.Itoa(hostcontrol_id), 864000)
				ctx.SetCookie("login_token", new_token, 864000)
				ctx.SetCookie("sudo", "", 864000)
			} else {
				ctx.SetCookie("hostcontrol_id", strconv.Itoa(hostcontrol_id), 0)
				ctx.SetCookie("login_token", new_token, 0)
				ctx.SetCookie("sudo", "", 0)
			}

			// send to dashboard
			ctx.Redirect("/dashboard", 302)
			return "Redirecting to the dashboard. Click <a href=\"/dashboard\">here</a> if you are not redirected."
		}
	} else {
		login_failed = true
	}

	var tpl vision.New
	tpl.TemplateFile("template/login.tpl")

	tpl.Parse("login")

	if login_failed {
		tpl.Parse("login/fail")
	}

	return tpl.Out()

}
Beispiel #18
0
func footer(ctx *macaron.Context) string {
	var tpl vision.New
	tpl.TemplateFile("template/overall.tpl")
	tpl.Parse("footer")
	return tpl.Out()
}
Beispiel #19
0
func header(ctx *macaron.Context) string {
	var tpl vision.New
	tpl.TemplateFile("template/overall.tpl")

	hcuser, auth := util.Auth(ctx, "any")
	if auth {
		tpl.Assign("username", hcuser.System_username)
	}

	hostname := string(ctx.Req.Header.Get("X-FORWARDED-HOST"))
	if hostname == "" {
		hostname = string(ctx.Req.Host)
	}
	hostname = strings.Split(hostname, ":")[0]

	tpl.Assign("console_url", "https://"+hostname+"/shellinabox")
	tpl.Parse("header")

	if auth {
		if (strings.Contains(hcuser.Privileges, "websites") || strings.Contains(hcuser.Privileges, "all")) && hcuser.System_username != "root" {
			tpl.Parse("header/websitesbtn")
		}
		if strings.Contains(hcuser.Privileges, "databases") || strings.Contains(hcuser.Privileges, "all") {
			tpl.Parse("header/databasesbtn")
		}
		if strings.Contains(hcuser.Privileges, "dns") || strings.Contains(hcuser.Privileges, "all") {
			tpl.Parse("header/dnsbtn")
		}
		if (strings.Contains(hcuser.Privileges, "mail") || strings.Contains(hcuser.Privileges, "all")) && hcuser.System_username != "root" {
			tpl.Parse("header/mailbtn")
		}
		if (strings.Contains(hcuser.Privileges, "ftpusers") || strings.Contains(hcuser.Privileges, "all")) && hcuser.System_username != "root" {
			tpl.Parse("header/ftpusersbtn")
		}
		if strings.Contains(hcuser.Privileges, "all") {
			tpl.Parse("header/firewallbtn")
		}
		if strings.Contains(hcuser.Privileges, "all") {
			tpl.Parse("header/servicesbtn")
		}
		if strings.Contains(hcuser.Privileges, "sysusers") || strings.Contains(hcuser.Privileges, "all") {
			tpl.Parse("header/usersbtn")
		}
	}

	err_str := ctx.GetCookie("err_str")
	if err_str != "" {
		tpl.Assign("message", err_str)
		tpl.Parse("header/error")
		ctx.SetCookie("err_str", "")
	}

	info_str := ctx.GetCookie("info_str")
	if info_str != "" {
		tpl.Assign("message", info_str)
		tpl.Parse("header/info")
		ctx.SetCookie("info_str", "")
	}

	return tpl.Out()
}
Beispiel #20
0
func databases(ctx *macaron.Context) string {
	_, auth := util.Auth(ctx, "databases")
	if !auth {
		ctx.Redirect("/", 302)
		return ""
	}

	//hostname := string(ctx.Req.Host)
	hostname := string(ctx.Req.Header.Get("X-FORWARDED-HOST"))
	if hostname == "" {
		hostname = string(ctx.Req.Host)
	}
	hostname = strings.Split(hostname, ":")[0]

	var tpl vision.New

	tpl.TemplateFile("template/databases.tpl")

	tpl.Assign("phpmyadmin_url", "https://"+hostname+"/phpmyadmin")
	tpl.Parse("databases")

	// list db users
	dbuser_data := API("/api/sql/users/list", ctx)
	var users []string
	json.Unmarshal([]byte(dbuser_data), &users)

	for _, user := range users {
		tpl.Assign("db_user", user)

		tpl.Parse("databases/user")
	}
	// end: list db users

	// list databases
	db_data := API("/api/sql/databases/list", ctx)
	var databases []string
	json.Unmarshal([]byte(db_data), &databases)

	for _, db_name := range databases {

		// list grants
		grant_data := API("/api/sql/grants/list?db_name="+db_name, ctx)
		var grants []string
		json.Unmarshal([]byte(grant_data), &grants)

		tpl.Assign("db_name", db_name)

		tpl.Parse("databases/database")

		// grant add dropdown
		for _, user := range users {
			tpl.Assign("db_user", user)
			tpl.Assign("db_name", db_name)
			tpl.Parse("databases/database/add_grant")
		}
		// END: grant add dropdown

		for _, gdb_user := range grants {
			tpl.Assign("db_user", gdb_user)
			tpl.Assign("db_name", db_name)
			tpl.Parse("databases/database/grant")
		}
		// end: list grants

	}
	// end: list databases

	return header(ctx) + tpl.Out() + footer(ctx)
}