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

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
## 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
```

15
TODO.md
View file

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

View file

@ -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
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"}}
<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
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: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; }
}

View file

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

View file

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