Merge pull request #377 from kinode-dao/tm/ui-dev-servers

configure dev servers' proxies; fix blackscreen
This commit is contained in:
doria 2024-06-05 01:58:40 +09:00 committed by GitHub
commit 553ad00d51
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 178 additions and 133 deletions

View File

@ -95,7 +95,7 @@ fn make_widget() -> String {
</div> </div>
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
fetch('/main:app_store:sys/apps/listed') fetch('/main:app_store:sys/apps/listed', { credentials: 'include' })
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
const container = document.getElementById('latest-apps'); const container = document.getElementById('latest-apps');

View File

@ -14,7 +14,7 @@
<meta httpEquiv="X-UA-Compatible" content="IE=edge" /> <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" <meta name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" /> content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" />
<script type="module" crossorigin src="/main:app_store:sys/assets/index-Mr04YvPM.js"></script> <script type="module" crossorigin src="/main:app_store:sys/assets/index-I5kjLT9f.js"></script>
<link rel="stylesheet" crossorigin href="/main:app_store:sys/assets/index-fGthT1qI.css"> <link rel="stylesheet" crossorigin href="/main:app_store:sys/assets/index-fGthT1qI.css">
</head> </head>

View File

@ -5,6 +5,7 @@ import Modal from "./Modal";
import { getAppName } from "../utils/app"; import { getAppName } from "../utils/app";
import Loader from "./Loader"; import Loader from "./Loader";
import classNames from "classnames"; import classNames from "classnames";
import { FaU } from "react-icons/fa6";
interface UpdateButtonProps extends React.HTMLAttributes<HTMLButtonElement> { interface UpdateButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
app: AppInfo; app: AppInfo;
@ -55,10 +56,12 @@ export default function UpdateButton({ app, isIcon = false, ...props }: UpdateBu
<button <button
{...props} {...props}
type="button" type="button"
className={classNames("text-sm self-start", props.className)} className={classNames("text-sm self-start", props.className, {
'icon clear': isIcon
})}
onClick={onClick} onClick={onClick}
> >
Update {isIcon ? <FaU /> : 'Update'}
</button> </button>
<Modal show={showModal} hide={() => setShowModal(false)}> <Modal show={showModal} hide={() => setShowModal(false)}>
{loading ? ( {loading ? (

View File

@ -9,7 +9,7 @@
<meta httpEquiv="X-UA-Compatible" content="IE=edge" /> <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" <meta name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" /> content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" />
<script type="module" crossorigin src="/assets/index-DqBTDSfz.js"></script> <script type="module" crossorigin src="/assets/index-BkgGa32-.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BS5LP50I.css"> <link rel="stylesheet" crossorigin href="/assets/index-BS5LP50I.css">
</head> </head>

View File

@ -9,7 +9,7 @@
<meta httpEquiv="X-UA-Compatible" content="IE=edge" /> <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" <meta name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" /> content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" />
<script type="module" crossorigin src="/assets/index-DqBTDSfz.js"></script> <script type="module" crossorigin src="/assets/index-BkgGa32-.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BS5LP50I.css"> <link rel="stylesheet" crossorigin href="/assets/index-BS5LP50I.css">
</head> </head>

View File

@ -15,7 +15,7 @@ const AllApps: React.FC<{ expanded: boolean }> = ({ expanded }) => {
})}> })}>
{apps.length === 0 {apps.length === 0
? <div>Loading apps...</div> ? <div>Loading apps...</div>
: apps.map(app => <AppDisplay app={app} />)} : apps.map(app => <AppDisplay key={app.package_name} app={app} />)}
</div> </div>
} }

View File

