mirror of https://github.com/jetkvm/kvm.git
feat(cloud): Add custom cloud API URL configuration support (#181)
* feat(cloud): Add custom cloud API URL configuration support - Implement RPC methods to set, get, and reset cloud URL - Update cloud registration to remove hardcoded cloud API URL - Modify UI to allow configuring custom cloud API URL in developer settings - Remove environment-specific cloud configuration files - Simplify cloud URL configuration in UI config * fix(ui): Update cloud app URL to production environment in device mode * refactor(ui): Remove SIGNAL_API env & Rename to DEVICE_API to make clear distinction between CLOUD_API and DEVICE_API. * feat(ui): Only show Cloud API URL Change on device mode * fix(cloud): Don't override the CloudURL on deregistration from the cloud.
This commit is contained in:
parent
de5403eada
commit
7304e6b672
3
cloud.go
3
cloud.go
|
@ -96,7 +96,6 @@ func handleCloudRegister(c *gin.Context) {
|
|||
}
|
||||
|
||||
config.CloudToken = tokenResp.SecretToken
|
||||
config.CloudURL = req.CloudAPI
|
||||
|
||||
provider, err := oidc.NewProvider(c, "https://accounts.google.com")
|
||||
if err != nil {
|
||||
|
@ -298,8 +297,8 @@ func rpcDeregisterDevice() error {
|
|||
// (e.g., wrong cloud token, already deregistered). Regardless of the reason, we can safely remove it.
|
||||
if resp.StatusCode == http.StatusNotFound || (resp.StatusCode >= 200 && resp.StatusCode < 300) {
|
||||
config.CloudToken = ""
|
||||
config.CloudURL = ""
|
||||
config.GoogleIdentity = ""
|
||||
|
||||
if err := SaveConfig(); err != nil {
|
||||
return fmt.Errorf("failed to save configuration after deregistering: %w", err)
|
||||
}
|
||||
|
|
30
jsonrpc.go
30
jsonrpc.go
|
@ -730,6 +730,33 @@ func rpcSetSerialSettings(settings SerialSettings) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func rpcSetCloudUrl(url string) error {
|
||||
if url == "" {
|
||||
// Reset to default by removing from config
|
||||
config.CloudURL = defaultConfig.CloudURL
|
||||
} else {
|
||||
config.CloudURL = url
|
||||
}
|
||||
|
||||
if err := SaveConfig(); err != nil {
|
||||
return fmt.Errorf("failed to save config: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func rpcGetCloudUrl() (string, error) {
|
||||
return config.CloudURL, nil
|
||||
}
|
||||
|
||||
func rpcResetCloudUrl() error {
|
||||
// Reset to default by removing from config
|
||||
config.CloudURL = defaultConfig.CloudURL
|
||||
if err := SaveConfig(); err != nil {
|
||||
return fmt.Errorf("failed to reset cloud URL: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var rpcHandlers = map[string]RPCHandler{
|
||||
"ping": {Func: rpcPing},
|
||||
"getDeviceID": {Func: rpcGetDeviceID},
|
||||
|
@ -786,4 +813,7 @@ var rpcHandlers = map[string]RPCHandler{
|
|||
"setATXPowerAction": {Func: rpcSetATXPowerAction, Params: []string{"action"}},
|
||||
"getSerialSettings": {Func: rpcGetSerialSettings},
|
||||
"setSerialSettings": {Func: rpcSetSerialSettings, Params: []string{"settings"}},
|
||||
"setCloudUrl": {Func: rpcSetCloudUrl, Params: []string{"url"}},
|
||||
"getCloudUrl": {Func: rpcGetCloudUrl},
|
||||
"resetCloudUrl": {Func: rpcResetCloudUrl},
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
# No need for VITE_CLOUD_APP it's only needed for the device build
|
||||
|
||||
# We use this for all the cloud API requests from the browser
|
||||
VITE_CLOUD_API=http://localhost:3000
|
|
@ -0,0 +1,4 @@
|
|||
# No need for VITE_CLOUD_APP it's only needed for the device build
|
||||
|
||||
# We use this for all the cloud API requests from the browser
|
||||
VITE_CLOUD_API=https://api.jetkvm.com
|
|
@ -0,0 +1,4 @@
|
|||
# No need for VITE_CLOUD_APP it's only needed for the device build
|
||||
|
||||
# We use this for all the cloud API requests from the browser
|
||||
VITE_CLOUD_API=https://staging-api.jetkvm.com
|
|
@ -1,6 +0,0 @@
|
|||
VITE_SIGNAL_API=http://localhost:3000
|
||||
|
||||
VITE_CLOUD_APP=http://localhost:5173
|
||||
VITE_CLOUD_API=http://localhost:3000
|
||||
|
||||
VITE_JETKVM_HEAD=
|
|
@ -1,6 +1,2 @@
|
|||
VITE_SIGNAL_API= # Uses the KVM device's IP address as the signal API endpoint
|
||||
|
||||
VITE_CLOUD_APP=https://app.jetkvm.com
|
||||
VITE_CLOUD_API=https://api.jetkvm.com
|
||||
|
||||
VITE_JETKVM_HEAD=<script src="/device/ui-config.js"></script>
|
||||
# Used in settings page to know where to link to when user wants to adopt a device to the cloud
|
||||
VITE_CLOUD_APP=http://localhost:5173
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
VITE_SIGNAL_API=https://api.jetkvm.com
|
||||
|
||||
VITE_CLOUD_APP=https://app.jetkvm.com
|
||||
VITE_CLOUD_API=https://api.jetkvm.com
|
||||
|
||||
VITE_JETKVM_HEAD=
|
|
@ -1,4 +0,0 @@
|
|||
VITE_SIGNAL_API=https://staging-api.jetkvm.com
|
||||
|
||||
VITE_CLOUD_APP=https://staging-app.jetkvm.com
|
||||
VITE_CLOUD_API=https://staging-api.jetkvm.com
|
|
@ -28,7 +28,6 @@
|
|||
<title>JetKVM</title>
|
||||
<link rel="stylesheet" href="/fonts/fonts.css" />
|
||||
<link rel="icon" href="/favicon.png" />
|
||||
%VITE_JETKVM_HEAD%
|
||||
<script>
|
||||
// Initial theme setup
|
||||
document.documentElement.classList.toggle(
|
||||
|
|
|
@ -8,11 +8,11 @@
|
|||
},
|
||||
"scripts": {
|
||||
"dev": "./dev_device.sh",
|
||||
"dev:cloud": "vite dev --mode=development",
|
||||
"dev:cloud": "vite dev --mode=cloud-development",
|
||||
"build": "npm run build:prod",
|
||||
"build:device": "tsc && vite build --mode=device --emptyOutDir",
|
||||
"build:staging": "tsc && vite build --mode=staging",
|
||||
"build:prod": "tsc && vite build --mode=production",
|
||||
"build:staging": "tsc && vite build --mode=cloud-staging",
|
||||
"build:prod": "tsc && vite build --mode=cloud-production",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
|
|
|
@ -14,7 +14,7 @@ import PeerConnectionStatusCard from "@components/PeerConnectionStatusCard";
|
|||
import api from "../api";
|
||||
import { isOnDevice } from "../main";
|
||||
import { Button, LinkButton } from "./Button";
|
||||
import { CLOUD_API, SIGNAL_API } from "@/ui.config";
|
||||
import { CLOUD_API, DEVICE_API } from "@/ui.config";
|
||||
|
||||
interface NavbarProps {
|
||||
isLoggedIn: boolean;
|
||||
|
@ -38,7 +38,7 @@ export default function DashboardNavbar({
|
|||
const navigate = useNavigate();
|
||||
const onLogout = useCallback(async () => {
|
||||
const logoutUrl = isOnDevice
|
||||
? `${SIGNAL_API}/auth/logout`
|
||||
? `${DEVICE_API}/auth/logout`
|
||||
: `${CLOUD_API}/logout`;
|
||||
const res = await api.POST(logoutUrl);
|
||||
if (!res.ok) return;
|
||||
|
|
|
@ -35,7 +35,7 @@ import { ExclamationTriangleIcon } from "@heroicons/react/20/solid";
|
|||
import notifications from "../notifications";
|
||||
import Fieldset from "./Fieldset";
|
||||
import { isOnDevice } from "../main";
|
||||
import { SIGNAL_API } from "@/ui.config";
|
||||
import { DEVICE_API } from "@/ui.config";
|
||||
|
||||
export default function MountMediaModal({
|
||||
open,
|
||||
|
@ -1120,7 +1120,7 @@ function UploadFileView({
|
|||
alreadyUploadedBytes: number,
|
||||
dataChannel: string,
|
||||
) {
|
||||
const uploadUrl = `${SIGNAL_API}/storage/upload?uploadId=${dataChannel}`;
|
||||
const uploadUrl = `${DEVICE_API}/storage/upload?uploadId=${dataChannel}`;
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", uploadUrl, true);
|
||||
|
|
|
@ -26,7 +26,8 @@ import LocalAuthPasswordDialog from "@/components/LocalAuthPasswordDialog";
|
|||
import { LocalDevice } from "@routes/devices.$id";
|
||||
import { useRevalidator } from "react-router-dom";
|
||||
import { ShieldCheckIcon } from "@heroicons/react/20/solid";
|
||||
import { CLOUD_APP, SIGNAL_API } from "@/ui.config";
|
||||
import { CLOUD_APP, DEVICE_API } from "@/ui.config";
|
||||
import { InputFieldWithLabel } from "../InputField";
|
||||
|
||||
export function SettingsItem({
|
||||
title,
|
||||
|
@ -277,6 +278,51 @@ export default function SettingsSidebar() {
|
|||
}
|
||||
};
|
||||
|
||||
const [cloudUrl, setCloudUrl] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
send("getCloudUrl", {}, resp => {
|
||||
if ("error" in resp) return;
|
||||
setCloudUrl(resp.result as string);
|
||||
});
|
||||
}, [send]);
|
||||
|
||||
const getCloudUrl = useCallback(() => {
|
||||
send("getCloudUrl", {}, resp => {
|
||||
if ("error" in resp) return;
|
||||
setCloudUrl(resp.result as string);
|
||||
});
|
||||
}, [send]);
|
||||
|
||||
const handleCloudUrlChange = useCallback(
|
||||
(url: string) => {
|
||||
send("setCloudUrl", { url }, resp => {
|
||||
if ("error" in resp) {
|
||||
notifications.error(
|
||||
`Failed to update cloud URL: ${resp.error.data || "Unknown error"}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
getCloudUrl();
|
||||
notifications.success("Cloud URL updated successfully");
|
||||
});
|
||||
},
|
||||
[send, getCloudUrl],
|
||||
);
|
||||
|
||||
const handleResetCloudUrl = useCallback(() => {
|
||||
send("resetCloudUrl", {}, resp => {
|
||||
if ("error" in resp) {
|
||||
notifications.error(
|
||||
`Failed to reset cloud URL: ${resp.error.data || "Unknown error"}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
getCloudUrl();
|
||||
notifications.success("Cloud URL reset to default successfully");
|
||||
});
|
||||
}, [send, getCloudUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
getCloudState();
|
||||
|
||||
|
@ -363,12 +409,19 @@ export default function SettingsSidebar() {
|
|||
if ("error" in resp) return;
|
||||
setUsbEmulationEnabled(resp.result as boolean);
|
||||
});
|
||||
}, [getCloudState, send, setBacklightSettings, setDeveloperMode, setHideCursor, setJiggler]);
|
||||
}, [
|
||||
getCloudState,
|
||||
send,
|
||||
setBacklightSettings,
|
||||
setDeveloperMode,
|
||||
setHideCursor,
|
||||
setJiggler,
|
||||
]);
|
||||
|
||||
const getDevice = useCallback(async () => {
|
||||
try {
|
||||
const status = await api
|
||||
.GET(`${SIGNAL_API}/device`)
|
||||
.GET(`${DEVICE_API}/device`)
|
||||
.then(res => res.json() as Promise<LocalDevice>);
|
||||
setLocalDevice(status);
|
||||
} catch (error) {
|
||||
|
@ -920,25 +973,58 @@ export default function SettingsSidebar() {
|
|||
</SettingsItem>
|
||||
|
||||
{settings.developerMode && (
|
||||
<div className="space-y-4">
|
||||
<TextAreaWithLabel
|
||||
label="SSH Public Key"
|
||||
value={sshKey || ""}
|
||||
rows={3}
|
||||
onChange={e => handleSSHKeyChange(e.target.value)}
|
||||
placeholder="Enter your SSH public key"
|
||||
/>
|
||||
<p className="text-xs text-slate-600 dark:text-slate-400">
|
||||
The default SSH user is <strong>root</strong>.
|
||||
</p>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Button
|
||||
size="SM"
|
||||
theme="primary"
|
||||
text="Update SSH Key"
|
||||
onClick={handleUpdateSSHKey}
|
||||
<div>
|
||||
<div className="space-y-4">
|
||||
<TextAreaWithLabel
|
||||
label="SSH Public Key"
|
||||
value={sshKey || ""}
|
||||
rows={3}
|
||||
onChange={e => handleSSHKeyChange(e.target.value)}
|
||||
placeholder="Enter your SSH public key"
|
||||
/>
|
||||
<p className="text-xs text-slate-600 dark:text-slate-400">
|
||||
The default SSH user is <strong>root</strong>.
|
||||
</p>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Button
|
||||
size="SM"
|
||||
theme="primary"
|
||||
text="Update SSH Key"
|
||||
onClick={handleUpdateSSHKey}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{isOnDevice && (
|
||||
<div className="mt-4 space-y-4">
|
||||
<SettingsItem
|
||||
title="Cloud API URL"
|
||||
description="Connect to a custom JetKVM Cloud API"
|
||||
/>
|
||||
|
||||
<InputFieldWithLabel
|
||||
size="SM"
|
||||
label="Cloud URL"
|
||||
value={cloudUrl}
|
||||
onChange={e => setCloudUrl(e.target.value)}
|
||||
placeholder="https://api.jetkvm.com"
|
||||
/>
|
||||
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Button
|
||||
size="SM"
|
||||
theme="primary"
|
||||
text="Save Cloud URL"
|
||||
onClick={() => handleCloudUrlChange(cloudUrl)}
|
||||
/>
|
||||
<Button
|
||||
size="SM"
|
||||
theme="light"
|
||||
text="Restore to default"
|
||||
onClick={handleResetCloudUrl}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<SettingsItem
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { LoaderFunctionArgs, redirect } from "react-router-dom";
|
||||
import api from "../api";
|
||||
import { CLOUD_API, CLOUD_APP, SIGNAL_API } from "@/ui.config";
|
||||
import { CLOUD_APP, DEVICE_API } from "@/ui.config";
|
||||
|
||||
const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
const url = new URL(request.url);
|
||||
|
@ -11,15 +11,11 @@ const loader = async ({ request }: LoaderFunctionArgs) => {
|
|||
const oidcGoogle = searchParams.get("oidcGoogle");
|
||||
const clientId = searchParams.get("clientId");
|
||||
|
||||
const res = await api.POST(
|
||||
`${SIGNAL_API}/cloud/register`,
|
||||
{
|
||||
token: tempToken,
|
||||
cloudApi: CLOUD_API,
|
||||
oidcGoogle,
|
||||
clientId,
|
||||
},
|
||||
);
|
||||
const res = await api.POST(`${DEVICE_API}/cloud/register`, {
|
||||
token: tempToken,
|
||||
oidcGoogle,
|
||||
clientId,
|
||||
});
|
||||
|
||||
if (!res.ok) throw new Error("Failed to register device");
|
||||
return redirect(CLOUD_APP + `/devices/${deviceId}/setup`);
|
||||
|
|
|
@ -36,7 +36,7 @@ import { DeviceStatus } from "./welcome-local";
|
|||
import FocusTrap from "focus-trap-react";
|
||||
import OtherSessionConnectedModal from "@/components/OtherSessionConnectedModal";
|
||||
import Terminal from "@components/Terminal";
|
||||
import { CLOUD_API, SIGNAL_API } from "@/ui.config";
|
||||
import { CLOUD_API, DEVICE_API } from "@/ui.config";
|
||||
|
||||
interface LocalLoaderResp {
|
||||
authMode: "password" | "noPassword" | null;
|
||||
|
@ -57,12 +57,12 @@ export interface LocalDevice {
|
|||
|
||||
const deviceLoader = async () => {
|
||||
const res = await api
|
||||
.GET(`${SIGNAL_API}/device/status`)
|
||||
.GET(`${DEVICE_API}/device/status`)
|
||||
.then(res => res.json() as Promise<DeviceStatus>);
|
||||
|
||||
if (!res.isSetup) return redirect("/welcome");
|
||||
|
||||
const deviceRes = await api.GET(`${SIGNAL_API}/device`);
|
||||
const deviceRes = await api.GET(`${DEVICE_API}/device`);
|
||||
if (deviceRes.status === 401) return redirect("/login-local");
|
||||
if (deviceRes.ok) {
|
||||
const device = (await deviceRes.json()) as LocalDevice;
|
||||
|
@ -78,9 +78,7 @@ const cloudLoader = async (params: Params<string>): Promise<CloudLoaderResp> =>
|
|||
const iceResp = await api.POST(`${CLOUD_API}/webrtc/ice_config`);
|
||||
const iceConfig = await iceResp.json();
|
||||
|
||||
const deviceResp = await api.GET(
|
||||
`${CLOUD_API}/devices/${params.id}`,
|
||||
);
|
||||
const deviceResp = await api.GET(`${CLOUD_API}/devices/${params.id}`);
|
||||
|
||||
if (!deviceResp.ok) {
|
||||
if (deviceResp.status === 404) {
|
||||
|
@ -143,7 +141,11 @@ export default function KvmIdRoute() {
|
|||
|
||||
try {
|
||||
const sd = btoa(JSON.stringify(pc.localDescription));
|
||||
const res = await api.POST(`${SIGNAL_API}/webrtc/session`, {
|
||||
|
||||
const sessionUrl = isOnDevice
|
||||
? `${DEVICE_API}/webrtc/session`
|
||||
: `${CLOUD_API}/webrtc/session`;
|
||||
const res = await api.POST(sessionUrl, {
|
||||
sd,
|
||||
// When on device, we don't need to specify the device id, as it's already known
|
||||
...(isOnDevice ? {} : { id: params.id }),
|
||||
|
|
|
@ -12,16 +12,16 @@ import LogoWhiteIcon from "@/assets/logo-white.svg";
|
|||
import api from "../api";
|
||||
import { DeviceStatus } from "./welcome-local";
|
||||
import ExtLink from "../components/ExtLink";
|
||||
import { SIGNAL_API } from "@/ui.config";
|
||||
import { DEVICE_API } from "@/ui.config";
|
||||
|
||||
const loader = async () => {
|
||||
const res = await api
|
||||
.GET(`${SIGNAL_API}/device/status`)
|
||||
.GET(`${DEVICE_API}/device/status`)
|
||||
.then(res => res.json() as Promise<DeviceStatus>);
|
||||
|
||||
if (!res.isSetup) return redirect("/welcome");
|
||||
|
||||
const deviceRes = await api.GET(`${SIGNAL_API}/device`);
|
||||
const deviceRes = await api.GET(`${DEVICE_API}/device`);
|
||||
if (deviceRes.ok) return redirect("/");
|
||||
return null;
|
||||
};
|
||||
|
@ -32,7 +32,7 @@ const action = async ({ request }: ActionFunctionArgs) => {
|
|||
|
||||
try {
|
||||
const response = await api.POST(
|
||||
`${SIGNAL_API}/auth/login-local`,
|
||||
`${DEVICE_API}/auth/login-local`,
|
||||
{
|
||||
password,
|
||||
},
|
||||
|
|
|
@ -9,11 +9,11 @@ import LogoWhiteIcon from "@/assets/logo-white.svg";
|
|||
import { cx } from "../cva.config";
|
||||
import api from "../api";
|
||||
import { DeviceStatus } from "./welcome-local";
|
||||
import { SIGNAL_API } from "@/ui.config";
|
||||
import { DEVICE_API } from "@/ui.config";
|
||||
|
||||
const loader = async () => {
|
||||
const res = await api
|
||||
.GET(`${SIGNAL_API}/device/status`)
|
||||
.GET(`${DEVICE_API}/device/status`)
|
||||
.then(res => res.json() as Promise<DeviceStatus>);
|
||||
|
||||
if (res.isSetup) return redirect("/login-local");
|
||||
|
@ -31,7 +31,7 @@ const action = async ({ request }: ActionFunctionArgs) => {
|
|||
|
||||
if (localAuthMode === "noPassword") {
|
||||
try {
|
||||
await api.POST(`${SIGNAL_API}/device/setup`, {
|
||||
await api.POST(`${DEVICE_API}/device/setup`, {
|
||||
localAuthMode,
|
||||
});
|
||||
return redirect("/");
|
||||
|
|
|
@ -10,11 +10,11 @@ import LogoBlueIcon from "@/assets/logo-blue.png";
|
|||
import LogoWhiteIcon from "@/assets/logo-white.svg";
|
||||
import api from "../api";
|
||||
import { DeviceStatus } from "./welcome-local";
|
||||
import { SIGNAL_API } from "@/ui.config";
|
||||
import { DEVICE_API } from "@/ui.config";
|
||||
|
||||
const loader = async () => {
|
||||
const res = await api
|
||||
.GET(`${SIGNAL_API}/device/status`)
|
||||
.GET(`${DEVICE_API}/device/status`)
|
||||
.then(res => res.json() as Promise<DeviceStatus>);
|
||||
|
||||
if (res.isSetup) return redirect("/login-local");
|
||||
|
@ -31,7 +31,7 @@ const action = async ({ request }: ActionFunctionArgs) => {
|
|||
}
|
||||
|
||||
try {
|
||||
const response = await api.POST(`${SIGNAL_API}/device/setup`, {
|
||||
const response = await api.POST(`${DEVICE_API}/device/setup`, {
|
||||
localAuthMode: "password",
|
||||
password,
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@ import LogoMark from "@/assets/logo-mark.png";
|
|||
import { cx } from "cva";
|
||||
import api from "../api";
|
||||
import { redirect } from "react-router-dom";
|
||||
import { SIGNAL_API } from "@/ui.config";
|
||||
import { DEVICE_API } from "@/ui.config";
|
||||
|
||||
export interface DeviceStatus {
|
||||
isSetup: boolean;
|
||||
|
@ -17,7 +17,7 @@ export interface DeviceStatus {
|
|||
|
||||
const loader = async () => {
|
||||
const res = await api
|
||||
.GET(`${SIGNAL_API}/device/status`)
|
||||
.GET(`${DEVICE_API}/device/status`)
|
||||
.then(res => res.json() as Promise<DeviceStatus>);
|
||||
|
||||
if (res.isSetup) return redirect("/login-local");
|
||||
|
|
|
@ -1,23 +1,5 @@
|
|||
interface JetKVMConfig {
|
||||
CLOUD_API?: string;
|
||||
CLOUD_APP?: string;
|
||||
DEVICE_VERSION?: string;
|
||||
}
|
||||
export const CLOUD_API = import.meta.env.VITE_CLOUD_API;
|
||||
export const CLOUD_APP = import.meta.env.VITE_CLOUD_APP;
|
||||
|
||||
declare global {
|
||||
interface Window { JETKVM_CONFIG?: JetKVMConfig; }
|
||||
}
|
||||
|
||||
const getAppURL = (api_url?: string) => {
|
||||
if (!api_url) {
|
||||
return;
|
||||
}
|
||||
const url = new URL(api_url);
|
||||
url.host = url.host.replace(/api\./, "app.");
|
||||
// remove the ending slash
|
||||
return url.toString().replace(/\/$/, "");
|
||||
}
|
||||
|
||||
export const CLOUD_API = window.JETKVM_CONFIG?.CLOUD_API || import.meta.env.VITE_CLOUD_API;
|
||||
export const CLOUD_APP = window.JETKVM_CONFIG?.CLOUD_APP || getAppURL(CLOUD_API) || import.meta.env.VITE_CLOUD_APP;
|
||||
export const SIGNAL_API = import.meta.env.VITE_SIGNAL_API;
|
||||
// In device mode, an empty string uses the current hostname (the JetKVM device's IP) as the API endpoint
|
||||
export const DEVICE_API = "";
|
||||
|
|
|
@ -9,7 +9,7 @@ declare const process: {
|
|||
};
|
||||
|
||||
export default defineConfig(({ mode, command }) => {
|
||||
const isCloud = mode === "production";
|
||||
const isCloud = mode.indexOf("cloud") !== -1;
|
||||
const onDevice = mode === "device";
|
||||
const { JETKVM_PROXY_URL } = process.env;
|
||||
|
||||
|
@ -18,15 +18,17 @@ export default defineConfig(({ mode, command }) => {
|
|||
build: { outDir: isCloud ? "dist" : "../static" },
|
||||
server: {
|
||||
host: "0.0.0.0",
|
||||
proxy: JETKVM_PROXY_URL ? {
|
||||
'/me': JETKVM_PROXY_URL,
|
||||
'/device': JETKVM_PROXY_URL,
|
||||
'/webrtc': JETKVM_PROXY_URL,
|
||||
'/auth': JETKVM_PROXY_URL,
|
||||
'/storage': JETKVM_PROXY_URL,
|
||||
'/cloud': JETKVM_PROXY_URL,
|
||||
} : undefined
|
||||
proxy: JETKVM_PROXY_URL
|
||||
? {
|
||||
"/me": JETKVM_PROXY_URL,
|
||||
"/device": JETKVM_PROXY_URL,
|
||||
"/webrtc": JETKVM_PROXY_URL,
|
||||
"/auth": JETKVM_PROXY_URL,
|
||||
"/storage": JETKVM_PROXY_URL,
|
||||
"/cloud": JETKVM_PROXY_URL,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
base: onDevice && command === 'build' ? "/static" : "/",
|
||||
base: onDevice && command === "build" ? "/static" : "/",
|
||||
};
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue