Add info page and version

This commit is contained in:
Andrew-71 2024-05-07 13:27:11 +03:00
parent e10d6112f5
commit fa292154f6
12 changed files with 122 additions and 11 deletions

View file

@ -1,6 +1,20 @@
# Changelog # Changelog
This file keeps track of changes in more human-readable fashion 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 ## 6 May 2024
* Grace period is now non-inclusive (so `4h` means the switch will happen right at `4:00`, not `4:01`) * 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 * Added API method to check if grace period is active

View file

@ -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 * Optional Telegram notifications for failed login attempts
## Technical details ## Technical details
Though Hibiscus.txt isn't versioned, [CHANGELOG.md](./CHANGELOG.md) can provide a good overview of changes. [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.
And there is a [TODO.md](./TODO.md) file that 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/). 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. 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). 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. 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 ### Executable flags
If you for some reason decide to run plain executable instead of docker, it supports following 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 - get JSON list of all named notes
GET /notes/<name> - get file contents for a specific note GET /notes/<name> - get file contents for a specific note
POST /notes/<name> - save request body into a named 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 GET /readme - get file contents for readme.txt in data dir's root
POST /readme - save request body into readme.txt 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 /grace - "true" if grace period is active, otherwise "false"
GET /version - get app's version
``` ```

15
TODO.md
View file

@ -1,8 +1,21 @@
# TODO # TODO
List of things to add to this project List of things to add to this project
## Agenda
* a 512x logo so I can enable PWA * a 512x logo so I can enable PWA
* CI/CD pipeline * CI/CD pipeline
* Better docs in case others want to use this for some reason * Better docs in case others want to use this for some reason
* Check export function for improvements * Check export function for improvements
* *Go* dependency-less? <-- this is a terrible idea
## 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
View file

@ -116,3 +116,9 @@ func GraceActiveApi(w http.ResponseWriter, r *http.Request) {
HandleWrite(w.Write([]byte(value))) HandleWrite(w.Write([]byte(value)))
w.WriteHeader(http.StatusOK) 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)
}

View file

@ -4,14 +4,21 @@
"title.today": "Your day so far", "title.today": "Your day so far",
"title.days": "Previous days", "title.days": "Previous days",
"title.notes": "Notes", "title.notes": "Notes",
"title.info": "Info",
"link.today": "today", "link.today": "today",
"link.tomorrow": "tomorrow", "link.tomorrow": "tomorrow",
"link.days": "previous days", "link.days": "previous days",
"link.notes": "notes", "link.notes": "notes",
"link.info": "app information",
"description.notes": "/notes/<name> for a new note", "description.notes": "/notes/<name> for a new note",
"time.date": "Today is", "time.date": "Today is",
"time.grace": "grace period active", "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"
} }

View file

@ -4,14 +4,21 @@
"title.today": "Сегодняшний день", "title.today": "Сегодняшний день",
"title.days": "Предыдущие дни", "title.days": "Предыдущие дни",
"title.notes": "Заметки", "title.notes": "Заметки",
"title.info": "Информация",
"link.today": "сегодня", "link.today": "сегодня",
"link.tomorrow": "завтра", "link.tomorrow": "завтра",
"link.days": "раньше", "link.days": "раньше",
"link.notes": "заметки", "link.notes": "заметки",
"link.info": "системная информация",
"description.notes": "/notes/<название> для новой заметки", "description.notes": "/notes/<название> для новой заметки",
"time.date": "Сегодня", "time.date": "Сегодня",
"time.grace": "льготный период", "time.grace": "льготный период",
"button.save": "Сохранить" "button.save": "Сохранить",
"info.version": "Версия",
"info.version.link": "исходный код",
"info.export": "Экспорт данных",
"info.readme": "Редактировать readme.txt"
} }

30
info.go Normal file
View 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
}
}

View file

@ -33,6 +33,7 @@
{{define "footer"}} {{define "footer"}}
<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> </footer>
{{end}} {{end}}

8
pages/info.html Normal file
View 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}}

View file

@ -13,6 +13,8 @@ body {
} }
a, a:visited { color: #f85552; } a, a:visited { color: #f85552; }
a:hover, a:visited:hover { color: #e66868; } 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; } h2 { margin-bottom:12px; }
.list-title { margin-bottom: 0} .list-title { margin-bottom: 0}
@ -65,4 +67,6 @@ header > h1, header > p {
color: #f5f0e1; color: #f5f0e1;
background-color: #383030; background-color: #383030;
} }
a.no-accent, a.no-accent:visited { color: #f5f0e1; }
a.no-accent:hover, a.no-accent:visited:hover { color: #a9a8a4; }
} }

View file

@ -25,7 +25,9 @@ type Entry struct {
type formatEntries func([]string) []Entry 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 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 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")) 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 // 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... // 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() { if v == TodayDate() {
dayString = TranslatableText("link.today") dayString = TranslatableText("link.today")
dayString = strings.ToTitle(string([]rune(dayString)[0])) + string([]rune(dayString)[1:]) 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) { func GetEntry(w http.ResponseWriter, r *http.Request, title string, filename string, editable bool) {
entry, err := ReadFile(filename) entry, err := ReadFile(filename)
if err != nil { if err != nil {
@ -208,3 +211,17 @@ func PostNote(w http.ResponseWriter, r *http.Request) {
} }
http.Redirect(w, r, r.Header.Get("Referer"), 302) 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)
}

View file

@ -25,6 +25,9 @@ func Serve() {
userRouter.Get("/notes", GetNotes) userRouter.Get("/notes", GetNotes)
userRouter.Get("/notes/{note}", GetNote) userRouter.Get("/notes/{note}", GetNote)
userRouter.Post("/notes/{note}", PostNote) userRouter.Post("/notes/{note}", PostNote)
userRouter.Get("/info", GetInfo)
userRouter.Get("/readme", GetReadme)
userRouter.Post("/readme", PostReadme)
r.Mount("/", userRouter) r.Mount("/", userRouter)
// API ============= // API =============
@ -41,6 +44,7 @@ func Serve() {
apiRouter.Post("/today", PostTodayApi) apiRouter.Post("/today", PostTodayApi)
apiRouter.Get("/export", GetExport) apiRouter.Get("/export", GetExport)
apiRouter.Get("/grace", GraceActiveApi) apiRouter.Get("/grace", GraceActiveApi)
apiRouter.Get("/version", GetVersionApi)
r.Mount("/api", apiRouter) r.Mount("/api", apiRouter)
// Static files // Static files