Add info page and version
This commit is contained in:
parent
e10d6112f5
commit
fa292154f6
12 changed files with 122 additions and 11 deletions
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -1,6 +1,20 @@
|
|||
# Changelog
|
||||
This file keeps track of changes in more human-readable fashion
|
||||
|
||||
## 7 May 2024 - v0.1.0
|
||||
* Began move towards [semantic versioning](https://semver.org/)
|
||||
* Current version is now 0.1.0
|
||||
* Added `version` api method
|
||||
* Versioned docker images may be provided in the future
|
||||
* Added version to footer
|
||||
* Added info page
|
||||
* Accessed by clicking version number in footer
|
||||
* Allows you to edit readme.txt
|
||||
* Provides UI link for `export` api method
|
||||
* Can be expanded with other functionality in the future (see [TODO](./TODO.md))
|
||||
* Bug fixes
|
||||
* Fixed export function failing
|
||||
|
||||
## 6 May 2024
|
||||
* Grace period is now non-inclusive (so `4h` means the switch will happen right at `4:00`, not `4:01`)
|
||||
* Added API method to check if grace period is active
|
||||
|
|
10
README.md
10
README.md
|
@ -15,8 +15,7 @@ As a result, I can't guarantee that it's either secure or stable.
|
|||
* Optional Telegram notifications for failed login attempts
|
||||
|
||||
## Technical details
|
||||
Though Hibiscus.txt isn't versioned, [CHANGELOG.md](./CHANGELOG.md) can provide a good overview of changes.
|
||||
And there is a [TODO.md](./TODO.md) file that shows what I will (or *may*) work on in the future.
|
||||
[CHANGELOG.md](./CHANGELOG.md) provides a good overview of updates, and [TODO.md](./TODO.md) file shows what I will (or *may*) work on in the future.
|
||||
|
||||
You can read a relevant entry in my blog [here](https://a71.su/notes/hibiscus/).
|
||||
It provides some useful information and context for why this app exists in the first place.
|
||||
|
@ -65,6 +64,8 @@ The [package](https://git.a71.su/Andrew71/hibiscus/packages) provided in this re
|
|||
and there is a [Dockerfile](./Dockerfile) in case you want to compile for something rarer (like a Pi).
|
||||
This repo contains the [compose.yml](./compose.yml) that I personally use.
|
||||
|
||||
**Note**: versioned images may be provided in the future, possibly via dockerhub
|
||||
|
||||
### Executable flags
|
||||
If you for some reason decide to run plain executable instead of docker, it supports following flags:
|
||||
```
|
||||
|
@ -91,11 +92,10 @@ GET /day/<name> - get file contents for a specific day
|
|||
GET /notes - get JSON list of all named notes
|
||||
GET /notes/<name> - get file contents for a specific note
|
||||
POST /notes/<name> - save request body into a named note
|
||||
|
||||
GET /export - get .zip archive of entire data directory
|
||||
|
||||
GET /readme - get file contents for readme.txt in data dir's root
|
||||
POST /readme - save request body into readme.txt
|
||||
|
||||
GET /export - get .zip archive of entire data directory
|
||||
GET /grace - "true" if grace period is active, otherwise "false"
|
||||
GET /version - get app's version
|
||||
```
|
13
TODO.md
13
TODO.md
|
@ -1,8 +1,21 @@
|
|||
# TODO
|
||||
List of things to add to this project
|
||||
|
||||
## Agenda
|
||||
* a 512x logo so I can enable PWA
|
||||
* CI/CD pipeline
|
||||
* Better docs in case others want to use this for some reason
|
||||
* Check export function for improvements
|
||||
|
||||
## Brainstorming
|
||||
Don't expect any of this, these are ideas floating inside my head
|
||||
* Further info page functionality
|
||||
* Edit config
|
||||
* Statistics e.g. mb taken, number of day pages/notes
|
||||
* Multi-user support through several username-pass keys
|
||||
* `/data/<user>/...`
|
||||
* This would be an *extremely* breaking change
|
||||
* How to handle exporting *all*? Admin account?
|
||||
* I don't need this, unless Hibiscus.txt somehow gets popular why bother?
|
||||
Is this even a feature that fits the vision?
|
||||
* *Go* dependency-less? <-- this is a terrible idea
|
6
api.go
6
api.go
|
@ -116,3 +116,9 @@ func GraceActiveApi(w http.ResponseWriter, r *http.Request) {
|
|||
HandleWrite(w.Write([]byte(value)))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
// GetVersionApi returns current app version
|
||||
func GetVersionApi(w http.ResponseWriter, r *http.Request) {
|
||||
HandleWrite(w.Write([]byte(Info.Version)))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
|
|
@ -4,14 +4,21 @@
|
|||
"title.today": "Your day so far",
|
||||
"title.days": "Previous days",
|
||||
"title.notes": "Notes",
|
||||
"title.info": "Info",
|
||||
|
||||
"link.today": "today",
|
||||
"link.tomorrow": "tomorrow",
|
||||
"link.days": "previous days",
|
||||
"link.notes": "notes",
|
||||
"link.info": "app information",
|
||||
|
||||
"description.notes": "/notes/<name> for a new note",
|
||||
"time.date": "Today is",
|
||||
"time.grace": "grace period active",
|
||||
"button.save": "Save"
|
||||
"button.save": "Save",
|
||||
|
||||
"info.version": "Version",
|
||||
"info.version.link": "source and changelog",
|
||||
"info.export": "Export data",
|
||||
"info.readme": "Edit readme.txt"
|
||||
}
|
|
@ -4,14 +4,21 @@
|
|||
"title.today": "Сегодняшний день",
|
||||
"title.days": "Предыдущие дни",
|
||||
"title.notes": "Заметки",
|
||||
"title.info": "Информация",
|
||||
|
||||
"link.today": "сегодня",
|
||||
"link.tomorrow": "завтра",
|
||||
"link.days": "раньше",
|
||||
"link.notes": "заметки",
|
||||
"link.info": "системная информация",
|
||||
|
||||
"description.notes": "/notes/<название> для новой заметки",
|
||||
"time.date": "Сегодня",
|
||||
"time.grace": "льготный период",
|
||||
"button.save": "Сохранить"
|
||||
"button.save": "Сохранить",
|
||||
|
||||
"info.version": "Версия",
|
||||
"info.version.link": "исходный код",
|
||||
"info.export": "Экспорт данных",
|
||||
"info.readme": "Редактировать readme.txt"
|
||||
}
|
30
info.go
Normal file
30
info.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var infoTemplate = template.Must(template.New("").Funcs(templateFuncs).ParseFiles("./pages/base.html", "./pages/info.html"))
|
||||
|
||||
type HibiscusInfo struct {
|
||||
Version string
|
||||
SourceLink string
|
||||
}
|
||||
|
||||
// Info contains app information
|
||||
var Info = HibiscusInfo{
|
||||
Version: "0.1.0",
|
||||
SourceLink: "https://git.a71.su/Andrew71/hibiscus",
|
||||
}
|
||||
|
||||
// GetInfo renders the info page
|
||||
func GetInfo(w http.ResponseWriter, r *http.Request) {
|
||||
err := infoTemplate.ExecuteTemplate(w, "base", Info)
|
||||
if err != nil {
|
||||
slog.Error("error executing template", "error", err)
|
||||
InternalError(w, r)
|
||||
return
|
||||
}
|
||||
}
|
|
@ -33,6 +33,7 @@
|
|||
|
||||
{{define "footer"}}
|
||||
<footer>
|
||||
<p><a href="/">{{ translatableText "link.today" }}</a> | <a href="/day">{{ translatableText "link.days" }}</a> | <a href="/notes">{{ translatableText "link.notes" }}</a></p>
|
||||
<p><a href="/">{{ translatableText "link.today" }}</a> | <a href="/day">{{ translatableText "link.days" }}</a> | <a href="/notes">{{ translatableText "link.notes" }}</a>
|
||||
<span style="float:right;"><a class="no-accent" href="/info" title="{{ translatableText "link.info" }}">{{ hibiscusVersion }}</a></span></p>
|
||||
</footer>
|
||||
{{end}}
|
8
pages/info.html
Normal file
8
pages/info.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
{{define "main"}}
|
||||
<h2>{{ translatableText "title.info" }}</h2>
|
||||
<ul>
|
||||
<li>{{ translatableText "info.version" }} - {{ .Version }} (<a href="{{ .SourceLink }}">{{ translatableText "info.version.link" }}</a>)</li>
|
||||
<li><a href="/api/export" download="hibiscus">{{ translatableText "info.export" }}</a></li>
|
||||
<li><a href="/readme">{{ translatableText "info.readme" }}</a></li>
|
||||
</ul>
|
||||
{{end}}
|
|
@ -13,6 +13,8 @@ body {
|
|||
}
|
||||
a, a:visited { color: #f85552; }
|
||||
a:hover, a:visited:hover { color: #e66868; }
|
||||
a.no-accent, a.no-accent:visited { color: #454545; }
|
||||
a.no-accent:hover, a.no-accent:visited:hover { color: #656565; }
|
||||
|
||||
h2 { margin-bottom:12px; }
|
||||
.list-title { margin-bottom: 0}
|
||||
|
@ -65,4 +67,6 @@ header > h1, header > p {
|
|||
color: #f5f0e1;
|
||||
background-color: #383030;
|
||||
}
|
||||
a.no-accent, a.no-accent:visited { color: #f5f0e1; }
|
||||
a.no-accent:hover, a.no-accent:visited:hover { color: #a9a8a4; }
|
||||
}
|
21
routes.go
21
routes.go
|
@ -25,7 +25,9 @@ type Entry struct {
|
|||
|
||||
type formatEntries func([]string) []Entry
|
||||
|
||||
var templateFuncs = map[string]interface{}{"translatableText": TranslatableText}
|
||||
var templateFuncs = map[string]interface{}{
|
||||
"translatableText": TranslatableText,
|
||||
"hibiscusVersion": func() string { return "v" + Info.Version }}
|
||||
var editTemplate = template.Must(template.New("").Funcs(templateFuncs).ParseFiles("./pages/base.html", "./pages/edit.html"))
|
||||
var viewTemplate = template.Must(template.New("").Funcs(templateFuncs).ParseFiles("./pages/base.html", "./pages/entry.html"))
|
||||
var listTemplate = template.Must(template.New("").Funcs(templateFuncs).ParseFiles("./pages/base.html", "./pages/list.html"))
|
||||
|
@ -104,7 +106,7 @@ func GetDays(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// Fancy text for today and tomorrow
|
||||
// This looks bad, but strings.Title is deprecated, and I'm not importing a golang.org/x package for this...
|
||||
// ( chances we ever run into tomorrow are really low)
|
||||
// (chances we ever run into tomorrow are really low)
|
||||
if v == TodayDate() {
|
||||
dayString = TranslatableText("link.today")
|
||||
dayString = strings.ToTitle(string([]rune(dayString)[0])) + string([]rune(dayString)[1:])
|
||||
|
@ -130,6 +132,7 @@ func GetNotes(w http.ResponseWriter, r *http.Request) {
|
|||
})
|
||||
}
|
||||
|
||||
// GetEntry handles showing a single file, editable or otherwise
|
||||
func GetEntry(w http.ResponseWriter, r *http.Request, title string, filename string, editable bool) {
|
||||
entry, err := ReadFile(filename)
|
||||
if err != nil {
|
||||
|
@ -208,3 +211,17 @@ func PostNote(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
http.Redirect(w, r, r.Header.Get("Referer"), 302)
|
||||
}
|
||||
|
||||
// GetReadme calls GetEntry for readme.txt
|
||||
func GetReadme(w http.ResponseWriter, r *http.Request) {
|
||||
GetEntry(w, r, "readme", "readme", true)
|
||||
}
|
||||
|
||||
// PostReadme saves contents of readme.txt file
|
||||
func PostReadme(w http.ResponseWriter, r *http.Request) {
|
||||
err := SaveFile("readme", []byte(r.FormValue("text")))
|
||||
if err != nil {
|
||||
slog.Error("error saving readme", "error", err)
|
||||
}
|
||||
http.Redirect(w, r, r.Header.Get("Referer"), 302)
|
||||
}
|
||||
|
|
4
serve.go
4
serve.go
|
@ -25,6 +25,9 @@ func Serve() {
|
|||
userRouter.Get("/notes", GetNotes)
|
||||
userRouter.Get("/notes/{note}", GetNote)
|
||||
userRouter.Post("/notes/{note}", PostNote)
|
||||
userRouter.Get("/info", GetInfo)
|
||||
userRouter.Get("/readme", GetReadme)
|
||||
userRouter.Post("/readme", PostReadme)
|
||||
r.Mount("/", userRouter)
|
||||
|
||||
// API =============
|
||||
|
@ -41,6 +44,7 @@ func Serve() {
|
|||
apiRouter.Post("/today", PostTodayApi)
|
||||
apiRouter.Get("/export", GetExport)
|
||||
apiRouter.Get("/grace", GraceActiveApi)
|
||||
apiRouter.Get("/version", GetVersionApi)
|
||||
r.Mount("/api", apiRouter)
|
||||
|
||||
// Static files
|
||||
|
|
Loading…
Reference in a new issue