Merge pull request #341 from kinode-dao/develop

Develop 0.7.3
This commit is contained in:
doria 2024-05-14 14:44:25 +09:00 committed by GitHub
commit 9222de17fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
76 changed files with 1451 additions and 1022 deletions

98
Cargo.lock generated
View File

@ -78,7 +78,7 @@ name = "alias"
version = "0.1.0"
dependencies = [
"anyhow",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)",
"kinode_process_lib 0.7.1",
"serde",
"serde_json",
"wit-bindgen",
@ -649,7 +649,7 @@ dependencies = [
"alloy-sol-types 0.7.0",
"anyhow",
"bincode",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?rev=1ea6eda)",
"kinode_process_lib 0.7.1",
"rand 0.8.5",
"serde",
"serde_json",
@ -1160,7 +1160,7 @@ name = "cat"
version = "0.1.0"
dependencies = [
"anyhow",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)",
"kinode_process_lib 0.7.1",
"serde",
"serde_json",
"wit-bindgen",
@ -1223,7 +1223,7 @@ dependencies = [
"anyhow",
"base64 0.22.0",
"bincode",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)",
"kinode_process_lib 0.7.1",
"pleco",
"serde",
"serde_json",
@ -1935,7 +1935,7 @@ name = "download"
version = "0.1.0"
dependencies = [
"anyhow",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)",
"kinode_process_lib 0.7.1",
"serde",
"serde_json",
"wit-bindgen",
@ -1966,7 +1966,7 @@ name = "echo"
version = "0.1.0"
dependencies = [
"anyhow",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)",
"kinode_process_lib 0.7.1",
"serde",
"serde_json",
"wit-bindgen",
@ -2186,7 +2186,7 @@ version = "0.2.0"
dependencies = [
"anyhow",
"bincode",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)",
"kinode_process_lib 0.7.1",
"rand 0.8.5",
"serde",
"serde_json",
@ -2346,7 +2346,7 @@ dependencies = [
name = "get_block"
version = "0.1.0"
dependencies = [
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)",
"kinode_process_lib 0.7.1",
"serde",
"serde_json",
"wit-bindgen",
@ -2546,7 +2546,7 @@ checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46"
name = "hi"
version = "0.1.0"
dependencies = [
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)",
"kinode_process_lib 0.7.1",
"serde",
"serde_json",
"wit-bindgen",
@ -2576,7 +2576,7 @@ version = "0.1.1"
dependencies = [
"anyhow",
"bincode",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)",
"kinode_process_lib 0.7.1",
"serde",
"serde_json",
"wit-bindgen",
@ -2887,7 +2887,7 @@ name = "install"
version = "0.1.0"
dependencies = [
"anyhow",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)",
"kinode_process_lib 0.7.1",
"serde",
"serde_json",
"wit-bindgen",
@ -3151,52 +3151,8 @@ dependencies = [
[[package]]
name = "kinode_process_lib"
version = "0.6.1"
source = "git+https://github.com/kinode-dao/process_lib?tag=v0.6.1#37a20b0249dc2c86ae6c2c69cfb199fb177f1520"
dependencies = [
"alloy-json-rpc 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=6f8ebb4)",
"alloy-primitives 0.6.4",
"alloy-rpc-types 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=6f8ebb4)",
"alloy-transport 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=6f8ebb4)",
"anyhow",
"bincode",
"http 1.1.0",
"mime_guess",
"rand 0.8.5",
"rmp-serde",
"serde",
"serde_json",
"thiserror",
"url",
"wit-bindgen",
]
[[package]]
name = "kinode_process_lib"
version = "0.7.0"
source = "git+https://github.com/kinode-dao/process_lib?tag=v0.7.0#b7c0abee62df683e6c78069c87072a1d52602408"
dependencies = [
"alloy-json-rpc 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=cad7935)",
"alloy-primitives 0.7.0",
"alloy-rpc-types 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=cad7935)",
"alloy-transport 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=cad7935)",
"anyhow",
"bincode",
"http 1.1.0",
"mime_guess",
"rand 0.8.5",
"rmp-serde",
"serde",
"serde_json",
"thiserror",
"url",
"wit-bindgen",
]
[[package]]
name = "kinode_process_lib"
version = "0.7.0"
source = "git+https://github.com/kinode-dao/process_lib?rev=1ea6eda#1ea6edad610b02a6fecb307c67dfa51fba0fb0de"
version = "0.7.1"
source = "git+https://github.com/kinode-dao/process_lib?tag=v0.7.2#61a8de975fd0a812a0a033ee0975fb83dd166224"
dependencies = [
"alloy-json-rpc 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=cad7935)",
"alloy-primitives 0.7.0",
@ -3292,7 +3248,7 @@ dependencies = [
"anyhow",
"bincode",
"hex",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?rev=1ea6eda)",
"kinode_process_lib 0.7.1",
"rmp-serde",
"serde",
"serde_json",
@ -3493,7 +3449,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)",
"kinode_process_lib 0.7.1",
"regex",
"serde",
"serde_json",
@ -3625,7 +3581,7 @@ dependencies = [
name = "namehash_to_name"
version = "0.1.0"
dependencies = [
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)",
"kinode_process_lib 0.7.1",
"rmp-serde",
"serde",
"wit-bindgen",
@ -3653,7 +3609,7 @@ dependencies = [
name = "net_diagnostics"
version = "0.1.0"
dependencies = [
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)",
"kinode_process_lib 0.7.1",
"rmp-serde",
"serde",
"wit-bindgen",
@ -3946,7 +3902,7 @@ dependencies = [
name = "peer"
version = "0.1.0"
dependencies = [
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)",
"kinode_process_lib 0.7.1",
"rmp-serde",
"serde",
"wit-bindgen",
@ -3956,7 +3912,7 @@ dependencies = [
name = "peers"
version = "0.1.0"
dependencies = [
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)",
"kinode_process_lib 0.7.1",
"rmp-serde",
"serde",
"wit-bindgen",
@ -4965,7 +4921,7 @@ dependencies = [
"anyhow",
"base64 0.22.0",
"bincode",
"kinode_process_lib 0.6.1",
"kinode_process_lib 0.7.1",
"rmp-serde",
"serde",
"serde_json",
@ -5184,7 +5140,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
name = "state"
version = "0.1.0"
dependencies = [
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)",
"kinode_process_lib 0.7.1",
"serde",
"serde_json",
"wit-bindgen",
@ -5350,7 +5306,7 @@ version = "0.1.1"
dependencies = [
"anyhow",
"bincode",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)",
"kinode_process_lib 0.7.1",
"rand 0.8.5",
"regex",
"serde",
@ -5364,7 +5320,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"bincode",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)",
"kinode_process_lib 0.7.1",
"serde",
"serde_json",
"thiserror",
@ -5378,7 +5334,7 @@ dependencies = [
"anyhow",
"bincode",
"indexmap",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)",
"kinode_process_lib 0.7.1",
"serde",
"serde_json",
"thiserror",
@ -5619,7 +5575,7 @@ name = "top"
version = "0.1.0"
dependencies = [
"anyhow",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)",
"kinode_process_lib 0.7.1",
"serde",
"serde_json",
"wit-bindgen",
@ -5941,7 +5897,7 @@ name = "uninstall"
version = "0.1.0"
dependencies = [
"anyhow",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)",
"kinode_process_lib 0.7.1",
"serde",
"serde_json",
"wit-bindgen",
@ -6636,7 +6592,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"bincode",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)",
"kinode_process_lib 0.7.1",
"serde",
"serde_json",
"url",

View File

@ -11,7 +11,7 @@ alloy-primitives = "0.7.0"
alloy-sol-types = "0.7.0"
anyhow = "1.0"
bincode = "1.3.3"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", rev = "1ea6eda" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
rand = "0.8"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@ -8,7 +8,7 @@ simulation-mode = []
[dependencies]
anyhow = "1.0"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = "0.24.0"

View File

@ -9,7 +9,7 @@ simulation-mode = []
[dependencies]
anyhow = "1.0"
bincode = "1.3.3"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
rand = "0.8"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@ -8,7 +8,7 @@ simulation-mode = []
[dependencies]
anyhow = "1.0"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = "0.24.0"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -14,8 +14,8 @@
<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-kpw1YN6W.js"></script>
<link rel="stylesheet" crossorigin href="/main:app_store:sys/assets/index-i4SytJ9j.css">
<script type="module" crossorigin src="/main:app_store:sys/assets/index-BxGs27ah.js"></script>
<link rel="stylesheet" crossorigin href="/main:app_store:sys/assets/index-umttRNrr.css">
</head>
<body>

View File

@ -1,5 +1,5 @@
{
"name": "kit-ui",
"name": "kinode-app-store",
"private": true,
"version": "0.0.0",
"type": "module",
@ -54,4 +54,4 @@
"typescript": "^5.2.2",
"vite": "^5.0.8"
}
}
}

View File

@ -110,7 +110,7 @@ function App() {
const props = { provider, packageAbi };
return (
<div className="flex flex-col c h-screen w-screen">
<div className="flex flex-col c h-screen w-screen max-h-screen max-w-screen overflow-x-hidden special-appstore-background">
<Web3ReactProvider connectors={connectors}>
<Router basename={BASE_URL}>
<Routes>

View File

@ -1,27 +1,19 @@
import React, { FormEvent, useCallback, useEffect, useMemo, useState } from "react";
import React, { useEffect, useMemo, useState } from "react";
import { AppInfo } from "../types/Apps";
import useAppsStore from "../store/apps-store";
import Modal from "./Modal";
import { getAppName } from "../utils/app";
import Loader from "./Loader";
import classNames from "classnames";
import { useNavigate } from "react-router-dom";
import UpdateButton from "./UpdateButton";
import DownloadButton from "./DownloadButton";
import InstallButton from "./InstallButton";
import LaunchButton from "./LaunchButton";
import { FaCheck } from "react-icons/fa6";
interface ActionButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
app: AppInfo;
isIcon?: boolean;
}
export default function ActionButton({ app, ...props }: ActionButtonProps) {
const { updateApp, downloadApp, installApp, getCaps, getMyApp } =
useAppsStore();
const [showModal, setShowModal] = useState(false);
const [mirror, setMirror] = useState(app.metadata?.properties?.mirrors?.[0] || "Other");
const [customMirror, setCustomMirror] = useState("");
const [caps, setCaps] = useState<string[]>([]);
const [launchPath, setLaunchPath] = useState('');
const [loading, setLoading] = useState("");
const { clean, installed, downloaded, updatable } = useMemo(() => {
export default function ActionButton({ app, isIcon = false, ...props }: ActionButtonProps) {
const [incrementNumber, setIncrementNumber] = useState(0);
const { installed, downloaded, updatable } = useMemo(() => {
const versions = Object.entries(app?.metadata?.properties?.code_hashes || {});
const latestHash = (versions.find(([v]) => v === app.metadata?.properties?.current_version) || [])[1];
@ -31,217 +23,46 @@ export default function ActionButton({ app, ...props }: ActionButtonProps) {
const updatable =
Boolean(app.state?.our_version && latestHash) &&
app.state?.our_version !== latestHash &&
app.publisher !== window.our.node;
app.publisher !== (window as any).our.node;
return {
clean: !installed && !downloaded && !updatable,
installed,
downloaded,
updatable,
};
}, [app]);
}, [app, incrementNumber]);
const [launchPath, setLaunchPath] = useState('');
useEffect(() => {
setMirror(app.metadata?.properties?.mirrors?.[0] || "Other");
}, [app.metadata?.properties?.mirrors]);
useEffect(() => {
if (installed) {
fetch('/apps').then(data => data.json())
.then((data: Array<{ package_name: string, path: string }>) => {
// console.log(data)
if (Array.isArray(data)) {
// console.log('is array')
const homepageAppData = data.find(otherApp => app.package === otherApp.package_name)
if (homepageAppData) {
// console.log('found the good appness', homepageAppData.package_name, homepageAppData.path);
setLaunchPath(homepageAppData.path)
}
fetch('/apps').then(data => data.json())
.then((data: Array<{ package_name: string, path: string }>) => {
if (Array.isArray(data)) {
const homepageAppData = data.find(otherApp => app.package === otherApp.package_name)
if (homepageAppData) {
setLaunchPath(homepageAppData.path)
}
})
}
}, [installed])
const onClick = useCallback(async () => {
if (installed && !updatable && launchPath) {
window.location.href = `/${launchPath.replace('/', '')}`
return;
} else {
if (downloaded) {
getCaps(app).then((manifest) => {
setCaps(manifest.request_capabilities);
});
}
setShowModal(true);
}
}, [app, installed, downloaded, updatable, setShowModal, getCaps, launchPath]);
const download = useCallback(async (e: FormEvent) => {
e.preventDefault();
e.stopPropagation();
const targetMirror = mirror === "Other" ? customMirror : mirror;
if (!targetMirror) {
window.alert("Please select a mirror");
return;
}
try {
setLoading(`Downloading ${getAppName(app)}...`);
await downloadApp(app, targetMirror);
const interval = setInterval(() => {
getMyApp(app)
.then(() => {
setLoading("");
setShowModal(false);
clearInterval(interval);
})
.catch(console.log);
}, 2000);
} catch (e) {
console.error(e);
window.alert(
`Failed to download app from ${targetMirror}, please try a different mirror.`
);
setLoading("");
}
}, [mirror, customMirror, app, downloadApp, getMyApp]);
const install = useCallback(async () => {
try {
setLoading(`Installing ${getAppName(app)}...`);
await installApp(app);
const interval = setInterval(() => {
getMyApp(app)
.then((app) => {
if (!app.installed) return;
setLoading("");
setShowModal(false);
clearInterval(interval);
})
.catch(console.log);
}, 2000);
} catch (e) {
console.error(e);
window.alert(`Failed to install, please try again.`);
setLoading("");
}
}, [app, installApp, getMyApp]);
const update = useCallback(async () => {
try {
setLoading(`Updating ${getAppName(app)}...`);
await updateApp(app);
const interval = setInterval(() => {
getMyApp(app)
.then((app) => {
if (!app.installed) return;
setLoading("");
setShowModal(false);
clearInterval(interval);
})
.catch(console.log);
}, 2000);
} catch (e) {
console.error(e);
window.alert(`Failed to update, please try again.`);
setLoading("");
}
}, [app, updateApp, getMyApp]);
const appName = getAppName(app);
}
})
}, [app, incrementNumber])
return (
<>
<button
{...props}
type="button"
className={classNames("text-sm min-w-[100px] px-2 py-1 self-start", props.className, {
'bg-orange': installed,
'hidden': installed && !updatable && !launchPath
})}
onClick={onClick}
>
{installed && updatable
? "Update"
: installed && launchPath
? "Launch"
: installed
? "Installed"
: downloaded
? "Install"
: "Download"}
</button>
<Modal show={showModal} hide={() => setShowModal(false)}>
{loading ? (
<Loader msg={loading} />
) : clean ? (
<form className="flex flex-col items-center gap-2" onSubmit={download}>
<h4>Download '{appName}'</h4>
<h5 style={{ margin: 0 }}>Select Mirror</h5>
<select value={mirror} onChange={(e) => setMirror(e.target.value)}>
{((app.metadata?.properties?.mirrors || []).concat(["Other"])).map((m) => (
<option key={m} value={m}>
{m}
</option>
))}
</select>
{mirror === "Other" && (
<input
type="text"
value={customMirror}
onChange={(e) => setCustomMirror(e.target.value)}
placeholder="Mirror, i.e. 'template.os'"
className="p-1 max-w-[240px] w-full"
required
autoFocus
/>
)}
<button type="submit">
Download
</button>
</form>
) : downloaded ? (
<>
<h4>Approve App Permissions</h4>
<h5 className="m-0">
{getAppName(app)} needs the following permissions:
</h5>
<ul className="flex flex-col items-start">
{caps.map((cap) => (
<li key={cap}>{cap}</li>
))}
</ul>
<button type="button" onClick={install}>
Approve & Install
</button>
</>
) : (
<>
<h4>Approve App Permissions</h4>
<h5 className="m-0">
{getAppName(app)} needs the following permissions:
</h5>
{/* <h5>Send Messages:</h5> */}
<br />
<ul className="flex flex-col items-start">
{caps.map((cap) => (
<li key={cap}>{cap}</li>
))}
</ul>
{/* <h5>Receive Messages:</h5>
<ul>
{caps.map((cap) => (
<li key={cap}>{cap}</li>
))}
</ul> */}
<button type="button" onClick={update}>
Approve & Update
</button>
</>
)}
</Modal>
{(installed && launchPath)
? <LaunchButton app={app} {...props} isIcon={isIcon} launchPath={launchPath} />
: (installed && updatable)
? <UpdateButton app={app} {...props} isIcon={isIcon} callback={() => setIncrementNumber(incrementNumber + 1)} />
: !downloaded
? <DownloadButton app={app} {...props} isIcon={isIcon} callback={() => setIncrementNumber(incrementNumber + 1)} />
: !installed
? <InstallButton app={app} {...props} isIcon={isIcon} callback={() => setIncrementNumber(incrementNumber + 1)} />
: isIcon
? <button
className="pointer-events none icon clear absolute top-0 right-0"
>
<FaCheck />
</button>
: <div>Installed</div>}
</>
);
}

View File

@ -4,25 +4,40 @@ import AppHeader from "./AppHeader";
import ActionButton from "./ActionButton";
import { AppInfo } from "../types/Apps";
import { appId } from "../utils/app";
import MoreActions from "./MoreActions";
import { isMobileCheck } from "../utils/dimensions";
import classNames from "classnames";
import { useNavigate } from "react-router-dom";
import { APP_DETAILS_PATH } from "../constants/path";
interface AppEntryProps extends React.HTMLAttributes<HTMLDivElement> {
app: AppInfo;
size?: "small" | "medium" | "large";
overrideImageSize?: "small" | "medium" | "large";
}
export default function AppEntry({ app, ...props }: AppEntryProps) {
export default function AppEntry({ app, size = "medium", overrideImageSize, ...props }: AppEntryProps) {
const isMobile = isMobileCheck()
const navigate = useNavigate()
return (
<div {...props} key={appId(app)} className={classNames("flex justify-between w-full rounded hover:bg-white/10 card", {
'flex-wrap gap-2': isMobile
})}>
<AppHeader app={app} size="small" />
<div className="flex mr-1 items-start">
<ActionButton app={app} className="mr-2" />
<MoreActions app={app} />
</div>
<div
{...props}
key={appId(app)}
className={classNames("flex justify-between rounded-lg hover:bg-white/10 card cursor-pointer", props.className, {
'flex-wrap gap-2': isMobile,
'flex-col relative': size !== 'large'
})}
onClick={() => navigate(`/${APP_DETAILS_PATH}/${appId(app)}`)}
>
<AppHeader app={app} size={size} overrideImageSize={overrideImageSize} />
<ActionButton
app={app}
isIcon={size !== 'large'}
className={classNames({
'absolute top-0 right-0': size !== 'large',
'bg-orange text-lg min-w-1/5': size === 'large',
'ml-auto': size === 'large' && isMobile
})} />
</div>
);
}

View File

@ -1,54 +1,84 @@
import React from "react";
import { AppInfo } from "../types/Apps";
import { appId } from "../utils/app";
import { useNavigate } from "react-router-dom";
import classNames from "classnames";
import { APP_DETAILS_PATH } from "../constants/path";
import ColorDot from "./ColorDot";
import { isMobileCheck } from "../utils/dimensions";
interface AppHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
app: AppInfo;
size?: "small" | "medium" | "large";
overrideImageSize?: "small" | "medium" | "large"
}
export default function AppHeader({
app,
size = "medium",
overrideImageSize,
...props
}: AppHeaderProps) {
const navigate = useNavigate()
const isMobile = isMobileCheck()
return (
<div
{...props}
className={classNames('flex w-full justify-content-start', size, props.className, { 'cursor-pointer': size !== 'large' })}
onClick={() => navigate(`/${APP_DETAILS_PATH}/${appId(app)}`)}
>
{app.metadata?.image
? <img
src={app.metadata.image}
alt="app icon"
className={classNames('mr-2', { 'h-32 rounded-md': size === 'large', 'h-12 rounded': size !== 'large' })}
/>
: <ColorDot
num={app.metadata_hash}
dotSize={size}
className={classNames('mr-2')}
/>}
<div className={classNames("flex flex-col", { 'gap-2 max-w-3/4': isMobile })}>
const appName = <div
className={classNames({
'text-3xl font-[OpenSans]': !isMobile && size === 'large',
'text-xl': !isMobile && size !== 'large',
'text-lg': isMobile
})}
>
{app.metadata?.name || appId(app)}
</div>
const imageSize = overrideImageSize || size
return <div
{...props}
className={classNames('flex w-full justify-content-start', size, props.className, {
'flex-col': size === 'small',
'gap-2': isMobile,
'gap-4': !isMobile,
'gap-6': !isMobile && size === 'large'
})}
>
{size === 'small' && appName}
{app.metadata?.image
? <img
src={app.metadata.image}
alt="app icon"
className={classNames('object-cover', {
'rounded': !imageSize,
'rounded-lg': imageSize === 'small',
'rounded-xl': imageSize === 'medium',
'rounded-2xl': imageSize === 'large',
'h-32': imageSize === 'large' || imageSize === 'small',
'h-20': imageSize === 'medium',
})}
/>
: <ColorDot
num={app.metadata_hash}
dotSize={imageSize}
/>}
<div className={classNames("flex flex-col", {
'gap-2': isMobile,
'gap-4 max-w-3/4': isMobile && size !== 'small'
})}>
{size !== 'small' && appName}
{app.metadata?.description && (
<div
className={classNames("whitespace-nowrap overflow-hidden text-ellipsis", { 'text-3xl': size === 'large', })}
style={{
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
textOverflow: 'ellipsis'
}}
className={classNames({
'text-2xl': size === 'large'
})}
>
{app.metadata?.name || appId(app)}
{app.metadata.description}
</div>
{app.metadata?.description && size !== "large" && (
<div className="whitespace-nowrap overflow-hidden text-ellipsis">
{app.metadata?.description?.slice(0, 100)}
</div>
)}
</div>
)}
</div>
);
</div>
}

View File

@ -33,8 +33,8 @@ const ColorDot: React.FC<ColorDotProps> = ({
<div {...props} className={classNames('flex', props.className)}>
<div
className={classNames('m-0 align-self-center border rounded-full outline-black', {
'h-20 w-20': !isMobile && dotSize === 'large',
'h-16 w-16': !isMobile && dotSize === 'medium',
'h-32 w-32': !isMobile && dotSize === 'large',
'h-18 w-18': !isMobile && dotSize === 'medium',
'h-12 w-12': isMobile || dotSize === 'small',
'border-4': !isMobile,
'border-2': isMobile,

View File

@ -0,0 +1,111 @@
import React, { FormEvent, useCallback, useEffect, useMemo, useState } from "react";
import { AppInfo } from "../types/Apps";
import useAppsStore from "../store/apps-store";
import Modal from "./Modal";
import { getAppName } from "../utils/app";
import Loader from "./Loader";
import classNames from "classnames";
import { useNavigate } from "react-router-dom";
import { FaDownload } from "react-icons/fa6";
interface DownloadButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
app: AppInfo;
isIcon?: boolean;
callback?: () => void;
}
export default function DownloadButton({ app, isIcon = false, callback, ...props }: DownloadButtonProps) {
const { downloadApp, getCaps, getMyApp } =
useAppsStore();
const [showModal, setShowModal] = useState(false);
const [mirror, setMirror] = useState(app.metadata?.properties?.mirrors?.[0] || "Other");
const [customMirror, setCustomMirror] = useState("");
const [loading, setLoading] = useState("");
useEffect(() => {
setMirror(app.metadata?.properties?.mirrors?.[0] || "Other");
}, [app.metadata?.properties?.mirrors]);
const onClick = useCallback(async (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
setShowModal(true);
}, [app, setShowModal, getCaps]);
const download = useCallback(async (e: FormEvent) => {
e.preventDefault();
e.stopPropagation();
const targetMirror = mirror === "Other" ? customMirror : mirror;
if (!targetMirror) {
window.alert("Please select a mirror");
return;
}
try {
setLoading(`Downloading ${getAppName(app)}...`);
await downloadApp(app, targetMirror);
const interval = setInterval(() => {
getMyApp(app)
.then(() => {
setLoading("");
setShowModal(false);
clearInterval(interval);
callback && callback();
})
.catch(console.log);
}, 2000);
} catch (e) {
console.error(e);
window.alert(
`Failed to download app from ${targetMirror}, please try a different mirror.`
);
setLoading("");
}
}, [mirror, customMirror, app, downloadApp, getMyApp]);
const appName = getAppName(app);
return (
<>
<button
{...props}
type="button"
className={classNames("text-sm self-start", props.className, { 'icon clear': isIcon })}
onClick={onClick}
>
{isIcon ? <FaDownload /> : 'Download'}
</button>
<Modal show={showModal} hide={() => setShowModal(false)}>
{loading ? (
<Loader msg={loading} />
) : (
<form className="flex flex-col items-center gap-2" onSubmit={download}>
<h4>Download '{appName}'</h4>
<h5>Select Mirror</h5>
<select value={mirror} onChange={(e) => setMirror(e.target.value)}>
{((app.metadata?.properties?.mirrors || []).concat(["Other"])).map((m) => (
<option key={m} value={m}>
{m}
</option>
))}
</select>
{mirror === "Other" && (
<input
type="text"
value={customMirror}
onChange={(e) => setCustomMirror(e.target.value)}
placeholder="Mirror, i.e. 'template.os'"
className="p-1 max-w-[240px] w-full"
required
autoFocus
/>
)}
<button type="submit">
Download
</button>
</form>
)}
</Modal>
</>
);
}

View File

@ -0,0 +1,19 @@
import classNames from "classnames";
import React from "react"
import { FaHome } from "react-icons/fa"
import { isMobileCheck } from "../utils/dimensions";
const HomeButton: React.FC = () => {
const isMobile = isMobileCheck()
return <button
className={classNames("clear absolute p-2", {
'top-2 left-2': isMobile,
'top-8 left-8': !isMobile
})}
onClick={() => window.location.href = '/'}
>
<FaHome size={24} />
</button>
}
export default HomeButton;

View File

@ -0,0 +1,88 @@
import React, { FormEvent, useCallback, useEffect, useMemo, useState } from "react";
import { AppInfo } from "../types/Apps";
import useAppsStore from "../store/apps-store";
import Modal from "./Modal";
import { getAppName } from "../utils/app";
import Loader from "./Loader";
import classNames from "classnames";
import { FaI } from "react-icons/fa6";
interface InstallButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
app: AppInfo;
isIcon?: boolean;
callback?: () => void;
}
export default function InstallButton({ app, isIcon = false, callback, ...props }: InstallButtonProps) {
const { installApp, getCaps, getMyApp } =
useAppsStore();
const [showModal, setShowModal] = useState(false);
const [caps, setCaps] = useState<string[]>([]);
const [loading, setLoading] = useState("");
const onClick = useCallback(async (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
getCaps(app).then((manifest) => {
setCaps(manifest.request_capabilities);
});
setShowModal(true);
}, [app, setShowModal, getCaps]);
const install = useCallback(async () => {
try {
setLoading(`Installing ${getAppName(app)}...`);
await installApp(app);
const interval = setInterval(() => {
getMyApp(app)
.then((app) => {
if (!app.installed) return;
setLoading("");
setShowModal(false);
clearInterval(interval);
callback && callback();
})
.catch(console.log);
}, 2000);
} catch (e) {
console.error(e);
window.alert(`Failed to install, please try again.`);
setLoading("");
}
}, [app, installApp, getMyApp]);
return (
<>
<button
{...props}
type="button"
className={classNames("text-sm self-start", props.className, {
'icon clear': isIcon
})}
onClick={onClick}
>
{isIcon ? <FaI /> : "Install"}
</button>
<Modal show={showModal} hide={() => setShowModal(false)}>
{loading ? (
<Loader msg={loading} />
) : (
<>
<h4>Approve App Permissions</h4>
<h5 className="m-0">
{getAppName(app)} needs the following permissions:
</h5>
<ul className="flex flex-col items-start">
{caps.map((cap) => (
<li key={cap}>{cap}</li>
))}
</ul>
<button type="button" onClick={install}>
Approve & Install
</button>
</>
)}
</Modal>
</>
);
}

View File

@ -0,0 +1,33 @@
import React, { useCallback, useEffect, useState } from "react";
import { AppInfo } from "../types/Apps";
import classNames from "classnames";
import { FaPlay } from "react-icons/fa6";
interface LaunchButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
app: AppInfo;
launchPath: string;
isIcon?: boolean;
}
export default function LaunchButton({ app, launchPath, isIcon = false, ...props }: LaunchButtonProps) {
const onLaunch = useCallback(async (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
window.location.href = `/${launchPath.replace('/', '')}`
return;
}, [app, launchPath]);
return (
<>
<button
{...props}
type="button"
className={classNames("text-sm self-start", props.className, {
'icon clear': isIcon
})}
onClick={onLaunch}
>
{isIcon ? <FaPlay /> : "Launch"}
</button>
</>
);
}

View File

@ -27,7 +27,7 @@ const Modal: React.FC<ModalProps> = ({
return (
<div
className={classNames(`bg-black/25 fixed top-0 bottom-0 left-0 right-0 flex flex-col c z-30 min-h-[10em] min-w-[30em]`,
className={classNames(`bg-black/25 backdrop-blur-lg fixed top-0 bottom-0 left-0 right-0 flex flex-col c z-30 min-h-[10em] min-w-[30em]`,
{ show }
)}
onClick={hide}

View File

@ -5,11 +5,12 @@ import {
FaDownload,
FaMagnifyingGlass,
FaUpload,
FaX,
} from "react-icons/fa6";
import { MY_APPS_PATH, PUBLISH_PATH } from "../constants/path";
import classNames from "classnames";
import { isMobileCheck } from "../utils/dimensions";
import HomeButton from "./HomeButton";
import { FaHome } from "react-icons/fa";
interface SearchHeaderProps {
@ -34,67 +35,73 @@ export default function SearchHeader({
const canGoBack = location.key !== "default";
const isMyAppsPage = location.pathname === MY_APPS_PATH;
const isMobile = isMobileCheck()
return (
<div className="flex justify-between">
{location.pathname !== '/' ? (
<button className="flex flex-col c mr-2 icon" onClick={() => {
if (onBack) {
onBack()
} else {
canGoBack ? navigate(-1) : navigate('/')
}
}}>
<div className={classNames("flex justify-between", {
"gap-4": isMobile,
"gap-8": !isMobile
})}>
{location.pathname !== '/'
? <button
className="flex flex-col c icon icon-orange"
onClick={() => {
if (onBack) {
onBack()
} else {
canGoBack ? navigate(-1) : navigate('/')
}
}}
>
<FaArrowLeft />
</button>
) : (
<button
className="flex flex-col c mr-2 icon"
onClick={() => window.location.href = '/'}
>
<FaHome />
</button>
)}
: isMobile
? <button
className={classNames("icon icon-orange", {
})}
onClick={() => window.location.href = '/'}
>
<FaHome />
</button>
: <></>}
{!hidePublish && <button
className="flex flex-col c mr-2 icon"
className="flex flex-col c icon icon-orange"
onClick={() => navigate(PUBLISH_PATH)}
>
<FaUpload />
</button>}
{!hideSearch && (
<div className="flex mr-2 flex-1 rounded-md">
<button
className="icon mr-2"
type="button"
onClick={() => inputRef.current?.focus()}
>
<FaMagnifyingGlass />
</button>
<div className="flex flex-1 rounded-md relative">
<input
type="text"
ref={inputRef}
onChange={(event) => onChange(event.target.value)}
value={value}
placeholder="Search for apps..."
className="w-full mr-2"
className="w-full self-stretch grow"
/>
{value.length > 0 && <button
className="icon"
onClick={() => onChange("")}
<button
className={classNames("icon border-0 absolute top-1/2 -translate-y-1/2", {
'right-2': isMobile,
'right-4': !isMobile
})}
type="button"
onClick={() => inputRef.current?.focus()}
>
<FaX />
</button>}
<FaMagnifyingGlass />
</button>
</div>
)}
<div className="flex">
<button
className={classNames("flex alt")}
onClick={() => (isMyAppsPage ? navigate(-1) : navigate(MY_APPS_PATH))}
>
<FaDownload className="mr-2" />
<span>My Apps</span>
</button>
</div>
<button
className={classNames("flex c", {
"gap-4": isMobile,
"gap-8 basis-1/5": !isMobile
})}
onClick={() => (isMyAppsPage ? navigate(-1) : navigate(MY_APPS_PATH))}
>
{!isMobile && <span>My Apps</span>}
<FaDownload />
</button>
</div>
);
}

View File

@ -0,0 +1,94 @@
import React, { FormEvent, useCallback, useEffect, useMemo, useState } from "react";
import { AppInfo } from "../types/Apps";
import useAppsStore from "../store/apps-store";
import Modal from "./Modal";
import { getAppName } from "../utils/app";
import Loader from "./Loader";
import classNames from "classnames";
interface UpdateButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
app: AppInfo;
isIcon?: boolean;
callback?: () => void
}
export default function UpdateButton({ app, callback, isIcon = false, ...props }: UpdateButtonProps) {
const { updateApp, getCaps, getMyApp } =
useAppsStore();
const [showModal, setShowModal] = useState(false);
const [caps, setCaps] = useState<string[]>([]);
const [loading, setLoading] = useState("");
const onClick = useCallback(async (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
getCaps(app).then((manifest) => {
setCaps(manifest.request_capabilities);
});
setShowModal(true);
}, [app, setShowModal, getCaps]);
const update = useCallback(async () => {
try {
setLoading(`Updating ${getAppName(app)}...`);
await updateApp(app);
const interval = setInterval(() => {
getMyApp(app)
.then((app) => {
if (!app.installed) return;
setLoading("");
setShowModal(false);
clearInterval(interval);
callback && callback();
})
.catch(console.log);
}, 2000);
} catch (e) {
console.error(e);
window.alert(`Failed to update, please try again.`);
setLoading("");
}
}, [app, updateApp, getMyApp]);
return (
<>
<button
{...props}
type="button"
className={classNames("text-sm self-start", props.className)}
onClick={onClick}
>
Update
</button>
<Modal show={showModal} hide={() => setShowModal(false)}>
{loading ? (
<Loader msg={loading} />
) : (
<>
<h4>Approve App Permissions</h4>
<h5 className="m-0">
{getAppName(app)} needs the following permissions:
</h5>
{/* <h5>Send Messages:</h5> */}
<br />
<ul className="flex flex-col items-start">
{caps.map((cap) => (
<li key={cap}>{cap}</li>
))}
</ul>
{/* <h5>Receive Messages:</h5>
<ul>
{caps.map((cap) => (
<li key={cap}>{cap}</li>
))}
</ul> */}
<button type="button" onClick={update}>
Approve & Update
</button>
</>
)}
</Modal>
</>
);
}

View File

@ -60,7 +60,7 @@ h6 {
button,
button[type="submit"],
.button {
@apply flex m-0 py-2 px-6 rounded border-orange bg-orange border-2 cursor-pointer place-items-center place-content-center text-center rounded-lg heading transition ease-in-out duration-100 hover:bg-black text-white font-[OpenSans];
@apply flex m-0 py-2 px-6 rounded border-orange bg-orange border cursor-pointer place-items-center place-content-center text-center rounded-lg heading transition ease-in-out duration-100 hover:bg-black text-white font-[OpenSans];
}
.clear {
@ -80,7 +80,15 @@ button[type="submit"],
}
.icon.alt {
@apply border-black/25 hover:border-white/25
@apply border-black/25 hover:border-white/25;
}
.icon.icon-orange {
@apply border-orange bg-orange/25;
}
.icon.clear {
@apply border-0;
}
body {
@ -98,7 +106,7 @@ textarea,
input[type="text"],
input[type="password"],
input[type="checkbox"] {
@apply px-4 py-2 rounded-lg bg-orange bg-opacity-25 text-white border border-orange border-2;
@apply px-4 py-2 rounded-lg bg-orange bg-opacity-25 text-white border border-solid border-orange;
}
input[type="checkbox"] {
@ -132,4 +140,9 @@ button:disabled {
.c {
@apply place-items-center place-content-center;
}
.special-appstore-background {
background-color: #22211f;
background-image: radial-gradient(circle at -20% -68%, #f75a2991 36%, transparent 56.05%), linear-gradient(37deg, #86000185 19%, transparent 45.05%), linear-gradient(-36deg, #8600016e 26%, transparent 50.05%);
}

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useMemo, useCallback } from "react";
import React, { useState, useEffect, useMemo, useCallback, ReactElement } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { AppInfo } from "../types/Apps";
@ -9,6 +9,10 @@ import SearchHeader from "../components/SearchHeader";
import { PageProps } from "../types/Page";
import { appId } from "../utils/app";
import { PUBLISH_PATH } from "../constants/path";
import HomeButton from "../components/HomeButton";
import classNames from "classnames";
import { isMobileCheck } from "../utils/dimensions";
import { FaGlobe, FaPeopleGroup, FaStar } from "react-icons/fa6";
interface AppPageProps extends PageProps { }
@ -48,80 +52,104 @@ export default function AppPage() {
app?.state?.our_version ||
(versions[(versions.length || 1) - 1] || ["", ""])[1];
const isMobile = isMobileCheck()
const appDetails: Array<{ top: ReactElement, middle: ReactElement, bottom: ReactElement }> = [
{
top: <div className={classNames({ 'text-sm': isMobile })}>0 ratings</div>,
middle: <span className="text-2xl">5.0</span>,
bottom: <div className={classNames("flex-center gap-1", {
'text-sm': isMobile
})}>
<FaStar />
<FaStar />
<FaStar />
<FaStar />
<FaStar />
</div>
},
{
top: <div className={classNames({ 'text-sm': isMobile })}>Developer</div>,
middle: <FaPeopleGroup size={36} />,
bottom: <div className={classNames({ 'text-sm': isMobile })}>
{app?.publisher}
</div>
},
{
top: <div className={classNames({ 'text-sm': isMobile })}>Version</div>,
middle: <span className="text-2xl">{version}</span>,
bottom: <div className={classNames({ 'text-xs': isMobile })}>
{hash.slice(0, 5)}...{hash.slice(-5)}
</div>
},
{
top: <div className={classNames({ 'text-sm': isMobile })}>Mirrors</div>,
middle: <FaGlobe size={36} />,
bottom: <div className={classNames({ 'text-sm': isMobile })}>
{app?.metadata?.properties?.mirrors?.length || 0}
</div>
}
]
return (
<div className="flex flex-col w-full max-w-[900px]">
<SearchHeader value="" onChange={() => null} hideSearch />
<div className="card mt1">
{app ? (
<>
<div className="flex justify-between">
<AppHeader app={app} size="large" />
<ActionButton app={app} className="mr-1" />
</div>
<div className="flex flex-col mt-2">
<div className="flex mt-1 items-start">
<div className="w-1/4">Description</div>
<div className="mb-1 w-3/4">
{(app.metadata?.description || "No description given").slice(
0,
2000
)}
</div>
<div className={classNames("flex flex-col w-full h-screen",
{
'gap-4 p-2 max-w-screen': isMobile,
'gap-8 max-w-[900px]': !isMobile,
})}
>
{!isMobile && <HomeButton />}
<SearchHeader
value=""
onChange={() => null}
hideSearch
hidePublish
/>
<div className={classNames("flex-col-center card !rounded-3xl", {
'p-12 gap-4 grow overflow-y-auto': isMobile,
'p-24 gap-8': !isMobile,
})}>
{app ? <>
<AppHeader app={app} size={isMobile ? "medium" : "large"} />
<div className="w-5/6 h-0 border border-orange" />
<div className={classNames("flex items-start text-xl", {
'gap-4 flex-wrap': isMobile,
'gap-8': !isMobile,
})}>
{appDetails.map((detail, index) => <>
<div
className={classNames("flex-col-center gap-2 justify-between self-stretch", {
'rounded-lg bg-white/10 p-1 min-w-1/4 grow': isMobile,
'opacity-50': !isMobile,
})}
key={index}
>
{detail.top}
{detail.middle}
{detail.bottom}
</div>
<div className="flex mt-1 items-start">
<div className="w-1/4">Publisher</div>
<div className="mb-1 w-3/4">{app.publisher}</div>
</div>
<div className="flex mt-1 items-start">
<div className="w-1/4">Version</div>
<div className="mb-1 w-3/4">{version}</div>
</div>
<div className="flex mt-1 items-start">
<div className="w-1/4">Mirrors</div>
<div className="w-3/4 flex flex-col">
{(app.metadata?.properties?.mirrors || []).map(
(mirror, index) => (
<div key={index + mirror} className="mb-1">
{mirror}
</div>
)
)}
</div>
</div>
{/* <div className="flex mt-1 items-start">
<div className="w-1/4">Permissions</div>
<div className="w-3/4 flex flex-col">
{app.permissions?.map((permission, index) => (
<div key={index + permission} className="mb-1">{permission}</div>
))}
</div>
</div> */}
<div className="flex mt-1 items-start">
<div className="w-1/4">Hash</div>
<div className="w-3/4 break-all">
{hash}
</div>
</div>
</div>
<div className="app-screenshots flex mt-2 overflow-x-auto max-w-full">
{(app.metadata?.properties?.screenshots || []).map(
{!isMobile && index !== appDetails.length - 1 && <div className="h-3/4 w-0 border border-orange self-center" />}
</>)}
</div>
{Array.isArray(app.metadata?.properties?.screenshots)
&& app.metadata?.properties.screenshots.length > 0
&& <div className="flex flex-wrap overflow-x-auto max-w-full">
{app.metadata.properties.screenshots.map(
(screenshot, index) => (
<img key={index + screenshot} src={screenshot} className="mr-2 max-h-20 max-w-full rounded border border-black" />
)
)}
</div>
{app.installed && (
<button type="button" onClick={goToPublish}>
Publish
</button>
)}
</>
) : (
<>
<h4>App details not found for </h4>
<h4>{params.id}</h4>
</>
)}
</div>}
<ActionButton app={app} className={classNames("self-center bg-orange text-lg px-12")} />
{app.installed && app.state?.mirroring && (
<button type="button" onClick={goToPublish}>
Publish
</button>
)}
</> : <>
<h4>App details not found for </h4>
<h4>{params.id}</h4>
</>}
</div>
</div>
);

View File

@ -9,6 +9,7 @@ import { PageProps } from "../types/Page";
import { useNavigate } from "react-router-dom";
import { appId } from "../utils/app";
import { PUBLISH_PATH } from "../constants/path";
import HomeButton from "../components/HomeButton";
export default function MyAppsPage() { // eslint-disable-line
const { myApps, getMyApps } = useAppsStore()
@ -53,6 +54,7 @@ export default function MyAppsPage() { // eslint-disable-line
return (
<div className="flex flex-col w-full max-w-[900px]">
<HomeButton />
<SearchHeader value={searchQuery} onChange={searchMyApps} />
<div className="flex justify-between items-center mt-2">
<h3>My Packages</h3>

View File

@ -16,6 +16,9 @@ import { AppInfo } from "../types/Apps";
import Checkbox from "../components/Checkbox";
import Jazzicon from "../components/Jazzicon";
import { Tooltip } from "../components/Tooltip";
import HomeButton from "../components/HomeButton";
import classNames from "classnames";
import { isMobileCheck } from "../utils/dimensions";
const { useIsActivating } = hooks;
@ -207,16 +210,22 @@ export default function PublishPage({
}
}, [listedApps, packageName, publisherId, isUpdate, setIsUpdate]);
const isMobile = isMobileCheck()
return (
<div className="max-w-[900px] w-full">
<div className={classNames("w-full flex flex-col gap-2", {
'max-w-[900px]': !isMobile,
'p-2 h-screen w-screen': isMobile
})}>
{!isMobile && <HomeButton />}
<SearchHeader
hideSearch
hidePublish
onBack={showMetadataForm ? () => setShowMetadataForm(false) : undefined}
/>
<div className="flex justify-between items-center my-2">
<div className="flex-center justify-between">
<h4>Publish Package</h4>
{Boolean(account) && <div className="card flex items-center">
{Boolean(account) && <div className="card flex-center">
<span>Publishing as:</span>
<Jazzicon address={account!} className="mx-2" />
<span className="font-mono">{account?.slice(0, 4)}...{account?.slice(-4)}</span>
@ -224,20 +233,20 @@ export default function PublishPage({
</div>
{loading ? (
<div className="flex flex-col items-center">
<div className="flex-col-center">
<Loader msg={loading} />
</div>
) : publishSuccess ? (
<div className="flex flex-col items-center">
<h4 className="mb-2">Package Published!</h4>
<div className="mb-2">
<div className="flex-col-center gap-2">
<h4>Package Published!</h4>
<div>
<strong>Package Name:</strong> {publishSuccess.packageName}
</div>
<div className="mb-2">
<div>
<strong>Publisher ID:</strong> {publishSuccess.publisherId}
</div>
<button
className={`flex ml-2 mt-2`}
className={`flex ml-2`}
onClick={() => setPublishSuccess(undefined)}
>
Publish Another Package
@ -247,7 +256,7 @@ export default function PublishPage({
<MetadataForm {...{ packageName, publisherId, app: state?.app }} goBack={() => setShowMetadataForm(false)} />
) : !account || !isActive ? (
<>
<h4 style={{}}>Please connect your wallet to publish a package</h4>
<h4>Please connect your wallet {isMobile && <br />} to publish a package</h4>
<button className={`connect-wallet row`} onClick={connectWallet}>
Connect Wallet
</button>
@ -256,7 +265,7 @@ export default function PublishPage({
<Loader msg="Approve connection in your wallet" />
) : (
<form
className="flex flex-col flex-1 overflow-y-auto"
className="flex flex-col flex-1 overflow-y-auto gap-2"
onSubmit={publishPackage}
>
<div
@ -270,7 +279,7 @@ export default function PublishPage({
Update existing package
</label>
</div>
<div className="flex flex-col mb-2">
<div className="flex flex-col">
<label htmlFor="package-name">Package Name</label>
<input
id="package-name"
@ -282,7 +291,7 @@ export default function PublishPage({
onBlur={checkIfUpdate}
/>
</div>
<div className="flex flex-col mb-2">
<div className="flex flex-col">
<label htmlFor="publisher-id">Publisher ID</label>
<input
id="publisher-id"
@ -293,7 +302,7 @@ export default function PublishPage({
onBlur={checkIfUpdate}
/>
</div>
<div className="flex flex-col mb-2">
<div className="flex flex-col gap-2">
<label htmlFor="metadata-url">
Metadata URL
</label>
@ -306,7 +315,7 @@ export default function PublishPage({
onBlur={calculateMetadataHash}
placeholder="https://github/my-org/my-repo/metadata.json"
/>
<div className="mt-2">
<div>
Metadata is a JSON file that describes your package.
<br /> You can{" "}
<a onClick={() => setShowMetadataForm(true)}
@ -317,7 +326,7 @@ export default function PublishPage({
.
</div>
</div>
<div className="flex flex-col mb-2">
<div className="flex flex-col">
<label htmlFor="metadata-hash">Metadata Hash</label>
<input
readOnly
@ -334,7 +343,7 @@ export default function PublishPage({
</form>
)}
<div className="flex flex-col my-2 mt-4">
<div className="flex flex-col">
<h4>Packages You Own</h4>
{myPublishedApps.length > 0 ? (
<div className="flex flex-col">
@ -344,11 +353,9 @@ export default function PublishPage({
<Jazzicon address={app.publisher} className="mr-2" />
<span>{app.package}</span>
</div>
{/* <Tooltip content="View Package"> */}
<button className="flex items-center" onClick={() => unpublishPackage(app.package, app.publisher)}>
<span>Unpublish</span>
</button>
{/* </Tooltip> */}
</div>
))}
</div>

View File

@ -9,6 +9,8 @@ import { PageProps } from "../types/Page";
import { appId } from "../utils/app";
import classNames from 'classnames';
import { FaArrowRotateRight } from "react-icons/fa6";
import { isMobileCheck } from "../utils/dimensions";
import HomeButton from "../components/HomeButton";
interface StorePageProps extends PageProps { }
@ -21,6 +23,7 @@ export default function StorePage() {
const [searchQuery, setSearchQuery] = useState<string>("");
const [displayedApps, setDisplayedApps] = useState<AppInfo[]>(listedApps);
const [page, setPage] = useState(1);
const [tags, setTags] = useState<string[]>([])
const pages = useMemo(
() =>
@ -42,6 +45,14 @@ export default function StorePage() {
getListedApps()
.then((apps) => {
setDisplayedApps(Object.values(apps));
let _tags: string[] = [];
for (const app of Object.values(apps)) {
_tags = _tags.concat((app.metadata as any || {}).tags || [])
}
if (_tags.length === 0) {
_tags = ['App', 'Tags', 'Coming', 'Soon', 'tm'];
}
setTags(Array.from(new Set(_tags)))
})
.catch((error) => console.error(error));
}, []); // eslint-disable-line
@ -112,25 +123,48 @@ export default function StorePage() {
}
}, [rebuildIndex]);
const isMobile = isMobileCheck()
return (
<div className="max-w-[900px] w-full">
<div className={classNames("flex flex-col w-full max-h-screen", {
'gap-4 max-w-screen p-2': isMobile,
'gap-6 max-w-[900px]': !isMobile
})}>
{!isMobile && <HomeButton />}
<SearchHeader value={searchQuery} onChange={searchApps} />
<div className="flex justify-between items-center my-2 mx-0">
<h4>New</h4>
<div className={classNames("flex items-center self-stretch justify-between", {
'gap-4 flex-wrap': isMobile,
'gap-8 grow': !isMobile
})}>
<button
className="flex flex-col c mr-auto ml-1 icon"
className="flex flex-col c icon icon-orange"
onClick={tryRebuildIndex}
title="Rebuild index"
>
<FaArrowRotateRight />
</button>
{tags.slice(0, isMobile ? 3 : 6).map(tag => (
<button
key={tag}
className="clear flex c rounded-full !bg-white/10 !hover:bg-white/25"
onClick={() => {
console.log('clicked tag', tag)
}}
>
{tag}
</button>
))}
<select
value={resultsSort}
onChange={(e) => {
setResultsSort(e.target.value);
sortApps(e.target.value);
}}
className={classNames({
'basis-1/5': !isMobile
})}
>
<option>Recently published</option>
<option>Most popular</option>
@ -138,13 +172,54 @@ export default function StorePage() {
<option>Recently updated</option>
</select>
</div>
<div className="flex flex-col flex-1 overflow-y-auto gap-2 max-h-[80vh]">
{displayedApps.map((app) => (
<AppEntry
key={appId(app) + (app.state?.our_version || "")}
app={app}
/>
))}
{!searchQuery ? <div className={classNames("flex flex-col", {
'grow overflow-y-auto gap-4 items-center px-2': isMobile
})}>
<h2>Top apps this week...</h2>
<div className={classNames("flex gap-2", {
'flex-col': isMobile
})}>
{displayedApps.slice(0, 4).map((app) => (
<AppEntry
key={appId(app) + (app.state?.our_version || "")}
size={'medium'}
app={app}
className={classNames("grow", {
'w-1/4': !isMobile,
'w-full': isMobile
})}
/>
))}
</div>
<h2>Must-have apps!</h2>
<div className={classNames("flex gap-2", {
'flex-col': isMobile
})}>
{displayedApps.slice(0, 6).map((app) => (
<AppEntry
key={appId(app) + (app.state?.our_version || "")}
size={isMobile ? 'medium' : 'small'}
app={app}
overrideImageSize={isMobile ? 'medium' : 'large'}
className={classNames("grow", {
'w-1/6': !isMobile,
'w-full': isMobile
})}
/>
))}
</div>
</div> : <div className={classNames("flex-col-center grow", {
'gap-2': isMobile,
'gap-4': !isMobile,
})}>
{displayedApps.map(app => <AppEntry
size='large'
app={app}
className="self-stretch items-center"
overrideImageSize="medium"
/>)}
</div>}
<div className="flex flex-col gap-2 overflow-y-auto">
{pages.length > 1 && (
<div className="flex self-center">
{page !== pages[0] && (

View File

@ -5,16 +5,16 @@ export const appId = (app: AppInfo) => `${app.package}:${app.publisher}`
export const getAppName = (app: AppInfo) => app.metadata?.name || appId(app)
export enum AppType {
Downloaded = 'downloaded',
Installed = 'installed',
Local = 'local',
System = 'system',
Downloaded = 'downloaded',
Installed = 'installed',
Local = 'local',
System = 'system',
}
export const getAppType = (app: AppInfo) => {
if (app.publisher === 'sys') {
return AppType.System
} else if (app.state?.our_version && !app.state?.capsApproved) {
} else if (app.state?.our_version && !app.state?.caps_approved) {
return AppType.Downloaded
} else if (!app.metadata) {
return AppType.Local

View File

@ -8,7 +8,7 @@ simulation-mode = []
[dependencies]
anyhow = "1.0"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = "0.24.0"

View File

@ -10,7 +10,7 @@ simulation-mode = []
anyhow = "1.0"
base64 = "0.22.0"
bincode = "1.3.3"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
pleco = "0.5"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@ -9,7 +9,7 @@ simulation-mode = []
[dependencies]
anyhow = "1.0"
bincode = "1.3.3"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = "0.24.0"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -9,8 +9,8 @@
<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-CiPfZ2kc.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-C9o9YkgK.css">
<script type="module" crossorigin src="/assets/index-BrbxaEm2.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-iMQiSiXv.css">
</head>
<body>

View File

@ -9,8 +9,8 @@
<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-CiPfZ2kc.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-C9o9YkgK.css">
<script type="module" crossorigin src="/assets/index-BrbxaEm2.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-iMQiSiXv.css">
</head>
<body>

View File

@ -1,5 +1,5 @@
{
"name": "ui",
"name": "kinode-homepage-ui",
"private": true,
"version": "0.0.0",
"type": "module",
@ -31,4 +31,4 @@
"typescript": "^5.2.2",
"vite": "^5.2.0"
}
}
}

View File

@ -7,10 +7,11 @@ const AllApps: React.FC<{ expanded: boolean }> = ({ expanded }) => {
const { apps } = useHomepageStore()
const isMobile = isMobileCheck()
return <div className={classNames('flex-center flex-wrap gap-4 overflow-y-auto self-stretch relative', {
'max-h-0': !expanded,
'p-8 max-h-[1000px]': expanded,
'placeholder': isMobile
return <div className={classNames('flex-center flex-wrap overflow-y-auto fixed h-screen w-screen backdrop-blur-md transition transition-all ease-in-out duration-500', {
'top-[100vh]': !expanded,
'top-0': expanded,
'gap-4 p-8': isMobile,
'gap-8 p-16': !isMobile,
})}>
{apps.length === 0
? <div>Loading apps...</div>

View File

@ -1,7 +1,7 @@
import { useEffect, useState } from 'react'
import KinodeText from '../components/KinodeText'
import KinodeBird from '../components/KinodeBird'
import useHomepageStore from '../store/homepageStore'
import useHomepageStore, { HomepageApp } from '../store/homepageStore'
import { FaChevronDown, FaChevronUp, FaScrewdriverWrench, FaV } from 'react-icons/fa6'
import AppsDock from '../components/AppsDock'
import AllApps from '../components/AllApps'
@ -20,37 +20,56 @@ interface AppStoreApp {
function Homepage() {
const [our, setOur] = useState('')
const [allAppsExpanded, setAllAppsExpanded] = useState(false)
const { apps, setApps, isHosted, fetchHostedStatus, showWidgetsSettings, setShowWidgetsSettings } = useHomepageStore()
const { setApps, isHosted, fetchHostedStatus, showWidgetsSettings, setShowWidgetsSettings } = useHomepageStore()
const isMobile = isMobileCheck()
const getAppPathsAndIcons = () => {
Promise.all([
fetch('/apps').then(res => res.json() as any as HomepageApp[]),
fetch('/main:app_store:sys/apps').then(res => res.json())
]).then(([appsData, appStoreData]) => {
console.log({ appsData, appStoreData })
const appz = appsData.map(app => ({
...app,
is_favorite: false, // Assuming initial state for all apps
}));
appStoreData.forEach((appStoreApp: AppStoreApp) => {
const existingAppIndex = appz.findIndex(a => a.package_name === appStoreApp.package);
if (existingAppIndex === -1) {
appz.push({
package_name: appStoreApp.package,
path: '',
label: appStoreApp.package,
state: appStoreApp.state,
is_favorite: false
});
} else {
appz[existingAppIndex] = {
...appz[existingAppIndex],
state: appStoreApp.state
};
}
});
setApps(appz);
// TODO: be less dumb about this edge case!
for (
let i = 0;
i < 5 && appz.find(a => a.package_name === 'app_store' && !a.base64_icon);
i++
) {
getAppPathsAndIcons();
}
});
}
useEffect(() => {
getAppPathsAndIcons();
}, [our]);
useEffect(() => {
fetch('/apps')
.then(res => res.json())
.then(data => setApps(data))
.then(() => {
fetch('/main:app_store:sys/apps')
.then(res => res.json())
.then(data => {
const appz = [...apps]
data.forEach((app: AppStoreApp) => {
if (!appz.find(a => a.package_name === app.package)) {
appz.push({
package_name: app.package,
path: '',
label: app.package,
state: app.state,
is_favorite: false
})
} else {
const i = appz.findIndex(a => a.package_name === app.package)
if (i !== -1) {
appz[i] = { ...appz[i], state: app.state }
}
}
})
setApps(appz)
})
})
fetch('/our')
.then(res => res.text())
.then(data => {
@ -95,13 +114,14 @@ function Homepage() {
<AppsDock />
<Widgets />
<button
className={classNames("clear flex-center self-center", {
'-mb-1': !allAppsExpanded,
className={classNames("fixed alt clear flex-center self-center z-20", {
'bottom-2 right-2': isMobile,
'bottom-8 right-8': !isMobile,
})}
onClick={() => setAllAppsExpanded(!allAppsExpanded)}
>
{allAppsExpanded ? <FaChevronDown /> : <FaChevronUp />}
<span className="ml-2">{allAppsExpanded ? 'Collapse' : 'All installed apps'}</span>
<span className="ml-2">{allAppsExpanded ? 'Collapse' : 'All apps'}</span>
</button>
<AllApps expanded={allAppsExpanded} />
{showWidgetsSettings && <WidgetsSettingsModal />}

View File

@ -9,7 +9,7 @@ simulation-mode = []
[dependencies]
anyhow = "1.0"
bincode = "1.3.3"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
url = "2.5.0"

View File

@ -7,7 +7,7 @@ edition = "2021"
simulation-mode = []
[dependencies]
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = "0.24.0"

View File

@ -12,7 +12,7 @@ alloy-primitives = "0.7.0"
alloy-sol-types = "0.7.0"
bincode = "1.3.3"
hex = "0.4.3"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", rev = "1ea6eda" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag="v0.7.2" }
rmp-serde = "1.1.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@ -7,7 +7,7 @@ edition = "2021"
simulation-mode = []
[dependencies]
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = "0.24.0"

View File

@ -12,45 +12,58 @@
<body>
<h1>system diagnostics & settings</h1>
<main>
<article id="net-diagnostics">
<h2>networking diagnostics</h2>
<p id="diagnostics"></p>
</article>
<article id="node-info">
<p>node info</p>
<h2>node info</h2>
<p id="node-name"></p>
<p id="net-key"></p>
<p id="ip-ports"></p>
<p id="routers"></p>
<button>reset networking key</button>
<button>adjust networking info</button>
<button>reset networking key (TODO)</button>
<button>adjust networking info (TODO)</button>
<button id="shutdown">shut down node</button>
</article>
<article id="net-diagnostics">
<p>networking diagnostics</p>
<p id="diagnostics"></p>
</article>
<article id="pings">
<p>fetch PKI data</p>
<h2>fetch PKI data</h2>
<form id="get-peer-pki">
<input type="text" name="peer" placeholder="peer-name.os">
<button type="submit">get peer info</button>
</form>
<p id="peer-pki-response"></p>
<p>ping a node</p>
<h2>ping a node</h2>
<form id="ping-peer">
<input type="text" name="peer" placeholder="peer-name.os">
<input type="text" name="content" placeholder="message">
<label><input type="number" name="timeout">timeout (seconds)</label>
<input type="number" name="timeout" placeholder="timeout (seconds)">
<button type="submit">ping</button>
</form>
<p id="peer-ping-response"></p>
</article>
<article id="eth-rpc-providers">
<p>ETH RPC providers</p>
<h2>ETH RPC providers</h2>
<div id="provider-edits">
<form id="add-eth-provider">
<input type="number" name="chain-id" placeholder="1">
<input type="text" name="rpc-url" placeholder="wss://rpc-url.com">
<button type="submit">add provider</button>
</form>
<form id="remove-eth-provider">
<input type="number" name="chain-id" placeholder="1">
<input type="text" name="rpc-url" placeholder="wss://rpc-url.com">
<button type="submit">remove provider</button>
</form>
</div>
<ul id="providers"></ul>
</article>
<article id="eth-rpc-settings">
<p>ETH RPC settings</p>
<h2>ETH RPC settings</h2>
<p id="public"></p>
<div>
<p>nodes allowed to connect:</p>
@ -63,7 +76,7 @@
</article>
<article id="kernel">
<p>running processes:</p>
<h2>running processes</h2>
<p>(TODO)</p>
<ul></ul>
</article>

View File

@ -56,17 +56,17 @@ function populate_eth_rpc_providers(providers) {
ul.innerHTML = '';
providers.forEach(provider => {
const li = document.createElement('li');
li.innerHTML = `<li>${JSON.stringify(provider)}</li>`;
li.innerHTML = `${JSON.stringify(provider, undefined, 2)}`;
ul.appendChild(li);
});
}
function populate_eth_rpc_settings(settings) {
if (settings.public) {
document.getElementById('public').innerText = 'public';
document.getElementById('public').innerText = 'status: public';
document.getElementById('allowed-nodes').style.display = 'none';
} else {
document.getElementById('public').innerText = 'private';
document.getElementById('public').innerText = 'status: private';
const ul = document.getElementById('allowed-nodes');
ul.innerHTML = '';
if (settings.allow.length === 0) {
@ -119,7 +119,8 @@ document.getElementById('get-peer-pki').addEventListener('submit', (e) => {
if (data === null) {
document.getElementById('peer-pki-response').innerText = "no pki data for peer";
} else {
document.getElementById('peer-pki-response').innerText = JSON.stringify(data);
e.target.reset();
document.getElementById('peer-pki-response').innerText = JSON.stringify(data, undefined, 2);
}
});
})
@ -143,6 +144,7 @@ document.getElementById('ping-peer').addEventListener('submit', (e) => {
}).then(response => response.json())
.then(data => {
if (data === null) {
e.target.reset();
document.getElementById('peer-ping-response').innerText = "ping successful!";
} else if (data === "HiTimeout") {
document.getElementById('peer-ping-response').innerText = "node timed out";
@ -152,6 +154,66 @@ document.getElementById('ping-peer').addEventListener('submit', (e) => {
});
})
document.getElementById('add-eth-provider').addEventListener('submit', (e) => {
e.preventDefault();
const data = new FormData(e.target);
const rpc_url = data.get('rpc-url');
// validate rpc url
if (!rpc_url.startsWith('wss://') && !rpc_url.startsWith('ws://')) {
alert('Invalid RPC URL');
return;
}
const body = {
"EthConfig": {
"AddProvider": {
chain_id: Number(data.get('chain-id')),
trusted: false,
provider: { "RpcUrl": rpc_url },
}
}
};
fetch(APP_PATH, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
}).then(response => response.json())
.then(data => {
if (data === null) {
e.target.reset();
return;
} else {
alert(data);
}
});
})
document.getElementById('remove-eth-provider').addEventListener('submit', (e) => {
e.preventDefault();
const data = new FormData(e.target);
const body = {
"EthConfig": {
"RemoveProvider": [Number(data.get('chain-id')), data.get('rpc-url')]
}
};
fetch(APP_PATH, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
}).then(response => response.json())
.then(data => {
if (data === null) {
e.target.reset();
return;
} else {
alert(data);
}
});
})
// Setup WebSocket connection
const wsProtocol = location.protocol === 'https:' ? 'wss://' : 'ws://';
const ws = new WebSocket(wsProtocol + location.host + "/settings:settings:sys/");

View File

@ -82,14 +82,58 @@ h1 {
}
main {
margin: 0 auto;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
grid-template-columns: 1fr 1fr 1fr;
gap: 20px 20px;
grid-auto-flow: row;
grid-template-areas:
"diagnostics diagnostics diagnostics"
"node-info pings pings"
"eth-rpc-providers eth-rpc-providers eth-rpc-settings"
"kernel kernel kernel";
padding: 20px;
max-width: 1200px;
max-width: 1100px;
min-width: 300px;
}
article#net-diagnostics {
grid-area: diagnostics;
}
p#diagnostics,
p#peer-pki-response,
p#peer-ping-response {
white-space: pre-wrap;
}
article#node-info {
grid-area: node-info;
}
article#pings {
grid-area: pings;
}
article#eth-rpc-providers {
grid-area: eth-rpc-providers;
}
article#eth-rpc-settings {
grid-area: eth-rpc-settings;
}
article#kernel {
grid-area: kernel;
}
div#provider-edits {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px 20px;
grid-auto-flow: row;
}
article {
background-color: #333;
border: 1px solid #444;
@ -129,11 +173,20 @@ button {
border-radius: 4px;
}
button#shutdown {
background-color: #f44336;
}
button:hover {
background-color: white;
color: #4CAF50;
}
button#shutdown:hover {
background-color: white;
color: #f44336;
}
input[type="text"],
input[type="number"],
select,
@ -178,4 +231,5 @@ li {
background-color: #2c2c2c;
border-radius: 4px;
word-wrap: break-word;
white-space: pre-wrap;
}

View File

@ -10,7 +10,7 @@ simulation-mode = []
anyhow = "1.0"
base64 = "0.22.0"
bincode = "1.3.3"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.6.1" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag="v0.7.2" }
rmp-serde = "1.2.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@ -163,12 +163,6 @@ fn initialize(our: Address) {
// Grab our state, then enter the main event loop.
let mut state: SettingsState = SettingsState::new(our);
match state.fetch() {
Ok(()) => {}
Err(e) => {
println!("failed to fetch initial state: {e}");
}
}
main_loop(&mut state);
}
@ -249,16 +243,18 @@ fn handle_http_request(
state: &mut SettingsState,
http_request: &http::IncomingHttpRequest,
) -> anyhow::Result<()> {
state.fetch()?;
match http_request.method()?.as_str() {
"GET" => Ok(http::send_response(
http::StatusCode::OK,
Some(HashMap::from([(
String::from("Content-Type"),
String::from("application/json"),
)])),
serde_json::to_vec(&state)?,
)),
"GET" => {
state.fetch()?;
Ok(http::send_response(
http::StatusCode::OK,
Some(HashMap::from([(
String::from("Content-Type"),
String::from("application/json"),
)])),
serde_json::to_vec(&state)?,
))
}
"POST" => {
let Some(blob) = get_blob() else {
return Ok(http::send_response(
@ -269,6 +265,7 @@ fn handle_http_request(
};
let request = serde_json::from_slice::<SettingsRequest>(&blob.bytes)?;
let response = handle_settings_request(state, request);
state.fetch()?;
state.ws_update();
Ok(http::send_response(
http::StatusCode::OK,

View File

@ -8,7 +8,7 @@ simulation-mode = []
[dependencies]
anyhow = "1.0"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = "0.24.0"

View File

@ -8,7 +8,7 @@ simulation-mode = []
[dependencies]
anyhow = "1.0"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = "0.24.0"

View File

@ -8,7 +8,7 @@ simulation-mode = []
[dependencies]
anyhow = "1.0"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = "0.24.0"

View File

@ -7,7 +7,7 @@ edition = "2021"
simulation-mode = []
[dependencies]
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = "0.24.0"

View File

@ -9,7 +9,7 @@ simulation-mode = []
[dependencies]
anyhow = "1.0"
clap = "4.4.18"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
regex = "1.10.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@ -7,7 +7,7 @@ edition = "2021"
simulation-mode = []
[dependencies]
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
rmp-serde = "1.1.2"
serde = { version = "1.0", features = ["derive"] }
wit-bindgen = "0.24.0"

View File

@ -7,7 +7,7 @@ edition = "2021"
simulation-mode = []
[dependencies]
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
rmp-serde = "1.1.2"
serde = { version = "1.0", features = ["derive"] }
wit-bindgen = "0.24.0"

View File

@ -7,7 +7,7 @@ edition = "2021"
simulation-mode = []
[dependencies]
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
rmp-serde = "1.1.2"
serde = { version = "1.0", features = ["derive"] }
wit-bindgen = "0.24.0"

View File

@ -7,7 +7,7 @@ edition = "2021"
simulation-mode = []
[dependencies]
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
rmp-serde = "1.1.2"
serde = { version = "1.0", features = ["derive"] }
wit-bindgen = "0.24.0"

View File

@ -9,7 +9,7 @@ simulation-mode = []
[dependencies]
anyhow = "1.0"
bincode = "1.3.3"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
rand = "0.8"
regex = "1.10.3"
serde = { version = "1.0", features = ["derive"] }

View File

@ -8,7 +8,7 @@ simulation-mode = []
[dependencies]
anyhow = "1.0"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = "0.24.0"

View File

@ -9,7 +9,7 @@ simulation-mode = []
[dependencies]
anyhow = "1.0"
bincode = "1.3.3"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1.0"

View File

@ -10,7 +10,7 @@ simulation-mode = []
anyhow = "1.0"
bincode = "1.3.3"
indexmap = "2.1"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1.0"

View File

@ -63,18 +63,26 @@ impl ActiveProviders {
kns_update,
use_as_provider,
} => {
self.nodes.push(NodeProvider {
trusted: new.trusted,
usable: use_as_provider,
kns_update,
});
self.remove_provider(&kns_update.name);
self.nodes.insert(
0,
NodeProvider {
trusted: new.trusted,
usable: use_as_provider,
kns_update,
},
);
}
NodeOrRpcUrl::RpcUrl(url) => {
self.urls.push(UrlProvider {
trusted: new.trusted,
url,
pubsub: None,
});
self.remove_provider(&url);
self.urls.insert(
0,
UrlProvider {
trusted: new.trusted,
url,
pubsub: None,
},
);
}
}
}

View File

@ -33,6 +33,23 @@ pub async fn create_new_subscription(
)
.await
{
// if building the subscription fails, send an error message to the target
Ok(Err(e)) => {
error_message(&our, km_id, target.clone(), e, &send_to_loop).await;
}
// if building the subscription times out, send an error message to the target
Err(_) => {
error_message(
&our,
km_id,
target.clone(),
EthError::RpcTimeout,
&send_to_loop,
)
.await;
}
// if building the subscription is successful, start the subscription
// and in this task, maintain and clean up after it.
Ok(Ok(maybe_raw_sub)) => {
// send a response to the target that the subscription was successful
kernel_message(
@ -49,47 +66,43 @@ pub async fn create_new_subscription(
let mut subs = active_subscriptions
.entry(target.clone())
.or_insert(HashMap::new());
let our = our.clone();
let send_to_loop = send_to_loop.clone();
let print_tx = print_tx.clone();
let active_subscriptions = active_subscriptions.clone();
match maybe_raw_sub {
Ok(rx) => {
let our = our.clone();
let send_to_loop = send_to_loop.clone();
let print_tx = print_tx.clone();
subs.insert(
sub_id,
// this is a local sub, as in, we connect to the rpc endpoint
ActiveSub::Local(tokio::spawn(async move {
// await the subscription error and kill it if so
if let Err(e) = maintain_local_subscription(
let e = maintain_local_subscription(
&our,
sub_id,
rx,
&target,
&rsvp,
&send_to_loop,
&active_subscriptions,
)
.await
{
verbose_print(
&print_tx,
"eth: closed local subscription due to error",
)
.await;
kernel_message(
&our,
rand::random(),
target.clone(),
rsvp,
true,
None,
EthSubResult::Err(e),
&send_to_loop,
)
.await;
active_subscriptions.entry(target).and_modify(|sub_map| {
sub_map.remove(&km_id);
});
}
.await;
verbose_print(
&print_tx,
&format!("eth: closed local subscription due to error {e:?}"),
)
.await;
kernel_message(
&our,
rand::random(),
target.clone(),
rsvp,
true,
None,
EthSubResult::Err(e),
&send_to_loop,
)
.await;
})),
);
}
@ -100,16 +113,13 @@ pub async fn create_new_subscription(
let (keepalive_err_sender, keepalive_err_receiver) =
tokio::sync::mpsc::channel(1);
response_channels.insert(keepalive_km_id, keepalive_err_sender);
let our = our.clone();
let send_to_loop = send_to_loop.clone();
let print_tx = print_tx.clone();
let response_channels = response_channels.clone();
subs.insert(
remote_sub_id,
ActiveSub::Remote {
provider_node: provider_node.clone(),
handle: tokio::spawn(async move {
if let Err(e) = maintain_remote_subscription(
let e = maintain_remote_subscription(
&our,
&provider_node,
remote_sub_id,
@ -119,30 +129,26 @@ pub async fn create_new_subscription(
keepalive_err_receiver,
&target,
&send_to_loop,
)
.await
{
verbose_print(
&print_tx,
"eth: closed subscription with provider node due to error",
&active_subscriptions,
&response_channels,
)
.await;
verbose_print(
&print_tx,
&format!("eth: closed subscription with provider node due to error {e:?}"),
)
.await;
kernel_message(
&our,
rand::random(),
target.clone(),
None,
true,
None,
EthSubResult::Err(e),
&send_to_loop,
)
.await;
kernel_message(
&our,
rand::random(),
target.clone(),
None,
true,
None,
EthSubResult::Err(e),
&send_to_loop,
)
.await;
active_subscriptions.entry(target).and_modify(|sub_map| {
sub_map.remove(&sub_id);
});
response_channels.remove(&keepalive_km_id);
}
}),
sender,
},
@ -150,19 +156,6 @@ pub async fn create_new_subscription(
}
}
}
Ok(Err(e)) => {
error_message(&our, km_id, target.clone(), e, &send_to_loop).await;
}
Err(_) => {
error_message(
&our,
km_id,
target.clone(),
EthError::RpcTimeout,
&send_to_loop,
)
.await;
}
}
});
}
@ -311,13 +304,18 @@ async fn maintain_local_subscription(
target: &Address,
rsvp: &Option<Address>,
send_to_loop: &MessageSender,
) -> Result<(), EthSubError> {
active_subscriptions: &ActiveSubscriptions,
) -> EthSubError {
while let Ok(value) = rx.recv().await {
let result: SubscriptionResult =
serde_json::from_str(value.get()).map_err(|e| EthSubError {
id: sub_id,
error: e.to_string(),
})?;
let result: SubscriptionResult = match serde_json::from_str(value.get()) {
Ok(res) => res,
Err(e) => {
return EthSubError {
id: sub_id,
error: e.to_string(),
}
}
};
kernel_message(
our,
rand::random(),
@ -330,10 +328,15 @@ async fn maintain_local_subscription(
)
.await;
}
Err(EthSubError {
active_subscriptions
.entry(target.clone())
.and_modify(|sub_map| {
sub_map.remove(&sub_id);
});
EthSubError {
id: sub_id,
error: "subscription closed unexpectedly".to_string(),
})
}
}
/// handle the subscription updates from a remote provider,
@ -352,12 +355,14 @@ async fn maintain_remote_subscription(
mut net_error_rx: ProcessMessageReceiver,
target: &Address,
send_to_loop: &MessageSender,
) -> Result<(), EthSubError> {
active_subscriptions: &ActiveSubscriptions,
response_channels: &ResponseChannels,
) -> EthSubError {
let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(30));
let mut last_received = tokio::time::Instant::now();
let two_hours = tokio::time::Duration::from_secs(2 * 3600);
loop {
let e = loop {
tokio::select! {
incoming = rx.recv() => {
match incoming {
@ -380,16 +385,16 @@ async fn maintain_remote_subscription(
.await;
}
Some(EthSubResult::Err(e)) => {
return Err(EthSubError {
break EthSubError {
id: sub_id,
error: e.error,
});
};
}
None => {
return Err(EthSubError {
break EthSubError {
id: sub_id,
error: "subscription closed unexpectedly".to_string(),
});
};
}
}
}
@ -406,20 +411,25 @@ async fn maintain_remote_subscription(
&send_to_loop,
).await;
}
incoming = net_error_rx.recv() => {
if let Some(Err(_net_error)) = incoming {
return Err(EthSubError {
id: sub_id,
error: "subscription node-provider failed keepalive".to_string(),
});
}
_incoming = net_error_rx.recv() => {
break EthSubError {
id: sub_id,
error: "subscription node-provider failed keepalive".to_string(),
};
}
_ = tokio::time::sleep_until(last_received + two_hours) => {
return Err(EthSubError {
break EthSubError {
id: sub_id,
error: "No updates received for 2 hours, subscription considered dead.".to_string(),
});
};
}
}
}
};
active_subscriptions
.entry(target.clone())
.and_modify(|sub_map| {
sub_map.remove(&remote_sub_id);
});
response_channels.remove(&keepalive_km_id);
e
}

View File

@ -1141,88 +1141,18 @@ Constrain images and videos to the parent width and preserve their intrinsic asp
</style>
</head>
<body><noscript>You need to enable JavaScript to run this app.</noscript>
<body
style="background-color: #22211f;
background-image: linear-gradient(-55deg, #f75a2977 0%, transparent 72.05%),linear-gradient(-11deg, #86000172 3%, transparent 57.05%);">
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<svg width="1440" height="1024" viewBox="0 0 1440 1024" fill="none" xmlns="http://www.w3.org/2000/svg" style="position: fixed;
top: -300px;
left: -350px;
z-index: -1;
filter: blur(100px);
transform: scale(3);">
<g clip-path="url(#clip0_152_8506)">
<rect width="1440" height="2777" transform="translate(0 -5)" fill="#22211F" />
<path
d="M2122.93 951.101C1925.12 1458.86 1658.87 2101.89 1376.85 1992.03C601.547 2045.26 -152.056 1158.27 434.949 891.737C1021.95 625.205 1605.89 -327.704 1970.46 -167.208C2361.3 4.85831 2320.74 443.343 2122.93 951.101Z"
fill="url(#paint0_radial_152_8506)" />
<path
d="M274.686 1388.42C15.0266 1578.85 -501.874 1262.71 -695.499 998.693C-889.123 734.676 -935.603 448.185 -675.943 257.757C-416.284 67.3282 500.306 639.9 693.93 903.918C887.555 1167.94 534.346 1197.99 274.686 1388.42Z"
fill="url(#paint1_linear_152_8506)" />
<path
d="M673.921 -604.363C829.107 -41.0363 308.158 505 -81.9719 505C-472.102 505 -788.365 72.0217 -788.365 -462.084C-788.365 -996.189 -1002.49 -1551 -612.361 -1551C-222.231 -1551 673.921 -1138.47 673.921 -604.363Z"
fill="#F75A29" fill-opacity="0.7" />
<g filter="url(#filter0_b_152_8506)">
<rect width="1464" height="2823" transform="translate(0 -5)" fill="#22211F" fill-opacity="0.01" />
</g>
</g>
<defs>
<filter id="filter0_b_152_8506" x="-314" y="-319" width="2092" height="3451" filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feGaussianBlur in="BackgroundImageFix" stdDeviation="157" />
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_152_8506" />
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_152_8506" result="shape" />
</filter>
<radialGradient id="paint0_radial_152_8506" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse"
gradientTransform="translate(1444 1488.65) rotate(-86.9561) scale(699.105 590.175)">
<stop offset="0.635" stop-color="#F35422" stop-opacity="0.7" />
<stop offset="1" stop-color="#E60D16" stop-opacity="0.35" />
</radialGradient>
<linearGradient id="paint1_linear_152_8506" x1="-389.52" y1="620.673" x2="747.127" y2="934.639"
gradientUnits="userSpaceOnUse">
<stop stop-color="#F35422" />
<stop offset="1" stop-color="#E60D16" stop-opacity="0.1" />
</linearGradient>
<clipPath id="clip0_152_8506">
<rect width="1440" height="2777" fill="white" transform="translate(0 -5)" />
</clipPath>
</defs>
</svg>
<div id="signup-page" class="flex flex-col place-items-center place-content-center h-screen font-sans">
<div id="home-form-header" class="flex flex-col place-content-center place-items-center"
style="gap: 24px; margin: 32px 0 56px;">
<h3 style="text-align: center;">Login to</h3>
<svg width="289.5" height="36" viewBox="0 0 580 72" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_6_641)">
<path d="M0.824922 1.07031L0.794922 70.0703H14.7949L14.8049 1.07031H0.824922Z" fill="#FFF5D9"></path>
<path d="M16.5947 36.8803L41.2547 1.07031H58.2447L33.1647 36.8803L61.2447 70.0703H42.9947L16.5947 36.8803Z"
fill="#FFF5D9"></path>
<path d="M119.885 1.07031H105.765V70.0703H119.885V1.07031Z" fill="#FFF5D9"></path>
<path
d="M173.185 1.07031V70.0703H186.775V26.8303L224.045 70.0703H234.825V1.07031H221.325V45.6803L183.445 1.07031H173.185Z"
fill="#FFF5D9"></path>
<path
d="M342.465 8.86C333.025 0.15 321.645 0 318.535 0C315.475 0 303.575 0.22 294.005 9.52C283.845 19.4 283.805 32.24 283.795 35.66C283.785 39.3 283.895 49.03 290.805 57.99C300.855 71.02 316.695 71.31 318.535 71.32C321.375 71.32 334.185 71 343.965 60.66C353.065 51.04 353.265 39.4 353.275 35.66C353.275 32.49 353.305 18.86 342.455 8.86H342.465ZM318.435 58.01C307.095 58.01 297.895 47.95 297.895 35.54C297.895 23.13 307.085 13.07 318.435 13.07C329.785 13.07 338.975 23.13 338.975 35.54C338.975 47.95 329.785 58.01 318.435 58.01Z"
fill="#FFF5D9"></path>
<path
d="M450.495 12.0802C444.975 5.46023 437.135 0.990234 427.955 0.990234C417.555 0.990234 405.295 1.07023 402.295 1.07023V69.9802C405.285 69.9802 417.555 70.0602 427.955 70.0602C445.525 70.0602 458.445 53.4102 459.065 36.8602C459.395 28.0102 456.185 18.9002 450.495 12.0802ZM440.085 49.9502C436.895 53.8702 432.705 56.6902 427.665 57.5602C424.025 58.1902 420.095 57.8302 416.405 57.8302C416.405 50.4002 416.405 42.9802 416.405 35.5502V13.2202C423.795 13.2202 430.525 12.7002 436.605 17.6002C440.275 20.5602 442.925 24.7102 444.165 29.2402C444.525 30.5402 444.765 31.8802 444.875 33.2302C445.395 39.3702 443.995 45.1402 440.085 49.9502Z"
fill="#FFF5D9"></path>
<path
d="M508.135 0.990234V70.0602H552.715V57.9302H522.035V40.4202H547.125V28.0702H521.995V13.3202H552.715V0.990234H508.135Z"
fill="#FFF5D9"></path>
<path
d="M574.835 66.0398H572.745L571.015 63.0698H569.845V66.0398H567.805V57.5498H571.765C572.845 57.5498 573.865 57.9298 574.425 58.9398C575.205 60.3698 574.665 62.3798 573.105 63.0298C573.725 64.1198 574.225 64.9498 574.845 66.0398H574.835ZM570.375 61.0798H570.845C571.335 61.0798 572.365 61.0798 572.365 60.2898C572.365 59.5598 571.335 59.5598 570.845 59.5598H570.375V61.0798Z"
fill="#FFF5D9"></path>
<path
d="M570.964 69.0002C574.913 69.0002 578.114 65.799 578.114 61.8502C578.114 57.9014 574.913 54.7002 570.964 54.7002C567.016 54.7002 563.814 57.9014 563.814 61.8502C563.814 65.799 567.016 69.0002 570.964 69.0002Z"
stroke="#FFF5D9" stroke-width="2.2" stroke-miterlimit="10"></path>
</g>
<defs>
<clipPath id="clip0_6_641">
<rect width="578.41" height="71.32" fill="white" transform="translate(0.794922)"></rect>
</clipPath>
</defs>
</svg>
<h1 style="font-family: 'Futura', sans-serif; letter-spacing: 0.4em; font-size: xxx-large; margin-left: 1em;">
KINODE<span style="font-size: small">&reg;</span>
</h1>
<svg style="margin-top: 0.5em;" width="67.5" height="48" viewBox="0 0 122 81" fill="none"
xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_6_651)">
@ -1240,7 +1170,7 @@ Constrain images and videos to the parent width and preserve their intrinsic asp
<form id="signup-form" class="flex flex-col">
<h3 class="flex flex-col ml-2 text-lg"> ${node} </h3>
<div class="flex mt-2 mb-2"> Enter Password </div>
<input type="password" id="password" required="" minlength="6" name="password" placeholder="Password"
<input autofocus type="password" id="password" required="" minlength="6" name="password" placeholder="Password"
oninput="document.getElementById('password-err').style.display = 'none';" value="" class="self-stretch mb-2">
<div id="password-err" class="login-row flex mb-2" style="display: none;"> Incorrect Password </div>
<div class="flex flex-col leading-6 self-stretch mb-2">
@ -1260,7 +1190,7 @@ Constrain images and videos to the parent width and preserve their intrinsic asp
if ('${fake}' === 'true') {
document.getElementById("fake-or-not").innerHTML = "Fake node -- any password will work!";
} else {
document.getElementById("fake-or-not").innerHTML = "Restart node to change networking info";
document.getElementById("fake-or-not").innerHTML = "To change your networking info, please restart your node.";
}
async function login(password) {

View File

@ -86,7 +86,7 @@ async fn main() {
Err(_) => serde_json::from_str(DEFAULT_ETH_PROVIDERS).unwrap(),
};
if let Some(rpc) = rpc {
eth_provider_config.push(lib::eth::ProviderConfig {
eth_provider_config.insert(lib::eth::ProviderConfig {
chain_id: CHAIN_ID,
trusted: true,
provider: lib::eth::NodeOrRpcUrl::RpcUrl(rpc.to_string()),

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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-DzEOiZ-5.js"></script>
<script type="module" crossorigin src="/assets/index-CqsVFnwl.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-B00cPdAQ.css">
</head>

View File

@ -1,5 +1,5 @@
{
"name": "register",
"name": "kinode-register-ui",
"version": "0.1.0",
"private": true,
"proxy": "http://localhost:8080",
@ -27,7 +27,7 @@
"eslint-config-react-app": "^7.0.1",
"eth-ens-namehash": "^2.0.8",
"ethers": "^5.7.2",
"idna-uts46-hx": "^2.3.1",
"idna-uts46-hx": "^6.0.4",
"is-valid-domain": "^0.1.6",
"jazzicon": "^1.5.0",
"react": "^18.2.0",

View File

@ -65,7 +65,7 @@ function App() {
const openConnect = () => setConnectOpen(true)
const closeConnect = () => setConnectOpen(false)
const rpcUrl = useMemo(() => provider?.network?.chainId === ChainId.SEPOLIA ? process.env.REACT_APP_SEPOLIA_RPC_URL : process.env.REACT_APP_OPTIMISM_RPC_URL, [provider])
const rpcUrl = useMemo(() => provider?.network?.chainId === ChainId.SEPOLIA ? import.meta.env.REACT_APP_SEPOLIA_RPC_URL : import.meta.env.REACT_APP_OPTIMISM_RPC_URL, [provider])
const [dotOs, setDotOs] = useState<DotOsRegistrar>(
DotOsRegistrar__factory.connect(
@ -83,7 +83,7 @@ function App() {
KNSEnsEntry__factory.connect(
provider?.network?.chainId === ChainId.SEPOLIA ? KNS_ENS_ENTRY_ADDRESSES[ChainId.SEPOLIA] : KNS_ENS_ENTRY_ADDRESSES[ChainId.MAINNET],
// set rpc url based on chain id
new ethers.providers.JsonRpcProvider(provider?.network?.chainId === ChainId.SEPOLIA ? process.env.REACT_APP_SEPOLIA_RPC_URL : process.env.REACT_APP_MAINNET_RPC_URL))
new ethers.providers.JsonRpcProvider(provider?.network?.chainId === ChainId.SEPOLIA ? import.meta.env.REACT_APP_SEPOLIA_RPC_URL : import.meta.env.REACT_APP_MAINNET_RPC_URL))
);
const [knsEnsExit, setKnsEnsExit] = useState<KNSEnsExit>(
@ -192,11 +192,11 @@ function App() {
))
setNameWrapper(NameWrapper__factory.connect(
NAMEWRAPPER_ADDRESSES[ChainId.MAINNET],
new ethers.providers.JsonRpcProvider(process.env.REACT_APP_MAINNET_RPC_URL)
new ethers.providers.JsonRpcProvider(import.meta.env.REACT_APP_MAINNET_RPC_URL)
))
setEnsRegistry(ENSRegistry__factory.connect(
ENS_REGISTRY_ADDRESSES[ChainId.MAINNET],
new ethers.providers.JsonRpcProvider(process.env.REACT_APP_MAINNET_RPC_URL)
new ethers.providers.JsonRpcProvider(import.meta.env.REACT_APP_MAINNET_RPC_URL)
))
}
})

View File

@ -0,0 +1,12 @@
/// <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;
// Add other environment variables as needed
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}

View File

@ -5318,6 +5318,13 @@ idna-uts46-hx@^2.3.1:
dependencies:
punycode "2.1.0"
idna-uts46-hx@^6.0.4:
version "6.0.4"
resolved "https://registry.yarnpkg.com/idna-uts46-hx/-/idna-uts46-hx-6.0.4.tgz#25f9f8af628bd4150b36340e99a1f9532c815905"
integrity sha512-sI5p40O39Mev3pk7dmG4m2vaSBQcpn8ZETN72ftaIPaDvY3MwgOj2psh20lYA9aVgQ5eS/Wcmo6l+USXyepffg==
dependencies:
tr46 "^5.0.0"
ieee754@^1.1.13, ieee754@^1.2.1:
version "1.2.1"
resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz"
@ -6601,9 +6608,9 @@ punycode@2.1.0:
resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz"
integrity sha512-Yxz2kRwT90aPiWEMHVYnEf4+rhwF1tBmmZ4KepCP+Wkium9JxtWnUm1nqGwpiAHr/tnTSeHqr3wb++jgSkXjhA==
punycode@^2.1.1:
punycode@^2.1.1, punycode@^2.3.1:
version "2.3.1"
resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
q@^1.1.2:
@ -7426,6 +7433,13 @@ totalist@^3.0.0:
resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.1.tgz#ba3a3d600c915b1a97872348f79c127475f6acf8"
integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==
tr46@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-5.0.0.tgz#3b46d583613ec7283020d79019f1335723801cec"
integrity sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==
dependencies:
punycode "^2.3.1"
tr46@~0.0.3:
version "0.0.3"
resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz"

View File

@ -125,8 +125,16 @@ pub async fn register(
} else {
"wss://optimism-rpc.publicnode.com".to_string()
};
let connector = WsConnect { url, auth: None };
let Ok(client) = ClientBuilder::default().ws(connector).await else {
println!(
"Connecting to Optimism RPC at {url}\n\
Specify a different RPC URL with the --rpc flag."
);
// this fails occasionally in certain networking environments. i'm not sure why.
// frustratingly, the exact same call does not fail in the eth module. more investigation needed.
let Ok(client) = ClientBuilder::default()
.ws(WsConnect { url, auth: None })
.await
else {
panic!(
"Error: runtime could not connect to ETH RPC.\n\
This is necessary in order to verify node identity onchain.\n\
@ -134,6 +142,7 @@ pub async fn register(
the --rpc flag, and you are connected to the internet."
);
};
println!("Connected to Optimism RPC");
let provider = Arc::new(Provider::new_with_client(client));

View File

@ -1583,7 +1583,7 @@ pub enum NetResponse {
Verified(bool),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize, Hash, Eq, PartialEq)]
pub struct KnsUpdate {
pub name: String, // actual username / domain name
pub owner: String,

View File

@ -141,17 +141,17 @@ pub struct AccessSettings {
pub deny: HashSet<String>, // blacklist for access (always used)
}
pub type SavedConfigs = Vec<ProviderConfig>;
pub type SavedConfigs = HashSet<ProviderConfig>;
/// Provider config. Can currently be a node or a ws provider instance.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[derive(Clone, Debug, Deserialize, Serialize, Hash, Eq, PartialEq)]
pub struct ProviderConfig {
pub chain_id: u64,
pub trusted: bool,
pub provider: NodeOrRpcUrl,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[derive(Clone, Debug, Deserialize, Serialize, Hash, Eq, PartialEq)]
pub enum NodeOrRpcUrl {
Node {
kns_update: crate::core::KnsUpdate,