feat: collect and display usage stats

This commit is contained in:
2023-09-24 12:21:44 -06:00
parent bf14a70efe
commit 6b1637d1d8
17 changed files with 394 additions and 25 deletions

View File

@ -2,16 +2,20 @@ package http
import (
"log"
"forge.cadoles.com/wpetit/rebound/stat"
)
type Options struct {
Logger func(message string, args ...any)
CustomDir string `env:"CUSTOM_DIR"`
TemplateData *TemplateData `envPrefix:"TEMPLATE_DATA_"`
Stats *stat.Store
}
type TemplateData struct {
Title string `env:"TITLE"`
Version string
SSHPublicHost string `env:"SSH_PUBLIC_HOST"`
SSHPublicPort int `env:"SSH_PUBLIC_PORT"`
}
@ -27,6 +31,7 @@ func DefaultOptions() *Options {
SSHPublicHost: "127.0.0.1",
SSHPublicPort: 2222,
},
Stats: stat.NewStore(),
}
}
@ -47,3 +52,9 @@ func WithTemplateData(templateData *TemplateData) func(*Options) {
opts.TemplateData = templateData
}
}
func WithStats(stats *stat.Store) func(*Options) {
return func(opts *Options) {
opts.Stats = stats
}
}

View File

@ -3,9 +3,11 @@ package http
import (
"bytes"
"embed"
"fmt"
"html/template"
"io"
"io/fs"
"log/slog"
"net"
"net/http"
"os"
@ -29,8 +31,45 @@ type Server struct {
templates template.Template
}
var templateFuncs = template.FuncMap{
"humanSize": func(b float64) string {
const unit = 1000
if b < unit {
return fmt.Sprintf("%d B", int64(b))
}
div, exp := int64(unit), 0
for n := b / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %cB",
float64(b)/float64(div), "kMGTPE"[exp])
},
}
func (s *Server) serveHomepage(w http.ResponseWriter, r *http.Request) {
s.renderTemplate(w, "index", s.opts.TemplateData)
stats, err := s.opts.Stats.Snapshot()
if err != nil {
slog.Error("could not make stats snapshot", slog.Any("error", errors.WithStack(err)))
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
data := struct {
TemplateData
Stats map[string]float64
}{
TemplateData: *s.opts.TemplateData,
Stats: stats,
}
s.opts.Stats.Add(StatTotalPageView, 1, 0)
s.renderTemplate(w, "index", data)
}
func (s *Server) Serve(l net.Listener) error {
@ -67,7 +106,7 @@ func (s *Server) Serve(l net.Listener) error {
}
func (s *Server) parseTemplates(fs fs.FS) error {
templates, err := template.ParseFS(fs, "templates/*.html")
templates, err := template.New("").Funcs(templateFuncs).ParseFS(fs, "templates/*.html")
if err != nil {
return errors.WithStack(err)
}

5
http/stats.go Normal file
View File

@ -0,0 +1,5 @@
package http
const (
StatTotalPageView = "total_page_view"
)

View File

@ -2,7 +2,7 @@
<footer class="footer">
<div class="container">
<div class="content has-text-centered">
Ce service est propulsé par <a href="https://forge.cadoles.com/wpetit/rebound" title="Rebound repository">Rebound</a>, un logiciel libre diffusé sous licence <a href="https://www.gnu.org/licenses/agpl-3.0.en.html#license-text">AGPL-3.0</a>.
Ce service est propulsé par <a href="https://forge.cadoles.com/wpetit/rebound" title="Rebound repository">rebound@{{ .Version }}</a>, un logiciel libre diffusé sous licence <a href="https://www.gnu.org/licenses/agpl-3.0.en.html#license-text">AGPL-3.0</a>.
</div>
</div>
</footer>

View File

@ -13,14 +13,46 @@
<p class="subtitle is-size-3">
Bienvenue sur <strong>Rebound</strong>!
</p>
<div class="content">
<p>Rebound est un serveur SSH permettant de créer des tunnels TCP/IP éphémères et privés entre 2 machines positionnées
derrière un <abbr title="Network Address Traversal">NAT</abbr>.</p>
<p>Pour l'utiliser <strong>un simple client SSH suffit !</strong></p>
<pre class="has-background-dark has-text-white-ter is-family-monospace">ssh -R 0:127.0.0.1:<span class="has-text-info">&lt;port&gt;</span> rebound@{{ .SSHPublicHost }} -p {{ .SSHPublicPort }}</pre>
<p class="is-italic"><span class="has-text-info">&lt;port&gt;</span> est à remplacer par le port du service
s'exécutant sur votre machine en local.</span>
<p>Une fois connecté, suivez les instructions. 😉</p>
<div class="block">
<div class="content">
<p>Rebound est un serveur SSH permettant de créer des tunnels TCP/IP éphémères et privés entre 2 machines positionnées
derrière un <abbr title="Network Address Traversal">NAT</abbr>.</p>
<p>Pour l'utiliser <strong>un simple client SSH suffit !</strong></p>
<pre class="has-background-dark has-text-white-ter is-family-monospace">ssh -R 0:127.0.0.1:<span class="has-text-info">&lt;port&gt;</span> rebound@{{ .SSHPublicHost }} -p {{ .SSHPublicPort }}</pre>
<p class="is-italic"><span class="has-text-info">&lt;port&gt;</span> est à remplacer par le port du service
s'exécutant sur votre machine en local.</span>
<p>Une fois connecté, suivez les instructions. 😉</p>
</div>
</div>
<hr />
<div class="block">
<div class="columns">
<div class="column is-4">
<h3 class="title is-size-4">En savoir plus</h3>
<div class="content">
À venir...
</div>
</div>
<div class="column is-4">
<h3 class="title is-size-4">Statistiques</h3>
<table class="table is-bordered is-striped is-fullwidth">
<tbody>
<tr>
<td><strong>Total tunnels ouverts</strong></td>
<td>{{ index .Stats "total_opened_tunnels" }}</td>
</tr>
<tr>
<td><strong>Total données entrantes</strong></td>
<td>{{ humanSize ( index .Stats "total_rx_bytes" ) }}</td>
</tr>
<tr>
<td><strong>Total données sortantes</strong></td>
<td>{{ humanSize ( index .Stats "total_tx_bytes" ) }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</section>