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>
 | 
				
			||||||
| 
						 | 
					@ -2,3 +2,5 @@ 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