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>
<script>
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(data => {
const container = document.getElementById('latest-apps');

View File

@ -14,7 +14,7 @@
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport"
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">
</head>

View File

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

View File

@ -9,7 +9,7 @@
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport"
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">
</head>

View File

@ -9,7 +9,7 @@
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport"
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">
</head>

View File

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

View File

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

View File

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

View File

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

View File

@ -11,6 +11,7 @@ import classNames from 'classnames'
import WidgetsSettingsModal from '../components/WidgetsSettingsModal'
import valetIcon from '../../public/valet-icon.png'
import { getFetchUrl } from '../utils/fetch'
interface AppStoreApp {
package: string,
@ -28,9 +29,9 @@ function Homepage() {
const getAppPathsAndIcons = () => {
Promise.all([
fetch('/apps').then(res => res.json() as any as HomepageApp[]),
fetch('/main:app_store:sys/apps').then(res => res.json()),
fetch('/version').then(res => res.text())
fetch(getFetchUrl('/apps'), { credentials: 'include' }).then(res => res.json() as any as HomepageApp[]).catch(() => []),
fetch(getFetchUrl('/main:app_store:sys/apps'), { credentials: 'include' }).then(res => res.json()).catch(() => []),
fetch(getFetchUrl('/version'), { credentials: 'include' }).then(res => res.text()).catch(() => '')
]).then(([appsData, appStoreData, version]) => {
console.log({ appsData, appStoreData, version })
@ -77,7 +78,7 @@ function Homepage() {
}, [our]);
useEffect(() => {
fetch('/our')
fetch(getFetchUrl('/our'), { credentials: 'include' })
.then(res => res.text())
.then(data => {
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" />
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()
],
// ...
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 name="viewport"
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">
</head>

View File

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

View File

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

View File

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

View File

@ -13,6 +13,7 @@ import { KinodeTitle } from "../components/KinodeTitle";
import { isMobileCheck } from "../utils/dimensions";
import classNames from "classnames";
import { generateNetworkingKeys, getNetworkName } from "../utils/chain";
import { getFetchUrl } from "../utils/fetch";
const { useProvider } = hooks;
@ -51,7 +52,7 @@ function Login({
(async () => {
try {
const infoData = (await fetch("/info", { method: "GET" }).then((res) =>
const infoData = (await fetch(getFetchUrl("/info"), { method: "GET", credentials: 'include' }).then((res) =>
res.json()
)) as UnencryptedIdentity;
setRouters(infoData.allowed_routers);
@ -81,8 +82,9 @@ function Login({
let hashed_password = utils.sha256(utils.toUtf8Bytes(pw));
// Replace this with network key generation
const response = await fetch("/vet-keyfile", {
const response = await fetch(getFetchUrl("/vet-keyfile"), {
method: "POST",
credentials: 'include',
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ password_hash: hashed_password, keyfile: "" }),
});
@ -119,9 +121,10 @@ function Login({
// Login or confirm new keys
const result = await fetch(
reset ? "confirm-change-network-keys" : "login",
getFetchUrl(reset ? "confirm-change-network-keys" : "login"),
{
method: "POST",
credentials: 'include',
headers: { "Content-Type": "application/json" },
body: reset
? JSON.stringify({ password_hash: hashed_password, direct })
@ -139,7 +142,7 @@ function Login({
}
const interval = setInterval(async () => {
const res = await fetch("/");
const res = await fetch(getFetchUrl("/"), { credentials: 'include' });
if (
res.status < 300 &&
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 { Tooltip } from "../components/Tooltip";
import { KinodeTitle } from "../components/KinodeTitle";
import { getFetchUrl } from "../utils/fetch";
type SetPasswordProps = {
direct: boolean;
@ -70,7 +71,7 @@ function SetPassword({
let signature = await signer?.signMessage(utils.toUtf8Bytes(sig_data));
try {
const result = await fetch("/boot", {
const result = await fetch(getFetchUrl("/boot"), {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
@ -90,7 +91,7 @@ function SetPassword({
downloadKeyfile(knsName, base64String);
const interval = setInterval(async () => {
const res = await fetch("/");
const res = await fetch(getFetchUrl("/"), { credentials: 'include' });
if (
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: {
proxy: {
'/generate-networking-info': {
target: 'http://localhost:8080/generate-networking-info',
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',
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}