added build/delete, started styling

This commit is contained in:
Iris Lightshard 2022-06-07 00:59:41 -06:00
parent e745665135
commit 8fdc9ddb46
Signed by: Iris Lightshard
GPG key ID: 3B7FBC22144E6398
14 changed files with 327 additions and 17 deletions

View file

@ -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
} }

View file

@ -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),
}
} }

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View file

@ -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 {
@ -71,4 +78,160 @@ body {
color: lightgray; color: lightgray;
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
View 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
View 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" . }}

View file

@ -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 }}

View file

@ -5,7 +5,9 @@
{{ $noEmpty := .FormValue "no-empty" }} {{ $noEmpty := .FormValue "no-empty" }}
{{ template "header" . }} {{ template "header" . }}
<a href="/">&laquo;</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>
{{ template "footer" . }} <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" . }}

View file

@ -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>

View file

@ -3,18 +3,23 @@
{{ $noEmpty := .FormValue "no-empty" }} {{ $noEmpty := .FormValue "no-empty" }}
{{ template "header" . }} {{ template "header" . }}
<a href="/">&laquo;</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>
{{ template "footer" . }} {{ template "footer" . }}

View file

@ -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
View 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" . }}

View file

@ -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 &mdash; CMS</title> <title>Nirvash &mdash; 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>