moved all ui stuff to the ui package.

The structure of converge server is now much more clear in the package
// structure below pkg/server.
This commit is contained in:
Erik Brakkee 2024-08-17 10:37:20 +02:00
parent 2b9821f967
commit 2c42f89547
10 changed files with 73 additions and 55 deletions

View File

@ -6,6 +6,7 @@ import (
"git.wamblee.org/converge/pkg/models"
"git.wamblee.org/converge/pkg/server/matchmaker"
"git.wamblee.org/converge/pkg/server/prometheus"
"git.wamblee.org/converge/pkg/server/ui"
"git.wamblee.org/converge/pkg/support/websocketutil"
"log"
"net"
@ -128,7 +129,7 @@ func main() {
// And the MatchMaker. The MatchMakers sends state notifications to websessions
// and prometheus.
notifications := NewStateNotifier(throttlingInterval)
websessions := matchmaker.NewWebSessions(notifications.webNotificationChannel)
websessions := ui.NewWebSessions(notifications.webNotificationChannel)
// monitoring
prometheusMux := http.NewServeMux()
prometheus.SetupPrometheus(prometheusMux, notifications.prometheusNotificationChannel)
@ -161,18 +162,18 @@ func setupWebUI(context HttpContext, registrationService websocketutil.WebSocket
context.HandleFunc("ws/sessions", sessionService.Handle)
// create filehandler with templating for html files.
context.Handle("docs/", http.StripPrefix("docs/", http.HandlerFunc(pageHandler)))
context.Handle("docs/", http.StripPrefix("docs/", http.HandlerFunc(ui.PageHandler)))
context.Handle("static/", http.StripPrefix("static/",
http.FileServer(http.Dir(staticdir))))
context.Handle("downloads/", http.StripPrefix("downloads/",
http.FileServer(http.Dir(downloaddir))))
// create usage generator
context.HandleFunc("usage", generateCLIExammple)
context.HandleFunc("usage", ui.GenerateCLIExammples)
return context
}
func setupWebSockets(admin *matchmaker.MatchMaker, websessions *matchmaker.WebSessions) (websocketutil.WebSocketService, websocketutil.WebSocketService, websocketutil.WebSocketService) {
func setupWebSockets(admin *matchmaker.MatchMaker, websessions *ui.WebSessions) (websocketutil.WebSocketService, websocketutil.WebSocketService, websocketutil.WebSocketService) {
// websocket endpoints
// For agents connecting
@ -214,7 +215,7 @@ func setupWebSockets(admin *matchmaker.MatchMaker, websessions *matchmaker.WebSe
ctx, cancel := context.WithCancel(context.Background())
websession := websessions.NewSession(conn, ctx, cancel)
defer websessions.SessionClosed(websession)
location, err := matchmaker.GetUserLocation(r)
location, err := ui.GetUserLocation(r)
if err != nil {
panic(err)
}

View File

@ -1,28 +0,0 @@
package main
import (
templates2 "git.wamblee.org/converge/pkg/server/ui"
"net/http"
)
func pageHandler(w http.ResponseWriter, r *http.Request) {
access := getConvergeAccess(r)
switch r.URL.Path {
case "":
fallthrough
case "/":
fallthrough
case "index.html":
templates2.AboutTab().Render(r.Context(), w)
// TODO use contexts later.
case "usage.html":
templates2.UsageTab(access).Render(r.Context(), w)
case "downloads.html":
templates2.DownloadsTab().Render(r.Context(), w)
case "sessions.html":
templates2.SessionsTab(nil, nil, access.Location).Render(r.Context(), w)
default:
http.NotFound(w, r)
}
}

4
pkg/server/admin/doc.go Normal file
View File

@ -0,0 +1,4 @@
// Basic administration of the matchmaker.
// Put in its separate package to make sure only public methods can be called because
// these are the only ones that are thread-safe.
package admin

View File

@ -0,0 +1,6 @@
// The matchmaker package matches up agents and clients based on their rendez-vous
// ids and pumps data between client and agent. This is the core cunctionality of
// converge. It uses notifications with the new state in case something changes.
// The notifications are routed to components that are interested in them: the web ui
// and prometheus integration.
package matchmaker

View File

@ -0,0 +1,4 @@
// Prometheus integration of converge. It relies on state notifications of converge that
// describe the current state. It computes the deltas based on the state itself and updates
// prometheus metrics accordingly.
package prometheus

View File

@ -1,13 +1,24 @@
package main
package ui
import (
"git.wamblee.org/converge/pkg/models"
"git.wamblee.org/converge/pkg/server/matchmaker"
"net/http"
"regexp"
"strings"
"time"
)
func GetUserLocation(r *http.Request) (*time.Location, error) {
tzName := r.URL.Query().Get("timezone")
if tzName == "" {
tzName = r.Header.Get("X-Timezone")
}
if tzName == "" {
tzName = "UTC"
}
return time.LoadLocation(tzName)
}
func getConvergeAccess(r *http.Request) models.ConvergeAccess {
pattern := regexp.MustCompile("^(.*)/usage$")
@ -32,7 +43,7 @@ func getConvergeAccess(r *http.Request) models.ConvergeAccess {
}
}
location, err := matchmaker.GetUserLocation(r)
location, err := GetUserLocation(r)
if err != nil {
panic(err)
}

6
pkg/server/ui/doc.go Normal file
View File

@ -0,0 +1,6 @@
// This provides the user interface consisting of a number of an information page,
// a downloads page, an interfactive usage page and a sessions page.
// The usage page uses HTMX to get updated CLI examples based on the user's input.
// The sessions page uses HTMX with a websocket so that the server can push the live
// status of the server to clients.
package ui

View File

@ -0,0 +1,27 @@
package ui
import (
"net/http"
)
func PageHandler(w http.ResponseWriter, r *http.Request) {
access := getConvergeAccess(r)
switch r.URL.Path {
case "":
fallthrough
case "/":
fallthrough
case "index.html":
AboutTab().Render(r.Context(), w)
// TODO use contexts later.
case "usage.html":
UsageTab(access).Render(r.Context(), w)
case "downloads.html":
DownloadsTab().Render(r.Context(), w)
case "sessions.html":
SessionsTab(nil, nil, access.Location).Render(r.Context(), w)
default:
http.NotFound(w, r)
}
}

View File

@ -1,8 +1,7 @@
package main
package ui
import (
"fmt"
"git.wamblee.org/converge/pkg/server/ui"
"github.com/gliderlabs/ssh"
"math/rand"
"net/http"
@ -11,7 +10,7 @@ import (
"strings"
)
func generateCLIExammple(w http.ResponseWriter, r *http.Request) {
func GenerateCLIExammples(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm()
if err != nil {
http.Error(w, "Error parsing form", http.StatusBadRequest)
@ -32,7 +31,7 @@ func generateCLIExammple(w http.ResponseWriter, r *http.Request) {
downloadCommand := r.FormValue("download-command")
certificateValidation := r.FormValue("certificate-validation") != ""
sshPublicKeys := strings.Split(keysString, "\n")
usageInputs := ui.NewUsageInputs(id, sshPublicKeys, remoteShells, localShells, downloadCommand,
usageInputs := NewUsageInputs(id, sshPublicKeys, remoteShells, localShells, downloadCommand,
certificateValidation)
if generatedId {
usageInputs.ErrorMessages = append(usageInputs.ErrorMessages, "rendez-vous id is randomly generated, changes on every page refresh")
@ -64,7 +63,7 @@ func generateCLIExammple(w http.ResponseWriter, r *http.Request) {
"No valid public keys configured. Without these the agent will not work.")
}
err = ui.ShellUsage(access, usageInputs).Render(r.Context(), w)
err = ShellUsage(access, usageInputs).Render(r.Context(), w)
if err != nil {
http.Error(w, err.Error(), 500)
}

View File

@ -1,9 +1,8 @@
package matchmaker
package ui
import (
"context"
"git.wamblee.org/converge/pkg/models"
"git.wamblee.org/converge/pkg/server/ui"
"log"
"net"
"net/http"
@ -98,17 +97,6 @@ func (sessions *WebSessions) NewSession(wsConnection net.Conn, ctx context.Conte
return session
}
func GetUserLocation(r *http.Request) (*time.Location, error) {
tzName := r.URL.Query().Get("timezone")
if tzName == "" {
tzName = r.Header.Get("X-Timezone")
}
if tzName == "" {
tzName = "UTC"
}
return time.LoadLocation(tzName)
}
func (session *WebSession) WriteNotifications(location *time.Location, ctx context.Context, cancel context.CancelFunc) {
timer := time.NewTicker(10 * time.Second)
defer timer.Stop()
@ -138,7 +126,7 @@ func (session *WebSession) WriteNotifications(location *time.Location, ctx conte
func (session *WebSession) writeNotificationToClient(location *time.Location, notification *models.State) bool {
agents, clients := notification.Slices()
err := ui.State(agents, clients, location).Render(context.Background(), session.conn)
err := State(agents, clients, location).Render(context.Background(), session.conn)
if err != nil {
log.Printf("WS connection closed: %v", err)
return false