Better
This commit is contained in:
25
cmd/serve.go
25
cmd/serve.go
@@ -128,6 +128,12 @@ Examples:
|
|||||||
ntfy serve --listen-http :8080 # Starts server with alternate port`,
|
ntfy serve --listen-http :8080 # Starts server with alternate port`,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// App metadata fields used to pass from
|
||||||
|
const (
|
||||||
|
MetadataKeyCommit = "commit"
|
||||||
|
MetadataKeyDate = "date"
|
||||||
|
)
|
||||||
|
|
||||||
func execServe(c *cli.Context) error {
|
func execServe(c *cli.Context) error {
|
||||||
if c.NArg() > 0 {
|
if c.NArg() > 0 {
|
||||||
return errors.New("no arguments expected, see 'ntfy serve --help' for help")
|
return errors.New("no arguments expected, see 'ntfy serve --help' for help")
|
||||||
@@ -501,7 +507,9 @@ func execServe(c *cli.Context) error {
|
|||||||
conf.WebPushStartupQueries = webPushStartupQueries
|
conf.WebPushStartupQueries = webPushStartupQueries
|
||||||
conf.WebPushExpiryDuration = webPushExpiryDuration
|
conf.WebPushExpiryDuration = webPushExpiryDuration
|
||||||
conf.WebPushExpiryWarningDuration = webPushExpiryWarningDuration
|
conf.WebPushExpiryWarningDuration = webPushExpiryWarningDuration
|
||||||
conf.Version = c.App.Version
|
conf.BuildVersion = c.App.Version
|
||||||
|
conf.BuildDate = maybeFromMetadata(c.App.Metadata, MetadataKeyDate)
|
||||||
|
conf.BuildCommit = maybeFromMetadata(c.App.Metadata, MetadataKeyCommit)
|
||||||
|
|
||||||
// Check if we should run as a Windows service
|
// Check if we should run as a Windows service
|
||||||
if ranAsService, err := maybeRunAsService(conf); err != nil {
|
if ranAsService, err := maybeRunAsService(conf); err != nil {
|
||||||
@@ -655,3 +663,18 @@ func parseTokens(users []*user.User, tokensRaw []string) (map[string][]*user.Tok
|
|||||||
}
|
}
|
||||||
return tokens, nil
|
return tokens, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func maybeFromMetadata(m map[string]any, key string) string {
|
||||||
|
if m == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
v, exists := m[key]
|
||||||
|
if !exists {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
s, ok := v.(string)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|||||||
19
main.go
19
main.go
@@ -2,12 +2,14 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"heckel.io/ntfy/v2/cmd"
|
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
"heckel.io/ntfy/v2/cmd"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// These variables are set during build time using -ldflags
|
||||||
var (
|
var (
|
||||||
version = "dev"
|
version = "dev"
|
||||||
commit = "unknown"
|
commit = "unknown"
|
||||||
@@ -24,13 +26,24 @@ the Matrix room (https://matrix.to/#/#ntfy:matrix.org).
|
|||||||
|
|
||||||
ntfy %s (%s), runtime %s, built at %s
|
ntfy %s (%s), runtime %s, built at %s
|
||||||
Copyright (C) Philipp C. Heckel, licensed under Apache License 2.0 & GPLv2
|
Copyright (C) Philipp C. Heckel, licensed under Apache License 2.0 & GPLv2
|
||||||
`, version, commit[:7], runtime.Version(), date)
|
`, version, maybeShortCommit(commit), runtime.Version(), date)
|
||||||
|
|
||||||
app := cmd.New()
|
app := cmd.New()
|
||||||
app.Version = version
|
app.Version = version
|
||||||
|
app.Metadata = map[string]any{
|
||||||
|
cmd.MetadataKeyDate: date,
|
||||||
|
cmd.MetadataKeyCommit: commit,
|
||||||
|
}
|
||||||
|
|
||||||
if err := app.Run(os.Args); err != nil {
|
if err := app.Run(os.Args); err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err.Error())
|
fmt.Fprintln(os.Stderr, err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func maybeShortCommit(commit string) string {
|
||||||
|
if len(commit) > 7 {
|
||||||
|
return commit[:7]
|
||||||
|
}
|
||||||
|
return commit
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"reflect"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -182,7 +183,9 @@ type Config struct {
|
|||||||
WebPushStartupQueries string
|
WebPushStartupQueries string
|
||||||
WebPushExpiryDuration time.Duration
|
WebPushExpiryDuration time.Duration
|
||||||
WebPushExpiryWarningDuration time.Duration
|
WebPushExpiryWarningDuration time.Duration
|
||||||
Version string // Injected by App
|
BuildVersion string // Injected by App
|
||||||
|
BuildDate string // Injected by App
|
||||||
|
BuildCommit string // Injected by App
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfig instantiates a default new server config
|
// NewConfig instantiates a default new server config
|
||||||
@@ -269,23 +272,31 @@ func NewConfig() *Config {
|
|||||||
EnableReservations: false,
|
EnableReservations: false,
|
||||||
RequireLogin: false,
|
RequireLogin: false,
|
||||||
AccessControlAllowOrigin: "*",
|
AccessControlAllowOrigin: "*",
|
||||||
Version: "",
|
|
||||||
WebPushPrivateKey: "",
|
WebPushPrivateKey: "",
|
||||||
WebPushPublicKey: "",
|
WebPushPublicKey: "",
|
||||||
WebPushFile: "",
|
WebPushFile: "",
|
||||||
WebPushEmailAddress: "",
|
WebPushEmailAddress: "",
|
||||||
WebPushExpiryDuration: DefaultWebPushExpiryDuration,
|
WebPushExpiryDuration: DefaultWebPushExpiryDuration,
|
||||||
WebPushExpiryWarningDuration: DefaultWebPushExpiryWarningDuration,
|
WebPushExpiryWarningDuration: DefaultWebPushExpiryWarningDuration,
|
||||||
|
BuildVersion: "",
|
||||||
|
BuildDate: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash computes a SHA-256 hash of the configuration. This is used to detect
|
// Hash computes an SHA-256 hash of the configuration. This is used to detect
|
||||||
// configuration changes for the web app version check feature.
|
// configuration changes for the web app version check feature. It uses reflection
|
||||||
|
// to include all JSON-serializable fields automatically.
|
||||||
func (c *Config) Hash() string {
|
func (c *Config) Hash() string {
|
||||||
b, err := json.Marshal(c)
|
v := reflect.ValueOf(*c)
|
||||||
if err != nil {
|
t := v.Type()
|
||||||
fmt.Println(err)
|
var result string
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
field := v.Field(i)
|
||||||
|
fieldName := t.Field(i).Name
|
||||||
|
// Try to marshal the field and skip if it fails (e.g. *template.Template, netip.Prefix)
|
||||||
|
if b, err := json.Marshal(field.Interface()); err == nil {
|
||||||
|
result += fmt.Sprintf("%s:%s|", fieldName, string(b))
|
||||||
}
|
}
|
||||||
fmt.Println(string(b))
|
}
|
||||||
return fmt.Sprintf("%x", sha256.Sum256(b))
|
return fmt.Sprintf("%x", sha256.Sum256([]byte(result)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ var (
|
|||||||
matrixPushPath = "/_matrix/push/v1/notify"
|
matrixPushPath = "/_matrix/push/v1/notify"
|
||||||
metricsPath = "/metrics"
|
metricsPath = "/metrics"
|
||||||
apiHealthPath = "/v1/health"
|
apiHealthPath = "/v1/health"
|
||||||
apiVersionPath = "/v1/version"
|
apiConfigPath = "/v1/config"
|
||||||
apiStatsPath = "/v1/stats"
|
apiStatsPath = "/v1/stats"
|
||||||
apiWebPushPath = "/v1/webpush"
|
apiWebPushPath = "/v1/webpush"
|
||||||
apiTiersPath = "/v1/tiers"
|
apiTiersPath = "/v1/tiers"
|
||||||
@@ -278,9 +278,9 @@ func (s *Server) Run() error {
|
|||||||
if s.config.ProfileListenHTTP != "" {
|
if s.config.ProfileListenHTTP != "" {
|
||||||
listenStr += fmt.Sprintf(" %s[http/profile]", s.config.ProfileListenHTTP)
|
listenStr += fmt.Sprintf(" %s[http/profile]", s.config.ProfileListenHTTP)
|
||||||
}
|
}
|
||||||
log.Tag(tagStartup).Info("Listening on%s, ntfy %s, log level is %s", listenStr, s.config.Version, log.CurrentLevel().String())
|
log.Tag(tagStartup).Info("Listening on%s, ntfy %s, log level is %s", listenStr, s.config.BuildVersion, log.CurrentLevel().String())
|
||||||
if log.IsFile() {
|
if log.IsFile() {
|
||||||
fmt.Fprintf(os.Stderr, "Listening on%s, ntfy %s\n", listenStr, s.config.Version)
|
fmt.Fprintf(os.Stderr, "Listening on%s, ntfy %s\n", listenStr, s.config.BuildVersion)
|
||||||
fmt.Fprintf(os.Stderr, "Logs are written to %s\n", log.File())
|
fmt.Fprintf(os.Stderr, "Logs are written to %s\n", log.File())
|
||||||
}
|
}
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
@@ -461,8 +461,8 @@ func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request, v *visit
|
|||||||
return s.ensureWebEnabled(s.handleEmpty)(w, r, v)
|
return s.ensureWebEnabled(s.handleEmpty)(w, r, v)
|
||||||
} else if r.Method == http.MethodGet && r.URL.Path == apiHealthPath {
|
} else if r.Method == http.MethodGet && r.URL.Path == apiHealthPath {
|
||||||
return s.handleHealth(w, r, v)
|
return s.handleHealth(w, r, v)
|
||||||
} else if r.Method == http.MethodGet && r.URL.Path == apiVersionPath {
|
} else if r.Method == http.MethodGet && r.URL.Path == apiConfigPath {
|
||||||
return s.handleVersion(w, r, v)
|
return s.handleConfig(w, r, v)
|
||||||
} else if r.Method == http.MethodGet && r.URL.Path == webConfigPath {
|
} else if r.Method == http.MethodGet && r.URL.Path == webConfigPath {
|
||||||
return s.ensureWebEnabled(s.handleWebConfig)(w, r, v)
|
return s.ensureWebEnabled(s.handleWebConfig)(w, r, v)
|
||||||
} else if r.Method == http.MethodGet && r.URL.Path == webManifestPath {
|
} else if r.Method == http.MethodGet && r.URL.Path == webManifestPath {
|
||||||
@@ -603,16 +603,24 @@ func (s *Server) handleHealth(w http.ResponseWriter, _ *http.Request, _ *visitor
|
|||||||
return s.writeJSON(w, response)
|
return s.writeJSON(w, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleVersion(w http.ResponseWriter, _ *http.Request, _ *visitor) error {
|
func (s *Server) handleConfig(w http.ResponseWriter, _ *http.Request, _ *visitor) error {
|
||||||
response := &apiVersionResponse{
|
w.Header().Set("Cache-Control", "no-cache")
|
||||||
Version: s.config.Version,
|
return s.writeJSON(w, s.configResponse())
|
||||||
ConfigHash: s.config.Hash(),
|
|
||||||
}
|
|
||||||
return s.writeJSON(w, response)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleWebConfig(w http.ResponseWriter, _ *http.Request, _ *visitor) error {
|
func (s *Server) handleWebConfig(w http.ResponseWriter, _ *http.Request, _ *visitor) error {
|
||||||
response := &apiConfigResponse{
|
b, err := json.MarshalIndent(s.configResponse(), "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "text/javascript")
|
||||||
|
w.Header().Set("Cache-Control", "no-cache")
|
||||||
|
_, err = io.WriteString(w, fmt.Sprintf("// Generated server configuration\nvar config = %s;\n", string(b)))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) configResponse() *apiConfigResponse {
|
||||||
|
return &apiConfigResponse{
|
||||||
BaseURL: "", // Will translate to window.location.origin
|
BaseURL: "", // Will translate to window.location.origin
|
||||||
AppRoot: s.config.WebRoot,
|
AppRoot: s.config.WebRoot,
|
||||||
EnableLogin: s.config.EnableLogin,
|
EnableLogin: s.config.EnableLogin,
|
||||||
@@ -628,14 +636,6 @@ func (s *Server) handleWebConfig(w http.ResponseWriter, _ *http.Request, _ *visi
|
|||||||
DisallowedTopics: s.config.DisallowedTopics,
|
DisallowedTopics: s.config.DisallowedTopics,
|
||||||
ConfigHash: s.config.Hash(),
|
ConfigHash: s.config.Hash(),
|
||||||
}
|
}
|
||||||
b, err := json.MarshalIndent(response, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
w.Header().Set("Content-Type", "text/javascript")
|
|
||||||
w.Header().Set("Cache-Control", "no-cache")
|
|
||||||
_, err = io.WriteString(w, fmt.Sprintf("// Generated server configuration\nvar config = %s;\n", string(b)))
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleWebManifest serves the web app manifest for the progressive web app (PWA)
|
// handleWebManifest serves the web app manifest for the progressive web app (PWA)
|
||||||
@@ -1003,7 +1003,7 @@ func (s *Server) forwardPollRequest(v *visitor, m *message) {
|
|||||||
logvm(v, m).Err(err).Warn("Unable to publish poll request")
|
logvm(v, m).Err(err).Warn("Unable to publish poll request")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
req.Header.Set("User-Agent", "ntfy/"+s.config.Version)
|
req.Header.Set("User-Agent", "ntfy/"+s.config.BuildVersion)
|
||||||
req.Header.Set("X-Poll-ID", m.ID)
|
req.Header.Set("X-Poll-ID", m.ID)
|
||||||
if s.config.UpstreamAccessToken != "" {
|
if s.config.UpstreamAccessToken != "" {
|
||||||
req.Header.Set("Authorization", util.BearerAuth(s.config.UpstreamAccessToken))
|
req.Header.Set("Authorization", util.BearerAuth(s.config.UpstreamAccessToken))
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ func (s *Server) callPhoneInternal(data url.Values) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
req.Header.Set("User-Agent", "ntfy/"+s.config.Version)
|
req.Header.Set("User-Agent", "ntfy/"+s.config.BuildVersion)
|
||||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||||
req.Header.Set("Authorization", util.BasicAuth(s.config.TwilioAccount, s.config.TwilioAuthToken))
|
req.Header.Set("Authorization", util.BasicAuth(s.config.TwilioAccount, s.config.TwilioAuthToken))
|
||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := http.DefaultClient.Do(req)
|
||||||
@@ -149,7 +149,7 @@ func (s *Server) verifyPhoneNumber(v *visitor, r *http.Request, phoneNumber, cha
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
req.Header.Set("User-Agent", "ntfy/"+s.config.Version)
|
req.Header.Set("User-Agent", "ntfy/"+s.config.BuildVersion)
|
||||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||||
req.Header.Set("Authorization", util.BasicAuth(s.config.TwilioAccount, s.config.TwilioAuthToken))
|
req.Header.Set("Authorization", util.BasicAuth(s.config.TwilioAccount, s.config.TwilioAuthToken))
|
||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := http.DefaultClient.Do(req)
|
||||||
@@ -175,7 +175,7 @@ func (s *Server) verifyPhoneNumberCheck(v *visitor, r *http.Request, phoneNumber
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
req.Header.Set("User-Agent", "ntfy/"+s.config.Version)
|
req.Header.Set("User-Agent", "ntfy/"+s.config.BuildVersion)
|
||||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||||
req.Header.Set("Authorization", util.BasicAuth(s.config.TwilioAccount, s.config.TwilioAuthToken))
|
req.Header.Set("Authorization", util.BasicAuth(s.config.TwilioAccount, s.config.TwilioAuthToken))
|
||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
|||||||
@@ -317,11 +317,6 @@ type apiHealthResponse struct {
|
|||||||
Healthy bool `json:"healthy"`
|
Healthy bool `json:"healthy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type apiVersionResponse struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
ConfigHash string `json:"config_hash"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type apiStatsResponse struct {
|
type apiStatsResponse struct {
|
||||||
Messages int64 `json:"messages"`
|
Messages int64 `json:"messages"`
|
||||||
MessagesRate float64 `json:"messages_rate"` // Average number of messages per second
|
MessagesRate float64 `json:"messages_rate"` // Average number of messages per second
|
||||||
|
|||||||
@@ -19,7 +19,11 @@ class Pruner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stopWorker() {
|
stopWorker() {
|
||||||
|
if (this.timer) {
|
||||||
clearTimeout(this.timer);
|
clearTimeout(this.timer);
|
||||||
|
this.timer = null;
|
||||||
|
}
|
||||||
|
console.log("[VersionChecker] Stopped pruner checker");
|
||||||
}
|
}
|
||||||
|
|
||||||
async prune() {
|
async prune() {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* VersionChecker polls the /v1/version endpoint to detect server restarts
|
* VersionChecker polls the /v1/config endpoint to detect new server versions
|
||||||
* or configuration changes, prompting users to refresh the page.
|
* or configuration changes, prompting users to refresh the page.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -9,7 +9,8 @@ class VersionChecker {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.initialConfigHash = null;
|
this.initialConfigHash = null;
|
||||||
this.listener = null;
|
this.listener = null;
|
||||||
this.intervalId = null;
|
console.log("XXXXXXxxxx set listener null");
|
||||||
|
this.timer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,73 +19,52 @@ class VersionChecker {
|
|||||||
*/
|
*/
|
||||||
startWorker() {
|
startWorker() {
|
||||||
// Store initial config hash from the config loaded at page load
|
// Store initial config hash from the config loaded at page load
|
||||||
this.initialConfigHash = window.config?.config_hash || null;
|
this.initialConfigHash = window.config?.config_hash || "";
|
||||||
|
console.log("[VersionChecker] Starting version checker");
|
||||||
if (!this.initialConfigHash) {
|
this.timer = setInterval(() => this.checkVersion(), CHECK_INTERVAL);
|
||||||
console.log("[VersionChecker] No initial config_hash found, version checking disabled");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("[VersionChecker] Starting version checker with initial hash:", this.initialConfigHash);
|
|
||||||
|
|
||||||
// Start polling
|
|
||||||
this.intervalId = setInterval(() => this.checkVersion(), CHECK_INTERVAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops the version checker worker.
|
|
||||||
*/
|
|
||||||
stopWorker() {
|
stopWorker() {
|
||||||
if (this.intervalId) {
|
if (this.timer) {
|
||||||
clearInterval(this.intervalId);
|
clearInterval(this.timer);
|
||||||
this.intervalId = null;
|
this.timer = null;
|
||||||
}
|
}
|
||||||
console.log("[VersionChecker] Stopped version checker");
|
console.log("[VersionChecker] Stopped version checker");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers a listener that will be called when a version change is detected.
|
|
||||||
* @param {function} listener - Callback function that receives no arguments
|
|
||||||
*/
|
|
||||||
registerListener(listener) {
|
registerListener(listener) {
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Resets the listener.
|
|
||||||
*/
|
|
||||||
resetListener() {
|
resetListener() {
|
||||||
this.listener = null;
|
this.listener = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches the current version from the server and compares it with the initial config hash.
|
|
||||||
*/
|
|
||||||
async checkVersion() {
|
async checkVersion() {
|
||||||
if (!this.initialConfigHash) {
|
if (!this.initialConfigHash) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${window.config?.base_url || ""}/v1/version`);
|
const response = await fetch(`${window.config?.base_url || ""}/v1/config`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.log("[VersionChecker] Failed to fetch version:", response.status);
|
console.log("[VersionChecker] Failed to fetch config:", response.status);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
const currentHash = data.config_hash;
|
const currentHash = data.config_hash;
|
||||||
|
|
||||||
console.log("[VersionChecker] Checked version, initial:", this.initialConfigHash, "current:", currentHash);
|
|
||||||
|
|
||||||
if (currentHash && currentHash !== this.initialConfigHash) {
|
if (currentHash && currentHash !== this.initialConfigHash) {
|
||||||
console.log("[VersionChecker] Config hash changed, notifying listener");
|
console.log("[VersionChecker] Version or config changed, showing banner");
|
||||||
if (this.listener) {
|
if (this.listener) {
|
||||||
this.listener();
|
this.listener();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
console.log("[VersionChecker] No version change detected");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("[VersionChecker] Error checking version:", error);
|
console.log("[VersionChecker] Error checking config:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
import {
|
import {
|
||||||
Drawer,
|
|
||||||
ListItemButton,
|
|
||||||
ListItemIcon,
|
|
||||||
ListItemText,
|
|
||||||
Toolbar,
|
|
||||||
Divider,
|
|
||||||
List,
|
|
||||||
Alert,
|
Alert,
|
||||||
AlertTitle,
|
AlertTitle,
|
||||||
Badge,
|
Badge,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
|
Divider,
|
||||||
|
Drawer,
|
||||||
|
IconButton,
|
||||||
Link,
|
Link,
|
||||||
|
List,
|
||||||
|
ListItemButton,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText,
|
||||||
ListSubheader,
|
ListSubheader,
|
||||||
Portal,
|
Portal,
|
||||||
|
Toolbar,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Typography,
|
Typography,
|
||||||
Box,
|
|
||||||
IconButton,
|
|
||||||
Button,
|
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useCallback, useContext, useState } from "react";
|
import { useContext, useState } from "react";
|
||||||
import ChatBubbleOutlineIcon from "@mui/icons-material/ChatBubbleOutline";
|
import ChatBubbleOutlineIcon from "@mui/icons-material/ChatBubbleOutline";
|
||||||
import Person from "@mui/icons-material/Person";
|
import Person from "@mui/icons-material/Person";
|
||||||
import SettingsIcon from "@mui/icons-material/Settings";
|
import SettingsIcon from "@mui/icons-material/Settings";
|
||||||
@@ -93,9 +93,9 @@ const NavList = (props) => {
|
|||||||
const [subscribeDialogOpen, setSubscribeDialogOpen] = useState(false);
|
const [subscribeDialogOpen, setSubscribeDialogOpen] = useState(false);
|
||||||
const [versionChanged, setVersionChanged] = useState(false);
|
const [versionChanged, setVersionChanged] = useState(false);
|
||||||
|
|
||||||
const handleVersionChange = useCallback(() => {
|
const handleVersionChange = () => {
|
||||||
setVersionChanged(true);
|
setVersionChanged(true);
|
||||||
}, []);
|
};
|
||||||
|
|
||||||
useVersionChangeListener(handleVersionChange);
|
useVersionChangeListener(handleVersionChange);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user