mirror of
https://github.com/henrygd/beszel.git
synced 2026-03-22 21:46:18 +01:00
Compare commits
1 Commits
cgroups
...
ssr-system
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
605e3f7e9d |
@@ -19,6 +19,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/pocketbase/dbx"
|
||||||
"github.com/pocketbase/pocketbase"
|
"github.com/pocketbase/pocketbase"
|
||||||
"github.com/pocketbase/pocketbase/apis"
|
"github.com/pocketbase/pocketbase/apis"
|
||||||
"github.com/pocketbase/pocketbase/core"
|
"github.com/pocketbase/pocketbase/core"
|
||||||
@@ -299,3 +300,30 @@ func (h *Hub) MakeLink(parts ...string) string {
|
|||||||
}
|
}
|
||||||
return base
|
return base
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SystemInfo struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Id string `json:"id"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Port uint16 `json:"port"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
Info string `json:"info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hub) getUserSystemsFromRequest(req *http.Request) ([]SystemInfo, error) {
|
||||||
|
systems := []SystemInfo{}
|
||||||
|
token, err := req.Cookie("beszauth")
|
||||||
|
if err != nil {
|
||||||
|
return systems, err
|
||||||
|
}
|
||||||
|
if token.Value != "" {
|
||||||
|
user, err := h.FindAuthRecordByToken(token.Value)
|
||||||
|
if err != nil {
|
||||||
|
return systems, err
|
||||||
|
}
|
||||||
|
h.DB().NewQuery("SELECT s.id, s.info, s.status, s.name, s.port, s.host FROM systems s JOIN json_each(s.users) AS je WHERE je.value = {:user_id}").Bind(dbx.Params{
|
||||||
|
"user_id": user.Id,
|
||||||
|
}).All(&systems)
|
||||||
|
}
|
||||||
|
return systems, err
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ package hub
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"beszel"
|
"beszel"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
@@ -15,29 +16,36 @@ import (
|
|||||||
"github.com/pocketbase/pocketbase/core"
|
"github.com/pocketbase/pocketbase/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Wraps http.RoundTripper to modify dev proxy HTML responses
|
// responseModifier wraps an http.RoundTripper to modify HTML responses
|
||||||
type responseModifier struct {
|
type responseModifier struct {
|
||||||
transport http.RoundTripper
|
transport http.RoundTripper
|
||||||
hub *Hub
|
hub *Hub
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RoundTrip implements http.RoundTripper interface with response modification
|
||||||
func (rm *responseModifier) RoundTrip(req *http.Request) (*http.Response, error) {
|
func (rm *responseModifier) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
resp, err := rm.transport.RoundTrip(req)
|
resp, err := rm.transport.RoundTrip(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only modify HTML responses
|
// Only modify HTML responses
|
||||||
contentType := resp.Header.Get("Content-Type")
|
contentType := resp.Header.Get("Content-Type")
|
||||||
if !strings.Contains(contentType, "text/html") {
|
if !strings.Contains(contentType, "text/html") {
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read the response body
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
|
|
||||||
|
// Modify the HTML content here
|
||||||
|
modifiedBody := rm.modifyHTML(string(body), req)
|
||||||
|
|
||||||
// Create a new response with the modified body
|
// Create a new response with the modified body
|
||||||
modifiedBody := rm.modifyHTML(string(body))
|
|
||||||
resp.Body = io.NopCloser(strings.NewReader(modifiedBody))
|
resp.Body = io.NopCloser(strings.NewReader(modifiedBody))
|
||||||
resp.ContentLength = int64(len(modifiedBody))
|
resp.ContentLength = int64(len(modifiedBody))
|
||||||
resp.Header.Set("Content-Length", fmt.Sprintf("%d", len(modifiedBody)))
|
resp.Header.Set("Content-Length", fmt.Sprintf("%d", len(modifiedBody)))
|
||||||
@@ -45,7 +53,8 @@ func (rm *responseModifier) RoundTrip(req *http.Request) (*http.Response, error)
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rm *responseModifier) modifyHTML(html string) string {
|
// modifyHTML applies modifications to HTML content
|
||||||
|
func (rm *responseModifier) modifyHTML(html string, req *http.Request) string {
|
||||||
parsedURL, err := url.Parse(rm.hub.appURL)
|
parsedURL, err := url.Parse(rm.hub.appURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return html
|
return html
|
||||||
@@ -54,7 +63,19 @@ func (rm *responseModifier) modifyHTML(html string) string {
|
|||||||
basePath := strings.TrimSuffix(parsedURL.Path, "/") + "/"
|
basePath := strings.TrimSuffix(parsedURL.Path, "/") + "/"
|
||||||
html = strings.ReplaceAll(html, "./", basePath)
|
html = strings.ReplaceAll(html, "./", basePath)
|
||||||
html = strings.Replace(html, "{{V}}", beszel.Version, 1)
|
html = strings.Replace(html, "{{V}}", beszel.Version, 1)
|
||||||
|
slog.Info("modifying HTML", "appURL", rm.hub.appURL)
|
||||||
html = strings.Replace(html, "{{HUB_URL}}", rm.hub.appURL, 1)
|
html = strings.Replace(html, "{{HUB_URL}}", rm.hub.appURL, 1)
|
||||||
|
|
||||||
|
systems, err := rm.hub.getUserSystemsFromRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
systemsJson, err := json.Marshal(systems)
|
||||||
|
if err != nil {
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
html = strings.Replace(html, "'{SYSTEMS}'", string(systemsJson), 1)
|
||||||
|
|
||||||
return html
|
return html
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,6 +87,7 @@ func (h *Hub) startServer(se *core.ServeEvent) error {
|
|||||||
Host: "localhost:5173",
|
Host: "localhost:5173",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Set up custom transport with response modification
|
||||||
proxy.Transport = &responseModifier{
|
proxy.Transport = &responseModifier{
|
||||||
transport: http.DefaultTransport,
|
transport: http.DefaultTransport,
|
||||||
hub: h,
|
hub: h,
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ package hub
|
|||||||
import (
|
import (
|
||||||
"beszel"
|
"beszel"
|
||||||
"beszel/site"
|
"beszel/site"
|
||||||
|
"encoding/json"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -45,6 +47,15 @@ func (h *Hub) startServer(se *core.ServeEvent) error {
|
|||||||
e.Response.Header().Del("X-Frame-Options")
|
e.Response.Header().Del("X-Frame-Options")
|
||||||
e.Response.Header().Set("Content-Security-Policy", csp)
|
e.Response.Header().Set("Content-Security-Policy", csp)
|
||||||
}
|
}
|
||||||
|
systems, err := h.getUserSystemsFromRequest(e.Request)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("error getting user systems", "error", err)
|
||||||
|
}
|
||||||
|
systemsJson, err := json.Marshal(systems)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("error marshalling user systems", "error", err)
|
||||||
|
}
|
||||||
|
html = strings.Replace(html, "'{SYSTEMS}'", string(systemsJson), 1)
|
||||||
return e.HTML(http.StatusOK, html)
|
return e.HTML(http.StatusOK, html)
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -10,7 +10,8 @@
|
|||||||
globalThis.BESZEL = {
|
globalThis.BESZEL = {
|
||||||
BASE_PATH: "%BASE_URL%",
|
BASE_PATH: "%BASE_URL%",
|
||||||
HUB_VERSION: "{{V}}",
|
HUB_VERSION: "{{V}}",
|
||||||
HUB_URL: "{{HUB_URL}}"
|
HUB_URL: "{{HUB_URL}}",
|
||||||
|
SYSTEMS: '{SYSTEMS}'
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -12,6 +12,13 @@ export const pb = new PocketBase(basePath)
|
|||||||
export const isAdmin = () => pb.authStore.record?.role === "admin"
|
export const isAdmin = () => pb.authStore.record?.role === "admin"
|
||||||
export const isReadOnlyUser = () => pb.authStore.record?.role === "readonly"
|
export const isReadOnlyUser = () => pb.authStore.record?.role === "readonly"
|
||||||
|
|
||||||
|
export const updateCookieToken = () => {
|
||||||
|
console.log("setting token", pb.authStore.token)
|
||||||
|
document.cookie = `beszauth=${pb.authStore.token}; path=/; expires=${new Date(
|
||||||
|
Date.now() + 7 * 24 * 60 * 60 * 1000
|
||||||
|
).toString()}`
|
||||||
|
}
|
||||||
|
|
||||||
export const verifyAuth = () => {
|
export const verifyAuth = () => {
|
||||||
pb.collection("users")
|
pb.collection("users")
|
||||||
.authRefresh()
|
.authRefresh()
|
||||||
|
|||||||
@@ -141,7 +141,13 @@ export async function subscribe() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Refresh all systems with latest data from the hub */
|
/** Refresh all systems with latest data from the hub */
|
||||||
export async function refresh() {
|
export async function refresh(records: SystemRecord[] = []) {
|
||||||
|
if (records.length) {
|
||||||
|
for (const record of records) {
|
||||||
|
add(record)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const records = await fetchSystems()
|
const records = await fetchSystems()
|
||||||
if (!records.length) {
|
if (!records.length) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import ReactDOM from "react-dom/client"
|
|||||||
import { ThemeProvider } from "./components/theme-provider.tsx"
|
import { ThemeProvider } from "./components/theme-provider.tsx"
|
||||||
import { DirectionProvider } from "@radix-ui/react-direction"
|
import { DirectionProvider } from "@radix-ui/react-direction"
|
||||||
import { $authenticated, $publicKey, $copyContent, $direction } from "./lib/stores.ts"
|
import { $authenticated, $publicKey, $copyContent, $direction } from "./lib/stores.ts"
|
||||||
import { pb, updateUserSettings } from "./lib/api.ts"
|
import { pb, updateUserSettings, updateCookieToken } from "./lib/api.ts"
|
||||||
import * as systemsManager from "./lib/systemsManager.ts"
|
import * as systemsManager from "./lib/systemsManager.ts"
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
import { Toaster } from "./components/ui/toaster.tsx"
|
import { Toaster } from "./components/ui/toaster.tsx"
|
||||||
@@ -27,8 +27,10 @@ const App = memo(() => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// change auth store on auth change
|
// change auth store on auth change
|
||||||
|
updateCookieToken()
|
||||||
pb.authStore.onChange(() => {
|
pb.authStore.onChange(() => {
|
||||||
$authenticated.set(pb.authStore.isValid)
|
$authenticated.set(pb.authStore.isValid)
|
||||||
|
updateCookieToken()
|
||||||
})
|
})
|
||||||
// get version / public key
|
// get version / public key
|
||||||
pb.send("/api/beszel/getkey", {}).then((data) => {
|
pb.send("/api/beszel/getkey", {}).then((data) => {
|
||||||
@@ -36,11 +38,17 @@ const App = memo(() => {
|
|||||||
})
|
})
|
||||||
// get user settings
|
// get user settings
|
||||||
updateUserSettings()
|
updateUserSettings()
|
||||||
|
const startingSystems = globalThis.BESZEL.SYSTEMS
|
||||||
|
for (const system of startingSystems) {
|
||||||
|
// if (typeof system.info === "string") {
|
||||||
|
system.info = JSON.parse(system.info as unknown as string)
|
||||||
|
// }
|
||||||
|
}
|
||||||
// need to get system list before alerts
|
// need to get system list before alerts
|
||||||
systemsManager.init()
|
systemsManager.init()
|
||||||
systemsManager
|
systemsManager
|
||||||
// get current systems list
|
// get current systems list
|
||||||
.refresh()
|
.refresh(startingSystems)
|
||||||
// subscribe to new system updates
|
// subscribe to new system updates
|
||||||
.then(systemsManager.subscribe)
|
.then(systemsManager.subscribe)
|
||||||
// get current alerts
|
// get current alerts
|
||||||
@@ -51,6 +59,7 @@ const App = memo(() => {
|
|||||||
// updateFavicon("favicon.svg")
|
// updateFavicon("favicon.svg")
|
||||||
alertManager.unsubscribe()
|
alertManager.unsubscribe()
|
||||||
systemsManager.unsubscribe()
|
systemsManager.unsubscribe()
|
||||||
|
globalThis.BESZEL.SYSTEMS = []
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|||||||
2
beszel/site/src/types.d.ts
vendored
2
beszel/site/src/types.d.ts
vendored
@@ -7,6 +7,8 @@ declare global {
|
|||||||
BASE_PATH: string
|
BASE_PATH: string
|
||||||
HUB_VERSION: string
|
HUB_VERSION: string
|
||||||
HUB_URL: string
|
HUB_URL: string
|
||||||
|
/** initial list of systems */
|
||||||
|
SYSTEMS: SystemRecord[]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -216,11 +216,11 @@ if [ "$UNINSTALL" = true ]; then
|
|||||||
echo "Removing the OpenRC service files..."
|
echo "Removing the OpenRC service files..."
|
||||||
rm -f /etc/init.d/beszel-agent
|
rm -f /etc/init.d/beszel-agent
|
||||||
|
|
||||||
# Remove the daily update cron job if it exists
|
# Remove the update service if it exists
|
||||||
echo "Removing the daily update cron job..."
|
echo "Removing the daily update service..."
|
||||||
if crontab -u root -l 2>/dev/null | grep -q "beszel-agent.*update"; then
|
rc-service beszel-agent-update stop 2>/dev/null
|
||||||
crontab -u root -l 2>/dev/null | grep -v "beszel-agent.*update" | crontab -u root -
|
rc-update del beszel-agent-update default 2>/dev/null
|
||||||
fi
|
rm -f /etc/init.d/beszel-agent-update
|
||||||
|
|
||||||
# Remove log files
|
# Remove log files
|
||||||
echo "Removing log files..."
|
echo "Removing log files..."
|
||||||
@@ -321,9 +321,6 @@ if [ -z "$KEY" ]; then
|
|||||||
read KEY
|
read KEY
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Remove newlines from KEY
|
|
||||||
KEY=$(echo "$KEY" | tr -d '\n')
|
|
||||||
|
|
||||||
# TOKEN and HUB_URL are optional for backwards compatibility - no interactive prompts
|
# TOKEN and HUB_URL are optional for backwards compatibility - no interactive prompts
|
||||||
# They will be set as empty environment variables if not provided
|
# They will be set as empty environment variables if not provided
|
||||||
|
|
||||||
@@ -526,19 +523,35 @@ EOF
|
|||||||
elif [ "$AUTO_UPDATE_FLAG" = "false" ]; then
|
elif [ "$AUTO_UPDATE_FLAG" = "false" ]; then
|
||||||
AUTO_UPDATE="n"
|
AUTO_UPDATE="n"
|
||||||
else
|
else
|
||||||
printf "\nEnable automatic daily updates for beszel-agent? (y/n): "
|
printf "\nWould you like to enable automatic daily updates for beszel-agent? (y/n): "
|
||||||
read AUTO_UPDATE
|
read AUTO_UPDATE
|
||||||
fi
|
fi
|
||||||
case "$AUTO_UPDATE" in
|
case "$AUTO_UPDATE" in
|
||||||
[Yy]*)
|
[Yy]*)
|
||||||
echo "Setting up daily automatic updates for beszel-agent..."
|
echo "Setting up daily automatic updates for beszel-agent..."
|
||||||
|
|
||||||
# Create cron job to run beszel-agent update command daily at midnight
|
cat >/etc/init.d/beszel-agent-update <<EOF
|
||||||
if ! crontab -u root -l 2>/dev/null | grep -q "beszel-agent.*update"; then
|
#!/sbin/openrc-run
|
||||||
(crontab -u root -l 2>/dev/null; echo "12 0 * * * /opt/beszel-agent/beszel-agent update >/dev/null 2>&1") | crontab -u root -
|
|
||||||
fi
|
|
||||||
|
|
||||||
printf "\nDaily updates have been enabled via cron job.\n"
|
name="beszel-agent-update"
|
||||||
|
description="Update beszel-agent if needed"
|
||||||
|
|
||||||
|
depend() {
|
||||||
|
need beszel-agent
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
ebegin "Checking for beszel-agent updates"
|
||||||
|
/opt/beszel-agent/beszel-agent update
|
||||||
|
eend $?
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x /etc/init.d/beszel-agent-update
|
||||||
|
rc-update add beszel-agent-update default
|
||||||
|
rc-service beszel-agent-update start
|
||||||
|
|
||||||
|
printf "\nAutomatic daily updates have been enabled.\n"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
@@ -599,7 +612,7 @@ EOF
|
|||||||
AUTO_UPDATE="n"
|
AUTO_UPDATE="n"
|
||||||
sleep 1 # give time for the service to start
|
sleep 1 # give time for the service to start
|
||||||
else
|
else
|
||||||
printf "\nEnable automatic daily updates for beszel-agent? (y/n): "
|
printf "\nWould you like to enable automatic daily updates for beszel-agent? (y/n): "
|
||||||
read AUTO_UPDATE
|
read AUTO_UPDATE
|
||||||
fi
|
fi
|
||||||
case "$AUTO_UPDATE" in
|
case "$AUTO_UPDATE" in
|
||||||
@@ -607,12 +620,12 @@ EOF
|
|||||||
echo "Setting up daily automatic updates for beszel-agent..."
|
echo "Setting up daily automatic updates for beszel-agent..."
|
||||||
|
|
||||||
cat >/etc/crontabs/beszel <<EOF
|
cat >/etc/crontabs/beszel <<EOF
|
||||||
12 0 * * * /etc/init.d/beszel-agent update
|
0 0 * * * /etc/init.d/beszel-agent update
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
/etc/init.d/cron restart
|
/etc/init.d/cron restart
|
||||||
|
|
||||||
printf "\nDaily updates have been enabled.\n"
|
printf "\nAutomatic daily updates have been enabled.\n"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
@@ -682,7 +695,7 @@ EOF
|
|||||||
AUTO_UPDATE="n"
|
AUTO_UPDATE="n"
|
||||||
sleep 1 # give time for the service to start
|
sleep 1 # give time for the service to start
|
||||||
else
|
else
|
||||||
printf "\nEnable automatic daily updates for beszel-agent? (y/n): "
|
printf "\nWould you like to enable automatic daily updates for beszel-agent? (y/n): "
|
||||||
read AUTO_UPDATE
|
read AUTO_UPDATE
|
||||||
fi
|
fi
|
||||||
case "$AUTO_UPDATE" in
|
case "$AUTO_UPDATE" in
|
||||||
@@ -717,7 +730,7 @@ EOF
|
|||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl enable --now beszel-agent-update.timer
|
systemctl enable --now beszel-agent-update.timer
|
||||||
|
|
||||||
printf "\nDaily updates have been enabled.\n"
|
printf "\nAutomatic daily updates have been enabled.\n"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user