mirror of https://github.com/jetkvm/kvm.git
feat: use the api url from device config (#161)
This commit is contained in:
parent
806792203f
commit
f3b4dbce49
|
@ -2,3 +2,5 @@ VITE_SIGNAL_API=http://localhost:3000
|
||||||
|
|
||||||
VITE_CLOUD_APP=http://localhost:5173
|
VITE_CLOUD_APP=http://localhost:5173
|
||||||
VITE_CLOUD_API=http://localhost:3000
|
VITE_CLOUD_API=http://localhost:3000
|
||||||
|
|
||||||
|
VITE_JETKVM_HEAD=
|
|
@ -2,3 +2,5 @@ VITE_SIGNAL_API= # Uses the KVM device's IP address as the signal API endpoint
|
||||||
|
|
||||||
VITE_CLOUD_APP=https://app.jetkvm.com
|
VITE_CLOUD_APP=https://app.jetkvm.com
|
||||||
VITE_CLOUD_API=https://api.jetkvm.com
|
VITE_CLOUD_API=https://api.jetkvm.com
|
||||||
|
|
||||||
|
VITE_JETKVM_HEAD=<script src="/device/ui-config.js"></script>
|
|
@ -1,4 +1,6 @@
|
||||||
VITE_SIGNAL_API=https://api.jetkvm.com
|
VITE_SIGNAL_API=https://api.jetkvm.com
|
||||||
|
|
||||||
VITE_CLOUD_APP=https://app.jetkvm.com
|
VITE_CLOUD_APP=https://app.jetkvm.com
|
||||||
VITE_CLOUD_API=https://api.jetkvm.com
|
VITE_CLOUD_API=https://api.jetkvm.com
|
||||||
|
|
||||||
|
VITE_JETKVM_HEAD=
|
|
@ -28,6 +28,7 @@
|
||||||
<title>JetKVM</title>
|
<title>JetKVM</title>
|
||||||
<link rel="stylesheet" href="/fonts/fonts.css" />
|
<link rel="stylesheet" href="/fonts/fonts.css" />
|
||||||
<link rel="icon" href="/favicon.png" />
|
<link rel="icon" href="/favicon.png" />
|
||||||
|
%VITE_JETKVM_HEAD%
|
||||||
<script>
|
<script>
|
||||||
// Initial theme setup
|
// Initial theme setup
|
||||||
document.documentElement.classList.toggle(
|
document.documentElement.classList.toggle(
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { useLocation, useNavigation, useSearchParams } from "react-router-dom";
|
||||||
import Fieldset from "@components/Fieldset";
|
import Fieldset from "@components/Fieldset";
|
||||||
import GridBackground from "@components/GridBackground";
|
import GridBackground from "@components/GridBackground";
|
||||||
import StepCounter from "@components/StepCounter";
|
import StepCounter from "@components/StepCounter";
|
||||||
|
import { CLOUD_API } from "@/ui.config";
|
||||||
|
|
||||||
type AuthLayoutProps = {
|
type AuthLayoutProps = {
|
||||||
title: string;
|
title: string;
|
||||||
|
@ -62,7 +63,7 @@ export default function AuthLayout({
|
||||||
<Fieldset className="space-y-12">
|
<Fieldset className="space-y-12">
|
||||||
<div className="max-w-sm mx-auto space-y-4">
|
<div className="max-w-sm mx-auto space-y-4">
|
||||||
<form
|
<form
|
||||||
action={`${import.meta.env.VITE_CLOUD_API}/oidc/google`}
|
action={`${CLOUD_API}/oidc/google`}
|
||||||
method="POST"
|
method="POST"
|
||||||
>
|
>
|
||||||
{/*This could be the KVM ID*/}
|
{/*This could be the KVM ID*/}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import PeerConnectionStatusCard from "@components/PeerConnectionStatusCard";
|
||||||
import api from "../api";
|
import api from "../api";
|
||||||
import { isOnDevice } from "../main";
|
import { isOnDevice } from "../main";
|
||||||
import { Button, LinkButton } from "./Button";
|
import { Button, LinkButton } from "./Button";
|
||||||
|
import { CLOUD_API, SIGNAL_API } from "@/ui.config";
|
||||||
|
|
||||||
interface NavbarProps {
|
interface NavbarProps {
|
||||||
isLoggedIn: boolean;
|
isLoggedIn: boolean;
|
||||||
|
@ -37,8 +38,8 @@ export default function DashboardNavbar({
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const onLogout = useCallback(async () => {
|
const onLogout = useCallback(async () => {
|
||||||
const logoutUrl = isOnDevice
|
const logoutUrl = isOnDevice
|
||||||
? `${import.meta.env.VITE_SIGNAL_API}/auth/logout`
|
? `${SIGNAL_API}/auth/logout`
|
||||||
: `${import.meta.env.VITE_CLOUD_API}/logout`;
|
: `${CLOUD_API}/logout`;
|
||||||
const res = await api.POST(logoutUrl);
|
const res = await api.POST(logoutUrl);
|
||||||
if (!res.ok) return;
|
if (!res.ok) return;
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,7 @@ import { ExclamationTriangleIcon } from "@heroicons/react/20/solid";
|
||||||
import notifications from "../notifications";
|
import notifications from "../notifications";
|
||||||
import Fieldset from "./Fieldset";
|
import Fieldset from "./Fieldset";
|
||||||
import { isOnDevice } from "../main";
|
import { isOnDevice } from "../main";
|
||||||
|
import { SIGNAL_API } from "@/ui.config";
|
||||||
|
|
||||||
export default function MountMediaModal({
|
export default function MountMediaModal({
|
||||||
open,
|
open,
|
||||||
|
@ -1119,7 +1120,7 @@ function UploadFileView({
|
||||||
alreadyUploadedBytes: number,
|
alreadyUploadedBytes: number,
|
||||||
dataChannel: string,
|
dataChannel: string,
|
||||||
) {
|
) {
|
||||||
const uploadUrl = `${import.meta.env.VITE_SIGNAL_API}/storage/upload?uploadId=${dataChannel}`;
|
const uploadUrl = `${SIGNAL_API}/storage/upload?uploadId=${dataChannel}`;
|
||||||
|
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
xhr.open("POST", uploadUrl, true);
|
xhr.open("POST", uploadUrl, true);
|
||||||
|
|
|
@ -26,6 +26,7 @@ import LocalAuthPasswordDialog from "@/components/LocalAuthPasswordDialog";
|
||||||
import { LocalDevice } from "@routes/devices.$id";
|
import { LocalDevice } from "@routes/devices.$id";
|
||||||
import { useRevalidator } from "react-router-dom";
|
import { useRevalidator } from "react-router-dom";
|
||||||
import { ShieldCheckIcon } from "@heroicons/react/20/solid";
|
import { ShieldCheckIcon } from "@heroicons/react/20/solid";
|
||||||
|
import { CLOUD_APP, SIGNAL_API } from "@/ui.config";
|
||||||
|
|
||||||
export function SettingsItem({
|
export function SettingsItem({
|
||||||
title,
|
title,
|
||||||
|
@ -366,7 +367,7 @@ export default function SettingsSidebar() {
|
||||||
const getDevice = useCallback(async () => {
|
const getDevice = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const status = await api
|
const status = await api
|
||||||
.GET(`${import.meta.env.VITE_SIGNAL_API}/device`)
|
.GET(`${SIGNAL_API}/device`)
|
||||||
.then(res => res.json() as Promise<LocalDevice>);
|
.then(res => res.json() as Promise<LocalDevice>);
|
||||||
setLocalDevice(status);
|
setLocalDevice(status);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -677,7 +678,7 @@ export default function SettingsSidebar() {
|
||||||
<div>
|
<div>
|
||||||
<LinkButton
|
<LinkButton
|
||||||
to={
|
to={
|
||||||
import.meta.env.VITE_CLOUD_APP +
|
CLOUD_APP +
|
||||||
"/signup?deviceId=" +
|
"/signup?deviceId=" +
|
||||||
deviceId +
|
deviceId +
|
||||||
`&returnTo=${location.href}adopt`
|
`&returnTo=${location.href}adopt`
|
||||||
|
|
|
@ -27,12 +27,13 @@ import LoginLocalRoute from "./routes/login-local";
|
||||||
import WelcomeLocalModeRoute from "./routes/welcome-local.mode";
|
import WelcomeLocalModeRoute from "./routes/welcome-local.mode";
|
||||||
import WelcomeRoute from "./routes/welcome-local";
|
import WelcomeRoute from "./routes/welcome-local";
|
||||||
import WelcomeLocalPasswordRoute from "./routes/welcome-local.password";
|
import WelcomeLocalPasswordRoute from "./routes/welcome-local.password";
|
||||||
|
import { CLOUD_API } from "./ui.config";
|
||||||
|
|
||||||
export const isOnDevice = import.meta.env.MODE === "device";
|
export const isOnDevice = import.meta.env.MODE === "device";
|
||||||
export const isInCloud = !isOnDevice;
|
export const isInCloud = !isOnDevice;
|
||||||
|
|
||||||
export async function checkAuth() {
|
export async function checkAuth() {
|
||||||
const res = await fetch(`${import.meta.env.VITE_CLOUD_API}/me`, {
|
const res = await fetch(`${CLOUD_API}/me`, {
|
||||||
mode: "cors",
|
mode: "cors",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { LoaderFunctionArgs, redirect } from "react-router-dom";
|
import { LoaderFunctionArgs, redirect } from "react-router-dom";
|
||||||
import api from "../api";
|
import api from "../api";
|
||||||
|
import { CLOUD_API, CLOUD_APP, SIGNAL_API } from "@/ui.config";
|
||||||
|
|
||||||
const loader = async ({ request }: LoaderFunctionArgs) => {
|
const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||||
const url = new URL(request.url);
|
const url = new URL(request.url);
|
||||||
|
@ -11,17 +12,17 @@ const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||||
const clientId = searchParams.get("clientId");
|
const clientId = searchParams.get("clientId");
|
||||||
|
|
||||||
const res = await api.POST(
|
const res = await api.POST(
|
||||||
`${import.meta.env.VITE_SIGNAL_API}/cloud/register`,
|
`${SIGNAL_API}/cloud/register`,
|
||||||
{
|
{
|
||||||
token: tempToken,
|
token: tempToken,
|
||||||
cloudApi: import.meta.env.VITE_CLOUD_API,
|
cloudApi: CLOUD_API,
|
||||||
oidcGoogle,
|
oidcGoogle,
|
||||||
clientId,
|
clientId,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!res.ok) throw new Error("Failed to register device");
|
if (!res.ok) throw new Error("Failed to register device");
|
||||||
return redirect(import.meta.env.VITE_CLOUD_APP + `/devices/${deviceId}/setup`);
|
return redirect(CLOUD_APP + `/devices/${deviceId}/setup`);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function AdoptRoute() {
|
export default function AdoptRoute() {
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { User } from "@/hooks/stores";
|
||||||
import { checkAuth } from "@/main";
|
import { checkAuth } from "@/main";
|
||||||
import Fieldset from "@components/Fieldset";
|
import Fieldset from "@components/Fieldset";
|
||||||
import { ChevronLeftIcon } from "@heroicons/react/16/solid";
|
import { ChevronLeftIcon } from "@heroicons/react/16/solid";
|
||||||
|
import { CLOUD_API } from "@/ui.config";
|
||||||
|
|
||||||
interface LoaderData {
|
interface LoaderData {
|
||||||
device: { id: string; name: string; user: { googleId: string } };
|
device: { id: string; name: string; user: { googleId: string } };
|
||||||
|
@ -24,7 +25,7 @@ const action = async ({ request }: ActionFunctionArgs) => {
|
||||||
const { deviceId } = Object.fromEntries(await request.formData());
|
const { deviceId } = Object.fromEntries(await request.formData());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${import.meta.env.VITE_CLOUD_API}/devices/${deviceId}`, {
|
const res = await fetch(`${CLOUD_API}/devices/${deviceId}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
|
@ -46,7 +47,7 @@ const loader = async ({ params }: LoaderFunctionArgs) => {
|
||||||
const { id } = params;
|
const { id } = params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${import.meta.env.VITE_CLOUD_API}/devices/${id}`, {
|
const res = await fetch(`${CLOUD_API}/devices/${id}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
mode: "cors",
|
mode: "cors",
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { User } from "@/hooks/stores";
|
||||||
import { checkAuth } from "@/main";
|
import { checkAuth } from "@/main";
|
||||||
import Fieldset from "@components/Fieldset";
|
import Fieldset from "@components/Fieldset";
|
||||||
import api from "../api";
|
import api from "../api";
|
||||||
|
import { CLOUD_API } from "@/ui.config";
|
||||||
|
|
||||||
interface LoaderData {
|
interface LoaderData {
|
||||||
device: { id: string; name: string; user: { googleId: string } };
|
device: { id: string; name: string; user: { googleId: string } };
|
||||||
|
@ -31,7 +32,7 @@ const action = async ({ params, request }: ActionFunctionArgs) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await api.PUT(`${import.meta.env.VITE_CLOUD_API}/devices/${id}`, {
|
const res = await api.PUT(`${CLOUD_API}/devices/${id}`, {
|
||||||
name,
|
name,
|
||||||
});
|
});
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
|
@ -49,7 +50,7 @@ const loader = async ({ params }: LoaderFunctionArgs) => {
|
||||||
const { id } = params;
|
const { id } = params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${import.meta.env.VITE_CLOUD_API}/devices/${id}`, {
|
const res = await fetch(`${CLOUD_API}/devices/${id}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
mode: "cors",
|
mode: "cors",
|
||||||
|
|
|
@ -16,10 +16,11 @@ import { InputFieldWithLabel } from "@components/InputField";
|
||||||
import { Button } from "@components/Button";
|
import { Button } from "@components/Button";
|
||||||
import { checkAuth } from "@/main";
|
import { checkAuth } from "@/main";
|
||||||
import api from "../api";
|
import api from "../api";
|
||||||
|
import { CLOUD_API } from "@/ui.config";
|
||||||
|
|
||||||
const loader = async ({ params }: LoaderFunctionArgs) => {
|
const loader = async ({ params }: LoaderFunctionArgs) => {
|
||||||
await checkAuth();
|
await checkAuth();
|
||||||
const res = await fetch(`${import.meta.env.VITE_CLOUD_API}/devices/${params.id}`, {
|
const res = await fetch(`${CLOUD_API}/devices/${params.id}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
mode: "cors",
|
mode: "cors",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
|
@ -35,7 +36,7 @@ const loader = async ({ params }: LoaderFunctionArgs) => {
|
||||||
const action = async ({ request }: ActionFunctionArgs) => {
|
const action = async ({ request }: ActionFunctionArgs) => {
|
||||||
// Handle form submission
|
// Handle form submission
|
||||||
const { name, id, returnTo } = Object.fromEntries(await request.formData());
|
const { name, id, returnTo } = Object.fromEntries(await request.formData());
|
||||||
const res = await api.PUT(`${import.meta.env.VITE_CLOUD_API}/devices/${id}`, { name });
|
const res = await api.PUT(`${CLOUD_API}/devices/${id}`, { name });
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
return redirect(returnTo?.toString() ?? `/devices/${id}`);
|
return redirect(returnTo?.toString() ?? `/devices/${id}`);
|
||||||
|
|
|
@ -36,6 +36,7 @@ import { DeviceStatus } from "./welcome-local";
|
||||||
import FocusTrap from "focus-trap-react";
|
import FocusTrap from "focus-trap-react";
|
||||||
import OtherSessionConnectedModal from "@/components/OtherSessionConnectedModal";
|
import OtherSessionConnectedModal from "@/components/OtherSessionConnectedModal";
|
||||||
import TerminalWrapper from "../components/Terminal";
|
import TerminalWrapper from "../components/Terminal";
|
||||||
|
import { CLOUD_API, SIGNAL_API } from "@/ui.config";
|
||||||
|
|
||||||
interface LocalLoaderResp {
|
interface LocalLoaderResp {
|
||||||
authMode: "password" | "noPassword" | null;
|
authMode: "password" | "noPassword" | null;
|
||||||
|
@ -56,12 +57,12 @@ export interface LocalDevice {
|
||||||
|
|
||||||
const deviceLoader = async () => {
|
const deviceLoader = async () => {
|
||||||
const res = await api
|
const res = await api
|
||||||
.GET(`${import.meta.env.VITE_SIGNAL_API}/device/status`)
|
.GET(`${SIGNAL_API}/device/status`)
|
||||||
.then(res => res.json() as Promise<DeviceStatus>);
|
.then(res => res.json() as Promise<DeviceStatus>);
|
||||||
|
|
||||||
if (!res.isSetup) return redirect("/welcome");
|
if (!res.isSetup) return redirect("/welcome");
|
||||||
|
|
||||||
const deviceRes = await api.GET(`${import.meta.env.VITE_SIGNAL_API}/device`);
|
const deviceRes = await api.GET(`${SIGNAL_API}/device`);
|
||||||
if (deviceRes.status === 401) return redirect("/login-local");
|
if (deviceRes.status === 401) return redirect("/login-local");
|
||||||
if (deviceRes.ok) {
|
if (deviceRes.ok) {
|
||||||
const device = (await deviceRes.json()) as LocalDevice;
|
const device = (await deviceRes.json()) as LocalDevice;
|
||||||
|
@ -74,11 +75,11 @@ const deviceLoader = async () => {
|
||||||
const cloudLoader = async (params: Params<string>): Promise<CloudLoaderResp> => {
|
const cloudLoader = async (params: Params<string>): Promise<CloudLoaderResp> => {
|
||||||
const user = await checkAuth();
|
const user = await checkAuth();
|
||||||
|
|
||||||
const iceResp = await api.POST(`${import.meta.env.VITE_CLOUD_API}/webrtc/ice_config`);
|
const iceResp = await api.POST(`${CLOUD_API}/webrtc/ice_config`);
|
||||||
const iceConfig = await iceResp.json();
|
const iceConfig = await iceResp.json();
|
||||||
|
|
||||||
const deviceResp = await api.GET(
|
const deviceResp = await api.GET(
|
||||||
`${import.meta.env.VITE_CLOUD_API}/devices/${params.id}`,
|
`${CLOUD_API}/devices/${params.id}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!deviceResp.ok) {
|
if (!deviceResp.ok) {
|
||||||
|
@ -142,7 +143,7 @@ export default function KvmIdRoute() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const sd = btoa(JSON.stringify(pc.localDescription));
|
const sd = btoa(JSON.stringify(pc.localDescription));
|
||||||
const res = await api.POST(`${import.meta.env.VITE_SIGNAL_API}/webrtc/session`, {
|
const res = await api.POST(`${SIGNAL_API}/webrtc/session`, {
|
||||||
sd,
|
sd,
|
||||||
// When on device, we don't need to specify the device id, as it's already known
|
// When on device, we don't need to specify the device id, as it's already known
|
||||||
...(isOnDevice ? {} : { id: params.id }),
|
...(isOnDevice ? {} : { id: params.id }),
|
||||||
|
@ -317,7 +318,7 @@ export default function KvmIdRoute() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fire and forget
|
// Fire and forget
|
||||||
api.POST(`${import.meta.env.VITE_CLOUD_API}/webrtc/turn_activity`, {
|
api.POST(`${CLOUD_API}/webrtc/turn_activity`, {
|
||||||
bytesReceived: bytesReceivedDelta,
|
bytesReceived: bytesReceivedDelta,
|
||||||
bytesSent: bytesSentDelta,
|
bytesSent: bytesSentDelta,
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { User } from "@/hooks/stores";
|
||||||
import EmptyCard from "@components/EmptyCard";
|
import EmptyCard from "@components/EmptyCard";
|
||||||
import { LuMonitorSmartphone } from "react-icons/lu";
|
import { LuMonitorSmartphone } from "react-icons/lu";
|
||||||
import { ArrowRightIcon } from "@heroicons/react/16/solid";
|
import { ArrowRightIcon } from "@heroicons/react/16/solid";
|
||||||
|
import { CLOUD_API } from "@/ui.config";
|
||||||
|
|
||||||
interface LoaderData {
|
interface LoaderData {
|
||||||
devices: { id: string; name: string; online: boolean; lastSeen: string }[];
|
devices: { id: string; name: string; online: boolean; lastSeen: string }[];
|
||||||
|
@ -19,7 +20,7 @@ export const loader = async () => {
|
||||||
const user = await checkAuth();
|
const user = await checkAuth();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${import.meta.env.VITE_CLOUD_API}/devices`, {
|
const res = await fetch(`${CLOUD_API}/devices`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
mode: "cors",
|
mode: "cors",
|
||||||
|
|
|
@ -12,15 +12,16 @@ import LogoWhiteIcon from "@/assets/logo-white.svg";
|
||||||
import api from "../api";
|
import api from "../api";
|
||||||
import { DeviceStatus } from "./welcome-local";
|
import { DeviceStatus } from "./welcome-local";
|
||||||
import ExtLink from "../components/ExtLink";
|
import ExtLink from "../components/ExtLink";
|
||||||
|
import { SIGNAL_API } from "@/ui.config";
|
||||||
|
|
||||||
const loader = async () => {
|
const loader = async () => {
|
||||||
const res = await api
|
const res = await api
|
||||||
.GET(`${import.meta.env.VITE_SIGNAL_API}/device/status`)
|
.GET(`${SIGNAL_API}/device/status`)
|
||||||
.then(res => res.json() as Promise<DeviceStatus>);
|
.then(res => res.json() as Promise<DeviceStatus>);
|
||||||
|
|
||||||
if (!res.isSetup) return redirect("/welcome");
|
if (!res.isSetup) return redirect("/welcome");
|
||||||
|
|
||||||
const deviceRes = await api.GET(`${import.meta.env.VITE_SIGNAL_API}/device`);
|
const deviceRes = await api.GET(`${SIGNAL_API}/device`);
|
||||||
if (deviceRes.ok) return redirect("/");
|
if (deviceRes.ok) return redirect("/");
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
@ -31,7 +32,7 @@ const action = async ({ request }: ActionFunctionArgs) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await api.POST(
|
const response = await api.POST(
|
||||||
`${import.meta.env.VITE_SIGNAL_API}/auth/login-local`,
|
`${SIGNAL_API}/auth/login-local`,
|
||||||
{
|
{
|
||||||
password,
|
password,
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,10 +9,11 @@ import LogoWhiteIcon from "@/assets/logo-white.svg";
|
||||||
import { cx } from "../cva.config";
|
import { cx } from "../cva.config";
|
||||||
import api from "../api";
|
import api from "../api";
|
||||||
import { DeviceStatus } from "./welcome-local";
|
import { DeviceStatus } from "./welcome-local";
|
||||||
|
import { SIGNAL_API } from "@/ui.config";
|
||||||
|
|
||||||
const loader = async () => {
|
const loader = async () => {
|
||||||
const res = await api
|
const res = await api
|
||||||
.GET(`${import.meta.env.VITE_SIGNAL_API}/device/status`)
|
.GET(`${SIGNAL_API}/device/status`)
|
||||||
.then(res => res.json() as Promise<DeviceStatus>);
|
.then(res => res.json() as Promise<DeviceStatus>);
|
||||||
|
|
||||||
if (res.isSetup) return redirect("/login-local");
|
if (res.isSetup) return redirect("/login-local");
|
||||||
|
@ -30,7 +31,7 @@ const action = async ({ request }: ActionFunctionArgs) => {
|
||||||
|
|
||||||
if (localAuthMode === "noPassword") {
|
if (localAuthMode === "noPassword") {
|
||||||
try {
|
try {
|
||||||
await api.POST(`${import.meta.env.VITE_SIGNAL_API}/device/setup`, {
|
await api.POST(`${SIGNAL_API}/device/setup`, {
|
||||||
localAuthMode,
|
localAuthMode,
|
||||||
});
|
});
|
||||||
return redirect("/");
|
return redirect("/");
|
||||||
|
|
|
@ -10,10 +10,11 @@ import LogoBlueIcon from "@/assets/logo-blue.png";
|
||||||
import LogoWhiteIcon from "@/assets/logo-white.svg";
|
import LogoWhiteIcon from "@/assets/logo-white.svg";
|
||||||
import api from "../api";
|
import api from "../api";
|
||||||
import { DeviceStatus } from "./welcome-local";
|
import { DeviceStatus } from "./welcome-local";
|
||||||
|
import { SIGNAL_API } from "@/ui.config";
|
||||||
|
|
||||||
const loader = async () => {
|
const loader = async () => {
|
||||||
const res = await api
|
const res = await api
|
||||||
.GET(`${import.meta.env.VITE_SIGNAL_API}/device/status`)
|
.GET(`${SIGNAL_API}/device/status`)
|
||||||
.then(res => res.json() as Promise<DeviceStatus>);
|
.then(res => res.json() as Promise<DeviceStatus>);
|
||||||
|
|
||||||
if (res.isSetup) return redirect("/login-local");
|
if (res.isSetup) return redirect("/login-local");
|
||||||
|
@ -30,7 +31,7 @@ const action = async ({ request }: ActionFunctionArgs) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await api.POST(`${import.meta.env.VITE_SIGNAL_API}/device/setup`, {
|
const response = await api.POST(`${SIGNAL_API}/device/setup`, {
|
||||||
localAuthMode: "password",
|
localAuthMode: "password",
|
||||||
password,
|
password,
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,6 +9,7 @@ import LogoMark from "@/assets/logo-mark.png";
|
||||||
import { cx } from "cva";
|
import { cx } from "cva";
|
||||||
import api from "../api";
|
import api from "../api";
|
||||||
import { redirect } from "react-router-dom";
|
import { redirect } from "react-router-dom";
|
||||||
|
import { SIGNAL_API } from "@/ui.config";
|
||||||
|
|
||||||
export interface DeviceStatus {
|
export interface DeviceStatus {
|
||||||
isSetup: boolean;
|
isSetup: boolean;
|
||||||
|
@ -16,7 +17,7 @@ export interface DeviceStatus {
|
||||||
|
|
||||||
const loader = async () => {
|
const loader = async () => {
|
||||||
const res = await api
|
const res = await api
|
||||||
.GET(`${import.meta.env.VITE_SIGNAL_API}/device/status`)
|
.GET(`${SIGNAL_API}/device/status`)
|
||||||
.then(res => res.json() as Promise<DeviceStatus>);
|
.then(res => res.json() as Promise<DeviceStatus>);
|
||||||
|
|
||||||
if (res.isSetup) return redirect("/login-local");
|
if (res.isSetup) return redirect("/login-local");
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
interface JetKVMConfig {
|
||||||
|
CLOUD_API?: string;
|
||||||
|
CLOUD_APP?: string;
|
||||||
|
DEVICE_VERSION?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
22
web.go
22
web.go
|
@ -2,6 +2,8 @@ package kvm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -77,6 +79,9 @@ func setupRouter() *gin.Engine {
|
||||||
// We use this to determine if the device is setup
|
// We use this to determine if the device is setup
|
||||||
r.GET("/device/status", handleDeviceStatus)
|
r.GET("/device/status", handleDeviceStatus)
|
||||||
|
|
||||||
|
// We use this to provide the UI with the device configuration
|
||||||
|
r.GET("/device/ui-config.js", handleDeviceUIConfig)
|
||||||
|
|
||||||
// We use this to setup the device in the welcome page
|
// We use this to setup the device in the welcome page
|
||||||
r.POST("/device/setup", handleSetup)
|
r.POST("/device/setup", handleSetup)
|
||||||
|
|
||||||
|
@ -361,6 +366,23 @@ func handleDeviceStatus(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, response)
|
c.JSON(http.StatusOK, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleDeviceUIConfig(c *gin.Context) {
|
||||||
|
LoadConfig()
|
||||||
|
|
||||||
|
config, _ := json.Marshal(gin.H{
|
||||||
|
"CLOUD_API": config.CloudURL,
|
||||||
|
"DEVICE_VERSION": builtAppVersion,
|
||||||
|
})
|
||||||
|
if config == nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to marshal config"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := fmt.Sprintf("window.JETKVM_CONFIG = %s;", config)
|
||||||
|
|
||||||
|
c.Data(http.StatusOK, "text/javascript; charset=utf-8", []byte(response))
|
||||||
|
}
|
||||||
|
|
||||||
func handleSetup(c *gin.Context) {
|
func handleSetup(c *gin.Context) {
|
||||||
LoadConfig()
|
LoadConfig()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue