mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2024-12-26 13:29:05 +01:00
Rename templates and port ClusterCockpit navbar + layout
This commit is contained in:
parent
3dd1d48f86
commit
d24e261db2
@ -190,7 +190,7 @@ func Login(db *sqlx.DB) http.Handler {
|
||||
if err != nil {
|
||||
log.Warnf("login of user %#v failed: %s", username, err.Error())
|
||||
rw.WriteHeader(http.StatusUnauthorized)
|
||||
templates.Render(rw, r, "login.html", &templates.Page{
|
||||
templates.Render(rw, r, "login.tmpl", &templates.Page{
|
||||
Title: "Login failed",
|
||||
Login: &templates.LoginPage{
|
||||
Error: "Username or password incorrect",
|
||||
@ -291,7 +291,7 @@ func Auth(next http.Handler) http.Handler {
|
||||
log.Warn("authentication failed: no session or jwt found")
|
||||
|
||||
rw.WriteHeader(http.StatusUnauthorized)
|
||||
templates.Render(rw, r, "login.html", &templates.Page{
|
||||
templates.Render(rw, r, "login.tmpl", &templates.Page{
|
||||
Title: "Authentication failed",
|
||||
Login: &templates.LoginPage{
|
||||
Error: "No valid session or JWT provided",
|
||||
@ -349,7 +349,7 @@ func Logout(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
templates.Render(rw, r, "login.html", &templates.Page{
|
||||
templates.Render(rw, r, "login.tmpl", &templates.Page{
|
||||
Title: "Logout successful",
|
||||
Login: &templates.LoginPage{
|
||||
Info: "Logout successful",
|
||||
|
96
server.go
96
server.go
@ -268,7 +268,7 @@ func main() {
|
||||
}
|
||||
|
||||
handleGetLogin := func(rw http.ResponseWriter, r *http.Request) {
|
||||
templates.Render(rw, r, "login.html", &templates.Page{
|
||||
templates.Render(rw, r, "login.tmpl", &templates.Page{
|
||||
Title: "Login",
|
||||
Login: &templates.LoginPage{},
|
||||
})
|
||||
@ -276,7 +276,7 @@ func main() {
|
||||
|
||||
r := mux.NewRouter()
|
||||
r.NotFoundHandler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
templates.Render(rw, r, "404.html", &templates.Page{
|
||||
templates.Render(rw, r, "404.tmpl", &templates.Page{
|
||||
Title: "Not found",
|
||||
})
|
||||
})
|
||||
@ -301,16 +301,17 @@ func main() {
|
||||
|
||||
infos := map[string]interface{}{
|
||||
"clusters": config.Clusters,
|
||||
"username": "",
|
||||
"admin": true,
|
||||
}
|
||||
|
||||
if user := auth.GetUser(r.Context()); user != nil {
|
||||
infos["username"] = user.Username
|
||||
infos["admin"] = user.HasRole(auth.RoleAdmin)
|
||||
} else {
|
||||
infos["username"] = false
|
||||
infos["admin"] = false
|
||||
}
|
||||
|
||||
templates.Render(rw, r, "home.html", &templates.Page{
|
||||
templates.Render(rw, r, "home.tmpl", &templates.Page{
|
||||
Title: "ClusterCockpit",
|
||||
Config: conf,
|
||||
Infos: infos,
|
||||
@ -393,6 +394,27 @@ func main() {
|
||||
log.Print("Gracefull shutdown completed!")
|
||||
}
|
||||
|
||||
func prepareRoute(r *http.Request) (map[string]interface{}, map[string]interface{}, error) {
|
||||
conf, err := config.GetUIConfig(r)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
infos := map[string]interface{}{
|
||||
"admin": true,
|
||||
}
|
||||
|
||||
if user := auth.GetUser(r.Context()); user != nil {
|
||||
infos["username"] = user.Username
|
||||
infos["admin"] = user.HasRole(auth.RoleAdmin)
|
||||
} else {
|
||||
infos["username"] = false
|
||||
infos["admin"] = false
|
||||
}
|
||||
|
||||
return conf, infos, nil
|
||||
}
|
||||
|
||||
func monitoringRoutes(router *mux.Router, resolver *graph.Resolver) {
|
||||
buildFilterPresets := func(query url.Values) map[string]interface{} {
|
||||
filterPresets := map[string]interface{}{}
|
||||
@ -444,21 +466,22 @@ func monitoringRoutes(router *mux.Router, resolver *graph.Resolver) {
|
||||
}
|
||||
|
||||
router.HandleFunc("/monitoring/jobs/", func(rw http.ResponseWriter, r *http.Request) {
|
||||
conf, err := config.GetUIConfig(r)
|
||||
conf, infos, err := prepareRoute(r)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
templates.Render(rw, r, "monitoring/jobs.html", &templates.Page{
|
||||
templates.Render(rw, r, "monitoring/jobs.tmpl", &templates.Page{
|
||||
Title: "Jobs - ClusterCockpit",
|
||||
Config: conf,
|
||||
Infos: infos,
|
||||
FilterPresets: buildFilterPresets(r.URL.Query()),
|
||||
})
|
||||
})
|
||||
|
||||
router.HandleFunc("/monitoring/job/{id:[0-9]+}", func(rw http.ResponseWriter, r *http.Request) {
|
||||
conf, err := config.GetUIConfig(r)
|
||||
conf, infos, err := prepareRoute(r)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@ -471,49 +494,53 @@ func monitoringRoutes(router *mux.Router, resolver *graph.Resolver) {
|
||||
return
|
||||
}
|
||||
|
||||
templates.Render(rw, r, "monitoring/job.html", &templates.Page{
|
||||
infos["id"] = id
|
||||
infos["jobId"] = job.JobID
|
||||
infos["clusterId"] = job.Cluster
|
||||
|
||||
templates.Render(rw, r, "monitoring/job.tmpl", &templates.Page{
|
||||
Title: fmt.Sprintf("Job %d - ClusterCockpit", job.JobID),
|
||||
Config: conf,
|
||||
Infos: map[string]interface{}{
|
||||
"id": id,
|
||||
"jobId": job.JobID,
|
||||
"clusterId": job.Cluster,
|
||||
},
|
||||
Infos: infos,
|
||||
})
|
||||
})
|
||||
|
||||
router.HandleFunc("/monitoring/users/", func(rw http.ResponseWriter, r *http.Request) {
|
||||
conf, err := config.GetUIConfig(r)
|
||||
conf, infos, err := prepareRoute(r)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
templates.Render(rw, r, "monitoring/list.html", &templates.Page{
|
||||
infos["listType"] = "USER"
|
||||
|
||||
templates.Render(rw, r, "monitoring/list.tmpl", &templates.Page{
|
||||
Title: "Users - ClusterCockpit",
|
||||
Config: conf,
|
||||
FilterPresets: buildFilterPresets(r.URL.Query()),
|
||||
Infos: map[string]interface{}{"listType": "USER"},
|
||||
Infos: infos,
|
||||
})
|
||||
})
|
||||
|
||||
router.HandleFunc("/monitoring/projects/", func(rw http.ResponseWriter, r *http.Request) {
|
||||
conf, err := config.GetUIConfig(r)
|
||||
conf, infos, err := prepareRoute(r)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
templates.Render(rw, r, "monitoring/list.html", &templates.Page{
|
||||
infos["listType"] = "PROJECT"
|
||||
|
||||
templates.Render(rw, r, "monitoring/list.tmpl", &templates.Page{
|
||||
Title: "Projects - ClusterCockpit",
|
||||
Config: conf,
|
||||
FilterPresets: buildFilterPresets(r.URL.Query()),
|
||||
Infos: map[string]interface{}{"listType": "PROJECT"},
|
||||
Infos: infos,
|
||||
})
|
||||
})
|
||||
|
||||
router.HandleFunc("/monitoring/user/{id}", func(rw http.ResponseWriter, r *http.Request) {
|
||||
conf, err := config.GetUIConfig(r)
|
||||
conf, infos, err := prepareRoute(r)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@ -522,17 +549,18 @@ func monitoringRoutes(router *mux.Router, resolver *graph.Resolver) {
|
||||
id := mux.Vars(r)["id"]
|
||||
// TODO: One could check if the user exists, but that would be unhelpfull if authentication
|
||||
// is disabled or the user does not exist but has started jobs.
|
||||
infos["username"] = id
|
||||
|
||||
templates.Render(rw, r, "monitoring/user.html", &templates.Page{
|
||||
templates.Render(rw, r, "monitoring/user.tmpl", &templates.Page{
|
||||
Title: fmt.Sprintf("User %s - ClusterCockpit", id),
|
||||
Config: conf,
|
||||
Infos: map[string]interface{}{"username": id},
|
||||
Infos: infos,
|
||||
FilterPresets: buildFilterPresets(r.URL.Query()),
|
||||
})
|
||||
})
|
||||
|
||||
router.HandleFunc("/monitoring/analysis/", func(rw http.ResponseWriter, r *http.Request) {
|
||||
conf, err := config.GetUIConfig(r)
|
||||
conf, infos, err := prepareRoute(r)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@ -544,15 +572,16 @@ func monitoringRoutes(router *mux.Router, resolver *graph.Resolver) {
|
||||
filterPresets["clusterId"] = query.Get("cluster")
|
||||
}
|
||||
|
||||
templates.Render(rw, r, "monitoring/analysis.html", &templates.Page{
|
||||
templates.Render(rw, r, "monitoring/analysis.tmpl", &templates.Page{
|
||||
Title: "Analysis View - ClusterCockpit",
|
||||
Config: conf,
|
||||
Infos: infos,
|
||||
FilterPresets: filterPresets,
|
||||
})
|
||||
})
|
||||
|
||||
router.HandleFunc("/monitoring/systems/", func(rw http.ResponseWriter, r *http.Request) {
|
||||
conf, err := config.GetUIConfig(r)
|
||||
conf, infos, err := prepareRoute(r)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@ -564,28 +593,29 @@ func monitoringRoutes(router *mux.Router, resolver *graph.Resolver) {
|
||||
filterPresets["clusterId"] = query.Get("cluster")
|
||||
}
|
||||
|
||||
templates.Render(rw, r, "monitoring/systems.html", &templates.Page{
|
||||
templates.Render(rw, r, "monitoring/systems.tmpl", &templates.Page{
|
||||
Title: "System View - ClusterCockpit",
|
||||
Config: conf,
|
||||
Infos: infos,
|
||||
FilterPresets: filterPresets,
|
||||
})
|
||||
})
|
||||
|
||||
router.HandleFunc("/monitoring/node/{clusterId}/{nodeId}", func(rw http.ResponseWriter, r *http.Request) {
|
||||
conf, err := config.GetUIConfig(r)
|
||||
conf, infos, err := prepareRoute(r)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
templates.Render(rw, r, "monitoring/node.html", &templates.Page{
|
||||
infos["nodeId"] = vars["nodeId"]
|
||||
infos["clusterId"] = vars["clusterId"]
|
||||
|
||||
templates.Render(rw, r, "monitoring/node.tmpl", &templates.Page{
|
||||
Title: fmt.Sprintf("Node %s - ClusterCockpit", vars["nodeId"]),
|
||||
Config: conf,
|
||||
Infos: map[string]interface{}{
|
||||
"nodeId": vars["nodeId"],
|
||||
"clusterId": vars["clusterId"],
|
||||
},
|
||||
Infos: infos,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{{template "base.html" .}}
|
||||
{{template "base.tmpl" .}}
|
||||
{{define "content"}}
|
||||
<div class="row">
|
||||
<div class="col">
|
@ -1,28 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<meta name='viewport' content='width=device-width,initial-scale=1'>
|
||||
<title>{{.Title}}</title>
|
||||
|
||||
<link rel='icon' type='image/png' href='/favicon.png'>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.4.1/font/bootstrap-icons.css">
|
||||
<link rel='stylesheet' href='/global.css'>
|
||||
<link rel='stylesheet' href='/uPlot.min.css'>
|
||||
|
||||
{{block "stylesheets" .}}{{end}}
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
{{block "content" .}}
|
||||
Whoops, you should not see this...
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{block "javascript" .}}{{end}}
|
||||
</body>
|
||||
</html>
|
119
templates/base.tmpl
Normal file
119
templates/base.tmpl
Normal file
@ -0,0 +1,119 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<meta name='viewport' content='width=device-width,initial-scale=1'>
|
||||
<title>{{.Title}}</title>
|
||||
|
||||
<link rel='icon' type='image/png' href='/favicon.png'>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.4.1/font/bootstrap-icons.css">
|
||||
<link rel='stylesheet' href='/global.css'>
|
||||
<link rel='stylesheet' href='/uPlot.min.css'>
|
||||
|
||||
{{block "stylesheets" .}}
|
||||
{{end}}
|
||||
</head>
|
||||
<body class="Site">
|
||||
<header>
|
||||
<nav class="navbar navbar-expand-lg navbar-light fixed-top bg-light">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/">
|
||||
{{block "brand" .}}
|
||||
<img alt="ClusterCockpit Logo" src="/img/logo.png" class="d-inline-block align-top">
|
||||
{{end}}
|
||||
</a>
|
||||
{{block "navigation" .}}
|
||||
<ul class="navbar-nav mr-auto">
|
||||
{{block "navitem_joblist" .}}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link fs-5" href="/monitoring/jobs/">
|
||||
<span class="cc-nav-text">Joblist</span>
|
||||
<i class="bi-card-list"></i>
|
||||
</a>
|
||||
</li>
|
||||
{{end}}
|
||||
{{if .Infos.admin }}
|
||||
{{block "navitem_analysis" .}}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link fs-5" href="/monitoring/analysis/">
|
||||
<span class="cc-nav-text">Analysis</span>
|
||||
<i class="bi-graph-up"></i>
|
||||
</a>
|
||||
</li>
|
||||
{{end}}
|
||||
{{block "navitem_systems" .}}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link fs-5" href="/monitoring/systems/">
|
||||
<span class="cc-nav-text">Systems</span>
|
||||
<i class="bi-graph-up"></i>
|
||||
</a>
|
||||
</li>
|
||||
{{end}}
|
||||
{{block "navitem_users" .}}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link fs-5" href="/monitoring/users/">
|
||||
<span class="cc-nav-text">Users</span>
|
||||
<i class="bi-people-fill"></i>
|
||||
</a>
|
||||
</li>
|
||||
{{end}}
|
||||
{{else}}
|
||||
{{block "navitem_stats" .}}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link fs-5" href="/monitoring/user/admin">
|
||||
<span class="cc-nav-text">Statistics</span>
|
||||
<i class="bi-bar-chart-line-fill"></i>
|
||||
</a>
|
||||
</li>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</ul>
|
||||
{{end}}
|
||||
{{if .Infos.username }}
|
||||
<div class="d-flex align-items-end">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item">
|
||||
<form method="post" action="/logout">
|
||||
<button type="submit" class="btn btn-link nav-link fs-5">
|
||||
<span class="cc-nav-text">{{ .Infos.username }} Logout</span>
|
||||
<i class="bi-box-arrow-right"></i>
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
<form class="d-flex my-0" onsubmit="this.action='/search';">
|
||||
{{if .Infos.admin }}
|
||||
<input class="form-control me-2" type="search" name="searchId" placeholder="jobId / userId" id="searchId" aria-label="Search">
|
||||
{{else}}
|
||||
<input class="form-control me-2" type="search" name="searchId" placeholder="jobId" id="searchId" aria-label="Search">
|
||||
{{end}}
|
||||
<button class="btn btn-outline-success fs-6" type="submit">Search</button>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="Site-content">
|
||||
<div class="container">
|
||||
{{block "content" .}}
|
||||
Whoops, you should not see this...
|
||||
{{end}}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{{block "footer" .}}
|
||||
<footer class="site-footer bg-light">
|
||||
<ul class="footer-list">
|
||||
<li class="footer-list-item"><a class="link-secondary fs-5" href="/imprint" title="Imprint" rel="nofollow">Imprint</a></li>
|
||||
<li class="footer-list-item"><a class="link-secondary fs-5" href="/privacy" title="Privacy Policy" rel="nofollow">Privacy Policy</a></li>
|
||||
</ul>
|
||||
</footer>
|
||||
{{end}}
|
||||
|
||||
{{block "javascript" .}}
|
||||
{{end}}
|
||||
</body>
|
||||
</html>
|
@ -1,47 +0,0 @@
|
||||
{{define "content"}}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1>
|
||||
ClusterCockpit Login
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
{{if .Login.Error}}
|
||||
<div class="alert alert-warning" role="alert">
|
||||
{{.Login.Error}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .Login.Info}}
|
||||
<div class="alert alert-success" role="alert">
|
||||
{{.Login.Info}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<form method="post" action="/login">
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="username">Username</label>
|
||||
<input class="form-control" type="text" id="username" name="username">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="password">Password</label>
|
||||
<input class="form-control" type="password" id="password" name="password">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<form method="post" action="/logout">
|
||||
<button type="submit" class="btn btn-primary">Logout</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
41
templates/login.tmpl
Normal file
41
templates/login.tmpl
Normal file
@ -0,0 +1,41 @@
|
||||
{{define "navigation"}}
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<section class="content-section">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-4 mx-auto">
|
||||
{{if .Login.Error}}
|
||||
<div class="alert alert-warning" role="alert">
|
||||
{{.Login.Error}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .Login.Info}}
|
||||
<div class="alert alert-success" role="alert">
|
||||
{{.Login.Info}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3>Login</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="/login" method="post">
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="username">Username</label>
|
||||
<input class="form-control" type="text" id="username" name="username" required autofocus/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="password">Password</label>
|
||||
<input class="form-control" type="password" id="password" name="password" required/>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{end}}
|
@ -27,16 +27,16 @@ type LoginPage struct {
|
||||
|
||||
func init() {
|
||||
templatesDir = "./templates/"
|
||||
base := template.Must(template.ParseFiles(templatesDir + "base.html"))
|
||||
base := template.Must(template.ParseFiles(templatesDir + "base.tmpl"))
|
||||
files := []string{
|
||||
"home.html", "404.html", "login.html",
|
||||
"monitoring/jobs.html",
|
||||
"monitoring/job.html",
|
||||
"monitoring/list.html",
|
||||
"monitoring/user.html",
|
||||
// "monitoring/analysis.html",
|
||||
// "monitoring/systems.html",
|
||||
// "monitoring/node.html",
|
||||
"home.tmpl", "404.tmpl", "login.tmpl",
|
||||
"monitoring/jobs.tmpl",
|
||||
"monitoring/job.tmpl",
|
||||
"monitoring/list.tmpl",
|
||||
"monitoring/user.tmpl",
|
||||
// "monitoring/analysis.tmpl",
|
||||
// "monitoring/systems.tmpl",
|
||||
// "monitoring/node.tmpl",
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
@ -51,7 +51,7 @@ func Render(rw http.ResponseWriter, r *http.Request, file string, page *Page) {
|
||||
}
|
||||
|
||||
if debugMode {
|
||||
t = template.Must(template.ParseFiles(templatesDir+"base.html", templatesDir+file))
|
||||
t = template.Must(template.ParseFiles(templatesDir+"base.tmpl", templatesDir+file))
|
||||
}
|
||||
|
||||
if err := t.Execute(rw, page); err != nil {
|
||||
|
Loading…
Reference in New Issue
Block a user