initial commit - cmd, config, and login working with Adapter interface and skeleton EurekaAdapter
This commit is contained in:
commit
b7e2dc3ab7
19 changed files with 586 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
nirvash
|
33
README.md
Normal file
33
README.md
Normal file
|
@ -0,0 +1,33 @@
|
|||
# NIRVASH
|
||||
|
||||
![view within the cockpit of Nirvash](./static/bg.png)
|
||||
|
||||
## about
|
||||
|
||||
`nirvash` is a content management system (CMS) written in Go using the [quartzgun](https://nilfm.cc/git/quartzgun) library, designed to be efficient, modular, and easy to use. It uses an `Adapter` system that in theory allows almost any backend SSG to be used for actual page generation and backend storage. It's inspired by [Joost Van Der Schee](https://usecue.com)'s Usecue CMS.
|
||||
|
||||
## installation and configuration
|
||||
|
||||
Clone this repository and run `go build` to build `nirvash`. Just running `./nirvash` from there, if you haven't run it before, should run the configuration wizard (it runs if the config file found in your user's config directory is missing or incomplete). The configuration file looks like:
|
||||
|
||||
```
|
||||
adapter=eureka // one of the supported adapters, currently just eureka
|
||||
root=/path/to/ssg/root // path to where your SSG content root is
|
||||
assetRoot=/path/to/asset/root // path to the Nirvash static assets (eg static/ directory in this repo)
|
||||
staticRoot=/path/to/static/root // path to static file storage on your webserver
|
||||
plugins=none // list of plugins to use, currently none are implemented
|
||||
```
|
||||
|
||||
You can also set the configuration options by running eg `nirvash configure adapter=eureka root=/var/www`. Key-value pairs given on the command line are written to the configuration file, and pairs not listed are unmodified.
|
||||
|
||||
User management is done from the command line as well:
|
||||
|
||||
- `nirvash adduser username password`
|
||||
- `nirvash rmuser username`
|
||||
- `nirvash passwd username oldpass newpass`
|
||||
|
||||
## usage
|
||||
|
||||
Running `nirvash` without any arguments starts the webserver on port 8080.
|
||||
|
||||
MORE TO COME
|
16
adapter/adapter.go
Normal file
16
adapter/adapter.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
package adapter
|
||||
|
||||
import (
|
||||
"nilfm.cc/git/nirvash/page"
|
||||
)
|
||||
|
||||
type Adapter interface {
|
||||
Name() string
|
||||
GetConfig(key string) (interface{}, error)
|
||||
SetConfig(key string, value interface{}) error
|
||||
ListPages() map[string]string
|
||||
GetPage(string) page.Page
|
||||
FormatPage(string) string
|
||||
FormattingHelp() string
|
||||
Build()
|
||||
}
|
41
adapter/eureka.go
Normal file
41
adapter/eureka.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
package adapter
|
||||
|
||||
import (
|
||||
"nilfm.cc/git/nirvash/page"
|
||||
)
|
||||
|
||||
type EurekaAdapter struct {
|
||||
Root string
|
||||
}
|
||||
|
||||
func (self *EurekaAdapter) Name() string {
|
||||
return "eureka"
|
||||
}
|
||||
|
||||
func (self *EurekaAdapter) GetConfig(key string) (interface{}, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (self *EurekaAdapter) SetConfig(key string, value interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *EurekaAdapter) ListPages() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (self *EurekaAdapter) GetPage(path string) page.Page {
|
||||
return page.Page{}
|
||||
}
|
||||
|
||||
func (self *EurekaAdapter) FormatPage(raw string) string {
|
||||
return raw
|
||||
}
|
||||
|
||||
func (self *EurekaAdapter) FormattingHelp() string {
|
||||
return "help!"
|
||||
}
|
||||
|
||||
func (self *EurekaAdapter) Build() {
|
||||
return
|
||||
}
|
61
cmd/cmd.go
Normal file
61
cmd/cmd.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"nilfm.cc/git/quartzgun/auth"
|
||||
"nilfm.cc/git/nirvash/config"
|
||||
)
|
||||
|
||||
func Process(args []string, userStore auth.UserStore, cfg *config.Config) bool {
|
||||
if len(args) == 1 {
|
||||
return false
|
||||
}
|
||||
switch args[1] {
|
||||
case "adduser":
|
||||
if len(args) < 4 {
|
||||
return help()
|
||||
}
|
||||
userStore.AddUser(args[2], args[3])
|
||||
case "rmuser":
|
||||
if len(args) < 3 {
|
||||
return help()
|
||||
}
|
||||
userStore.DeleteUser(args[2])
|
||||
case "passwd":
|
||||
if len(args) < 5 {
|
||||
return help()
|
||||
}
|
||||
userStore.ChangePassword(args[2], args[3], args[4])
|
||||
case "configure":
|
||||
fmt.Printf("configuring\n")
|
||||
for _, token := range args[2:] {
|
||||
kvp := strings.Split(token, "=")
|
||||
k := kvp[0]
|
||||
v := kvp[1]
|
||||
fmt.Printf("%s = %s\n", k, v)
|
||||
switch k {
|
||||
case "adapter":
|
||||
config.SetAdapter(cfg, v)
|
||||
case "root":
|
||||
cfg.Root = v
|
||||
case "assetRoot":
|
||||
cfg.AssetRoot = v
|
||||
case "staticRoot":
|
||||
cfg.StaticRoot = v
|
||||
case "plugins":
|
||||
// handle plugins later
|
||||
default:
|
||||
panic("unknown configuration option: " + v)
|
||||
}
|
||||
}
|
||||
config.Write(cfg)
|
||||
default:
|
||||
help()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func help() bool {
|
||||
return true
|
||||
}
|
173
config/config.go
Normal file
173
config/config.go
Normal file
|
@ -0,0 +1,173 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"os"
|
||||
"strings"
|
||||
"path/filepath"
|
||||
"nilfm.cc/git/nirvash/adapter"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Adapter adapter.Adapter // adapter for this instance
|
||||
Root string // root of the site data
|
||||
StaticRoot string // root of static files for StaticFileManager
|
||||
AssetRoot string // root of Nirvash dist files (CSS, images)
|
||||
Plugins map[string]interface{}
|
||||
}
|
||||
|
||||
func GetConfigLocation() string {
|
||||
home := os.Getenv("HOME")
|
||||
appdata := os.Getenv("APPDATA")
|
||||
switch (runtime.GOOS) {
|
||||
case "windows":
|
||||
return filepath.Join(appdata, "nirvash")
|
||||
case "darwin":
|
||||
return filepath.Join(home, "Library", "Application Support", "nirvash")
|
||||
case "plan9":
|
||||
return filepath.Join(home, "lib", "nirvash")
|
||||
default:
|
||||
return filepath.Join(home, ".config", "nirvash")
|
||||
}
|
||||
}
|
||||
|
||||
func ensureConfigLocationExists() {
|
||||
_, err := os.Stat(GetConfigLocation())
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
os.MkdirAll(GetConfigLocation(), os.ModePerm)
|
||||
}
|
||||
}
|
||||
|
||||
func Read() *Config {
|
||||
ensureConfigLocationExists()
|
||||
return parseConfig(filepath.Join(GetConfigLocation(), "nirvash.conf"))
|
||||
}
|
||||
|
||||
func Write(cfg *Config) error {
|
||||
ensureConfigLocationExists()
|
||||
return writeConfig(cfg, filepath.Join(GetConfigLocation(), "nirvash.conf"))
|
||||
}
|
||||
|
||||
|
||||
func SetAdapter(cfg *Config, adptr string) {
|
||||
switch adptr {
|
||||
case "eureka":
|
||||
cfg.Adapter = &adapter.EurekaAdapter{}
|
||||
default:
|
||||
panic("Unsupported adapter! Try one of [ eureka ]")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
func IsNull(cfg *Config) bool {
|
||||
return cfg.Adapter == nil || len(cfg.Root) == 0 || len(cfg.StaticRoot) == 0 || len(cfg.AssetRoot) == 0
|
||||
}
|
||||
|
||||
func RunWizard(cfg *Config) {
|
||||
fmt.Printf("All options are required.\n")
|
||||
defer func(cfg *Config) {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf("Invalid selection, starting over...")
|
||||
RunWizard(cfg)
|
||||
}
|
||||
}(cfg)
|
||||
inputBuf := ""
|
||||
fmt.Printf("adapter? (eureka) [eureka] ")
|
||||
fmt.Scanln(&inputBuf)
|
||||
if len(strings.TrimSpace(inputBuf)) == 0 {
|
||||
inputBuf = "eureka"
|
||||
}
|
||||
SetAdapter(cfg, inputBuf)
|
||||
|
||||
inputBuf = ""
|
||||
fmt.Printf("site data root? ")
|
||||
ensureNonEmptyOption(&inputBuf)
|
||||
cfg.Root = inputBuf
|
||||
|
||||
inputBuf = ""
|
||||
|
||||
fmt.Printf("static file root? ")
|
||||
ensureNonEmptyOption(&inputBuf)
|
||||
cfg.StaticRoot = inputBuf
|
||||
|
||||
inputBuf = ""
|
||||
fmt.Printf("nirvash asset root? ")
|
||||
ensureNonEmptyOption(&inputBuf)
|
||||
cfg.AssetRoot = inputBuf
|
||||
|
||||
inputBuf = ""
|
||||
fmt.Printf("plugins? (not implemented yet) ")
|
||||
ensureNonEmptyOption(&inputBuf)
|
||||
//cfg.Plugins = processPlugins(inputBuf)
|
||||
|
||||
fmt.Printf("Configuration complete!\n")
|
||||
Write(cfg)
|
||||
}
|
||||
|
||||
func ensureNonEmptyOption(buffer *string) {
|
||||
for ;; {
|
||||
fmt.Scanln(buffer)
|
||||
if len(strings.TrimSpace(*buffer)) != 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeConfig(cfg *Config, configFile string) error {
|
||||
f, err := os.Create(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
f.WriteString("root=" + cfg.Root + "\n")
|
||||
f.WriteString("staticRoot=" + cfg.StaticRoot + "\n")
|
||||
f.WriteString("assetRoot=" + cfg.AssetRoot + "\n")
|
||||
f.WriteString("adapter=" + cfg.Adapter.Name() + "\n")
|
||||
f.WriteString("plugins=\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseConfig(configFile string) *Config {
|
||||
f, err := os.ReadFile(configFile)
|
||||
cfg := &Config{}
|
||||
if err != nil {
|
||||
return cfg
|
||||
}
|
||||
|
||||
fileData := string(f[:])
|
||||
|
||||
lines := strings.Split(fileData, "\n")
|
||||
|
||||
for _, l := range lines {
|
||||
if len(l) == 0 {
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(l, "=") {
|
||||
panic("Malformed config not in INI format")
|
||||
}
|
||||
|
||||
kvp := strings.Split(l, "=")
|
||||
k := strings.TrimSpace(kvp[0])
|
||||
v := strings.TrimSpace(kvp[1])
|
||||
switch k {
|
||||
case "root":
|
||||
cfg.Root = v
|
||||
case "staticRoot":
|
||||
cfg.StaticRoot = v
|
||||
case "assetRoot":
|
||||
cfg.AssetRoot = v
|
||||
case "plugins":
|
||||
// not implemented
|
||||
case "adapter":
|
||||
SetAdapter(cfg, v)
|
||||
default:
|
||||
panic("Unrecognized config option: " + k)
|
||||
}
|
||||
}
|
||||
return cfg
|
||||
}
|
7
go.mod
Normal file
7
go.mod
Normal file
|
@ -0,0 +1,7 @@
|
|||
module nilfm.cc/git/nirvash
|
||||
|
||||
go 1.17
|
||||
|
||||
require nilfm.cc/git/quartzgun v0.1.0
|
||||
|
||||
require golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 // indirect
|
25
go.sum
Normal file
25
go.sum
Normal file
|
@ -0,0 +1,25 @@
|
|||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 h1:SLP7Q4Di66FONjDJbCYrCRrh97focO6sLogHO7/g8F0=
|
||||
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
nilfm.cc/git/quartzgun v0.0.0-20220516042416-1dbca325d20a h1:NtR/vUiY7nhEARMOXgabxwd4Z2kbC/z0AJgtpQ04ai0=
|
||||
nilfm.cc/git/quartzgun v0.0.0-20220516042416-1dbca325d20a/go.mod h1:YqXoEQkRNOU1fZXeq5r2kTzvNbaH2VmULRP9an/sBX4=
|
||||
nilfm.cc/git/quartzgun v0.0.0-20220516045132-9bf93d5c7575 h1:68aITeSQJ2EMuyWVNPsQvYw9W/sUsbzt2CNyg6Jg7bs=
|
||||
nilfm.cc/git/quartzgun v0.0.0-20220516045132-9bf93d5c7575/go.mod h1:/DDvt1DtzNuUf3HHaP29WMei/kkdaRW+ySmEzybvVto=
|
||||
nilfm.cc/git/quartzgun v0.0.0-20220516045804-ac526a0d7890 h1:R+jc5HoSg88gUlj5tVsm9ZsEkaNw0i+4e9xzeCJE9ig=
|
||||
nilfm.cc/git/quartzgun v0.0.0-20220516045804-ac526a0d7890/go.mod h1:/DDvt1DtzNuUf3HHaP29WMei/kkdaRW+ySmEzybvVto=
|
||||
nilfm.cc/git/quartzgun v0.0.0-20220516052922-27b61b7e68a2 h1:xufV1FtykeEITJegz7qSqQOnsESTt1mIBJ09zAAzpgg=
|
||||
nilfm.cc/git/quartzgun v0.0.0-20220516052922-27b61b7e68a2/go.mod h1:/DDvt1DtzNuUf3HHaP29WMei/kkdaRW+ySmEzybvVto=
|
||||
nilfm.cc/git/quartzgun v0.0.0-20220516055202-14a8c12fd440 h1:R1b9Jl6vDVAaCs+MaYI4LMVVajwQ2jGZcqDL8L33SA0=
|
||||
nilfm.cc/git/quartzgun v0.0.0-20220516055202-14a8c12fd440/go.mod h1:/DDvt1DtzNuUf3HHaP29WMei/kkdaRW+ySmEzybvVto=
|
||||
nilfm.cc/git/quartzgun v0.0.0-20220516061509-0e5a81f27b63 h1:HlIWrDDJjOFLrxPQzldzDz78K8Z5NDtTCoYkmmI8/JA=
|
||||
nilfm.cc/git/quartzgun v0.0.0-20220516061509-0e5a81f27b63/go.mod h1:/DDvt1DtzNuUf3HHaP29WMei/kkdaRW+ySmEzybvVto=
|
||||
nilfm.cc/git/quartzgun v0.1.0 h1:G+f/UnGpm5FAEqaY3Lj5UHvq0eB5sytM5s4FLesLC3E=
|
||||
nilfm.cc/git/quartzgun v0.1.0/go.mod h1:/DDvt1DtzNuUf3HHaP29WMei/kkdaRW+ySmEzybvVto=
|
46
nirvash.go
Normal file
46
nirvash.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"net/http"
|
||||
"nilfm.cc/git/quartzgun/indentalUserDB"
|
||||
"nilfm.cc/git/quartzgun/router"
|
||||
"nilfm.cc/git/quartzgun/renderer"
|
||||
"nilfm.cc/git/quartzgun/middleware"
|
||||
"nilfm.cc/git/nirvash/cmd"
|
||||
"nilfm.cc/git/nirvash/config"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg := config.Read()
|
||||
udb := indentalUserDB.CreateIndentalUserDB(
|
||||
filepath.Join(
|
||||
config.GetConfigLocation(),
|
||||
"user.db"))
|
||||
if cmd.Process(os.Args, udb, cfg) {
|
||||
os.Exit(0)
|
||||
}
|
||||
if config.IsNull(cfg) {
|
||||
config.RunWizard(cfg)
|
||||
}
|
||||
|
||||
rtr := &router.Router{
|
||||
StaticPaths: map[string]string{
|
||||
"/static": cfg.AssetRoot,
|
||||
},
|
||||
}
|
||||
|
||||
rtr.Get("/login", renderer.Template(
|
||||
"templates/login.html"))
|
||||
|
||||
rtr.Post("/login", middleware.Authorize("/", udb, "/login?tryagain=1"))
|
||||
|
||||
rtr.Get("/", middleware.Protected(
|
||||
renderer.Template(
|
||||
"templates/cms_list.html",
|
||||
"templates/header.html",
|
||||
"templates/footer.html"), http.MethodGet, udb, "/login"))
|
||||
|
||||
http.ListenAndServe(":8080", rtr)
|
||||
}
|
11
page/page.go
Normal file
11
page/page.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
package page
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Page struct {
|
||||
Title string
|
||||
Content string
|
||||
Edited time.Time
|
||||
}
|
BIN
static/bg.png
Normal file
BIN
static/bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 93 KiB |
74
static/style.css
Normal file
74
static/style.css
Normal file
|
@ -0,0 +1,74 @@
|
|||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: sans;
|
||||
}
|
||||
|
||||
.login-body {
|
||||
background: url('/static/bg.png');
|
||||
background-size: cover;
|
||||
background-position: center center;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.login {
|
||||
text-align: center;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
height: auto;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
-webkit-transform: translateY(-50%);
|
||||
background-color: rgba(0,0,0,0.8);
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.login h1 {
|
||||
font-size: 225%;
|
||||
text-transform: uppercase;
|
||||
color: lightgray;
|
||||
}
|
||||
|
||||
.login form input {
|
||||
display: block;
|
||||
margin: 1em;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
background: transparent;
|
||||
border: solid 2px lightgray;
|
||||
font-size: 200%;
|
||||
color: lightgray;
|
||||
padding: 0.2em;
|
||||
}
|
||||
|
||||
.login form input[type="text"], login form input[type="password"] {
|
||||
transition: border 1s;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.login form input:focus {
|
||||
border: solid 2px cyan;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
|
||||
.login form input[type="submit"] {
|
||||
text-transform: uppercase;
|
||||
transition: background 1s, color 1s;
|
||||
}
|
||||
|
||||
.login form input[type="submit"]:hover {
|
||||
background: lightgray;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.login .error {
|
||||
positon: relative;
|
||||
text-align: center;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
color: lightgray;
|
||||
border-bottom: 2px solid crimson;
|
||||
width: auto;
|
||||
}
|
3
templates/cms_list.html
Normal file
3
templates/cms_list.html
Normal file
|
@ -0,0 +1,3 @@
|
|||
{{ template "header" .}}
|
||||
<h1>It works!</h1>
|
||||
{{ template "footer" .}}
|
17
templates/error.html
Normal file
17
templates/error.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
{{ $params := (.Context).Value "params" }}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang='en'>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
|
||||
<meta name='viewport' content='width=device-width,initial-scale=1'>
|
||||
<link rel='shortcut icon' href='/favicon.ico'>
|
||||
<title>test — error</title>
|
||||
</head>
|
||||
<body>
|
||||
<header><h1>{{ $params.ErrorCode }}</h1></header>
|
||||
<main>
|
||||
{{ $params.ErrorMessage }}
|
||||
</main>
|
||||
{{ template "footer" .}}
|
5
templates/footer.html
Normal file
5
templates/footer.html
Normal file
|
@ -0,0 +1,5 @@
|
|||
{{define "footer"}}
|
||||
TEST
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
11
templates/header.html
Normal file
11
templates/header.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
{{define "header"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang='en'>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<meta name='description' content='Nirvash CMS'/>
|
||||
<meta name='viewport' content='width=device-width,initial-scale=1'>
|
||||
<title>Nirvash — Test</title>
|
||||
</head>
|
||||
<body>
|
||||
{{end}}
|
26
templates/login.html
Normal file
26
templates/login.html
Normal file
|
@ -0,0 +1,26 @@
|
|||
{{ $tryagain := .FormValue "tryagain" }}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang='en'>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<meta name='description' content='Nirvash CMS'/>
|
||||
<meta name='viewport' content='width=device-width,initial-scale=1'>
|
||||
<title>Nirvash — Login</title>
|
||||
<link rel='stylesheet' type='text/css' href='/static/style.css'>
|
||||
<link rel='shortcut icon' href='/static/favicon.png'>
|
||||
</head>
|
||||
<body class="login-body">
|
||||
<div class="login">
|
||||
<h1>Nirvash</h1>
|
||||
{{ if $tryagain }}
|
||||
<span class="error">Incorrect credentials; please try again.</span>
|
||||
{{ end }}
|
||||
<form action='/login' method='post'>
|
||||
<input type="text" name="user" placeholder="user">
|
||||
<input type="password" name="password" placeholder="password">
|
||||
<input type="submit" value="Login">
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
19
templates/paramTest.html
Normal file
19
templates/paramTest.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
{{ $params := (.Context).Value "params" }}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang='en'>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
|
||||
<meta name='viewport' content='width=device-width,initial-scale=1'>
|
||||
<link rel='stylesheet' type='text/css' href='/style.css'>
|
||||
<link rel='shortcut icon' href='/favicon.ico'>
|
||||
<title>test — thing</title>
|
||||
</head>
|
||||
<body>
|
||||
<header><h1>nilFM</h1></header>
|
||||
<main>
|
||||
{{ $params.Thing }}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
17
templates/test.html
Normal file
17
templates/test.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang='en'>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
|
||||
<meta name='viewport' content='width=device-width,initial-scale=1'>
|
||||
<link rel='stylesheet' type='text/css' href='/style.css'>
|
||||
<link rel='shortcut icon' href='/favicon.ico'>
|
||||
<title>test — something</title>
|
||||
</head>
|
||||
<body>
|
||||
<header><h1>nilFM</h1></header>
|
||||
<main>
|
||||
{{ .Form.Get "Content" }}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in a new issue