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
|
||||
|
||||
type BuildStatus struct {
|
||||
Success bool
|
||||
Message string
|
||||
}
|
||||
|
||||
type Adapter interface {
|
||||
Init(cfg *Config)
|
||||
Name() string
|
||||
|
@ -14,5 +19,5 @@ type Adapter interface {
|
|||
CreatePage(slug, title, content string) error
|
||||
SavePage(oldSlug, newSlug, title, content 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"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
@ -141,7 +142,20 @@ func (self *EurekaAdapter) DeletePage(slug string) error {
|
|||
return os.Remove(filepath.Join(self.Root, "inc", slug))
|
||||
}
|
||||
|
||||
func (self *EurekaAdapter) Build(buildOptions map[string]string) (bool, string) {
|
||||
// TODO: shell out to build.sh with buildOptions, record exit status and output
|
||||
return true, "Build successful"
|
||||
func (self *EurekaAdapter) Build(buildOptions map[string][]string) BuildStatus {
|
||||
|
||||
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,
|
||||
"/"))
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
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;
|
||||
margin: 0;
|
||||
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 {
|
||||
background: url('/static/bg.png');
|
||||
background-size: cover;
|
||||
background-position: center center;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.login {
|
||||
|
@ -72,3 +79,159 @@ body {
|
|||
border-bottom: 2px solid crimson;
|
||||
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" . }}
|
||||
|
||||
{{ if $createErr }}
|
||||
<h2>Page Creation Error</h2>
|
||||
<span class="adapter-error">There was an error creating the page: {{ ($createErr).Error }}</span>
|
||||
{{ else }}
|
||||
<h2>Page Created</h2>
|
||||
<span class="adapter-success">Page '{{ $title }}' created successfully</span>
|
||||
{{ end }}
|
||||
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
{{ $noEmpty := .FormValue "no-empty" }}
|
||||
|
||||
{{ template "header" . }}
|
||||
<a href="/">«</a>
|
||||
|
||||
<h2>Edit Page</h2>
|
||||
|
||||
<form class="editor" method="POST" action="/save/{{$slug}}">
|
||||
{{ if $noEmpty }}
|
||||
<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="oldSlug" value="{{$slug}}"/>
|
||||
{{ 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 }}
|
||||
<input class="title-input" type="text" name="title" value="{{($page).Title}}"/><br/>
|
||||
<span class="edited-time">last edited {{($page).Edited}}</span><br/>
|
||||
<textarea class="content-input" name="content">{{($page).Content}}</textarea><br/>
|
||||
<label for="title">Title</label><br/>
|
||||
<input class="title-input" id="title" type="text" name="title" value="{{($page).Title}}" required/><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"/>
|
||||
</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" . }}
|
|
@ -1,8 +1,11 @@
|
|||
{{ $pages := ((.Context).Value "adapter").ListPages }}
|
||||
|
||||
{{ template "header" .}}
|
||||
|
||||
<h2>Pages</h2>
|
||||
|
||||
<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>
|
||||
{{ range $slug, $title := $pages }}
|
||||
<li><a href="/edit/{{$slug}}">{{$title}}</a></li>
|
||||
|
|
|
@ -3,17 +3,22 @@
|
|||
{{ $noEmpty := .FormValue "no-empty" }}
|
||||
|
||||
{{ template "header" . }}
|
||||
<a href="/">«</a>
|
||||
|
||||
<h2>New Page</h2>
|
||||
|
||||
<form class="editor" method="POST" action="/create">
|
||||
{{ if $noEmpty }}
|
||||
<span class="edit-error">Empty fields are not allowed - please try again</span><br/>
|
||||
{{ end }}
|
||||
<input hidden name="csrfToken" value="{{$csrfToken}}"/>
|
||||
{{ 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 }}
|
||||
<input class="title-input" type="text" name="title"/><br/>
|
||||
<textarea class="content-input" name="content"></textarea><br/>
|
||||
<label for="title">Title</label><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"/>
|
||||
</form>
|
||||
|
||||
|
|
|
@ -7,8 +7,10 @@
|
|||
{{ template "header" . }}
|
||||
|
||||
{{ if $saveErr }}
|
||||
<h2>Page Save Error</h2>
|
||||
<span class="adapter-error">There was an error saving the page: {{ ($saveErr).Error }}</span>
|
||||
{{ else }}
|
||||
<h2>Page Saved</h2>
|
||||
<span class="adapter-success">Page '{{ $title }}' saved successfully</span>
|
||||
{{ 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 name='description' content='Nirvash CMS'/>
|
||||
<meta name='viewport' content='width=device-width,initial-scale=1'>
|
||||
<link rel='stylesheet' type='text/css' href='/static/style.css'>
|
||||
<title>Nirvash — CMS</title>
|
||||
</head>
|
||||
<body>
|
||||
<header><h1>Nirvash CMS</h1></header>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">Pages</a></li>
|
||||
<li><a href="/static-mgr">Static Files</a></li>
|
||||
<li><a href="/build">Build</a></li>
|
||||
<li><a href="/config">Configuration</a></li>
|
||||
<li><a href="/logout">Logout</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
|
Loading…
Reference in a new issue