go1.16 and embed

This commit is contained in:
ston1th 2021-02-18 22:36:16 +01:00
commit 82fd34b288
42 changed files with 95 additions and 1236 deletions

View file

@ -22,8 +22,5 @@ const (
UsersURI = "/users"
UserURI = "/user"
ViewURI = "/view"
BootstrapCSS = "/bootstrap.css"
CustomCSS = "/custom.css"
Favicon = "/favicon.ico"
StaticURI = "/static"
)

View file

@ -1,3 +1,5 @@
// Copyright (C) 2020 Marius Schellenberger
package core
// Contains checks if slice a contains string str

View file

@ -28,6 +28,7 @@ var reservedURIs = []string{
core.UsersURI,
core.UserURI,
core.ViewURI,
core.StaticURI,
}
var reservedNames []string

View file

@ -54,6 +54,9 @@ func (db *DB) SectionExists(section string) error {
func (db *DB) CreateSection(section string, members []string, user bool) error {
section = userRe.ReplaceAllString(section, "")
if _, ok := core.Contains(section, reservedNames); ok {
return fmt.Errorf("%s: %s", errNameReserved, section)
}
if err := db.SectionExists(section); err == nil {
return errSectionExists
}

View file

@ -103,24 +103,6 @@ func authHandler(h ctxHandler) ctxHandler {
}
}
func staticHandler(ctx *Context) {
var err error
ctx.SetHeader("Content-Type", "text/css; charset=utf-8")
ctx.SetHeader("Expires", time.Now().UTC().Add(max).Format(http.TimeFormat))
switch ctx.Path() {
case core.BootstrapCSS:
err = ctx.Write(ctx.Srv.res["bootstrap.css"])
case core.CustomCSS:
err = ctx.Write(ctx.Srv.res["custom.css"])
case core.Favicon:
ctx.SetHeader("Content-Type", "image/x-icon")
err = ctx.Write(ctx.Srv.res["favicon.ico"])
}
if err != nil {
log.Println("static:", err)
}
}
func indexHandler(ctx *Context) {
ctx.Redirect(core.IndexURI, http.StatusFound)
}

View file

@ -10,24 +10,6 @@ type route struct {
Methods []string
}
var static = []route{
{
core.Favicon,
staticHandler,
[]string{"GET"},
},
{
core.BootstrapCSS,
staticHandler,
[]string{"GET"},
},
{
core.CustomCSS,
staticHandler,
[]string{"GET"},
},
}
var routes = []route{
{
core.RootURI,

View file

@ -5,6 +5,7 @@ package server
import (
"bytes"
"context"
"embed"
"errors"
"git.giftfish.de/ston1th/godrop/v2"
"git.giftfish.de/ston1th/gowiki/pkg/core"
@ -78,17 +79,19 @@ func (s *HTTPServer) Stop() error {
return s.srv.Shutdown(ctx)
}
//go:embed static/*
var static embed.FS
func (s *HTTPServer) buildRoutes() http.Handler {
m := mux.NewRouter()
m.NotFoundHandler = &notFoundHandler{s}
for _, v := range static {
m.HandleFunc(v.Path, s.contextWrapper(v.Handler)).Methods(v.Methods...)
}
m.Handle("/static/{file}", http.StripPrefix("/", http.FileServer(http.FS(static))))
for _, v := range routes {
m.HandleFunc(v.Path, s.contextWrapper(v.Handler)).Methods(v.Methods...)
}
return m
}
func (s *HTTPServer) contextWrapper(h ctxHandler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
h(newContext(w, r, s))

12
pkg/server/static/bootstrap.css vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,150 @@
@media (min-width: 768px) {
.sm-pull-right {
float: right !important;
}
.sm-pull-left {
float: left !important;
}
}
@media (min-width: 992px) {
.md-pull-right {
float: right !important;
}
.md-pull-left {
float: left !important;
}
}
@media (min-width: 1200px) {
.lg-pull-right {
float: right !important;
}
.lg-pull-left {
float: left !important;
}
}
body {
padding-top: 60px;
}
.msg {
margin-top: 30px;
}
a.dark {
color: #222222;
}
a.para {
text-decoration: none;
font-size: 18px;
}
nav {
padding-top: 20px;
}
*[id]:before {
display: block;
content: " ";
margin-top: -75px;
height: 75px;
visibility: hidden;
}
.alert-primary {
background-color: #375a7f;
border-color: #375a7f;
color: #ffffff;
}
h1,h2,h3,h4,h5,h6 {
font-weight: bold;
-webkit-background-clip: content-box !important;
background-clip: content-box !important;
}
h1 {
font-size: 2em;
}
h2 {
font-size: 1.75em;
}
h3 {
font-size: 1.5em;
}
h4 {
font-size: 1.25em;
}
h5,h6 {
font-size: 1em;
}
.md-text {
font-family: 'Courier New';
font-size: 14;
}
pre {
display: block;
padding: 10px;
margin: 0 0 10.5px;
font-size: 14px;
line-height: 1.42857143;
word-break: break-all;
word-wrap: break-word;
color: #0cdba6;
background-color: #444444;
border: 1px solid #444444;
border-radius: 2px;
}
pre.wrap {
white-space: pre-wrap;
}
code {
padding: 3px 3px 1px 0px;
font-size: 90%;
color: #0cdba6;
background-color: #444444;
border-radius: 2px;
}
textarea,input,select {
color: #e2e2e2 !important;
background-color: #444444 !important;
}
select.sections {
height: 120px !important;
}
input.menu {
color: #e2e2e2 !important;
background-color: #222222 !important;
}
.menu-search-bar {
padding-top: 4px;
}
.menu-search-input {
background-color: #2f2f2f !important;
}
html,body,.container,.content {
height: 100% !important;
}
.container,.content {
position: relative;
}
.proper-content {
padding-top: 40px;
}
.section {
margin-bottom: 20px;
width: 250px;
}
.wrapper {
min-height: 100%;
height: auto !important;
height: 100%;
margin: 0 auto -60px;
}
.push {
height: 60px;
}
.full-width {
width: 100%;
}
.relative {
position: relative;
}
.btn-breadcrumb {
position: absolute;
right: 1rem;
top: 38%;
transform: translateY(-50%);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,17 @@
{{define "body"}}
<div class="page-header">
<div class="row">
<div class="col-xs-4 offset-4">
<div class="card border-danger mx-auto">
<div class="card-header">
<strong>403 - Forbidden</strong>
</div>
<div class="card-body">
<h5>Access to this page is restricted</h5>
<a href="/" class="btn btn-sm btn-primary">Back</a>
</div>
</div>
</div>
</div>
</div>
{{end}}

View file

@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>GoWiki | {{.Title}}</title>
<link rel="shortcut icon" type="image/x-icon" href="/static/favicon.ico" integrity="sha256-bgotk/IPq4Yvt6s8AQGPTM5ZjPjQ6rrBRa2EyxpnFSc="></link>
<link rel="stylesheet" type="text/css" href="/static/bootstrap.css" media="screen" integrity="sha256-3YeveuySvBgp2GItUS8eV1R/9T1zcPMyvw22SyVf8qo="></link>
<link rel="stylesheet" type="text/css" href="/static/custom.css" media="screen" integrity="sha256-IwnADqysqqVpTpQ4sbG3Bz7v8FM4lqMiozQPozsFBUQ="></link>
<meta name="theme-color" content="#375a7f">
<meta name="description" content="{{.Title}}">
</head>
<body id="top">
<div class="navbar navbar-expand fixed-top navbar-dark bg-primary">
<div class="container">
<a href="/" class="navbar-brand">GoWiki</a>
<div class="collapse navbar-collapse">
{{template "menu" .}}
</div>
</div>
</div>
<div class="container">
<div class="proper-content">
{{if .Msg}}
<div class="alert alert-danger msg">
<h4>Error!</h4>
<p>{{.Msg}}</p>
</div>
{{end}}
{{template "body" .}}
</div>
<div class="push"></div>
<footer>
<div class="row">
<div class="col">
<ul class="list-unstyled">
<li class="float-md-right"><a href="#top">Back to top</a></li>
{{if .Login}}
<li><a href="/blacklist">Blacklist</a></li>
{{end}}
</ul>
<p>© GoWiki {{.Version}} Request: <strong>{{.Time}}</strong></p>
</div>
</div>
</footer>
</div>
</body>
</html>

View file

@ -0,0 +1,34 @@
{{define "body"}}
<div class="page-header">
<div class="row">
<div class="col-xs-4 offset-4">
<div class="card border-primary mx-auto">
<div class="card-header">
<strong>{{.BodyTitle}}</strong>
</div>
<div class="card-body">
<form class="form-horizontal" action="/login" method="post">
<input type="hidden" name="token" value="{{.Token}}">
<input type="hidden" name="referer" value="{{.Data.Referer}}">
<div class="form-group">
<label class="col-form-label" for="user">Username</label>
<input class="form-control input-sm" type="text" id="user" name="user" value="{{.Data.User}}" autofocus required>
</div>
<div class="form-group">
<label class="col-form-label" for="password">Password</label>
<input class="form-control input-sm" type="password" id="password" name="password" required>
</div>
<div class="form-group">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="remember" name="remember">
<label class="custom-control-label" for="remember">Remember Me</label>
</div>
</div>
<button class="btn btn-sm btn-primary" type="submit">Login</button>
</form>
</div>
</div>
</div>
</div>
</div>
{{end}}

View file

@ -0,0 +1,24 @@
{{define "body"}}
<div class="page-header">
<div class="row">
<div class="col-xs-4 offset-4">
<div class="card border-primary mx-auto">
<div class="card-header">
<strong>{{.BodyTitle}}</strong>
</div>
<div class="card-body">
<form class="form-horizontal" action="/totp" method="post">
<input type="hidden" name="token" value="{{.Token}}">
<div class="form-group">
<label class="col-form-label" for="pin">PIN</label>
<input class="form-control input-sm" type="text" id="pin" name="pin" autocomplete="off" autofocus required>
</div>
<button class="btn btn-sm btn-primary" type="submit">Verify</button>
<a href="/logout" class="btn btn-sm btn-primary">Cancel</a>
</form>
</div>
</div>
</div>
</div>
</div>
{{end}}

View file

@ -0,0 +1,27 @@
{{define "menu"}}
<ul class="navbar-nav">
<li class="nav-item"><a class="nav-link" href="/wiki/Index">Index</a></li>
<li class="nav-item"><a class="nav-link" href="/sections">Sections</a></li>
{{if .Login}}
<li class="nav-item"><a class="nav-link" href="/new">New Page</a></li>
{{end}}
</ul>
<form class="form-inline" method="post" action="/search">
<input type="hidden" name="token" value="{{.Token}}">
<input type="text" class="form-control mr-sm-2 menu" placeholder="Search" name="search" value="{{.Search}}" autocomplete="off">
<button class="btn btn-sm btn-info" type="submit">Search</button>
</form>
<ul class="nav navbar-nav ml-auto">
{{if .Login}}
{{if .Admin}}
<li class="nav-item"><a class="nav-link" href="/users">Users</a></li>
<li class="nav-item"><a class="nav-link" href="/stats">Stats</a></li>
{{else}}
<li class="nav-item"><a class="nav-link" href="/user/edit/{{.User}}">Profile</a></li>
{{end}}
<li class="nav-item"><a class="nav-link" href="/logout">Logout</a></li>
{{else}}
<li class="nav-item"><a class="nav-link" href="/login">Login</a></li>
{{end}}
</ul>
{{end}}

View file

@ -0,0 +1,17 @@
{{define "body"}}
<div class="page-header">
<div class="row">
<div class="col-xs-4 offset-4">
<div class="card border-danger mx-auto">
<div class="card-header">
<strong>404 - Page Not Found</strong>
</div>
<div class="card-body">
<h5>The requested page could not be found</h5>
<a href="/" class="btn btn-sm btn-primary">Back</a>
</div>
</div>
</div>
</div>
</div>
{{end}}

View file

@ -0,0 +1,40 @@
{{define "body"}}
<div class="row">
<div class="nav-container full-width relative">
<ol class="breadcrumb full-width">
<li class="breadcrumb-item"></li>
<li class="breadcrumb-item"><a href="/{{.BodyTitle}}">{{.BodyTitle}}</a></li>
<li class="breadcrumb-item active">{{.Data.Title}}</li>
</ol>
<div class="btn-group btn-breadcrumb">
<a href="/{{.Data.StoreTitle}}/md" class="btn btn-sm btn-primary">Markdown</a>
{{if .Login}}
<a href="/{{.Data.StoreTitle}}/edit" class="btn btn-sm btn-primary">Edit</a>
{{if eq .Data.Perm 2}}
<a href="/{{.Data.StoreTitle}}/share" class="btn btn-sm btn-primary">Share</a>
{{end}}
{{if ne .Data.StoreTitle "wiki/Index"}}
<a href="/{{.Data.StoreTitle}}/del" class="btn btn-sm btn-danger">Delete</a>
{{end}}
{{end}}
</div>
</div>
</div>
<div class="row mt-3">
<div class="col">
<strong>Created by:</strong> {{.Data.Created}}<br>
{{if .Data.Updated}}
<strong>Updated by:</strong> {{.Data.Updated}}<br>
{{end}}
<strong>Visibility:</strong>
{{if eq .Data.Perm 1}}
<span class="badge badge-primary">Public</span>
{{end}}
{{if eq .Data.Perm 2}}
<span class="badge badge-danger">Private</span>
{{end}}
<br>
</div>
</div>
{{.Data.HTML}}
{{end}}

View file

@ -0,0 +1,23 @@
{{define "body"}}
<div class="page-header">
<div class="row">
<div class="col-xs-4 offset-4">
<div class="card border-danger mx-auto">
<div class="card-header">
<strong>{{.BodyTitle}}</strong>
</div>
<div class="card-body">
<form class="form-horizontal" action="/blacklist" method="post">
<input type="hidden" name="token" value="{{.Token}}">
<div class="form-group">
<label class="col-form-label" for="token">Token</label>
<input class="form-control input-sm" type="text" id="token" name="share" autocomplete="off" autofocus required>
</div>
<button class="btn btn-sm btn-danger" type="submit">Blacklist</button>
</form>
</div>
</div>
</div>
</div>
</div>
{{end}}

View file

@ -0,0 +1,20 @@
{{define "body"}}
<div class="page-header">
<div class="row">
<div class="col-xs-4 offset-4">
<div class="card border-danger mx-auto">
<div class="card-header">
<strong>Delete {{.Data}}</strong>
</div>
<div class="card-body">
<form class="form-horizontal" action="/{{.Data}}/del" method="post">
<input type="hidden" name="token" value="{{.Token}}">
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
<a href="/{{.Data}}" class="btn btn-sm btn-primary">Back</a>
</form>
</div>
</div>
</div>
</div>
</div>
{{end}}

View file

@ -0,0 +1,30 @@
{{define "body"}}
<div class="page-header">
<div class="row">
<h2>{{.BodyTitle}}</h2>
</div>
</div>
<form class="form-horizontal" action="/{{.Data.StoreTitle}}/edit" method="post">
<input type="hidden" name="token" value="{{.Token}}">
<div class="form-group">
<label class="col-form-label" for="title">Title</label>
<input class="form-control input-sm" type="text" id="title" name="title" value="{{.Data.Title}}" spellcheck="false" disabled>
</div>
<div class="form-group">
<label class="col-form-label" for="markdown">Markdown</label>
<textarea class="form-control input-sm md-text" id="markdown" name="markdown" rows="30" spellcheck="false">{{.Data.Markdown}}</textarea>
</div>
<div class="form-group">
<div class="custom-control custom-radio">
<input type="radio" id="perm1" name="perm" value="1" class="custom-control-input" {{if eq .Data.Perm 1}}checked=""{{end}}>
<label class="custom-control-label" for="perm1">Public</label>
</div>
<div class="custom-control custom-radio">
<input type="radio" id="perm2" name="perm" value="2" class="custom-control-input" {{if eq .Data.Perm 2}}checked=""{{end}}>
<label class="custom-control-label" for="perm2">Private</label>
</div>
</div>
<button class="btn btn-sm btn-primary" type="submit">Update</button>
<a href="/{{.Data.StoreTitle}}" class="btn btn-sm btn-primary">Back</a>
</form>
{{end}}

View file

@ -0,0 +1,14 @@
{{define "body"}}
<div class="row">
<div class="nav-container full-width relative">
<ol class="breadcrumb full-width">
<li class="breadcrumb-item"></li>
<li class="breadcrumb-item"><a href="/{{.BodyTitle}}">{{.BodyTitle}}</a></li>
<li class="breadcrumb-item active"><a href="/{{if .Key}}view/{{.Key}}{{else}}{{.Data.StoreTitle}}{{end}}">{{.Data.Title}}</a></li>
</ol>
</div>
</div>
<div class="row mt-3">
<textarea class="form-control input-sm md-text" rows="30" spellcheck="false" autofocus>{{.Data.Markdown}}</textarea>
</div>
{{end}}

View file

@ -0,0 +1,41 @@
{{define "body"}}
<div class="page-header">
<div class="row">
<h2>{{.BodyTitle}}</h2>
</div>
</div>
<form class="form-horizontal" action="/new" method="post">
<input type="hidden" name="token" value="{{.Token}}">
<div class="form-group">
<label class="col-form-label" for="section">Section</label>
<select class="form-control input-sm" id="section" name="section" required>
{{range $section, $selected := .Data.Sections}}
{{if $selected}}
<option value="{{$section}}" selected>{{$section}}</option>
{{else}}
<option value="{{$section}}">{{$section}}</option>
{{end}}
{{end}}
</select>
</div>
<div class="form-group">
<label class="col-form-label" for="title">Title</label>
<input class="form-control input-sm" type="text" id="title" name="title" placeholder="Title" value="{{.Data.Page.Title}}" spellcheck="false" autocomplete="off" autofocus required>
</div>
<div class="form-group">
<label class="col-form-label" for="markdown">Markdown</label>
<textarea class="form-control input-sm md-text" id="markdown" name="markdown" rows="30" spellcheck="false">{{.Data.Page.Markdown}}</textarea>
</div>
<div class="form-group">
<div class="custom-control custom-radio">
<input type="radio" id="perm1" name="perm" value="1" class="custom-control-input" {{if eq .Data.Page.Perm 1}}checked=""{{end}}>
<label class="custom-control-label" for="perm1">Public</label>
</div>
<div class="custom-control custom-radio">
<input type="radio" id="perm2" name="perm" value="2" class="custom-control-input" {{if eq .Data.Page.Perm 2}}checked=""{{end}}>
<label class="custom-control-label" for="perm2">Private</label>
</div>
</div>
<button class="btn btn-sm btn-primary" type="submit">Create</button>
</form>
{{end}}

View file

@ -0,0 +1,43 @@
{{define "body"}}
<div class="page-header">
<div class="row">
<div class="col-xs-4 offset-4">
<div class="card border-primary mx-auto">
<div class="card-header">
<strong>Share {{.Data}}</strong>
</div>
<div class="card-body">
{{if .Key}}
<div class="form-group">
<label class="col-form-label" for="key">Key</label>
<input class="form-control input-sm" type="text" id="key" value="{{.Key}}" disabled>
</div>
<a href="/view/{{.Key}}" class="btn btn-sm btn-danger">Share Link</a>
<a href="/{{.Data}}/share" class="btn btn-sm btn-primary">Back</a>
{{else}}
<form class="form-horizontal" action="/{{.Data}}/share" method="post">
<input type="hidden" name="token" value="{{.Token}}">
<div class="form-group">
<label class="col-form-label" for="duration">Lifetime</label>
<input class="form-control input-sm" type="text" id="duration" name="duration" autocomplete="off" autofocus required>
</div>
<div class="form-group">
<div class="custom-control custom-radio">
<input type="radio" id="mode1" name="mode" value="1" class="custom-control-input" checked="">
<label class="custom-control-label" for="mode1">Parse (15m, 10h)</label>
</div>
<div class="custom-control custom-radio">
<input type="radio" id="mode2" name="mode" value="2" class="custom-control-input">
<label class="custom-control-label" for="mode2">Day(s) (Numbers only)</label>
</div>
</div>
<button class="btn btn-sm btn-primary" type="submit">Share</button>
<a href="/{{.Data}}" class="btn btn-sm btn-primary">Back</a>
</form>
{{end}}
</div>
</div>
</div>
</div>
</div>
{{end}}

View file

@ -0,0 +1,31 @@
{{define "body"}}
<div class="row">
<div class="nav-container full-width relative">
<ol class="breadcrumb full-width">
<li class="breadcrumb-item"></li>
<li class="breadcrumb-item"><a href="/{{.BodyTitle}}">{{.BodyTitle}}</a></li>
<li class="breadcrumb-item active">{{.Data.Title}}</li>
</ol>
<div class="btn-group btn-breadcrumb">
<a href="/view/{{.Key}}/md" class="btn btn-sm btn-primary">Markdown</a>
</div>
</div>
</div>
<div class="row mt-3">
<div class="col">
<strong>Created by:</strong> {{.Data.Created}}<br>
{{if .Data.Updated}}
<strong>Updated by:</strong> {{.Data.Updated}}<br>
{{end}}
<strong>Visibility:</strong>
{{if eq .Data.Perm 1}}
<span class="badge badge-primary">Public</span>
{{end}}
{{if eq .Data.Perm 2}}
<span class="badge badge-danger">Private</span>
{{end}}
<br>
</div>
</div>
{{.Data.HTML}}
{{end}}

View file

@ -0,0 +1,30 @@
{{define "body"}}
<div class="page-header">
<div class="row">
<h2>{{.BodyTitle}}</h2>
</div>
</div>
<div class="row mt-3">
<div class="col-xs-6">
<form class="form-horizontal" action="/search" method="post">
<input type="hidden" name="token" value="{{.Token}}">
<div class="form-group">
<div class="input-group">
<input type="text" class="form-control input-sm" placeholder="Search" name="search" value="{{.Search}}" autocomplete="off" autofocus>
<div class="input-group-append">
<button class="btn btn-sm btn-primary" type="submit">Search</button>
</div>
</div>
</div>
</form>
</div>
</div>
<div class="row mt-3">
<div class="col">
{{range $item := .Data}}
<a href="/{{$item.StoreTitle}}"><b>{{$item.Section}} / {{$item.Title}}</b></a>
<pre class="wrap">{{$item.HTML}}</pre><br>
{{end}}
</div>
</div>
{{end}}

View file

@ -0,0 +1,39 @@
{{define "body"}}
<!--<div class="page-header">
<div class="row">
<div class="col">
<h2>{{.BodyTitle}}</h2>
</div>
<div class="col">
{{if .Admin}}
<div class="btn-group float-right">
<a class="btn btn-sm btn-primary" href="/section/edit/{{.BodyTitle}}">Edit</a>
<a class="btn btn-sm btn-danger" href="/section/del/{{.BodyTitle}}">Delete</a>
</div>
{{end}}
</div>
</div>-->
<div class="row">
<div class="nav-container full-width relative">
<ol class="breadcrumb full-width">
<li class="breadcrumb-item"></li>
<li class="breadcrumb-item">{{.BodyTitle}}</li>
</ol>
{{if .Admin}}
<div class="btn-group btn-breadcrumb">
<a class="btn btn-sm btn-primary" href="/section/edit/{{.BodyTitle}}">Edit</a>
<a class="btn btn-sm btn-danger" href="/section/del/{{.BodyTitle}}">Delete</a>
</div>
{{end}}
</div>
</div>
{{if .Data}}
<div class="row">
<ul type="circle">
{{range $item := .Data}}
<li><a href="/{{$item.StoreTitle}}"><b>{{$item.Title}}</b></a></li>
{{end}}
</ul>
</div>
{{end}}
{{end}}

View file

@ -0,0 +1,23 @@
{{define "body"}}
{{if .Data}}
<div class="page-header">
<div class="row">
<div class="col-xs-4 offset-4">
<div class="card border-danger mx-auto">
<div class="card-header">
<strong>Delete {{.Data}}?</strong>
</div>
<div class="card-body">
<p>You are about to delete section <a href="/{{.Data}}">{{.Data}}</a>.<br>This will also remove all pages of this section.</p>
<form class="form-horizontal" action="/section/del/{{.Data}}" method="post">
<input type="hidden" name="token" value="{{.Token}}">
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
<a href="/{{.Data}}" class="btn btn-sm btn-primary">Back</a>
</form>
</div>
</div>
</div>
</div>
</div>
{{end}}
{{end}}

View file

@ -0,0 +1,34 @@
{{define "body"}}
<div class="row">
<div class="col-xs-4 offset-4">
<div class="card border-primary mx-auto">
<div class="card-header">
<strong>{{.BodyTitle}}</strong>
</div>
<div class="card-body">
<form class="form-horizontal" action="/section/edit/{{.Data.Name}}" method="post">
<input type="hidden" name="token" value="{{.Token}}">
<div class="form-group">
<label class="col-form-label" for="section">Section</label>
<input class="form-control input-sm" type="text" id="section" name="section" value="{{.Data.Name}}" disabled>
</div>
<div class="form-group">
<label class="col-form-label" for="members">Members</label>
<select class="form-control input-sm sections" id="members" name="members" multiple required>
{{range $user, $member := .Data.Members}}
{{if $member}}
<option value="{{$user}}" selected>{{$user}}</option>
{{else}}
<option value="{{$user}}">{{$user}}</option>
{{end}}
{{end}}
</select>
</div>
<button class="btn btn-sm btn-primary" type="submit">Update</button>
<a href="/{{.Data.Name}}" class="btn btn-sm btn-primary">Back</a>
</form>
</div>
</div>
</div>
</div>
{{end}}

View file

@ -0,0 +1,34 @@
{{define "body"}}
<div class="row">
<div class="col-xs-4 offset-4">
<div class="card border-primary mx-auto">
<div class="card-header">
<strong>{{.BodyTitle}}</strong>
</div>
<div class="card-body">
<form class="form-horizontal" action="/section/new" method="post">
<input type="hidden" name="token" value="{{.Token}}">
<div class="form-group">
<label class="col-form-label" for="section">Section</label>
<input class="form-control input-sm" type="text" id="section" name="section" autocomplete="off" value="{{.Data.Name}}" autofocus required>
</div>
<div class="form-group">
<label class="col-form-label" for="members">Members</label>
<select class="form-control input-sm sections" id="members" name="members" multiple required>
{{range $user, $member := .Data.Members}}
{{if $member}}
<option value="{{$user}}" selected>{{$user}}</option>
{{else}}
<option value="{{$user}}">{{$user}}</option>
{{end}}
{{end}}
</select>
</div>
<button class="btn btn-sm btn-primary" type="submit">Create</button>
<a href="/sections" class="btn btn-sm btn-primary">Back</a>
</form>
</div>
</div>
</div>
</div>
{{end}}

View file

@ -0,0 +1,37 @@
{{define "body"}}
<div class="page-header">
<div class="row">
<h2>{{.BodyTitle}}</h2>
</div>
</div>
{{if .Admin}}
<div class="row mb-3">
<div class="col-xs-5">
<a class="btn btn-sm btn-primary" href="/section/new"><b>New Section</b></a>
</div>
</div>
{{end}}
{{if .Data}}
<div class="row">
{{range $index, $item := .Data}}
<div class="col-xs-4 section">
<div class="card border-primary mx-auto">
<div class="card-header">
<strong><a href="/{{$item.Section}}">{{$item.Section}}</a></strong>
</div>
<div class="card-body">
{{if $item.Pages}}
{{range $page := $item.Pages}}
<strong><a href="/{{$page.StoreTitle}}">{{$page.Title}}</a></strong><br>
{{end}}
{{else}}
<strong>Empty section</strong>
{{end}}
</div>
</div>
</div>
<div class="offset-1"></div>
{{end}}
</div>
{{end}}
{{end}}

View file

@ -0,0 +1,33 @@
{{define "body"}}
<div class="page-header">
<div class="row">
<h2>{{.BodyTitle}}</h2>
</div>
</div>
<div class="row">
<table class="table table-stripped table-hover">
<tbody>
<tr>
<td><b>Goroutines</b></td>
<td>{{.Data.Goroutines}}</td>
</tr>
<tr>
<td><b>Memory usage</b></td>
<td>{{.Data.Alloc}} MiB</td>
</tr>
<tr>
<td><b>Memory allocated</b></td>
<td>{{.Data.Sys}} MiB</td>
</tr>
<tr>
<td><b>Documents</b></td>
<td>{{.Data.Docs}}</td>
</tr>
<tr>
<td><b>Go Version</b></td>
<td>{{.Data.GoVersion}}</td>
</tr>
</tbody>
</table>
</div>
{{end}}

View file

@ -0,0 +1,23 @@
{{define "body"}}
{{if .Data}}
<div class="page-header">
<div class="row">
<div class="col-xs-4 offset-4">
<div class="card border-danger mx-auto">
<div class="card-header">
<strong>Delete {{.Data.Username}}?</strong>
</div>
<div class="card-body">
<p>You are about to delete user <a href="/{{.Data.Username}}">{{.Data.Username}}</a>.<br>This will also remove all pages of this user.</p>
<form class="form-horizontal" action="/user/del/{{.Data.Username}}" method="post">
<input type="hidden" name="token" value="{{.Token}}">
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
<a href="/users" class="btn btn-sm btn-primary">Back</a>
</form>
</div>
</div>
</div>
</div>
</div>
{{end}}
{{end}}

View file

@ -0,0 +1,65 @@
{{define "body"}}
{{if .Data}}
<div class="row">
<div class="col-xs-4 offset-4">
<div class="card border-primary mx-auto">
<div class="card-header">
<strong>{{.BodyTitle}}</strong>
{{if .Data.Secret}}
<a href="/user/totp/{{.Data.Username}}" class="btn btn-sm btn-danger float-right">Disable TOTP</a>
{{else}}
<a href="/user/totp/{{.Data.Username}}" class="btn btn-sm btn-success float-right">Enable TOTP</a>
{{end}}
</div>
<div class="card-body">
<form class="form-horizontal" action="/user/edit/{{.Data.Username}}" method="post">
<input type="hidden" name="token" value="{{.Token}}">
<div class="form-group">
<label class="col-form-label" for="user">Username</label>
<input class="form-control input-sm" type="text" id="user" name="user" value="{{.Data.Username}}" disabled>
</div>
<div class="form-group">
<label class="col-form-label" for="password">Password</label>
<input class="form-control input-sm" type="password" id="password" name="password" autofocus>
</div>
<div class="form-group">
<label class="col-form-label" for="repeat">Repeat</label>
<input class="form-control input-sm" type="password" id="repeat" name="repeat">
</div>
{{if .Admin}}
<div class="form-group">
<div class="custom-control custom-checkbox">
{{if .Data.Admin}}
<input type="checkbox" class="custom-control-input" id="admin" name="admin" value="0" checked>
{{else}}
<input type="checkbox" class="custom-control-input" id="admin" name="admin" value="0">
{{end}}
<label class="custom-control-label" for="admin">Admin Privileges</label>
</div>
</div>
{{end}}
<button class="btn btn-sm btn-primary" type="submit">Update</button>
{{if .Admin}}
<a href="/users" class="btn btn-sm btn-primary">Back</a>
{{else}}
<a href="/" class="btn btn-sm btn-primary">Back</a>
{{end}}
<a class="btn btn-sm btn-danger" href="/user/del/{{.Data.Username}}">Delete</a>
</form>
{{if .Admin}}
{{if eq .Data.Locked 3}}
<form class="form-horizontal" action="/user/unlock/{{.Data.Username}}" method="post">
<input type="hidden" name="token" value="{{.Token}}">
<div class="form-group">
<label class="col-form-label" for="unlock">Locked</label>
<button class="btn btn-sm btn-warning" type="submit" id="unlock">Unlock</button>
</div>
</form>
{{end}}
{{end}}
</div>
</div>
</div>
</div>
{{end}}
{{end}}

View file

@ -0,0 +1,36 @@
{{define "body"}}
<div class="row">
<div class="col-xs-4 offset-4">
<div class="card border-primary mx-auto">
<div class="card-header">
<strong>{{.BodyTitle}}</strong>
</div>
<div class="card-body">
<form class="form-horizontal" action="/user/new" method="post">
<input type="hidden" name="token" value="{{.Token}}">
<div class="form-group">
<label class="col-form-label" for="user">Username</label>
<input class="form-control input-sm" type="text" id="user" name="user" autocomplete="off" autofocus required>
</div>
<div class="form-group">
<label class="col-form-label" for="password">Password</label>
<input class="form-control input-sm" type="password" id="password" name="password" required>
</div>
<div class="form-group">
<label class="col-form-label" for="repeat">Repeat</label>
<input class="form-control input-sm" type="password" id="repeat" name="repeat" required>
</div>
<div class="form-group">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="admin" name="admin" value="0">
<label class="custom-control-label" for="admin">Admin Privileges</label>
</div>
</div>
<button class="btn btn-sm btn-primary" type="submit">Create</button>
<a href="/users" class="btn btn-sm btn-primary">Back</a>
</form>
</div>
</div>
</div>
</div>
{{end}}

View file

@ -0,0 +1,38 @@
{{define "body"}}
{{if .Data}}
<div class="row">
<div class="col-xs-4 offset-4">
<div class="card border-primary mx-auto">
<div class="card-header">
<strong>{{.BodyTitle}} - {{.Data.Username}}</strong>
</div>
<div class="card-body">
<form class="form-horizontal" action="/user/totp/{{.Data.Username}}" method="post">
<input type="hidden" name="token" value="{{.Token}}">
{{if .Data.Secret}}
<input type="hidden" name="url" value="{{.Data.URL}}">
<div class="form-group">
<img src="{{.Data.Image}}" alt="{{.Data.Secret}}">
</div>
<div class="form-group">
<label class="col-form-label" for="secret">Secret</label>
<pre><code id="secret">{{.Data.Secret}}</code></pre>
</div>
{{end}}
<div class="form-group">
<label class="col-form-label" for="pin">PIN</label>
<input class="form-control input-sm" type="text" id="pin" name="pin" autocomplete="off" autofocus required>
</div>
{{if .Data.Secret}}
<button class="btn btn-sm btn-success" type="submit">Enable</button>
{{else}}
<button class="btn btn-sm btn-danger" type="submit">Disable</button>
{{end}}
<a href="/user/edit/{{.Data.Username}}" class="btn btn-sm btn-primary">Back</a>
</form>
</div>
</div>
</div>
</div>
{{end}}
{{end}}

View file

@ -0,0 +1,55 @@
{{define "body"}}
<div class="page-header">
<div class="row">
<h2>{{.BodyTitle}}</h2>
</div>
</div>
{{if .Admin}}
<div class="row mb-3">
<div class="col-xs-5">
<a class="btn btn-sm btn-primary" href="/user/new"><b>New User</b></a>
</div>
</div>
{{end}}
<div class="row">
<div class="col">
{{if .Data}}
<table class="table table-hover">
<thead>
<tr>
<th>Username</th>
<th>Flags</th>
<th>Options</th>
</tr>
</thead>
<tbody>
{{$user := .User}}
{{$admin := .Admin}}
{{range $item := .Data}}
<tr>
<td>{{$item.Username}}</td>
<td>
{{if $item.Secret}}<span class="badge badge-success">TOTP</span>{{end}}
{{if eq $item.Locked 3}}<span class="badge badge-warning">Locked</span>{{end}}
{{if $item.Admin}}<span class="badge badge-danger">Admin</span>{{end}}
</td>
<td>
<div class="btn-group">
{{if $admin}}
<a class="btn btn-sm btn-primary" href="/user/edit/{{$item.Username}}">Edit</a>
<a class="btn btn-sm btn-danger" href="/user/del/{{$item.Username}}">Delete</a>
{{else}}
{{if eq $item.Username $user}}
<a class="btn btn-sm btn-primary" href="/user/edit/{{$item.Username}}">Edit</a>
{{end}}
{{end}}
</div>
</td>
</tr>
{{end}}
</tbody>
</table>
{{end}}
</div>
</div>
{{end}}