fix: allow approved private notification hosts (#1330)

This commit is contained in:
ZacharyZcR
2026-08-25 00:55:36 +08:00
committed by GitHub
parent f06d540466
commit d35458f78b
12 changed files with 250 additions and 13 deletions
+21
View File
@@ -216,3 +216,24 @@ export async function setAiPrivateEndpoints(
throw handleApiError(error, "update AI endpoint allowlist");
}
}
export async function getNotificationPrivateEndpoints(): Promise<string[]> {
try {
return (await authApi.get("/users/notification-private-endpoints")).data
.hosts;
} catch (error) {
throw handleApiError(error, "get notification endpoint allowlist");
}
}
export async function setNotificationPrivateEndpoints(
hosts: string[],
): Promise<string[]> {
try {
return (
await authApi.patch("/users/notification-private-endpoints", { hosts })
).data.hosts;
} catch (error) {
throw handleApiError(error, "update notification endpoint allowlist");
}
}
+3
View File
@@ -3595,6 +3595,9 @@
"aiGloballyEnabledDesc": "Let users turn on the AI assistant. While this is off, the assistant is hidden and blocked for everyone.",
"aiPrivateEndpoints": "Allowed private AI hosts",
"aiPrivateEndpointsDesc": "Hosts on your private network that users may point a provider at, such as a self-hosted Ollama. Separate them with commas.",
"notificationPrivateEndpoints": "Allowed private notification hosts",
"notificationPrivateEndpointsDesc": "Exact private hosts that notification channels may contact. Separate them with commas.",
"updateNotificationEndpointsFailed": "Failed to update notification endpoint allowlist",
"updateAiEnabledFailed": "Could not update the AI setting",
"updateAiEndpointsFailed": "Could not update the allowed hosts"
},
+26
View File
@@ -4,8 +4,10 @@ import { notifyAiStatusChanged } from "@/hooks/use-ai-availability";
import {
getAiGloballyEnabled,
getAiPrivateEndpoints,
getNotificationPrivateEndpoints,
setAiGloballyEnabled as setAiGloballyEnabledApi,
setAiPrivateEndpoints as setAiPrivateEndpointsApi,
setNotificationPrivateEndpoints as setNotificationPrivateEndpointsApi,
} from "@/api/ai-api";
import {
getUserList,
@@ -169,6 +171,8 @@ export function AdminSettingsPanel({
useState(true);
const [aiGloballyEnabled, setAiGloballyEnabled] = useState(false);
const [aiPrivateEndpoints, setAiPrivateEndpoints] = useState<string[]>([]);
const [notificationPrivateEndpoints, setNotificationPrivateEndpoints] =
useState<string[]>([]);
const [hostDefaults, setHostDefaults] = useState<HostDefaults>({});
const [touchInputSettings, setTouchInputSettings] =
useState<TouchInputSettings>({ ...TOUCH_INPUT_DEFAULTS });
@@ -365,6 +369,7 @@ export function AdminSettingsPanel({
touchInput,
aiEnabled,
aiEndpoints,
notificationEndpoints,
imageStorage,
] = await Promise.allSettled([
getRegistrationAllowed(),
@@ -383,6 +388,7 @@ export function AdminSettingsPanel({
getTouchInputSettings(),
getAiGloballyEnabled(),
getAiPrivateEndpoints(),
getNotificationPrivateEndpoints(),
getTerminalImageStorageSettings(),
]);
@@ -435,6 +441,9 @@ export function AdminSettingsPanel({
if (aiEndpoints.status === "fulfilled") {
setAiPrivateEndpoints(aiEndpoints.value);
}
if (notificationEndpoints.status === "fulfilled") {
setNotificationPrivateEndpoints(notificationEndpoints.value);
}
if (imageStorage.status === "fulfilled") {
setImageStorageSettings(imageStorage.value);
}
@@ -594,6 +603,19 @@ export function AdminSettingsPanel({
}
}
async function handleSaveNotificationPrivateEndpoints(hosts: string[]) {
const previous = notificationPrivateEndpoints;
setNotificationPrivateEndpoints(hosts);
try {
setNotificationPrivateEndpoints(
await setNotificationPrivateEndpointsApi(hosts),
);
} catch {
setNotificationPrivateEndpoints(previous);
toast.error(t("admin.updateNotificationEndpointsFailed"));
}
}
async function saveTouchInputSettings(settings = touchInputSettings) {
try {
const saved = await updateTouchInputSettings(settings);
@@ -1119,6 +1141,10 @@ export function AdminSettingsPanel({
onToggleAiGloballyEnabled={handleToggleAiGloballyEnabled}
aiPrivateEndpoints={aiPrivateEndpoints}
onSaveAiPrivateEndpoints={handleSaveAiPrivateEndpoints}
notificationPrivateEndpoints={notificationPrivateEndpoints}
onSaveNotificationPrivateEndpoints={
handleSaveNotificationPrivateEndpoints
}
handleToggleSessionSharingGloballyEnabled={
handleToggleSessionSharingGloballyEnabled
}
+25
View File
@@ -31,6 +31,8 @@ type GeneralSettingsSectionProps = {
onToggleAiGloballyEnabled: () => void;
aiPrivateEndpoints: string[];
onSaveAiPrivateEndpoints: (hosts: string[]) => void;
notificationPrivateEndpoints: string[];
onSaveNotificationPrivateEndpoints: (hosts: string[]) => void;
handleToggleSessionSharingGloballyEnabled: () => void;
allowRegistration: boolean;
handleToggleRegistration: () => void;
@@ -80,6 +82,8 @@ export function AdminGeneralSettingsSection({
onToggleAiGloballyEnabled,
aiPrivateEndpoints,
onSaveAiPrivateEndpoints,
notificationPrivateEndpoints,
onSaveNotificationPrivateEndpoints,
handleToggleSessionSharingGloballyEnabled,
allowRegistration,
handleToggleRegistration,
@@ -184,6 +188,27 @@ export function AdminGeneralSettingsSection({
/>
</div>
)}
<div className="flex flex-col gap-1.5 py-2">
<span className="text-xs font-medium">
{t("admin.notificationPrivateEndpoints")}
</span>
<span className="text-[11px] leading-snug text-muted-foreground">
{t("admin.notificationPrivateEndpointsDesc")}
</span>
<Input
className="rounded-none"
defaultValue={notificationPrivateEndpoints.join(", ")}
placeholder="ntfy.internal, 192.168.1.20"
onBlur={(event) =>
onSaveNotificationPrivateEndpoints(
event.target.value
.split(",")
.map((entry) => entry.trim())
.filter(Boolean),
)
}
/>
</div>
<SettingRow
label={t("admin.allowRegistration")}
description={t("admin.allowRegistrationDesc")}