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:
parent
2b9821f967
commit
2c42f89547
@ -6,6 +6,7 @@ import (
|
|||||||
"git.wamblee.org/converge/pkg/models"
|
"git.wamblee.org/converge/pkg/models"
|
||||||
"git.wamblee.org/converge/pkg/server/matchmaker"
|
"git.wamblee.org/converge/pkg/server/matchmaker"
|
||||||
"git.wamblee.org/converge/pkg/server/prometheus"
|
"git.wamblee.org/converge/pkg/server/prometheus"
|
||||||
|
"git.wamblee.org/converge/pkg/server/ui"
|
||||||
"git.wamblee.org/converge/pkg/support/websocketutil"
|
"git.wamblee.org/converge/pkg/support/websocketutil"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
@ -128,7 +129,7 @@ func main() {
|
|||||||
// And the MatchMaker. The MatchMakers sends state notifications to websessions
|
// And the MatchMaker. The MatchMakers sends state notifications to websessions
|
||||||
// and prometheus.
|
// and prometheus.
|
||||||
notifications := NewStateNotifier(throttlingInterval)
|
notifications := NewStateNotifier(throttlingInterval)
|
||||||
websessions := matchmaker.NewWebSessions(notifications.webNotificationChannel)
|
websessions := ui.NewWebSessions(notifications.webNotificationChannel)
|
||||||
// monitoring
|
// monitoring
|
||||||
prometheusMux := http.NewServeMux()
|
prometheusMux := http.NewServeMux()
|
||||||
prometheus.SetupPrometheus(prometheusMux, notifications.prometheusNotificationChannel)
|
prometheus.SetupPrometheus(prometheusMux, notifications.prometheusNotificationChannel)
|
||||||
@ -161,18 +162,18 @@ func setupWebUI(context HttpContext, registrationService websocketutil.WebSocket
|
|||||||
context.HandleFunc("ws/sessions", sessionService.Handle)
|
context.HandleFunc("ws/sessions", sessionService.Handle)
|
||||||
|
|
||||||
// create filehandler with templating for html files.
|
// 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/",
|
context.Handle("static/", http.StripPrefix("static/",
|
||||||
http.FileServer(http.Dir(staticdir))))
|
http.FileServer(http.Dir(staticdir))))
|
||||||
context.Handle("downloads/", http.StripPrefix("downloads/",
|
context.Handle("downloads/", http.StripPrefix("downloads/",
|
||||||
http.FileServer(http.Dir(downloaddir))))
|
http.FileServer(http.Dir(downloaddir))))
|
||||||
// create usage generator
|
// create usage generator
|
||||||
context.HandleFunc("usage", generateCLIExammple)
|
context.HandleFunc("usage", ui.GenerateCLIExammples)
|
||||||
|
|
||||||
return context
|
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
|
// websocket endpoints
|
||||||
|
|
||||||
// For agents connecting
|
// For agents connecting
|
||||||
@ -214,7 +215,7 @@ func setupWebSockets(admin *matchmaker.MatchMaker, websessions *matchmaker.WebSe
|
|||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
websession := websessions.NewSession(conn, ctx, cancel)
|
websession := websessions.NewSession(conn, ctx, cancel)
|
||||||
defer websessions.SessionClosed(websession)
|
defer websessions.SessionClosed(websession)
|
||||||
location, err := matchmaker.GetUserLocation(r)
|
location, err := ui.GetUserLocation(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -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
4
pkg/server/admin/doc.go
Normal 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
|
6
pkg/server/matchmaker/doc.go
Normal file
6
pkg/server/matchmaker/doc.go
Normal 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
|
4
pkg/server/prometheus/doc.go
Normal file
4
pkg/server/prometheus/doc.go
Normal 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
|
@ -1,13 +1,24 @@
|
|||||||
package main
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.wamblee.org/converge/pkg/models"
|
"git.wamblee.org/converge/pkg/models"
|
||||||
"git.wamblee.org/converge/pkg/server/matchmaker"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"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 {
|
func getConvergeAccess(r *http.Request) models.ConvergeAccess {
|
||||||
|
|
||||||
pattern := regexp.MustCompile("^(.*)/usage$")
|
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 {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
6
pkg/server/ui/doc.go
Normal file
6
pkg/server/ui/doc.go
Normal 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
|
27
pkg/server/ui/pagehandler.go
Normal file
27
pkg/server/ui/pagehandler.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,7 @@
|
|||||||
package main
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.wamblee.org/converge/pkg/server/ui"
|
|
||||||
"github.com/gliderlabs/ssh"
|
"github.com/gliderlabs/ssh"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -11,7 +10,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func generateCLIExammple(w http.ResponseWriter, r *http.Request) {
|
func GenerateCLIExammples(w http.ResponseWriter, r *http.Request) {
|
||||||
err := r.ParseForm()
|
err := r.ParseForm()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Error parsing form", http.StatusBadRequest)
|
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")
|
downloadCommand := r.FormValue("download-command")
|
||||||
certificateValidation := r.FormValue("certificate-validation") != ""
|
certificateValidation := r.FormValue("certificate-validation") != ""
|
||||||
sshPublicKeys := strings.Split(keysString, "\n")
|
sshPublicKeys := strings.Split(keysString, "\n")
|
||||||
usageInputs := ui.NewUsageInputs(id, sshPublicKeys, remoteShells, localShells, downloadCommand,
|
usageInputs := NewUsageInputs(id, sshPublicKeys, remoteShells, localShells, downloadCommand,
|
||||||
certificateValidation)
|
certificateValidation)
|
||||||
if generatedId {
|
if generatedId {
|
||||||
usageInputs.ErrorMessages = append(usageInputs.ErrorMessages, "rendez-vous id is randomly generated, changes on every page refresh")
|
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.")
|
"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 {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), 500)
|
||||||
}
|
}
|
@ -1,9 +1,8 @@
|
|||||||
package matchmaker
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"git.wamblee.org/converge/pkg/models"
|
"git.wamblee.org/converge/pkg/models"
|
||||||
"git.wamblee.org/converge/pkg/server/ui"
|
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -98,17 +97,6 @@ func (sessions *WebSessions) NewSession(wsConnection net.Conn, ctx context.Conte
|
|||||||
return session
|
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) {
|
func (session *WebSession) WriteNotifications(location *time.Location, ctx context.Context, cancel context.CancelFunc) {
|
||||||
timer := time.NewTicker(10 * time.Second)
|
timer := time.NewTicker(10 * time.Second)
|
||||||
defer timer.Stop()
|
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 {
|
func (session *WebSession) writeNotificationToClient(location *time.Location, notification *models.State) bool {
|
||||||
agents, clients := notification.Slices()
|
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 {
|
if err != nil {
|
||||||
log.Printf("WS connection closed: %v", err)
|
log.Printf("WS connection closed: %v", err)
|
||||||
return false
|
return false
|
Loading…
Reference in New Issue
Block a user