feat: add Web App Port management alongside API Server Port and fix square 512x512 app launcher icon
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 112 512 288" width="512" height="288" role="img" aria-label="PubScript">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512" preserveAspectRatio="xMidYMid meet" role="img" aria-label="PubScript">
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="ps-gold" x1="0" y1="0" x2="0" y2="1">
|
<linearGradient id="ps-gold" x1="0" y1="0" x2="0" y2="1">
|
||||||
<stop offset="0" stop-color="#f9edc4"/>
|
<stop offset="0" stop-color="#f9edc4"/>
|
||||||
@@ -19,18 +19,18 @@
|
|||||||
|
|
||||||
<g filter="url(#ps-shadow)">
|
<g filter="url(#ps-shadow)">
|
||||||
<!-- P tile -->
|
<!-- P tile -->
|
||||||
<circle cx="126" cy="256" r="120" fill="url(#ps-gold)"/>
|
<circle cx="132" cy="256" r="120" fill="url(#ps-gold)"/>
|
||||||
<circle cx="126" cy="256" r="104" fill="url(#ps-ivory)"/>
|
<circle cx="132" cy="256" r="104" fill="url(#ps-ivory)"/>
|
||||||
<circle cx="126" cy="256" r="104" fill="none" stroke="#8a6d1c" stroke-width="1.5" opacity="0.5"/>
|
<circle cx="132" cy="256" r="104" fill="none" stroke="#8a6d1c" stroke-width="1.5" opacity="0.5"/>
|
||||||
<text x="126" y="256" text-anchor="middle" dominant-baseline="central"
|
<text x="132" y="256" text-anchor="middle" dominant-baseline="central"
|
||||||
font-family="Georgia, 'Times New Roman', serif" font-size="152" font-weight="600"
|
font-family="Georgia, 'Times New Roman', serif" font-size="152" font-weight="600"
|
||||||
fill="#21242c">P</text>
|
fill="#21242c">P</text>
|
||||||
|
|
||||||
<!-- S tile -->
|
<!-- S tile -->
|
||||||
<circle cx="386" cy="256" r="120" fill="url(#ps-gold)"/>
|
<circle cx="380" cy="256" r="120" fill="url(#ps-gold)"/>
|
||||||
<circle cx="386" cy="256" r="104" fill="url(#ps-ivory)"/>
|
<circle cx="380" cy="256" r="104" fill="url(#ps-ivory)"/>
|
||||||
<circle cx="386" cy="256" r="104" fill="none" stroke="#8a6d1c" stroke-width="1.5" opacity="0.5"/>
|
<circle cx="380" cy="256" r="104" fill="none" stroke="#8a6d1c" stroke-width="1.5" opacity="0.5"/>
|
||||||
<text x="386" y="256" text-anchor="middle" dominant-baseline="central"
|
<text x="380" y="256" text-anchor="middle" dominant-baseline="central"
|
||||||
font-family="Georgia, 'Times New Roman', serif" font-size="152" font-weight="600"
|
font-family="Georgia, 'Times New Roman', serif" font-size="152" font-weight="600"
|
||||||
fill="#21242c">S</text>
|
fill="#21242c">S</text>
|
||||||
</g>
|
</g>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
@@ -372,6 +372,8 @@ export const api = {
|
|||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
port: number;
|
port: number;
|
||||||
activePort?: number;
|
activePort?: number;
|
||||||
|
webPort?: number;
|
||||||
|
activeWebPort?: number;
|
||||||
startOnBoot: boolean;
|
startOnBoot: boolean;
|
||||||
projectDirectory?: string;
|
projectDirectory?: string;
|
||||||
}> => {
|
}> => {
|
||||||
@@ -388,6 +390,8 @@ export const api = {
|
|||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
port: number;
|
port: number;
|
||||||
activePort?: number;
|
activePort?: number;
|
||||||
|
webPort?: number;
|
||||||
|
activeWebPort?: number;
|
||||||
startOnBoot: boolean;
|
startOnBoot: boolean;
|
||||||
projectDirectory?: string;
|
projectDirectory?: string;
|
||||||
};
|
};
|
||||||
@@ -396,6 +400,8 @@ export const api = {
|
|||||||
enabled: data.enabled,
|
enabled: data.enabled,
|
||||||
port: data.port,
|
port: data.port,
|
||||||
...(data.activePort !== undefined ? { activePort: data.activePort } : {}),
|
...(data.activePort !== undefined ? { activePort: data.activePort } : {}),
|
||||||
|
...(data.webPort !== undefined ? { webPort: data.webPort } : {}),
|
||||||
|
...(data.activeWebPort !== undefined ? { activeWebPort: data.activeWebPort } : {}),
|
||||||
startOnBoot: data.startOnBoot,
|
startOnBoot: data.startOnBoot,
|
||||||
...(data.projectDirectory ? { projectDirectory: data.projectDirectory } : {}),
|
...(data.projectDirectory ? { projectDirectory: data.projectDirectory } : {}),
|
||||||
};
|
};
|
||||||
@@ -408,6 +414,7 @@ export const api = {
|
|||||||
online: false,
|
online: false,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
port: portHint ?? 8081,
|
port: portHint ?? 8081,
|
||||||
|
webPort: 5173,
|
||||||
startOnBoot: false,
|
startOnBoot: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -416,6 +423,7 @@ export const api = {
|
|||||||
patch: {
|
patch: {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
port?: number;
|
port?: number;
|
||||||
|
webPort?: number;
|
||||||
startOnBoot?: boolean;
|
startOnBoot?: boolean;
|
||||||
projectDirectory?: string;
|
projectDirectory?: string;
|
||||||
},
|
},
|
||||||
@@ -425,6 +433,8 @@ export const api = {
|
|||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
port: number;
|
port: number;
|
||||||
activePort?: number;
|
activePort?: number;
|
||||||
|
webPort?: number;
|
||||||
|
activeWebPort?: number;
|
||||||
startOnBoot: boolean;
|
startOnBoot: boolean;
|
||||||
projectDirectory?: string;
|
projectDirectory?: string;
|
||||||
}> => {
|
}> => {
|
||||||
@@ -446,6 +456,8 @@ export const api = {
|
|||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
port: number;
|
port: number;
|
||||||
activePort?: number;
|
activePort?: number;
|
||||||
|
webPort?: number;
|
||||||
|
activeWebPort?: number;
|
||||||
startOnBoot: boolean;
|
startOnBoot: boolean;
|
||||||
projectDirectory?: string;
|
projectDirectory?: string;
|
||||||
};
|
};
|
||||||
@@ -454,6 +466,8 @@ export const api = {
|
|||||||
enabled: data.enabled,
|
enabled: data.enabled,
|
||||||
port: data.port,
|
port: data.port,
|
||||||
...(data.activePort !== undefined ? { activePort: data.activePort } : {}),
|
...(data.activePort !== undefined ? { activePort: data.activePort } : {}),
|
||||||
|
...(data.webPort !== undefined ? { webPort: data.webPort } : {}),
|
||||||
|
...(data.activeWebPort !== undefined ? { activeWebPort: data.activeWebPort } : {}),
|
||||||
startOnBoot: data.startOnBoot,
|
startOnBoot: data.startOnBoot,
|
||||||
...(data.projectDirectory ? { projectDirectory: data.projectDirectory } : {}),
|
...(data.projectDirectory ? { projectDirectory: data.projectDirectory } : {}),
|
||||||
};
|
};
|
||||||
@@ -466,6 +480,7 @@ export const api = {
|
|||||||
online: false,
|
online: false,
|
||||||
enabled: patch.enabled ?? true,
|
enabled: patch.enabled ?? true,
|
||||||
port: patch.port ?? portHint ?? 8081,
|
port: patch.port ?? portHint ?? 8081,
|
||||||
|
...(patch.webPort !== undefined ? { webPort: patch.webPort } : {}),
|
||||||
startOnBoot: patch.startOnBoot ?? false,
|
startOnBoot: patch.startOnBoot ?? false,
|
||||||
...(patch.projectDirectory ? { projectDirectory: patch.projectDirectory } : {}),
|
...(patch.projectDirectory ? { projectDirectory: patch.projectDirectory } : {}),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ export function Settings() {
|
|||||||
const [probing, setProbing] = useState(false);
|
const [probing, setProbing] = useState(false);
|
||||||
|
|
||||||
// Server & Project Directory state
|
// Server & Project Directory state
|
||||||
|
const [webPortDraft, setWebPortDraft] = useState<string>(String(s.webPort || 5173));
|
||||||
const [portDraft, setPortDraft] = useState<string>(String(s.serverPort));
|
const [portDraft, setPortDraft] = useState<string>(String(s.serverPort));
|
||||||
const [projectDirDraft, setProjectDirDraft] = useState<string>(
|
const [projectDirDraft, setProjectDirDraft] = useState<string>(
|
||||||
s.projectDirectory || "~/Documents/PubScript",
|
s.projectDirectory || "~/Documents/PubScript",
|
||||||
@@ -74,6 +75,7 @@ export function Settings() {
|
|||||||
const [browseProjectDirOpen, setBrowseProjectDirOpen] = useState<boolean>(false);
|
const [browseProjectDirOpen, setBrowseProjectDirOpen] = useState<boolean>(false);
|
||||||
const [serverOnline, setServerOnline] = useState<boolean>(false);
|
const [serverOnline, setServerOnline] = useState<boolean>(false);
|
||||||
const [activeRunningPort, setActiveRunningPort] = useState<number | null>(null);
|
const [activeRunningPort, setActiveRunningPort] = useState<number | null>(null);
|
||||||
|
const [activeRunningWebPort, setActiveRunningWebPort] = useState<number | null>(null);
|
||||||
const [savingServerCfg, setSavingServerCfg] = useState<boolean>(false);
|
const [savingServerCfg, setSavingServerCfg] = useState<boolean>(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -82,14 +84,19 @@ export function Settings() {
|
|||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
setServerOnline(res.online);
|
setServerOnline(res.online);
|
||||||
setActiveRunningPort(res.activePort ?? res.port);
|
setActiveRunningPort(res.activePort ?? res.port);
|
||||||
|
setActiveRunningWebPort(res.activeWebPort ?? res.webPort ?? s.webPort);
|
||||||
if (res.online) {
|
if (res.online) {
|
||||||
setPortDraft(String(res.port));
|
setPortDraft(String(res.port));
|
||||||
|
if (res.webPort) {
|
||||||
|
setWebPortDraft(String(res.webPort));
|
||||||
|
}
|
||||||
if (res.projectDirectory) {
|
if (res.projectDirectory) {
|
||||||
setProjectDirDraft(res.projectDirectory);
|
setProjectDirDraft(res.projectDirectory);
|
||||||
}
|
}
|
||||||
set({
|
set({
|
||||||
serverEnabled: res.enabled,
|
serverEnabled: res.enabled,
|
||||||
serverPort: res.port,
|
serverPort: res.port,
|
||||||
|
...(res.webPort ? { webPort: res.webPort } : {}),
|
||||||
serverStartOnBoot: res.startOnBoot,
|
serverStartOnBoot: res.startOnBoot,
|
||||||
...(res.projectDirectory ? { projectDirectory: res.projectDirectory } : {}),
|
...(res.projectDirectory ? { projectDirectory: res.projectDirectory } : {}),
|
||||||
});
|
});
|
||||||
@@ -139,10 +146,28 @@ export function Settings() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleApplyWebPort = async () => {
|
||||||
|
const parsed = parseInt(webPortDraft.trim(), 10);
|
||||||
|
if (Number.isNaN(parsed) || parsed < 1024 || parsed > 65535) {
|
||||||
|
toast.error("Please enter a valid web app port between 1024 and 65535");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
set({ webPort: parsed });
|
||||||
|
setSavingServerCfg(true);
|
||||||
|
try {
|
||||||
|
const res = await api.updateServerConfig({ webPort: parsed }, s.serverPort);
|
||||||
|
setServerOnline(res.online);
|
||||||
|
setActiveRunningWebPort(res.activeWebPort ?? parsed);
|
||||||
|
toast.success(`Web App port saved and active on http://localhost:${parsed}`);
|
||||||
|
} finally {
|
||||||
|
setSavingServerCfg(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleApplyServerPort = async () => {
|
const handleApplyServerPort = async () => {
|
||||||
const parsed = parseInt(portDraft.trim(), 10);
|
const parsed = parseInt(portDraft.trim(), 10);
|
||||||
if (Number.isNaN(parsed) || parsed < 1024 || parsed > 65535) {
|
if (Number.isNaN(parsed) || parsed < 1024 || parsed > 65535) {
|
||||||
toast.error("Please enter a valid port between 1024 and 65535");
|
toast.error("Please enter a valid API server port between 1024 and 65535");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
set({ serverPort: parsed });
|
set({ serverPort: parsed });
|
||||||
@@ -151,7 +176,7 @@ export function Settings() {
|
|||||||
const res = await api.updateServerConfig({ port: parsed }, s.serverPort);
|
const res = await api.updateServerConfig({ port: parsed }, s.serverPort);
|
||||||
setServerOnline(res.online);
|
setServerOnline(res.online);
|
||||||
setActiveRunningPort(res.activePort ?? parsed);
|
setActiveRunningPort(res.activePort ?? parsed);
|
||||||
toast.success(`Server port saved (${parsed})`);
|
toast.success(`API Server port saved (${parsed})`);
|
||||||
} finally {
|
} finally {
|
||||||
setSavingServerCfg(false);
|
setSavingServerCfg(false);
|
||||||
}
|
}
|
||||||
@@ -1004,9 +1029,48 @@ export function Settings() {
|
|||||||
|
|
||||||
<div className="ps-settings-row">
|
<div className="ps-settings-row">
|
||||||
<div>
|
<div>
|
||||||
<div className="ps-settings-label">Server port</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}`}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
<div className="ps-settings-desc">
|
<div className="ps-settings-desc">
|
||||||
TCP port for the local/self-hosted PubScript server (1024–65535). Default: 8081 (web) / 18081 (desktop).
|
TCP port where the PubScript Web App UI is served (1024–65535). Default: 5173. Changing this immediately binds the web UI on the selected port.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
className="ps-input"
|
||||||
|
min={1024}
|
||||||
|
max={65535}
|
||||||
|
style={{ width: 110 }}
|
||||||
|
value={webPortDraft}
|
||||||
|
onChange={(e) => setWebPortDraft(e.target.value)}
|
||||||
|
aria-label="Web app port"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
size="sm"
|
||||||
|
disabled={savingServerCfg}
|
||||||
|
onClick={() => void handleApplyWebPort()}
|
||||||
|
>
|
||||||
|
Apply Web Port
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="ps-settings-row">
|
||||||
|
<div>
|
||||||
|
<div className="ps-settings-label" style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||||||
|
<span>API Server Port</span>
|
||||||
|
<Badge variant={!s.serverEnabled ? "neutral" : serverOnline ? "success" : "warning"}>
|
||||||
|
{`Port ${activeRunningPort ?? s.serverPort}`}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<div className="ps-settings-desc">
|
||||||
|
TCP port for the PubScript API & SQLite server (1024–65535). Default: 8081 (web) / 18081 (desktop).
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
|
<div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
|
||||||
@@ -1018,7 +1082,7 @@ export function Settings() {
|
|||||||
style={{ width: 110 }}
|
style={{ width: 110 }}
|
||||||
value={portDraft}
|
value={portDraft}
|
||||||
onChange={(e) => setPortDraft(e.target.value)}
|
onChange={(e) => setPortDraft(e.target.value)}
|
||||||
aria-label="Server port"
|
aria-label="API server port"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
@@ -1026,7 +1090,7 @@ export function Settings() {
|
|||||||
disabled={savingServerCfg}
|
disabled={savingServerCfg}
|
||||||
onClick={() => void handleApplyServerPort()}
|
onClick={() => void handleApplyServerPort()}
|
||||||
>
|
>
|
||||||
Apply Port
|
Apply API Port
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -274,6 +274,7 @@ export interface AppSettings {
|
|||||||
activeAgentId: string;
|
activeAgentId: string;
|
||||||
// Local / Self-Hosted Server & Startup Settings
|
// Local / Self-Hosted Server & Startup Settings
|
||||||
serverEnabled: boolean;
|
serverEnabled: boolean;
|
||||||
|
webPort: number;
|
||||||
serverPort: number;
|
serverPort: number;
|
||||||
serverStartOnBoot: boolean;
|
serverStartOnBoot: boolean;
|
||||||
}
|
}
|
||||||
@@ -309,6 +310,7 @@ export const DEFAULT_SETTINGS: AppSettings = {
|
|||||||
activeAgentId: DEFAULT_AI_AGENTS[0]!.id,
|
activeAgentId: DEFAULT_AI_AGENTS[0]!.id,
|
||||||
// Local / Self-Hosted Server & Startup Settings
|
// Local / Self-Hosted Server & Startup Settings
|
||||||
serverEnabled: true,
|
serverEnabled: true,
|
||||||
|
webPort: 5173,
|
||||||
serverPort: 8081,
|
serverPort: 8081,
|
||||||
serverStartOnBoot: false,
|
serverStartOnBoot: false,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -144,8 +144,10 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ps-sidebar-logo-only {
|
.ps-sidebar-logo-only {
|
||||||
width: 88px;
|
width: 92px;
|
||||||
height: auto;
|
height: 52px;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: center;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
display: block;
|
display: block;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
@@ -153,7 +155,7 @@ body {
|
|||||||
}
|
}
|
||||||
.ps-sidebar.is-collapsed .ps-sidebar-logo-only {
|
.ps-sidebar.is-collapsed .ps-sidebar-logo-only {
|
||||||
width: 48px;
|
width: 48px;
|
||||||
height: auto;
|
height: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ps-sidebar-toggle {
|
.ps-sidebar-toggle {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
statSync,
|
statSync,
|
||||||
writeFileSync,
|
writeFileSync,
|
||||||
} from "node:fs";
|
} from "node:fs";
|
||||||
|
import { createServer, type Server as HttpServer } from "node:http";
|
||||||
import { homedir } from "node:os";
|
import { homedir } from "node:os";
|
||||||
import { basename, dirname, join, resolve } from "node:path";
|
import { basename, dirname, join, resolve } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
@@ -101,10 +102,14 @@ export async function buildApp() {
|
|||||||
interface PersistedServerSysConfig {
|
interface PersistedServerSysConfig {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
port: number;
|
port: number;
|
||||||
|
webPort: number;
|
||||||
startOnBoot: boolean;
|
startOnBoot: boolean;
|
||||||
projectDirectory: string;
|
projectDirectory: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let secondaryWebServer: HttpServer | null = null;
|
||||||
|
let activeWebPort: number = 5173;
|
||||||
|
|
||||||
function loadPersistedServerSysConfig(): PersistedServerSysConfig {
|
function loadPersistedServerSysConfig(): PersistedServerSysConfig {
|
||||||
try {
|
try {
|
||||||
if (existsSync(sysConfigPath)) {
|
if (existsSync(sysConfigPath)) {
|
||||||
@@ -115,6 +120,10 @@ export async function buildApp() {
|
|||||||
typeof raw.port === "number" && raw.port >= 1024 && raw.port <= 65535
|
typeof raw.port === "number" && raw.port >= 1024 && raw.port <= 65535
|
||||||
? raw.port
|
? raw.port
|
||||||
: config.port,
|
: config.port,
|
||||||
|
webPort:
|
||||||
|
typeof raw.webPort === "number" && raw.webPort >= 1024 && raw.webPort <= 65535
|
||||||
|
? raw.webPort
|
||||||
|
: 5173,
|
||||||
startOnBoot:
|
startOnBoot:
|
||||||
typeof raw.startOnBoot === "boolean"
|
typeof raw.startOnBoot === "boolean"
|
||||||
? raw.startOnBoot
|
? raw.startOnBoot
|
||||||
@@ -131,11 +140,36 @@ export async function buildApp() {
|
|||||||
return {
|
return {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
port: config.port,
|
port: config.port,
|
||||||
|
webPort: 5173,
|
||||||
startOnBoot: existsSync(autostartDesktopPath) || existsSync(systemdServicePath),
|
startOnBoot: existsSync(autostartDesktopPath) || existsSync(systemdServicePath),
|
||||||
projectDirectory: defaultProjectDir,
|
projectDirectory: defaultProjectDir,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function bindSecondaryWebPort(targetWebPort: number, enabled: boolean): Promise<number> {
|
||||||
|
activeWebPort = targetWebPort;
|
||||||
|
if (secondaryWebServer) {
|
||||||
|
await new Promise<void>((res) => secondaryWebServer!.close(() => res()));
|
||||||
|
secondaryWebServer = null;
|
||||||
|
}
|
||||||
|
if (!enabled || targetWebPort === config.port) {
|
||||||
|
return targetWebPort;
|
||||||
|
}
|
||||||
|
return await new Promise<number>((res) => {
|
||||||
|
const srv = createServer((req, reply) => {
|
||||||
|
app.server.emit("request", req, reply);
|
||||||
|
});
|
||||||
|
srv.once("error", () => {
|
||||||
|
// Port may already be served by Vite dev server (e.g. :5173); keep activeWebPort
|
||||||
|
res(targetWebPort);
|
||||||
|
});
|
||||||
|
srv.listen(targetWebPort, "0.0.0.0", () => {
|
||||||
|
secondaryWebServer = srv;
|
||||||
|
res(targetWebPort);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function applySystemStartupFiles(cfg: PersistedServerSysConfig): boolean {
|
function applySystemStartupFiles(cfg: PersistedServerSysConfig): boolean {
|
||||||
try {
|
try {
|
||||||
mkdirSync(sysConfigDir, { recursive: true });
|
mkdirSync(sysConfigDir, { recursive: true });
|
||||||
@@ -225,6 +259,8 @@ export async function buildApp() {
|
|||||||
enabled: persisted.enabled,
|
enabled: persisted.enabled,
|
||||||
port: persisted.port,
|
port: persisted.port,
|
||||||
activePort: config.port,
|
activePort: config.port,
|
||||||
|
webPort: persisted.webPort,
|
||||||
|
activeWebPort: activeWebPort || persisted.webPort,
|
||||||
startOnBoot: persisted.startOnBoot,
|
startOnBoot: persisted.startOnBoot,
|
||||||
projectDirectory: persisted.projectDirectory,
|
projectDirectory: persisted.projectDirectory,
|
||||||
autostartPath: autostartDesktopPath,
|
autostartPath: autostartDesktopPath,
|
||||||
@@ -240,6 +276,11 @@ export async function buildApp() {
|
|||||||
if (!Number.isFinite(nextPort) || nextPort < 1024 || nextPort > 65535) {
|
if (!Number.isFinite(nextPort) || nextPort < 1024 || nextPort > 65535) {
|
||||||
return reply.code(400).send({ error: "invalid_port" });
|
return reply.code(400).send({ error: "invalid_port" });
|
||||||
}
|
}
|
||||||
|
const nextWebPort =
|
||||||
|
typeof body.webPort === "number" ? Math.trunc(body.webPort) : current.webPort;
|
||||||
|
if (!Number.isFinite(nextWebPort) || nextWebPort < 1024 || nextWebPort > 65535) {
|
||||||
|
return reply.code(400).send({ error: "invalid_web_port" });
|
||||||
|
}
|
||||||
const nextProjectDir =
|
const nextProjectDir =
|
||||||
typeof body.projectDirectory === "string" && body.projectDirectory.trim()
|
typeof body.projectDirectory === "string" && body.projectDirectory.trim()
|
||||||
? body.projectDirectory.trim()
|
? body.projectDirectory.trim()
|
||||||
@@ -247,16 +288,20 @@ export async function buildApp() {
|
|||||||
const next: PersistedServerSysConfig = {
|
const next: PersistedServerSysConfig = {
|
||||||
enabled: typeof body.enabled === "boolean" ? body.enabled : current.enabled,
|
enabled: typeof body.enabled === "boolean" ? body.enabled : current.enabled,
|
||||||
port: nextPort,
|
port: nextPort,
|
||||||
|
webPort: nextWebPort,
|
||||||
startOnBoot:
|
startOnBoot:
|
||||||
typeof body.startOnBoot === "boolean" ? body.startOnBoot : current.startOnBoot,
|
typeof body.startOnBoot === "boolean" ? body.startOnBoot : current.startOnBoot,
|
||||||
projectDirectory: nextProjectDir,
|
projectDirectory: nextProjectDir,
|
||||||
};
|
};
|
||||||
const configured = applySystemStartupFiles(next);
|
const configured = applySystemStartupFiles(next);
|
||||||
|
const boundWebPort = await bindSecondaryWebPort(next.webPort, next.enabled);
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
enabled: next.enabled,
|
enabled: next.enabled,
|
||||||
port: next.port,
|
port: next.port,
|
||||||
activePort: config.port,
|
activePort: config.port,
|
||||||
|
webPort: next.webPort,
|
||||||
|
activeWebPort: boundWebPort,
|
||||||
startOnBoot: next.startOnBoot,
|
startOnBoot: next.startOnBoot,
|
||||||
projectDirectory: next.projectDirectory,
|
projectDirectory: next.projectDirectory,
|
||||||
autostartConfigured: configured,
|
autostartConfigured: configured,
|
||||||
@@ -355,6 +400,21 @@ export async function buildApp() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.addHook("onClose", async () => {
|
||||||
|
if (secondaryWebServer) {
|
||||||
|
await new Promise<void>((res) => secondaryWebServer!.close(() => res()));
|
||||||
|
secondaryWebServer = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
process.argv[1] !== undefined &&
|
||||||
|
fileURLToPath(import.meta.url) === process.argv[1]
|
||||||
|
) {
|
||||||
|
const persisted = loadPersistedServerSysConfig();
|
||||||
|
void bindSecondaryWebPort(persisted.webPort, persisted.enabled);
|
||||||
|
}
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user