@ -7,7 +7,7 @@ import { isMobileCheck } from "../utils/dimensions"
import AppIconPlaceholder from "./AppIconPlaceholder" import AppIconPlaceholder from "./AppIconPlaceholder"
interface AppDisplayProps { interface AppDisplayProps {
app: HomepageApp app?: HomepageApp
} }
const AppDisplay: React.FC<AppDisplayProps> = ({ app }) => { const AppDisplay: React.FC<AppDisplayProps> = ({ app }) => {
@ -17,15 +17,15 @@ const AppDisplay: React.FC<AppDisplayProps> = ({ app }) => {
return <a return <a
className={classNames("flex-col-center gap-2 relative hover:opacity-90 transition-opacity", { className={classNames("flex-col-center gap-2 relative hover:opacity-90 transition-opacity", {
'cursor-pointer': app.path, 'cursor-pointer': app?.path,
'cursor-not-allowed': !app.path, 'cursor-not-allowed': !app?.path,
})} })}
id={app.package_name} id={app?.package_name}
href={app.path} href={app?.path}
onMouseEnter={() => setIsHovered(true)} onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)} onMouseLeave={() => setIsHovered(false)}
> >
{app.base64_icon {app?.base64_icon
? <img ? <img
src={app.base64_icon} src={app.base64_icon}
className={classNames('rounded', { className={classNames('rounded', {
@ -34,12 +34,12 @@ const AppDisplay: React.FC<AppDisplayProps> = ({ app }) => {
})} })}
/> />
: <AppIconPlaceholder : <AppIconPlaceholder
text={app.state?.our_version || '0'} text={app?.state?.our_version || '0'}
size={'small'} size={'small'}
className="h-16 w-16" className="h-16 w-16"
/>} />}
<h6>{app.label}</h6> <h6>{app?.label}</h6>
{app.path && isHovered && <button {app?.path && isHovered && <button
className="absolute p-2 -top-2 -right-2 clear text-sm" className="absolute p-2 -top-2 -right-2 clear text-sm"
onClick={(e) => { onClick={(e) => {
e.preventDefault() e.preventDefault()

View File

@ -5,6 +5,7 @@ import { useEffect, useState } from "react"
import { isMobileCheck } from "../utils/dimensions" import { isMobileCheck } from "../utils/dimensions"
import classNames from "classnames" import classNames from "classnames"
import { DragDropContext, Draggable, DropResult, Droppable } from 'react-beautiful-dnd' import { DragDropContext, Draggable, DropResult, Droppable } from 'react-beautiful-dnd'
import { getFetchUrl } from "../utils/fetch"
const AppsDock: React.FC = () => { const AppsDock: React.FC = () => {
const { apps } = useHomepageStore() const { apps } = useHomepageStore()
@ -66,11 +67,12 @@ const AppsDock: React.FC = () => {
console.log({ favoriteApps }) console.log({ favoriteApps })
fetch('/order', { fetch(getFetchUrl('/order'), {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
credentials: 'include',
body: JSON.stringify(packageNames) body: JSON.stringify(packageNames)
}) })
.then(data => { .then(data => {

View File

@ -19,6 +19,7 @@ const Widgets = () => {
package_name={package_name} package_name={package_name}
widget={widget!} widget={widget!}
forceLarge={_appsWithWidgets.length === 1} forceLarge={_appsWithWidgets.length === 1}
key={package_name}
/>)} />)}
</div> </div>
} }

View File

@ -11,6 +11,7 @@ import classNames from 'classnames'
import WidgetsSettingsModal from '../components/WidgetsSettingsModal' import WidgetsSettingsModal from '../components/WidgetsSettingsModal'
import valetIcon from '../../public/valet-icon.png' import valetIcon from '../../public/valet-icon.png'
import { getFetchUrl } from '../utils/fetch'
interface AppStoreApp { interface AppStoreApp {
package: string, package: string,
@ -28,9 +29,9 @@ function Homepage() {
const getAppPathsAndIcons = () => { const getAppPathsAndIcons = () => {
Promise.all([ Promise.all([
fetch('/apps').then(res => res.json() as any as HomepageApp[]), fetch(getFetchUrl('/apps'), { credentials: 'include' }).then(res => res.json() as any as HomepageApp[]).catch(() => []),
fetch('/main:app_store:sys/apps').then(res => res.json()), fetch(getFetchUrl('/main:app_store:sys/apps'), { credentials: 'include' }).then(res => res.json()).catch(() => []),
fetch('/version').then(res => res.text()) fetch(getFetchUrl('/version'), { credentials: 'include' }).then(res => res.text()).catch(() => '')
]).then(([appsData, appStoreData, version]) => { ]).then(([appsData, appStoreData, version]) => {
console.log({ appsData, appStoreData, version }) console.log({ appsData, appStoreData, version })
@ -77,7 +78,7 @@ function Homepage() {
}, [our]); }, [our]);
useEffect(() => { useEffect(() => {
fetch('/our') fetch(getFetchUrl('/our'), { credentials: 'include' })
.then(res => res.text()) .then(res => res.text())
.then(data => { .then(data => {
if (data.match(/^[a-zA-Z0-9\-\.]+\.[a-zA-Z]+$/)) { if (data.match(/^[a-zA-Z0-9\-\.]+\.[a-zA-Z]+$/)) {

View File

@ -0,0 +1,12 @@
/**
* Prepends or strips '/api/' based on the environment.
* @param {string} path The original path.
* @return {string} The modified path.
*/
export function getFetchUrl(path: string) {
const isDevelopment = import.meta.env.DEV;
if (isDevelopment) {
return `/api${path}`;
}
return path.replace(/^\/api/, '');
}

View File

@ -1 +1,13 @@
/// <reference types="vite/client" /> /// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_APP_TITLE: string;
readonly REACT_APP_MAINNET_RPC_URL: string;
readonly REACT_APP_SEPOLIA_RPC_URL: string;
readonly VITE_NODE_URL: string;
// Add other environment variables as needed
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}

View File

@ -8,4 +8,13 @@ export default defineConfig({
react() react()
], ],
// ... // ...
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
}) })

View File

@ -11,7 +11,7 @@
<meta httpEquiv="X-UA-Compatible" content="IE=edge" /> <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" <meta name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" /> content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" />
<script type="module" crossorigin src="/assets/index-iKMNbHhl.js"></script> <script type="module" crossorigin src="/assets/index--NeI-a3U.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-B00cPdAQ.css"> <link rel="stylesheet" crossorigin href="/assets/index-B00cPdAQ.css">
</head> </head>

View File

@ -36,6 +36,7 @@ import KinodeHome from "./pages/KinodeHome"
import ResetNode from "./pages/ResetNode"; import ResetNode from "./pages/ResetNode";
import ImportKeyfile from "./pages/ImportKeyfile"; import ImportKeyfile from "./pages/ImportKeyfile";
import { UnencryptedIdentity } from "./lib/types"; import { UnencryptedIdentity } from "./lib/types";
import { getFetchUrl } from "./utils/fetch";
const { const {
useProvider, useProvider,
@ -117,7 +118,7 @@ function App() {
useEffect(() => { useEffect(() => {
(async () => { (async () => {
try { try {
const infoResponse = await fetch('/info', { method: 'GET' }) const infoResponse = await fetch(getFetchUrl('/info'), { method: 'GET', credentials: 'include' })
if (infoResponse.status > 399) { if (infoResponse.status > 399) {
console.log('no info, unbooted') console.log('no info, unbooted')
@ -136,7 +137,7 @@ function App() {
} }
try { try {
const currentChainResponse = await fetch('/current-chain', { method: 'GET' }) const currentChainResponse = await fetch(getFetchUrl('/current-chain'), { method: 'GET', credentials: 'include' })
if (currentChainResponse.status < 400) { if (currentChainResponse.status < 400) {
const nodeChainId = await currentChainResponse.json() const nodeChainId = await currentChainResponse.json()

View File

@ -9,6 +9,7 @@ import { ipToNumber } from "../utils/ipToNumber";
import DirectCheckbox from "../components/DirectCheckbox"; import DirectCheckbox from "../components/DirectCheckbox";
import { KinodeTitle } from "../components/KinodeTitle"; import { KinodeTitle } from "../components/KinodeTitle";
import { Tooltip } from "../components/Tooltip"; import { Tooltip } from "../components/Tooltip";
import { getFetchUrl } from "../utils/fetch";
const { useAccounts, useProvider } = hooks; const { useAccounts, useProvider } = hooks;
@ -51,7 +52,7 @@ function ClaimOsInvite({
if (invite !== "") { if (invite !== "") {
const url = process.env.REACT_APP_INVITE_GET + invite; const url = process.env.REACT_APP_INVITE_GET + invite;
const response = await fetch(url, { method: "GET" }); const response = await fetch(getFetchUrl(url), { method: "GET", credentials: 'include' });
if (response!.status === 200) { if (response!.status === 200) {
setInviteValidity(""); setInviteValidity("");
@ -80,7 +81,7 @@ function ClaimOsInvite({
routers routers
} }
} }
} = (await fetch("/generate-networking-info", { method: "POST" }).then( } = (await fetch(getFetchUrl("/generate-networking-info"), { method: "POST", credentials: 'include' }).then(
(res) => res.json() (res) => res.json()
)) as NetworkingInfo; )) as NetworkingInfo;
@ -106,9 +107,10 @@ function ClaimOsInvite({
console.log("BUILDING", networking_key, ipAddress, ws_port, tcp_port, routers); console.log("BUILDING", networking_key, ipAddress, ws_port, tcp_port, routers);
try { try {
response = await fetch(process.env.REACT_APP_BUILD_USER_OP_POST!, { response = await fetch(getFetchUrl(process.env.REACT_APP_BUILD_USER_OP_POST!), {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
credentials: 'include',
body: JSON.stringify({ body: JSON.stringify({
name: name + ".os", name: name + ".os",
address: accounts![0], address: accounts![0],
@ -143,9 +145,10 @@ function ClaimOsInvite({
data.userOperation.signature = signature; data.userOperation.signature = signature;
try { try {
response = await fetch(process.env.REACT_APP_BROADCAST_USER_OP_POST!, { response = await fetch(getFetchUrl(process.env.REACT_APP_BROADCAST_USER_OP_POST!), {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
credentials: 'include',
body: JSON.stringify({ body: JSON.stringify({
userOp: data.userOperation, userOp: data.userOperation,
code: invite, code: invite,

View File

@ -9,6 +9,7 @@ import { utils } from "ethers";
import KinodeHeader from "../components/KnsHeader"; import KinodeHeader from "../components/KnsHeader";
import { PageProps } from "../lib/types"; import { PageProps } from "../lib/types";
import Loader from "../components/Loader"; import Loader from "../components/Loader";
import { getFetchUrl } from "../utils/fetch";
interface ImportKeyfileProps extends PageProps { } interface ImportKeyfileProps extends PageProps { }
@ -36,8 +37,9 @@ function ImportKeyfile({
// const handlePassword = useCallback(async () => { // const handlePassword = useCallback(async () => {
// try { // try {
// const response = await fetch("/vet-keyfile", { // const response = await fetch(getFetchUrl("/vet-keyfile"), {
// method: "POST", // method: "POST",
// credentials: 'include',
// headers: { "Content-Type": "application/json" }, // headers: { "Content-Type": "application/json" },
// body: JSON.stringify({ // body: JSON.stringify({
// keyfile: localKey, // keyfile: localKey,
@ -127,8 +129,9 @@ function ImportKeyfile({
if (keyErrs.length === 0 && localKey !== "") { if (keyErrs.length === 0 && localKey !== "") {
let hashed_password = utils.sha256(utils.toUtf8Bytes(pw)); let hashed_password = utils.sha256(utils.toUtf8Bytes(pw));
const response = await fetch("/vet-keyfile", { const response = await fetch(getFetchUrl("/vet-keyfile"), {
method: "POST", method: "POST",
credentials: 'include',
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ body: JSON.stringify({
keyfile: localKey, keyfile: localKey,
@ -140,8 +143,9 @@ function ImportKeyfile({
throw new Error("Incorrect password"); throw new Error("Incorrect password");
} }
const result = await fetch("/import-keyfile", { const result = await fetch(getFetchUrl("/import-keyfile"), {
method: "POST", method: "POST",
credentials: 'include',
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ body: JSON.stringify({
keyfile: localKey, keyfile: localKey,
@ -154,7 +158,7 @@ function ImportKeyfile({
} }
const interval = setInterval(async () => { const interval = setInterval(async () => {
const res = await fetch("/"); const res = await fetch(getFetchUrl("/"), { credentials: 'include' });
if ( if (
res.status < 300 && res.status < 300 &&
Number(res.headers.get("content-length")) !== appSizeOnLoad Number(res.headers.get("content-length")) !== appSizeOnLoad

View File

@ -13,6 +13,7 @@ import { KinodeTitle } from "../components/KinodeTitle";
import { isMobileCheck } from "../utils/dimensions"; import { isMobileCheck } from "../utils/dimensions";
import classNames from "classnames"; import classNames from "classnames";
import { generateNetworkingKeys, getNetworkName } from "../utils/chain"; import { generateNetworkingKeys, getNetworkName } from "../utils/chain";
import { getFetchUrl } from "../utils/fetch";
const { useProvider } = hooks; const { useProvider } = hooks;
@ -51,7 +52,7 @@ function Login({
(async () => { (async () => {
try { try {
const infoData = (await fetch("/info", { method: "GET" }).then((res) => const infoData = (await fetch(getFetchUrl("/info"), { method: "GET", credentials: 'include' }).then((res) =>
res.json() res.json()
)) as UnencryptedIdentity; )) as UnencryptedIdentity;
setRouters(infoData.allowed_routers); setRouters(infoData.allowed_routers);
@ -81,8 +82,9 @@ function Login({
let hashed_password = utils.sha256(utils.toUtf8Bytes(pw)); let hashed_password = utils.sha256(utils.toUtf8Bytes(pw));
// Replace this with network key generation // Replace this with network key generation
const response = await fetch("/vet-keyfile", { const response = await fetch(getFetchUrl("/vet-keyfile"), {
method: "POST", method: "POST",
credentials: 'include',
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ password_hash: hashed_password, keyfile: "" }), body: JSON.stringify({ password_hash: hashed_password, keyfile: "" }),
}); });
@ -119,9 +121,10 @@ function Login({
// Login or confirm new keys // Login or confirm new keys
const result = await fetch( const result = await fetch(
reset ? "confirm-change-network-keys" : "login", getFetchUrl(reset ? "confirm-change-network-keys" : "login"),
{ {
method: "POST", method: "POST",
credentials: 'include',
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: reset body: reset
? JSON.stringify({ password_hash: hashed_password, direct }) ? JSON.stringify({ password_hash: hashed_password, direct })
@ -139,7 +142,7 @@ function Login({
} }
const interval = setInterval(async () => { const interval = setInterval(async () => {
const res = await fetch("/"); const res = await fetch(getFetchUrl("/"), { credentials: 'include' });
if ( if (
res.status < 300 && res.status < 300 &&
Number(res.headers.get("content-length")) !== appSizeOnLoad Number(res.headers.get("content-length")) !== appSizeOnLoad

View File

@ -5,6 +5,7 @@ import { utils, providers } from "ethers";
import { downloadKeyfile } from "../utils/download-keyfile"; import { downloadKeyfile } from "../utils/download-keyfile";
import { Tooltip } from "../components/Tooltip"; import { Tooltip } from "../components/Tooltip";
import { KinodeTitle } from "../components/KinodeTitle"; import { KinodeTitle } from "../components/KinodeTitle";
import { getFetchUrl } from "../utils/fetch";
type SetPasswordProps = { type SetPasswordProps = {
direct: boolean; direct: boolean;
@ -70,7 +71,7 @@ function SetPassword({
let signature = await signer?.signMessage(utils.toUtf8Bytes(sig_data)); let signature = await signer?.signMessage(utils.toUtf8Bytes(sig_data));
try { try {
const result = await fetch("/boot", { const result = await fetch(getFetchUrl("/boot"), {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
credentials: "include", credentials: "include",
@ -90,7 +91,7 @@ function SetPassword({
downloadKeyfile(knsName, base64String); downloadKeyfile(knsName, base64String);
const interval = setInterval(async () => { const interval = setInterval(async () => {
const res = await fetch("/"); const res = await fetch(getFetchUrl("/"), { credentials: 'include' });
if ( if (
res.status < 300 && res.status < 300 &&

View File

@ -0,0 +1,12 @@
/**
* Prepends or strips '/api/' based on the environment.
* @param {string} path The original path.
* @return {string} The modified path.
*/
export function getFetchUrl(path: string) {
const isDevelopment = import.meta.env.DEV;
if (isDevelopment) {
return `/api${path}`;
}
return path.replace(/^\/api/, '');
}

View File

@ -44,29 +44,10 @@ export default defineConfig({
// ... // ...
server: { server: {
proxy: { proxy: {
'/generate-networking-info': { '/api': {
target: 'http://localhost:8080/generate-networking-info', target: 'http://localhost:8080',
changeOrigin: true,
},
'/vet-keyfile': {
target: 'http://localhost:8080/vet-keyfile',
changeOrigin: true,
},
'/import-keyfile': {
target: 'http://localhost:8080/import-keyfile',
changeOrigin: true,
},
'/info': {
target: 'http://localhost:8080/info',
changeOrigin: true,
},
'/current-chain': {
target: 'http://localhost:8080/current-chain',
changeOrigin: true,
},
'/boot': {
target: 'http://localhost:8080/boot',
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
} }
} }
} }