added build/delete, started styling
This commit is contained in:
parent
e745665135
commit
8fdc9ddb46
14 changed files with 327 additions and 17 deletions
|
@ -1,5 +1,10 @@
|
||||||
package archetype
|
package archetype
|
||||||
|
|
||||||
|
type BuildStatus struct {
|
||||||
|
Success bool
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
type Adapter interface {
|
type Adapter interface {
|
||||||
Init(cfg *Config)
|
Init(cfg *Config)
|
||||||
Name() string
|
Name() string
|
||||||
|
@ -14,5 +19,5 @@ type Adapter interface {
|
||||||
CreatePage(slug, title, content string) error
|
CreatePage(slug, title, content string) error
|
||||||
SavePage(oldSlug, newSlug, title, content string) error
|
SavePage(oldSlug, newSlug, title, content string) error
|
||||||
DeletePage(slug string) error
|
DeletePage(slug string) error
|
||||||
Build(buildOptions map[string]string) (bool, string)
|
Build(buildOptions map[string][]string) BuildStatus
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -141,7 +142,20 @@ func (self *EurekaAdapter) DeletePage(slug string) error {
|
||||||
return os.Remove(filepath.Join(self.Root, "inc", slug))
|
return os.Remove(filepath.Join(self.Root, "inc", slug))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *EurekaAdapter) Build(buildOptions map[string]string) (bool, string) {
|
func (self *EurekaAdapter) Build(buildOptions map[string][]string) BuildStatus {
|
||||||
// TODO: shell out to build.sh with buildOptions, record exit status and output
|
|
||||||
return true, "Build successful"
|
twtxt := buildOptions["twtxt"][0]
|
||||||
|
cmdArgs := ""
|
||||||
|
if twtxt != "" {
|
||||||
|
cmdArgs += " -t " + twtxt
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command("./build.sh", cmdArgs)
|
||||||
|
cmd.Dir = self.Root
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
|
||||||
|
return BuildStatus{
|
||||||
|
Success: err == nil,
|
||||||
|
Message: string(out),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
45
nirvash.go
45
nirvash.go
|
@ -117,5 +117,50 @@ func main() {
|
||||||
udb,
|
udb,
|
||||||
"/"))
|
"/"))
|
||||||
|
|
||||||
|
rtr.Get(
|
||||||
|
`/build`,
|
||||||
|
middleware.Fortify(
|
||||||
|
middleware.Protected(
|
||||||
|
shell.WithAdapter(
|
||||||
|
renderer.Template(
|
||||||
|
"templates/build.html",
|
||||||
|
"templates/header.html",
|
||||||
|
"templates/footer.html"),
|
||||||
|
cfg.Adapter),
|
||||||
|
http.MethodGet,
|
||||||
|
udb,
|
||||||
|
"/login")))
|
||||||
|
|
||||||
|
rtr.Post(
|
||||||
|
`/build-run`,
|
||||||
|
middleware.Defend(
|
||||||
|
middleware.Protected(
|
||||||
|
shell.WithAdapter(
|
||||||
|
renderer.Template(
|
||||||
|
"templates/build_run.html",
|
||||||
|
"templates/header.html",
|
||||||
|
"templates/footer.html"),
|
||||||
|
cfg.Adapter),
|
||||||
|
http.MethodGet,
|
||||||
|
udb,
|
||||||
|
"/login"),
|
||||||
|
udb,
|
||||||
|
"/"))
|
||||||
|
|
||||||
|
rtr.Post(
|
||||||
|
`/delete/(?P<Slug>\S+)`,
|
||||||
|
middleware.Defend(
|
||||||
|
middleware.Protected(
|
||||||
|
shell.WithAdapter(
|
||||||
|
renderer.Template(
|
||||||
|
"templates/delete.html",
|
||||||
|
"templates/header.html",
|
||||||
|
"templates/footer.html"),
|
||||||
|
cfg.Adapter),
|
||||||
|
http.MethodGet,
|
||||||
|
udb,
|
||||||
|
"/login"),
|
||||||
|
udb,
|
||||||
|
"/"))
|
||||||
http.ListenAndServe(":8080", rtr)
|
http.ListenAndServe(":8080", rtr)
|
||||||
}
|
}
|
||||||
|
|
BIN
static/bg2.png
Normal file
BIN
static/bg2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 48 KiB |
165
static/style.css
165
static/style.css
|
@ -2,13 +2,20 @@ body {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: sans;
|
font-family: sans;
|
||||||
|
font-size: 16px;
|
||||||
|
height: 100vh;
|
||||||
|
background: black;
|
||||||
|
color: white;
|
||||||
|
background: url('/static/bg2.png');
|
||||||
|
background-size: cover;
|
||||||
|
background-position: top left;
|
||||||
|
background-attachment: fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-body {
|
.login-body {
|
||||||
background: url('/static/bg.png');
|
background: url('/static/bg.png');
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position: center center;
|
background-position: center center;
|
||||||
height: 100vh;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.login {
|
.login {
|
||||||
|
@ -72,3 +79,159 @@ body {
|
||||||
border-bottom: 2px solid crimson;
|
border-bottom: 2px solid crimson;
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 225%;
|
||||||
|
text-transform: uppercase;
|
||||||
|
background: rgba(0,0,0,0.8);
|
||||||
|
margin: 0;
|
||||||
|
padding-top: 0.5em;
|
||||||
|
padding-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login h1 {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
text-align: center;
|
||||||
|
background: rgba(0,0,0,0.8);
|
||||||
|
padding-top: 0.5em;
|
||||||
|
padding-bottom: 0.5em;
|
||||||
|
position: sticky;
|
||||||
|
top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav ul li {
|
||||||
|
display: inline;
|
||||||
|
margin-left: 0.5em;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: white;
|
||||||
|
font-weight: normal;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: cyan;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.new-page-button {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
text-decoration: none;
|
||||||
|
background: transparent;
|
||||||
|
border: solid 2px lightgray;
|
||||||
|
font-size: 150%;
|
||||||
|
color: lightgray;
|
||||||
|
padding: 0.2em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
transition: background 1s, color 1s;
|
||||||
|
float: right;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.new-page-button:hover {
|
||||||
|
background: lightgray;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 1em;
|
||||||
|
font-size: 200%;
|
||||||
|
text-transform: uppercase;
|
||||||
|
background: rgba(0,0,0,0.8);
|
||||||
|
display: block;
|
||||||
|
border-left: 8px solid cyan;
|
||||||
|
padding-left: 8px;
|
||||||
|
position: sticky;
|
||||||
|
top: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-list, form.editor, form.build, span.adapter-error, span.adapter-success, .danger-zone {
|
||||||
|
width: 80%;
|
||||||
|
max-width: 500px;
|
||||||
|
background: rgba(0,0,0,0.8);
|
||||||
|
padding: 2em;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
max-height: calc(100vh - 20em);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.editor label, form.build label {
|
||||||
|
font-size: 80%;
|
||||||
|
color: lightgray;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.editor input, form.build input, form.editor textarea {
|
||||||
|
display: block;
|
||||||
|
margin: 0;
|
||||||
|
margin-top: 0.2em;
|
||||||
|
margin-bottom: 0.2em;
|
||||||
|
background: transparent;
|
||||||
|
border: solid 2px lightgray;
|
||||||
|
color: lightgray;
|
||||||
|
padding: 0.2em;
|
||||||
|
transition: border 1s;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.editor input.title-input {
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 150%;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.editor, .danger-zone {
|
||||||
|
max-width: 80em;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.editor textarea {
|
||||||
|
margin: 0;
|
||||||
|
width: 80em;
|
||||||
|
font-size: 16px;
|
||||||
|
height: 25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.editor input[type="submit"], form.build input[type="submit"] {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: 0;
|
||||||
|
font-size: 150%;
|
||||||
|
text-transform: uppercase;
|
||||||
|
transition: background 1s, color 1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.editor input[type="submit"]:hover {
|
||||||
|
background: lightgray;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edited-time {
|
||||||
|
font-size: 75%;
|
||||||
|
color: lightgray;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-list ul {
|
||||||
|
margin: 0;
|
||||||
|
position: relative;
|
||||||
|
list-style: none;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-list ul li a {
|
||||||
|
line-height: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
form input[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
23
templates/build.html
Normal file
23
templates/build.html
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{{ $buildOpts := ((.Context).Value "adapter").BuildOptions }}
|
||||||
|
{{ $csrfToken := (.Context).Value "csrfToken" }}
|
||||||
|
|
||||||
|
{{ template "header" . }}
|
||||||
|
|
||||||
|
<h2>Build</h2>
|
||||||
|
|
||||||
|
<form class="build" method="POST" action="/build-run">
|
||||||
|
<input hidden type="text" name="csrfToken" value="{{$csrfToken}}"/>
|
||||||
|
<details><summary>Build Options</summary>
|
||||||
|
{{ if $buildOpts }}
|
||||||
|
{{ range $optName := $buildOpts }}
|
||||||
|
<label>{{$optName}} <input type="text" name="{{$optName}}"/></label>
|
||||||
|
{{ end }}
|
||||||
|
{{ else }}
|
||||||
|
<span>There are no build options for this adapter.</span>
|
||||||
|
{{ end }}
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<input type="submit" value="Build"/>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{{ template "footer" . }}
|
14
templates/build_run.html
Normal file
14
templates/build_run.html
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{{ $buildOpts := .PostForm }}
|
||||||
|
{{ $status := ((.Context).Value "adapter").Build $buildOpts }}
|
||||||
|
|
||||||
|
{{ template "header" . }}
|
||||||
|
|
||||||
|
{{ if ne ($status).Success true }}
|
||||||
|
<h2>Build Error</h2>
|
||||||
|
<span class="adapter-error"><pre>{{($status).Message}}</pre></span>
|
||||||
|
{{ else }}
|
||||||
|
<h2>Build Successful</h2>
|
||||||
|
<span class="adapter-success"><pre>{{($status).Message}}</pre></span>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ template "footer" . }}
|
|
@ -6,8 +6,10 @@
|
||||||
{{ template "header" . }}
|
{{ template "header" . }}
|
||||||
|
|
||||||
{{ if $createErr }}
|
{{ if $createErr }}
|
||||||
|
<h2>Page Creation Error</h2>
|
||||||
<span class="adapter-error">There was an error creating the page: {{ ($createErr).Error }}</span>
|
<span class="adapter-error">There was an error creating the page: {{ ($createErr).Error }}</span>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
|
<h2>Page Created</h2>
|
||||||
<span class="adapter-success">Page '{{ $title }}' created successfully</span>
|
<span class="adapter-success">Page '{{ $title }}' created successfully</span>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,9 @@
|
||||||
{{ $noEmpty := .FormValue "no-empty" }}
|
{{ $noEmpty := .FormValue "no-empty" }}
|
||||||
|
|
||||||
{{ template "header" . }}
|
{{ template "header" . }}
|
||||||
<a href="/">«</a>
|
|
||||||
|
<h2>Edit Page</h2>
|
||||||
|
|
||||||
<form class="editor" method="POST" action="/save/{{$slug}}">
|
<form class="editor" method="POST" action="/save/{{$slug}}">
|
||||||
{{ if $noEmpty }}
|
{{ if $noEmpty }}
|
||||||
<span class="edit-error">Empty fields are not allowed - please try again</span><br/>
|
<span class="edit-error">Empty fields are not allowed - please try again</span><br/>
|
||||||
|
@ -13,12 +15,27 @@
|
||||||
<input hidden name="csrfToken" value="{{$csrfToken}}"/>
|
<input hidden name="csrfToken" value="{{$csrfToken}}"/>
|
||||||
<input hidden name="oldSlug" value="{{$slug}}"/>
|
<input hidden name="oldSlug" value="{{$slug}}"/>
|
||||||
{{ if $editableSlugs }}
|
{{ if $editableSlugs }}
|
||||||
<input class="slug-input" type="text" name="slug" value="{{$slug}}"/><br/>
|
<label for="slug">Slug</label><br/>
|
||||||
|
<input class="slug-input" id="slug" type="text" name="slug" value="{{$slug}}" required/><br/>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<input class="title-input" type="text" name="title" value="{{($page).Title}}"/><br/>
|
<label for="title">Title</label><br/>
|
||||||
<span class="edited-time">last edited {{($page).Edited}}</span><br/>
|
<input class="title-input" id="title" type="text" name="title" value="{{($page).Title}}" required/><br/>
|
||||||
<textarea class="content-input" name="content">{{($page).Content}}</textarea><br/>
|
<span class="edited-time">last edited {{($page).Edited.Format "2006-01-02 15:04"}}</span><br/>
|
||||||
|
<label for="content">Content</label><br/>
|
||||||
|
<textarea class="content-input" id="content" name="content" required>{{($page).Content}}</textarea><br/>
|
||||||
<input type="submit" value="Save"/>
|
<input type="submit" value="Save"/>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<details class="danger-zone"><summary>Danger Zone</summary>
|
||||||
|
<form class="eraser" method="POST" action="/delete/{{$slug}}">
|
||||||
|
<input hidden name="csrfToken" value="{{$csrfToken}}"/>
|
||||||
|
<label>I want to delete this page
|
||||||
|
<input type="checkbox" required/>
|
||||||
|
</label><br/>
|
||||||
|
<label>Yes, I'm sure
|
||||||
|
<input type="checkbox" required/>
|
||||||
|
</label><br/>
|
||||||
|
<input type="submit" value="DELETE"/>
|
||||||
|
</form>
|
||||||
|
</details>
|
||||||
{{ template "footer" . }}
|
{{ template "footer" . }}
|
|
@ -1,8 +1,11 @@
|
||||||
{{ $pages := ((.Context).Value "adapter").ListPages }}
|
{{ $pages := ((.Context).Value "adapter").ListPages }}
|
||||||
|
|
||||||
{{ template "header" .}}
|
{{ template "header" .}}
|
||||||
|
|
||||||
|
<h2>Pages</h2>
|
||||||
|
|
||||||
<div class="page-list">
|
<div class="page-list">
|
||||||
<a class="new-page-button" href="/new/">New Page</a>
|
<a class="new-page-button" href="/new">New Page</a>
|
||||||
<ul>
|
<ul>
|
||||||
{{ range $slug, $title := $pages }}
|
{{ range $slug, $title := $pages }}
|
||||||
<li><a href="/edit/{{$slug}}">{{$title}}</a></li>
|
<li><a href="/edit/{{$slug}}">{{$title}}</a></li>
|
||||||
|
|
|
@ -3,17 +3,22 @@
|
||||||
{{ $noEmpty := .FormValue "no-empty" }}
|
{{ $noEmpty := .FormValue "no-empty" }}
|
||||||
|
|
||||||
{{ template "header" . }}
|
{{ template "header" . }}
|
||||||
<a href="/">«</a>
|
|
||||||
|
<h2>New Page</h2>
|
||||||
|
|
||||||
<form class="editor" method="POST" action="/create">
|
<form class="editor" method="POST" action="/create">
|
||||||
{{ if $noEmpty }}
|
{{ if $noEmpty }}
|
||||||
<span class="edit-error">Empty fields are not allowed - please try again</span><br/>
|
<span class="edit-error">Empty fields are not allowed - please try again</span><br/>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<input hidden name="csrfToken" value="{{$csrfToken}}"/>
|
<input hidden name="csrfToken" value="{{$csrfToken}}"/>
|
||||||
{{ if $editableSlugs }}
|
{{ if $editableSlugs }}
|
||||||
<input class="slug-input" type="text" name="slug"/><br/>
|
<label for="slug">Slug</label><br/>
|
||||||
|
<input class="slug-input" id="slug" type="text" name="slug" required/><br/>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<input class="title-input" type="text" name="title"/><br/>
|
<label for="title">Title</label><br/>
|
||||||
<textarea class="content-input" name="content"></textarea><br/>
|
<input class="title-input" id="title" type="text" name="title" required/><br/>
|
||||||
|
<label for="content">Content</label><br/>
|
||||||
|
<textarea class="content-input" id="content" name="content" required></textarea><br/>
|
||||||
<input type="submit" value="Save"/>
|
<input type="submit" value="Save"/>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,10 @@
|
||||||
{{ template "header" . }}
|
{{ template "header" . }}
|
||||||
|
|
||||||
{{ if $saveErr }}
|
{{ if $saveErr }}
|
||||||
|
<h2>Page Save Error</h2>
|
||||||
<span class="adapter-error">There was an error saving the page: {{ ($saveErr).Error }}</span>
|
<span class="adapter-error">There was an error saving the page: {{ ($saveErr).Error }}</span>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
|
<h2>Page Saved</h2>
|
||||||
<span class="adapter-success">Page '{{ $title }}' saved successfully</span>
|
<span class="adapter-success">Page '{{ $title }}' saved successfully</span>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
|
14
templates/delete.html
Normal file
14
templates/delete.html
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{{ $slug := ((.Context).Value "params").Slug }}
|
||||||
|
{{ $deleteErr := ((.Context).Value "adapter").DeletePage $slug }}
|
||||||
|
|
||||||
|
{{ template "header" . }}
|
||||||
|
|
||||||
|
{{ if $deleteErr }}
|
||||||
|
<h2>Deletion Error</h2>
|
||||||
|
<span class="adapter-error">There was an error deleting the page: {{ ($deleteErr).Error }}</span>
|
||||||
|
{{ else }}
|
||||||
|
<h2>Page Deleted</h2>
|
||||||
|
<span class="adapter-success">Page at '{{ $slug }}' was deleted</span>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ template "footer" . }}
|
|
@ -5,14 +5,17 @@
|
||||||
<meta charset='utf-8'>
|
<meta charset='utf-8'>
|
||||||
<meta name='description' content='Nirvash CMS'/>
|
<meta name='description' content='Nirvash CMS'/>
|
||||||
<meta name='viewport' content='width=device-width,initial-scale=1'>
|
<meta name='viewport' content='width=device-width,initial-scale=1'>
|
||||||
|
<link rel='stylesheet' type='text/css' href='/static/style.css'>
|
||||||
<title>Nirvash — CMS</title>
|
<title>Nirvash — CMS</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<header><h1>Nirvash CMS</h1></header>
|
||||||
<nav>
|
<nav>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/">Pages</a></li>
|
<li><a href="/">Pages</a></li>
|
||||||
<li><a href="/static-mgr">Static Files</a></li>
|
<li><a href="/static-mgr">Static Files</a></li>
|
||||||
<li><a href="/build">Build</a></li>
|
<li><a href="/build">Build</a></li>
|
||||||
|
<li><a href="/config">Configuration</a></li>
|
||||||
<li><a href="/logout">Logout</a></li>
|
<li><a href="/logout">Logout</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
Loading…
Reference in a new issue