Refine
This commit is contained in:
@@ -182,7 +182,7 @@ type Config struct {
|
|||||||
WebPushStartupQueries string
|
WebPushStartupQueries string
|
||||||
WebPushExpiryDuration time.Duration
|
WebPushExpiryDuration time.Duration
|
||||||
WebPushExpiryWarningDuration time.Duration
|
WebPushExpiryWarningDuration time.Duration
|
||||||
Version string // injected by App
|
Version string // Injected by App
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfig instantiates a default new server config
|
// NewConfig instantiates a default new server config
|
||||||
@@ -279,86 +279,13 @@ func NewConfig() *Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// configHashData is a subset of Config fields used for computing the config hash.
|
|
||||||
// It excludes sensitive fields (keys, passwords, tokens) and runtime-only fields.
|
|
||||||
type configHashData struct {
|
|
||||||
BaseURL string
|
|
||||||
ListenHTTP string
|
|
||||||
ListenHTTPS string
|
|
||||||
ListenUnix string
|
|
||||||
CacheDuration time.Duration
|
|
||||||
AttachmentTotalSizeLimit int64
|
|
||||||
AttachmentFileSizeLimit int64
|
|
||||||
AttachmentExpiryDuration time.Duration
|
|
||||||
KeepaliveInterval time.Duration
|
|
||||||
ManagerInterval time.Duration
|
|
||||||
DisallowedTopics []string
|
|
||||||
WebRoot string
|
|
||||||
MessageDelayMin time.Duration
|
|
||||||
MessageDelayMax time.Duration
|
|
||||||
MessageSizeLimit int
|
|
||||||
TotalTopicLimit int
|
|
||||||
VisitorSubscriptionLimit int
|
|
||||||
VisitorAttachmentTotalSizeLimit int64
|
|
||||||
VisitorAttachmentDailyBandwidthLimit int64
|
|
||||||
VisitorRequestLimitBurst int
|
|
||||||
VisitorRequestLimitReplenish time.Duration
|
|
||||||
VisitorMessageDailyLimit int
|
|
||||||
VisitorEmailLimitBurst int
|
|
||||||
VisitorEmailLimitReplenish time.Duration
|
|
||||||
EnableSignup bool
|
|
||||||
EnableLogin bool
|
|
||||||
RequireLogin bool
|
|
||||||
EnableReservations bool
|
|
||||||
EnableMetrics bool
|
|
||||||
EnablePayments bool
|
|
||||||
EnableCalls bool
|
|
||||||
EnableEmails bool
|
|
||||||
EnableWebPush bool
|
|
||||||
BillingContact string
|
|
||||||
Version string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hash computes a SHA-256 hash of the configuration. This is used to detect
|
// Hash computes a 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.
|
||||||
func (c *Config) Hash() string {
|
func (c *Config) Hash() string {
|
||||||
data := configHashData{
|
b, err := json.Marshal(c)
|
||||||
BaseURL: c.BaseURL,
|
if err != nil {
|
||||||
ListenHTTP: c.ListenHTTP,
|
fmt.Println(err)
|
||||||
ListenHTTPS: c.ListenHTTPS,
|
|
||||||
ListenUnix: c.ListenUnix,
|
|
||||||
CacheDuration: c.CacheDuration,
|
|
||||||
AttachmentTotalSizeLimit: c.AttachmentTotalSizeLimit,
|
|
||||||
AttachmentFileSizeLimit: c.AttachmentFileSizeLimit,
|
|
||||||
AttachmentExpiryDuration: c.AttachmentExpiryDuration,
|
|
||||||
KeepaliveInterval: c.KeepaliveInterval,
|
|
||||||
ManagerInterval: c.ManagerInterval,
|
|
||||||
DisallowedTopics: c.DisallowedTopics,
|
|
||||||
WebRoot: c.WebRoot,
|
|
||||||
MessageDelayMin: c.MessageDelayMin,
|
|
||||||
MessageDelayMax: c.MessageDelayMax,
|
|
||||||
MessageSizeLimit: c.MessageSizeLimit,
|
|
||||||
TotalTopicLimit: c.TotalTopicLimit,
|
|
||||||
VisitorSubscriptionLimit: c.VisitorSubscriptionLimit,
|
|
||||||
VisitorAttachmentTotalSizeLimit: c.VisitorAttachmentTotalSizeLimit,
|
|
||||||
VisitorAttachmentDailyBandwidthLimit: c.VisitorAttachmentDailyBandwidthLimit,
|
|
||||||
VisitorRequestLimitBurst: c.VisitorRequestLimitBurst,
|
|
||||||
VisitorRequestLimitReplenish: c.VisitorRequestLimitReplenish,
|
|
||||||
VisitorMessageDailyLimit: c.VisitorMessageDailyLimit,
|
|
||||||
VisitorEmailLimitBurst: c.VisitorEmailLimitBurst,
|
|
||||||
VisitorEmailLimitReplenish: c.VisitorEmailLimitReplenish,
|
|
||||||
EnableSignup: c.EnableSignup,
|
|
||||||
EnableLogin: c.EnableLogin,
|
|
||||||
RequireLogin: c.RequireLogin,
|
|
||||||
EnableReservations: c.EnableReservations,
|
|
||||||
EnableMetrics: c.EnableMetrics,
|
|
||||||
EnablePayments: c.StripeSecretKey != "",
|
|
||||||
EnableCalls: c.TwilioAccount != "",
|
|
||||||
EnableEmails: c.SMTPSenderFrom != "",
|
|
||||||
EnableWebPush: c.WebPushPublicKey != "",
|
|
||||||
BillingContact: c.BillingContact,
|
|
||||||
Version: c.Version,
|
|
||||||
}
|
}
|
||||||
b, _ := json.Marshal(data)
|
fmt.Println(string(b))
|
||||||
return fmt.Sprintf("%x", sha256.Sum256(b))
|
return fmt.Sprintf("%x", sha256.Sum256(b))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
"common_back": "Back",
|
"common_back": "Back",
|
||||||
"common_copy_to_clipboard": "Copy to clipboard",
|
"common_copy_to_clipboard": "Copy to clipboard",
|
||||||
"common_refresh": "Refresh",
|
"common_refresh": "Refresh",
|
||||||
"version_update_available": "New ntfy version available. Please refresh the page.",
|
"version_update_available_title": "New version available",
|
||||||
|
"version_update_available_description": "The ntfy server has been updated. Please refresh the page.",
|
||||||
"signup_title": "Create a ntfy account",
|
"signup_title": "Create a ntfy account",
|
||||||
"signup_form_username": "Username",
|
"signup_form_username": "Username",
|
||||||
"signup_form_password": "Password",
|
"signup_form_password": "Password",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* or configuration changes, prompting users to refresh the page.
|
* or configuration changes, prompting users to refresh the page.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const CHECK_INTERVAL = 5 * 60 * 1000; // 5 minutes
|
const CHECK_INTERVAL = 30 * 1000; // 5 * 60 * 1000; // 5 minutes
|
||||||
|
|
||||||
class VersionChecker {
|
class VersionChecker {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|||||||
@@ -1,18 +1,6 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { createContext, Suspense, useContext, useEffect, useState, useMemo, useCallback } from "react";
|
import { createContext, Suspense, useContext, useEffect, useState, useMemo } from "react";
|
||||||
import {
|
import { Box, Toolbar, CssBaseline, Backdrop, CircularProgress, useMediaQuery, ThemeProvider, createTheme } from "@mui/material";
|
||||||
Box,
|
|
||||||
Toolbar,
|
|
||||||
CssBaseline,
|
|
||||||
Backdrop,
|
|
||||||
CircularProgress,
|
|
||||||
useMediaQuery,
|
|
||||||
ThemeProvider,
|
|
||||||
createTheme,
|
|
||||||
Snackbar,
|
|
||||||
Button,
|
|
||||||
Alert,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { useLiveQuery } from "dexie-react-hooks";
|
import { useLiveQuery } from "dexie-react-hooks";
|
||||||
import { BrowserRouter, Outlet, Route, Routes, useParams } from "react-router-dom";
|
import { BrowserRouter, Outlet, Route, Routes, useParams } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -26,13 +14,7 @@ import userManager from "../app/UserManager";
|
|||||||
import { expandUrl, getKebabCaseLangStr } from "../app/utils";
|
import { expandUrl, getKebabCaseLangStr } from "../app/utils";
|
||||||
import ErrorBoundary from "./ErrorBoundary";
|
import ErrorBoundary from "./ErrorBoundary";
|
||||||
import routes from "./routes";
|
import routes from "./routes";
|
||||||
import {
|
import { useAccountListener, useBackgroundProcesses, useConnectionListeners, useWebPushTopics } from "./hooks";
|
||||||
useAccountListener,
|
|
||||||
useBackgroundProcesses,
|
|
||||||
useConnectionListeners,
|
|
||||||
useWebPushTopics,
|
|
||||||
useVersionChangeListener,
|
|
||||||
} from "./hooks";
|
|
||||||
import PublishDialog from "./PublishDialog";
|
import PublishDialog from "./PublishDialog";
|
||||||
import Messaging from "./Messaging";
|
import Messaging from "./Messaging";
|
||||||
import Login from "./Login";
|
import Login from "./Login";
|
||||||
@@ -118,12 +100,10 @@ const updateTitle = (newNotificationsCount) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Layout = () => {
|
const Layout = () => {
|
||||||
const { t } = useTranslation();
|
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const { account, setAccount } = useContext(AccountContext);
|
const { account, setAccount } = useContext(AccountContext);
|
||||||
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
|
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
|
||||||
const [sendDialogOpenMode, setSendDialogOpenMode] = useState("");
|
const [sendDialogOpenMode, setSendDialogOpenMode] = useState("");
|
||||||
const [versionChanged, setVersionChanged] = useState(false);
|
|
||||||
const users = useLiveQuery(() => userManager.all());
|
const users = useLiveQuery(() => userManager.all());
|
||||||
const subscriptions = useLiveQuery(() => subscriptionManager.all());
|
const subscriptions = useLiveQuery(() => subscriptionManager.all());
|
||||||
const webPushTopics = useWebPushTopics();
|
const webPushTopics = useWebPushTopics();
|
||||||
@@ -135,18 +115,9 @@ const Layout = () => {
|
|||||||
(config.base_url === s.baseUrl && params.topic === s.topic)
|
(config.base_url === s.baseUrl && params.topic === s.topic)
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleVersionChange = useCallback(() => {
|
|
||||||
setVersionChanged(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleRefresh = useCallback(() => {
|
|
||||||
window.location.reload();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useConnectionListeners(account, subscriptions, users, webPushTopics);
|
useConnectionListeners(account, subscriptions, users, webPushTopics);
|
||||||
useAccountListener(setAccount);
|
useAccountListener(setAccount);
|
||||||
useBackgroundProcesses();
|
useBackgroundProcesses();
|
||||||
useVersionChangeListener(handleVersionChange);
|
|
||||||
useEffect(() => updateTitle(newNotificationsCount), [newNotificationsCount]);
|
useEffect(() => updateTitle(newNotificationsCount), [newNotificationsCount]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -169,23 +140,6 @@ const Layout = () => {
|
|||||||
/>
|
/>
|
||||||
</Main>
|
</Main>
|
||||||
<Messaging selected={selected} dialogOpenMode={sendDialogOpenMode} onDialogOpenModeChange={setSendDialogOpenMode} />
|
<Messaging selected={selected} dialogOpenMode={sendDialogOpenMode} onDialogOpenModeChange={setSendDialogOpenMode} />
|
||||||
<Snackbar
|
|
||||||
open={versionChanged}
|
|
||||||
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
|
|
||||||
sx={{ bottom: { xs: 70, md: 24 } }}
|
|
||||||
>
|
|
||||||
<Alert
|
|
||||||
severity="info"
|
|
||||||
variant="filled"
|
|
||||||
action={
|
|
||||||
<Button color="inherit" size="small" onClick={handleRefresh}>
|
|
||||||
{t("common_refresh")}
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{t("version_update_available")}
|
|
||||||
</Alert>
|
|
||||||
</Snackbar>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import {
|
|||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useContext, useState } from "react";
|
import { useCallback, 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";
|
||||||
@@ -44,7 +44,7 @@ import UpgradeDialog from "./UpgradeDialog";
|
|||||||
import { AccountContext } from "./App";
|
import { AccountContext } from "./App";
|
||||||
import { PermissionDenyAll, PermissionRead, PermissionReadWrite, PermissionWrite } from "./ReserveIcons";
|
import { PermissionDenyAll, PermissionRead, PermissionReadWrite, PermissionWrite } from "./ReserveIcons";
|
||||||
import { SubscriptionPopup } from "./SubscriptionPopup";
|
import { SubscriptionPopup } from "./SubscriptionPopup";
|
||||||
import { useNotificationPermissionListener } from "./hooks";
|
import { useNotificationPermissionListener, useVersionChangeListener } from "./hooks";
|
||||||
|
|
||||||
const navWidth = 280;
|
const navWidth = 280;
|
||||||
|
|
||||||
@@ -91,6 +91,13 @@ const NavList = (props) => {
|
|||||||
const { account } = useContext(AccountContext);
|
const { account } = useContext(AccountContext);
|
||||||
const [subscribeDialogKey, setSubscribeDialogKey] = useState(0);
|
const [subscribeDialogKey, setSubscribeDialogKey] = useState(0);
|
||||||
const [subscribeDialogOpen, setSubscribeDialogOpen] = useState(false);
|
const [subscribeDialogOpen, setSubscribeDialogOpen] = useState(false);
|
||||||
|
const [versionChanged, setVersionChanged] = useState(false);
|
||||||
|
|
||||||
|
const handleVersionChange = useCallback(() => {
|
||||||
|
setVersionChanged(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useVersionChangeListener(handleVersionChange);
|
||||||
|
|
||||||
const handleSubscribeReset = () => {
|
const handleSubscribeReset = () => {
|
||||||
setSubscribeDialogOpen(false);
|
setSubscribeDialogOpen(false);
|
||||||
@@ -119,6 +126,7 @@ const NavList = (props) => {
|
|||||||
const showNotificationContextNotSupportedBox = notifier.browserSupported() && !notifier.contextSupported(); // Only show if notifications are generally supported in the browser
|
const showNotificationContextNotSupportedBox = notifier.browserSupported() && !notifier.contextSupported(); // Only show if notifications are generally supported in the browser
|
||||||
|
|
||||||
const alertVisible =
|
const alertVisible =
|
||||||
|
versionChanged ||
|
||||||
showNotificationPermissionRequired ||
|
showNotificationPermissionRequired ||
|
||||||
showNotificationPermissionDenied ||
|
showNotificationPermissionDenied ||
|
||||||
showNotificationIOSInstallRequired ||
|
showNotificationIOSInstallRequired ||
|
||||||
@@ -129,6 +137,7 @@ const NavList = (props) => {
|
|||||||
<>
|
<>
|
||||||
<Toolbar sx={{ display: { xs: "none", sm: "block" } }} />
|
<Toolbar sx={{ display: { xs: "none", sm: "block" } }} />
|
||||||
<List component="nav" sx={{ paddingTop: { xs: 0, sm: alertVisible ? 0 : "" } }}>
|
<List component="nav" sx={{ paddingTop: { xs: 0, sm: alertVisible ? 0 : "" } }}>
|
||||||
|
{versionChanged && <VersionUpdateBanner />}
|
||||||
{showNotificationPermissionRequired && <NotificationPermissionRequired />}
|
{showNotificationPermissionRequired && <NotificationPermissionRequired />}
|
||||||
{showNotificationPermissionDenied && <NotificationPermissionDeniedAlert />}
|
{showNotificationPermissionDenied && <NotificationPermissionDeniedAlert />}
|
||||||
{showNotificationBrowserNotSupportedBox && <NotificationBrowserNotSupportedAlert />}
|
{showNotificationBrowserNotSupportedBox && <NotificationBrowserNotSupportedAlert />}
|
||||||
@@ -425,4 +434,20 @@ const NotificationContextNotSupportedAlert = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const VersionUpdateBanner = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const handleRefresh = () => {
|
||||||
|
window.location.reload();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Alert severity="info" sx={{ paddingTop: 2 }}>
|
||||||
|
<AlertTitle>{t("version_update_available_title")}</AlertTitle>
|
||||||
|
<Typography gutterBottom>{t("version_update_available_description")}</Typography>
|
||||||
|
<Button sx={{ float: "right" }} color="inherit" size="small" onClick={handleRefresh}>
|
||||||
|
{t("common_refresh")}
|
||||||
|
</Button>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default Navigation;
|
export default Navigation;
|
||||||
|
|||||||
Reference in New Issue
Block a user