mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-08-29 18:31:33 +00:00
fix: allow approved private notification hosts (#1330)
This commit is contained in:
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")}
|
||||
|
||||
Reference in New Issue
Block a user