feat: add Bind Host IP and Allowed Hosts / Reverse Proxy Domain settings

This commit is contained in:
Schwitzz
2026-10-30 14:59:09 -05:00
parent 29dbc27c41
commit 723fafdef8
5 changed files with 253 additions and 6 deletions
+24
View File
@@ -370,6 +370,9 @@ export const api = {
getServerConfig: async (portHint?: number): Promise<{
online: boolean;
enabled: boolean;
host?: string;
activeHost?: string;
allowedHosts?: string;
port: number;
activePort?: number;
webPort?: number;
@@ -388,6 +391,9 @@ export const api = {
if (res.ok && ct.includes("application/json")) {
const data = (await res.json()) as {
enabled: boolean;
host?: string;
activeHost?: string;
allowedHosts?: string;
port: number;
activePort?: number;
webPort?: number;
@@ -398,6 +404,9 @@ export const api = {
return {
online: true,
enabled: data.enabled,
...(data.host ? { host: data.host } : {}),
...(data.activeHost ? { activeHost: data.activeHost } : {}),
...(data.allowedHosts ? { allowedHosts: data.allowedHosts } : {}),
port: data.port,
...(data.activePort !== undefined ? { activePort: data.activePort } : {}),
...(data.webPort !== undefined ? { webPort: data.webPort } : {}),
@@ -413,6 +422,8 @@ export const api = {
return {
online: false,
enabled: true,
host: "127.0.0.1",
allowedHosts: "localhost, 127.0.0.1",
port: portHint ?? 8081,
webPort: 5173,
startOnBoot: false,
@@ -422,6 +433,8 @@ export const api = {
updateServerConfig: async (
patch: {
enabled?: boolean;
host?: string;
allowedHosts?: string;
port?: number;
webPort?: number;
startOnBoot?: boolean;
@@ -431,6 +444,9 @@ export const api = {
): Promise<{
online: boolean;
enabled: boolean;
host?: string;
activeHost?: string;
allowedHosts?: string;
port: number;
activePort?: number;
webPort?: number;
@@ -454,6 +470,9 @@ export const api = {
if (res.ok && ct.includes("application/json")) {
const data = (await res.json()) as {
enabled: boolean;
host?: string;
activeHost?: string;
allowedHosts?: string;
port: number;
activePort?: number;
webPort?: number;
@@ -464,6 +483,9 @@ export const api = {
return {
online: true,
enabled: data.enabled,
...(data.host ? { host: data.host } : {}),
...(data.activeHost ? { activeHost: data.activeHost } : {}),
...(data.allowedHosts ? { allowedHosts: data.allowedHosts } : {}),
port: data.port,
...(data.activePort !== undefined ? { activePort: data.activePort } : {}),
...(data.webPort !== undefined ? { webPort: data.webPort } : {}),
@@ -479,6 +501,8 @@ export const api = {
return {
online: false,
enabled: patch.enabled ?? true,
...(patch.host ? { host: patch.host } : {}),
...(patch.allowedHosts ? { allowedHosts: patch.allowedHosts } : {}),
port: patch.port ?? portHint ?? 8081,
...(patch.webPort !== undefined ? { webPort: patch.webPort } : {}),
startOnBoot: patch.startOnBoot ?? false,
+132 -1
View File
@@ -67,6 +67,10 @@ export function Settings() {
const [probing, setProbing] = useState(false);
// Server & Project Directory state
const [hostDraft, setHostDraft] = useState<string>(s.serverHost || "127.0.0.1");
const [allowedHostsDraft, setAllowedHostsDraft] = useState<string>(
s.allowedHosts || "localhost, 127.0.0.1",
);
const [webPortDraft, setWebPortDraft] = useState<string>(String(s.webPort || 5173));
const [portDraft, setPortDraft] = useState<string>(String(s.serverPort));
const [projectDirDraft, setProjectDirDraft] = useState<string>(
@@ -74,6 +78,7 @@ export function Settings() {
);
const [browseProjectDirOpen, setBrowseProjectDirOpen] = useState<boolean>(false);
const [serverOnline, setServerOnline] = useState<boolean>(false);
const [activeRunningHost, setActiveRunningHost] = useState<string>(s.serverHost || "127.0.0.1");
const [activeRunningPort, setActiveRunningPort] = useState<number | null>(null);
const [activeRunningWebPort, setActiveRunningWebPort] = useState<number | null>(null);
const [savingServerCfg, setSavingServerCfg] = useState<boolean>(false);
@@ -83,9 +88,12 @@ export function Settings() {
void api.getServerConfig(s.serverPort).then((res) => {
if (cancelled) return;
setServerOnline(res.online);
setActiveRunningHost(res.activeHost ?? res.host ?? s.serverHost);
setActiveRunningPort(res.activePort ?? res.port);
setActiveRunningWebPort(res.activeWebPort ?? res.webPort ?? s.webPort);
if (res.online) {
if (res.host) setHostDraft(res.host);
if (res.allowedHosts) setAllowedHostsDraft(res.allowedHosts);
setPortDraft(String(res.port));
if (res.webPort) {
setWebPortDraft(String(res.webPort));
@@ -95,6 +103,8 @@ export function Settings() {
}
set({
serverEnabled: res.enabled,
...(res.host ? { serverHost: res.host } : {}),
...(res.allowedHosts ? { allowedHosts: res.allowedHosts } : {}),
serverPort: res.port,
...(res.webPort ? { webPort: res.webPort } : {}),
serverStartOnBoot: res.startOnBoot,
@@ -146,6 +156,26 @@ export function Settings() {
}
};
const handleApplyHostAndAllowed = async (overrideHost?: string, overrideAllowed?: string) => {
const nextHost = (overrideHost ?? hostDraft).trim() || "127.0.0.1";
const nextAllowed = (overrideAllowed ?? allowedHostsDraft).trim() || "localhost, 127.0.0.1";
setHostDraft(nextHost);
setAllowedHostsDraft(nextAllowed);
set({ serverHost: nextHost, allowedHosts: nextAllowed });
setSavingServerCfg(true);
try {
const res = await api.updateServerConfig(
{ host: nextHost, allowedHosts: nextAllowed },
s.serverPort,
);
setServerOnline(res.online);
setActiveRunningHost(res.activeHost ?? nextHost);
toast.success(`Host (${nextHost}) & Allowed Hosts updated`);
} finally {
setSavingServerCfg(false);
}
};
const handleApplyWebPort = async () => {
const parsed = parseInt(webPortDraft.trim(), 10);
if (Number.isNaN(parsed) || parsed < 1024 || parsed > 65535) {
@@ -1027,12 +1057,113 @@ export function Settings() {
</label>
</div>
<div className="ps-settings-row" style={{ flexDirection: "column", alignItems: "stretch", gap: 10 }}>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 8 }}>
<div>
<div className="ps-settings-label" style={{ display: "flex", alignItems: "center", gap: 8 }}>
<span>Bind Host IP</span>
<Badge variant={!s.serverEnabled ? "neutral" : "accent"}>
{`${activeRunningHost || s.serverHost}:${activeRunningWebPort ?? s.webPort}`}
</Badge>
</div>
<div className="ps-settings-desc">
IP address the server listens on. Default is <code>127.0.0.1</code> (localhost only). Set to <code>0.0.0.0</code> to expose on all network interfaces for a reverse proxy or LAN.
</div>
</div>
<div style={{ display: "flex", alignItems: "center", gap: 6, flexWrap: "wrap" }}>
<Button
variant={hostDraft === "127.0.0.1" ? "primary" : "secondary"}
size="sm"
onClick={() => void handleApplyHostAndAllowed("127.0.0.1", allowedHostsDraft)}
>
127.0.0.1 (Localhost)
</Button>
<Button
variant={hostDraft === "0.0.0.0" ? "primary" : "secondary"}
size="sm"
onClick={() =>
void handleApplyHostAndAllowed(
"0.0.0.0",
allowedHostsDraft.includes("0.0.0.0")
? allowedHostsDraft
: `${allowedHostsDraft}, 0.0.0.0`,
)
}
>
0.0.0.0 (All Interfaces)
</Button>
</div>
</div>
<div style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
<input
type="text"
className="ps-input"
style={{ width: 200, fontFamily: "var(--ps-font-mono)", fontSize: 12.5 }}
value={hostDraft}
onChange={(e) => setHostDraft(e.target.value)}
placeholder="127.0.0.1 or 0.0.0.0"
aria-label="Bind host IP"
/>
<Button
variant="secondary"
size="sm"
disabled={savingServerCfg}
onClick={() => void handleApplyHostAndAllowed()}
>
Apply Host IP
</Button>
</div>
</div>
<div className="ps-settings-row" style={{ flexDirection: "column", alignItems: "stretch", gap: 10 }}>
<div>
<div className="ps-settings-label">Allowed Hosts / Reverse Proxy Domain URLs</div>
<div className="ps-settings-desc">
Comma-separated list of allowable hostnames, IPs, or domain URLs (e.g. <code>localhost, 127.0.0.1, https://pubscript.mydomain.com, *.mydomain.com, 0.0.0.0</code>).
</div>
</div>
<div style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
<input
type="text"
className="ps-input"
style={{ flex: 1, minWidth: 260, fontFamily: "var(--ps-font-mono)", fontSize: 12.5 }}
value={allowedHostsDraft}
onChange={(e) => setAllowedHostsDraft(e.target.value)}
placeholder="localhost, 127.0.0.1, https://write.example.com"
aria-label="Allowed hosts and domains"
/>
<Button
variant="secondary"
size="sm"
disabled={savingServerCfg}
onClick={() =>
void handleApplyHostAndAllowed(
hostDraft,
allowedHostsDraft.includes("*")
? allowedHostsDraft
: `${allowedHostsDraft}, 0.0.0.0, *`,
)
}
>
+ Allow All (0.0.0.0 / *)
</Button>
<Button
variant="primary"
size="sm"
disabled={savingServerCfg}
onClick={() => void handleApplyHostAndAllowed()}
>
Save Allowed Hosts
</Button>
</div>
</div>
<div className="ps-settings-row">
<div>
<div className="ps-settings-label" style={{ display: "flex", alignItems: "center", gap: 8 }}>
<span>Web App Port</span>
<Badge variant={!s.serverEnabled ? "neutral" : "accent"}>
{`http://localhost:${activeRunningWebPort ?? s.webPort}`}
{`http://${activeRunningHost || s.serverHost}:${activeRunningWebPort ?? s.webPort}`}
</Badge>
</div>
<div className="ps-settings-desc">
+4
View File
@@ -274,6 +274,8 @@ export interface AppSettings {
activeAgentId: string;
// Local / Self-Hosted Server & Startup Settings
serverEnabled: boolean;
serverHost: string;
allowedHosts: string;
webPort: number;
serverPort: number;
serverStartOnBoot: boolean;
@@ -310,6 +312,8 @@ export const DEFAULT_SETTINGS: AppSettings = {
activeAgentId: DEFAULT_AI_AGENTS[0]!.id,
// Local / Self-Hosted Server & Startup Settings
serverEnabled: true,
serverHost: "127.0.0.1",
allowedHosts: "localhost, 127.0.0.1",
webPort: 5173,
serverPort: 8081,
serverStartOnBoot: false,