feat: add Bind Host IP and Allowed Hosts / Reverse Proxy Domain settings
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -16,6 +16,7 @@ export default defineConfig({
|
||||
host: "0.0.0.0",
|
||||
port: 5173,
|
||||
strictPort: false,
|
||||
allowedHosts: true,
|
||||
proxy: {
|
||||
"/api": {
|
||||
target: process.env.VITE_API_ORIGIN ?? "http://127.0.0.1:8081",
|
||||
@@ -30,5 +31,6 @@ export default defineConfig({
|
||||
preview: {
|
||||
host: "0.0.0.0",
|
||||
port: 4173,
|
||||
allowedHosts: true,
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user