feat: backend api
This commit is contained in:
parent
daa640068b
commit
3c7736dae7
@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<link rel="icon" type="image/jpeg" href="/logo.jpg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="description" content="Bitcoin Lightning VPS provider" />
|
||||
@ -14,7 +14,7 @@
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content="LNVPS" />
|
||||
<meta property="og:description" content="Bitcoin Lightning VPS provider" />
|
||||
<meta property="og:image" content="/logo.png" />
|
||||
<meta property="og:image" content="/logo.jpg" />
|
||||
<title>LNVPS</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
@ -27,6 +27,5 @@
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
<script src="https://btcpay.v0l.io/modal/btcpay.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
10
package.json
10
package.json
@ -10,11 +10,15 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@scure/base": "^1.2.1",
|
||||
"@snort/shared": "^1.0.17",
|
||||
"@snort/system": "^1.5.1",
|
||||
"@snort/system-react": "^1.5.1",
|
||||
"@snort/system": "^1.5.7",
|
||||
"@snort/system-react": "^1.5.7",
|
||||
"classnames": "^2.5.1",
|
||||
"qr-code-styling": "^1.8.4",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^7.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.8.0",
|
||||
|
BIN
public/logo.jpg
Normal file
BIN
public/logo.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
Before Width: | Height: | Size: 1.5 KiB |
111
src/App.tsx
111
src/App.tsx
@ -1,111 +0,0 @@
|
||||
import { SnortContext } from "@snort/system-react";
|
||||
import { CostInterval, DiskType, MachineSpec } from "./api";
|
||||
import VpsCard from "./components/vps-card";
|
||||
import { GiB, NostrProfile } from "./const";
|
||||
import { NostrSystem } from "@snort/system";
|
||||
import Profile from "./components/profile";
|
||||
import LoginButton from "./components/login-button";
|
||||
|
||||
import pgp from "../public/lnvps.asc?url";
|
||||
|
||||
const Offers: Array<MachineSpec> = [
|
||||
{
|
||||
id: "2x2x80",
|
||||
location: "IE",
|
||||
active: true,
|
||||
cpu: 2,
|
||||
ram: 2 * GiB,
|
||||
disk: {
|
||||
type: DiskType.SSD,
|
||||
size: 80 * GiB,
|
||||
},
|
||||
cost: {
|
||||
interval: CostInterval.Month,
|
||||
count: 3,
|
||||
currency: "EUR",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "4x4x160",
|
||||
location: "IE",
|
||||
active: true,
|
||||
cpu: 4,
|
||||
ram: 4 * GiB,
|
||||
disk: {
|
||||
type: DiskType.SSD,
|
||||
size: 160 * GiB,
|
||||
},
|
||||
cost: {
|
||||
interval: CostInterval.Month,
|
||||
count: 5,
|
||||
currency: "EUR",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "8x8x400",
|
||||
location: "IE",
|
||||
active: true,
|
||||
cpu: 8,
|
||||
ram: 8 * GiB,
|
||||
disk: {
|
||||
type: DiskType.SSD,
|
||||
size: 400 * GiB,
|
||||
},
|
||||
cost: {
|
||||
interval: CostInterval.Month,
|
||||
count: 12,
|
||||
currency: "EUR",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const system = new NostrSystem({
|
||||
automaticOutboxModel: false,
|
||||
buildFollowGraph: false,
|
||||
});
|
||||
[
|
||||
"wss://relay.snort.social/",
|
||||
"wss://relay.damus.io/",
|
||||
"wss://relay.nostr.band/",
|
||||
"wss://nos.lol/",
|
||||
].forEach((a) => system.ConnectToRelay(a, { read: true, write: true }));
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<SnortContext.Provider value={system}>
|
||||
<div className="w-[700px] mx-auto m-2 p-2">
|
||||
<div className="flex items-center justify-between">
|
||||
LNVPS
|
||||
<LoginButton />
|
||||
</div>
|
||||
|
||||
<h1>VPS Offers</h1>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{Offers.map((a) => (
|
||||
<VpsCard spec={a} key={a.id} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<small>
|
||||
All VPS come with 1x IPv4 and 1x IPv6 address and unmetered
|
||||
traffic
|
||||
</small>
|
||||
<div className="flex flex-col gap-4">
|
||||
<b>
|
||||
Please email <a href="mailto:sales@lnvps.net">sales</a> after
|
||||
paying the invoice with your order id, desired OS and ssh key.
|
||||
</b>
|
||||
<b>You can also find us on nostr: </b>
|
||||
<a target="_blank" href={`nostr:${NostrProfile.encode()}`}>
|
||||
<Profile link={NostrProfile} />
|
||||
</a>
|
||||
<div>
|
||||
<a target="_blank" href="http://speedtest.v0l.io">Speedtest</a> | <a href={pgp}>PGP</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SnortContext.Provider>
|
||||
);
|
||||
}
|
235
src/api.ts
235
src/api.ts
@ -1,30 +1,215 @@
|
||||
export interface MachineSpec {
|
||||
id: string;
|
||||
active: boolean;
|
||||
import { EventKind, EventPublisher } from "@snort/system";
|
||||
import { base64 } from "@scure/base";
|
||||
|
||||
export interface ApiResponseBase {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export type ApiResponse<T> = ApiResponseBase & {
|
||||
data: T;
|
||||
};
|
||||
|
||||
export interface VmCostPlan {
|
||||
id: number;
|
||||
name: string;
|
||||
created: Date;
|
||||
amount: number;
|
||||
currency: "EUR" | "BTC";
|
||||
interval_amount: number;
|
||||
interval_type: string;
|
||||
}
|
||||
|
||||
export interface VmHostRegion {
|
||||
id: number;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export interface VmTemplate {
|
||||
id: number;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
created: Date;
|
||||
expires?: Date;
|
||||
cpu: number;
|
||||
ram: number;
|
||||
disk: {
|
||||
type: DiskType;
|
||||
size: number;
|
||||
memory: number;
|
||||
disk_size: number;
|
||||
disk_type: string;
|
||||
disk_interface: string;
|
||||
cost_plan_id: number;
|
||||
region_id: number;
|
||||
|
||||
cost_plan?: VmCostPlan;
|
||||
region?: VmHostRegion;
|
||||
}
|
||||
|
||||
export interface VmStatus {
|
||||
state: "running" | "stopped";
|
||||
cpu_usage: number;
|
||||
mem_usage: number;
|
||||
uptime: number;
|
||||
net_in: number;
|
||||
net_out: number;
|
||||
disk_write: number;
|
||||
disk_read: number;
|
||||
}
|
||||
|
||||
export interface VmInstance {
|
||||
id: number;
|
||||
host_id: number;
|
||||
user_id: number;
|
||||
image_id: number;
|
||||
template_id: number;
|
||||
ssh_key_id: number;
|
||||
created: Date;
|
||||
expires: Date;
|
||||
cpu: number;
|
||||
memory: number;
|
||||
disk_size: number;
|
||||
disk_id: number;
|
||||
status?: VmStatus;
|
||||
|
||||
template?: VmTemplate;
|
||||
image?: VmOsImage;
|
||||
ssh_key?: UserSshKey;
|
||||
}
|
||||
|
||||
export interface VmOsImage {
|
||||
id: number;
|
||||
distribution: string;
|
||||
flavour: string;
|
||||
version: string;
|
||||
release_date: string;
|
||||
}
|
||||
|
||||
export interface UserSshKey {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface VmPayment {
|
||||
id: string;
|
||||
invoice: string;
|
||||
created: string;
|
||||
expires: string;
|
||||
amount: number;
|
||||
is_paid: boolean;
|
||||
}
|
||||
|
||||
export class LNVpsApi {
|
||||
constructor(
|
||||
readonly url: string,
|
||||
readonly publisher: EventPublisher | undefined,
|
||||
) {}
|
||||
|
||||
async listVms() {
|
||||
const { data } = await this.#handleResponse<ApiResponse<Array<VmInstance>>>(
|
||||
await this.#req("/api/v1/vm", "GET"),
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
async getVm(id: number) {
|
||||
const { data } = await this.#handleResponse<ApiResponse<VmInstance>>(
|
||||
await this.#req(`/api/v1/vm/${id}`, "GET"),
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
async listOffers() {
|
||||
const { data } = await this.#handleResponse<ApiResponse<Array<VmTemplate>>>(
|
||||
await this.#req("/api/v1/vm/templates", "GET"),
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
async listOsImages() {
|
||||
const { data } = await this.#handleResponse<ApiResponse<Array<VmOsImage>>>(
|
||||
await this.#req("/api/v1/image", "GET"),
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
async listSshKeys() {
|
||||
const { data } = await this.#handleResponse<ApiResponse<Array<UserSshKey>>>(
|
||||
await this.#req("/api/v1/ssh-key", "GET"),
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
async addSshKey(name: string, key: string) {
|
||||
const { data } = await this.#handleResponse<ApiResponse<UserSshKey>>(
|
||||
await this.#req("/api/v1/ssh-key", "POST", {
|
||||
name,
|
||||
key_data: key,
|
||||
}),
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
async orderVm(template_id: number, image_id: number, ssh_key_id: number) {
|
||||
const { data } = await this.#handleResponse<ApiResponse<VmInstance>>(
|
||||
await this.#req("/api/v1/vm", "POST", {
|
||||
template_id,
|
||||
image_id,
|
||||
ssh_key_id,
|
||||
}),
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
async renewVm(vm_id: number) {
|
||||
const { data } = await this.#handleResponse<ApiResponse<VmPayment>>(
|
||||
await this.#req(`/api/v1/vm/${vm_id}/renew`, "GET"),
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
async paymentStatus(id: string) {
|
||||
const { data } = await this.#handleResponse<ApiResponse<VmPayment>>(
|
||||
await this.#req(`/api/v1/payment/${id}`, "GET"),
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
async #handleResponse<T extends ApiResponseBase>(rsp: Response) {
|
||||
if (rsp.ok) {
|
||||
return (await rsp.json()) as T;
|
||||
} else {
|
||||
const text = await rsp.text();
|
||||
try {
|
||||
const obj = JSON.parse(text) as ApiResponseBase;
|
||||
throw new Error(obj.error);
|
||||
} catch {
|
||||
throw new Error(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async #req(path: string, method: "GET" | "POST" | "DELETE", body?: object) {
|
||||
const auth = async (url: string, method: string) => {
|
||||
const auth = await this.publisher?.generic((eb) => {
|
||||
return eb
|
||||
.kind(EventKind.HttpAuthentication)
|
||||
.tag(["u", url])
|
||||
.tag(["method", method]);
|
||||
});
|
||||
if (auth) {
|
||||
return `Nostr ${base64.encode(
|
||||
new TextEncoder().encode(JSON.stringify(auth)),
|
||||
)}`;
|
||||
}
|
||||
};
|
||||
cost: {
|
||||
interval: CostInterval;
|
||||
count: number;
|
||||
currency: CostCurrency;
|
||||
};
|
||||
location: string;
|
||||
}
|
||||
|
||||
export enum DiskType {
|
||||
HDD,
|
||||
SSD,
|
||||
const u = `${this.url}${path}`;
|
||||
return await fetch(u, {
|
||||
method,
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"content-type": "application/json",
|
||||
authorization: (await auth(u, method)) ?? "",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export enum CostInterval {
|
||||
Hour,
|
||||
Day,
|
||||
Month,
|
||||
Year,
|
||||
}
|
||||
|
||||
export type CostCurrency = "EUR" | "USD" | "BTC";
|
||||
|
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
Before Width: | Height: | Size: 4.0 KiB |
@ -1,15 +1,25 @@
|
||||
import classNames from "classnames";
|
||||
import { forwardRef, HTMLProps } from "react";
|
||||
|
||||
export type AsyncButtonProps = {
|
||||
onClick?: (e: React.MouseEvent) => Promise<void>;
|
||||
onClick?: (e: React.MouseEvent) => Promise<void> | void;
|
||||
} & Omit<HTMLProps<HTMLButtonElement>, "type" | "ref" | "onClick">;
|
||||
|
||||
const AsyncButton = forwardRef<HTMLButtonElement, AsyncButtonProps>(
|
||||
function AsyncButton(props, ref) {
|
||||
function AsyncButton({ className, ...props }, ref) {
|
||||
const hasBg = className?.includes("bg-");
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
className="bg-slate-700 py-1 px-2 rounded-xl"
|
||||
className={classNames(
|
||||
"py-1 px-2 rounded-xl font-medium",
|
||||
{
|
||||
"bg-neutral-800 cursor-not-allowed text-neutral-500":
|
||||
!hasBg && props.disabled === true,
|
||||
"bg-neutral-900": !hasBg && !props.disabled,
|
||||
},
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{props.children}
|
||||
|
@ -1,22 +1,20 @@
|
||||
import { CostInterval, MachineSpec } from "../api";
|
||||
import { VmCostPlan } from "../api";
|
||||
|
||||
export default function CostLabel({ cost }: { cost: MachineSpec["cost"] }) {
|
||||
function intervalName(n: number) {
|
||||
export default function CostLabel({ cost }: { cost: VmCostPlan }) {
|
||||
function intervalName(n: string) {
|
||||
switch (n) {
|
||||
case CostInterval.Hour:
|
||||
return "Hour";
|
||||
case CostInterval.Day:
|
||||
case "day":
|
||||
return "Day";
|
||||
case CostInterval.Month:
|
||||
case "month":
|
||||
return "Month";
|
||||
case CostInterval.Year:
|
||||
case "year":
|
||||
return "Year";
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{cost.count} {cost.currency}/{intervalName(cost.interval)}
|
||||
{cost.amount} {cost.currency}/{intervalName(cost.interval_type)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
26
src/components/icon.tsx
Normal file
26
src/components/icon.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import { MouseEventHandler } from "react";
|
||||
|
||||
import Icons from "../icons.svg";
|
||||
|
||||
type Props = {
|
||||
name: string;
|
||||
size?: number;
|
||||
className?: string;
|
||||
onClick?: MouseEventHandler<SVGSVGElement>;
|
||||
};
|
||||
|
||||
export function Icon(props: Props) {
|
||||
const size = props.size || 20;
|
||||
const href = `${Icons}#${props.name}`;
|
||||
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
className={props.className}
|
||||
onClick={props.onClick}
|
||||
>
|
||||
<use href={href} />
|
||||
</svg>
|
||||
);
|
||||
}
|
@ -5,6 +5,7 @@ import { loginNip7 } from "../login";
|
||||
import useLogin from "../hooks/login";
|
||||
import Profile from "./profile";
|
||||
import { NostrLink } from "@snort/system";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export default function LoginButton() {
|
||||
const system = useContext(SnortContext);
|
||||
@ -19,6 +20,8 @@ export default function LoginButton() {
|
||||
Sign In
|
||||
</AsyncButton>
|
||||
) : (
|
||||
<Link to="/account">
|
||||
<Profile link={NostrLink.publicKey(login.pubkey)} />
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
85
src/components/modal.tsx
Normal file
85
src/components/modal.tsx
Normal file
@ -0,0 +1,85 @@
|
||||
import classNames from "classnames";
|
||||
import React, { ReactNode, useEffect } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { AsyncButton } from "./button";
|
||||
|
||||
export interface ModalProps {
|
||||
id: string;
|
||||
className?: string;
|
||||
bodyClassName?: string;
|
||||
onClose?: (e: React.MouseEvent | KeyboardEvent) => void;
|
||||
onClick?: (e: React.MouseEvent) => void;
|
||||
children?: ReactNode;
|
||||
showClose?: boolean;
|
||||
ready?: boolean;
|
||||
largeModal?: boolean;
|
||||
}
|
||||
|
||||
export default function Modal(props: ModalProps) {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape" && props.onClose) {
|
||||
props.onClose(e);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
document.body.classList.add("scroll-lock");
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
|
||||
return () => {
|
||||
document.body.classList.remove("scroll-lock");
|
||||
document.removeEventListener("keydown", handleKeyDown);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleBackdropClick = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
props.onClose?.(e);
|
||||
};
|
||||
|
||||
return createPortal(
|
||||
<div
|
||||
className={classNames(
|
||||
"z-[42] w-screen h-screen top-0 left-0 fixed bg-black/80 flex justify-center overflow-y-auto",
|
||||
)}
|
||||
onMouseDown={handleBackdropClick}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={
|
||||
props.bodyClassName ??
|
||||
classNames(
|
||||
"relative bg-layer-1 p-8 transition max-xl:rounded-t-3xl xl:rounded-3xl max-xl:mt-auto xl:my-auto max-lg:w-full",
|
||||
{
|
||||
"max-xl:-translate-y-[calc(100vh-100dvh)]": props.ready ?? true,
|
||||
"max-xl:translate-y-[50vh]": !(props.ready ?? true),
|
||||
"lg:w-[50vw]": !(props.largeModal ?? false),
|
||||
"lg:w-[80vw]": props.largeModal ?? false,
|
||||
},
|
||||
)
|
||||
}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
props.onClick?.(e);
|
||||
}}
|
||||
>
|
||||
{(props.showClose ?? true) && (
|
||||
<div className="absolute right-4 top-4">
|
||||
<AsyncButton
|
||||
onClick={async (e) => {
|
||||
e.stopPropagation();
|
||||
props.onClose?.(e);
|
||||
}}
|
||||
className="rounded-full aspect-square bg-layer-2 p-3"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{props.children}
|
||||
</div>
|
||||
</div>,
|
||||
document.body,
|
||||
) as JSX.Element;
|
||||
}
|
9
src/components/os-image-name.tsx
Normal file
9
src/components/os-image-name.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import { VmOsImage } from "../api";
|
||||
|
||||
export default function OsImageName({ image }: { image: VmOsImage }) {
|
||||
return (
|
||||
<span>
|
||||
{image.distribution} {image.version}
|
||||
</span>
|
||||
);
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
.btcpay-form {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.btcpay-form--inline {
|
||||
flex-direction: row;
|
||||
}
|
||||
.btcpay-form--block {
|
||||
flex-direction: column;
|
||||
}
|
||||
.btcpay-form--inline .submit {
|
||||
margin-left: 15px;
|
||||
}
|
||||
.btcpay-form--block select {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.btcpay-form .btcpay-custom-container {
|
||||
text-align: center;
|
||||
}
|
||||
.btcpay-custom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.btcpay-form .plus-minus {
|
||||
cursor: pointer;
|
||||
font-size: 25px;
|
||||
line-height: 25px;
|
||||
background: #dfe0e1;
|
||||
height: 30px;
|
||||
width: 45px;
|
||||
border: none;
|
||||
border-radius: 60px;
|
||||
margin: auto 5px;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.btcpay-form select {
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
color: currentColor;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
display: block;
|
||||
padding: 1px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
font-size: 11px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.btcpay-form select:hover {
|
||||
border-color: #ccc;
|
||||
}
|
||||
.btcpay-form option {
|
||||
color: #000;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.btcpay-input-price {
|
||||
-moz-appearance: textfield;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
text-align: center;
|
||||
font-size: 25px;
|
||||
margin: auto;
|
||||
border-radius: 5px;
|
||||
line-height: 35px;
|
||||
background: #fff;
|
||||
}
|
||||
.btcpay-input-price::-webkit-outer-spin-button,
|
||||
.btcpay-input-price::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
import { MachineSpec } from "../api";
|
||||
|
||||
import "./pay-button.css";
|
||||
import { ReactNode } from "react";
|
||||
import { VmTemplate } from "../api";
|
||||
import useLogin from "../hooks/login";
|
||||
import { AsyncButton } from "./button";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@ -10,55 +12,33 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
export default function VpsPayButton({ spec }: { spec: MachineSpec }) {
|
||||
const serverUrl = "https://btcpay.v0l.io/api/v1/invoices";
|
||||
export default function VpsPayButton({ spec }: { spec: VmTemplate }) {
|
||||
const login = useLogin();
|
||||
const classNames =
|
||||
"w-full text-center text-lg uppercase rounded-xl py-3 font-bold cursor-pointer select-none";
|
||||
const navigte = useNavigate();
|
||||
|
||||
function handleFormSubmit(event: React.FormEvent) {
|
||||
event.preventDefault();
|
||||
const form = event.target as HTMLFormElement;
|
||||
const xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function () {
|
||||
if (this.readyState == 4 && this.status == 200 && this.responseText) {
|
||||
window.btcpay?.appendInvoiceFrame(
|
||||
JSON.parse(this.responseText).invoiceId,
|
||||
);
|
||||
}
|
||||
};
|
||||
xhttp.open("POST", serverUrl, true);
|
||||
xhttp.send(new FormData(form));
|
||||
function placeholder(inner: ReactNode) {
|
||||
return <div className={`${classNames} bg-red-900`}>{inner}</div>;
|
||||
}
|
||||
|
||||
if (!spec.active) {
|
||||
if (!spec.enabled) {
|
||||
return placeholder("Unavailable");
|
||||
}
|
||||
|
||||
if (!login) {
|
||||
return placeholder("Please Login");
|
||||
}
|
||||
return (
|
||||
<div className="text-center text-xl uppercase bg-red-800 rounded-xl py-3 font-bold">
|
||||
Unavailable
|
||||
</div>
|
||||
);
|
||||
<AsyncButton
|
||||
className={`${classNames} bg-green-800`}
|
||||
onClick={() =>
|
||||
navigte("/order", {
|
||||
state: spec,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
method="POST"
|
||||
action={serverUrl}
|
||||
className="btcpay-form btcpay-form--block w-full"
|
||||
onSubmit={handleFormSubmit}
|
||||
>
|
||||
<input
|
||||
type="hidden"
|
||||
name="storeId"
|
||||
value="CdaHy1puLx4kLC9BG3A9mu88XNyLJukMJRuuhAfbDrxg"
|
||||
/>
|
||||
<input type="hidden" name="jsonResponse" value="true" />
|
||||
<input type="hidden" name="orderId" value={spec.id} />
|
||||
<input type="hidden" name="price" value={spec.cost.count} />
|
||||
<input type="hidden" name="currency" value={spec.cost.currency} />
|
||||
<input
|
||||
type="image"
|
||||
className="w-full"
|
||||
name="submit"
|
||||
src="https://btcpay.v0l.io/img/paybutton/pay.svg"
|
||||
alt="Pay with BTCPay Server, a Self-Hosted Bitcoin Payment Processor"
|
||||
/>
|
||||
</form>
|
||||
Buy Now
|
||||
</AsyncButton>
|
||||
);
|
||||
}
|
||||
|
50
src/components/qr.tsx
Normal file
50
src/components/qr.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import QRCodeStyling from "qr-code-styling";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
export interface QrCodeProps {
|
||||
data?: string;
|
||||
link?: string;
|
||||
avatar?: string;
|
||||
height?: number;
|
||||
width?: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function QrCode(props: QrCodeProps) {
|
||||
const qrRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if ((props.data?.length ?? 0) > 0 && qrRef.current) {
|
||||
const qr = new QRCodeStyling({
|
||||
width: props.width || 256,
|
||||
height: props.height || 256,
|
||||
data: props.data,
|
||||
margin: 5,
|
||||
type: "canvas",
|
||||
image: props.avatar,
|
||||
dotsOptions: {
|
||||
type: "rounded",
|
||||
},
|
||||
cornersSquareOptions: {
|
||||
type: "extra-rounded",
|
||||
},
|
||||
imageOptions: {
|
||||
crossOrigin: "anonymous",
|
||||
},
|
||||
});
|
||||
qrRef.current.innerHTML = "";
|
||||
qr.append(qrRef.current);
|
||||
if (props.link) {
|
||||
qrRef.current.onclick = function () {
|
||||
const elm = document.createElement("a");
|
||||
elm.href = props.link ?? "";
|
||||
elm.click();
|
||||
};
|
||||
}
|
||||
} else if (qrRef.current) {
|
||||
qrRef.current.innerHTML = "";
|
||||
}
|
||||
}, [props.data, props.link, props.width, props.height, props.avatar]);
|
||||
|
||||
return <div className={props.className} ref={qrRef}></div>;
|
||||
}
|
28
src/components/vps-actions.tsx
Normal file
28
src/components/vps-actions.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import { VmInstance } from "../api";
|
||||
import { Icon } from "./icon";
|
||||
|
||||
export default function VmActions({ vm }: { vm: VmInstance }) {
|
||||
const state = vm.status?.state;
|
||||
if (!state) return;
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex gap-2">
|
||||
<Icon
|
||||
name={state === "running" ? "stop" : "start"}
|
||||
className="bg-neutral-700 p-2 rounded-lg hover:bg-neutral-600"
|
||||
size={40}
|
||||
/>
|
||||
<Icon
|
||||
name="delete"
|
||||
className="bg-neutral-700 p-2 rounded-lg hover:bg-neutral-600"
|
||||
size={40}
|
||||
/>
|
||||
<Icon
|
||||
name="refresh-1"
|
||||
className="bg-neutral-700 p-2 rounded-lg hover:bg-neutral-600"
|
||||
size={40}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,28 +1,23 @@
|
||||
import { DiskType, MachineSpec } from "../api";
|
||||
import { VmTemplate } from "../api";
|
||||
import BytesSize from "./bytes";
|
||||
import CostLabel from "./cost";
|
||||
import VpsPayButton from "./pay-button";
|
||||
|
||||
export default function VpsCard({ spec }: { spec: MachineSpec }) {
|
||||
export default function VpsCard({ spec }: { spec: VmTemplate }) {
|
||||
return (
|
||||
<div className="rounded-xl border border-neutral-600 px-3 py-2">
|
||||
<h2>{spec.id}</h2>
|
||||
<h2>{spec.name}</h2>
|
||||
<ul>
|
||||
<li>CPU: {spec.cpu}vCPU</li>
|
||||
<li>
|
||||
RAM: <BytesSize value={spec.ram} />
|
||||
RAM: <BytesSize value={spec.memory} />
|
||||
</li>
|
||||
<li>
|
||||
{spec.disk.type === DiskType.SSD ? "SSD" : "HDD"}:{" "}
|
||||
<BytesSize value={spec.disk.size} />
|
||||
</li>
|
||||
<li>
|
||||
Location: {spec.location}
|
||||
{spec.disk_type.toUpperCase()}: <BytesSize value={spec.disk_size} />
|
||||
</li>
|
||||
<li>Location: {spec.region?.name}</li>
|
||||
</ul>
|
||||
<h2>
|
||||
<CostLabel cost={spec.cost} />
|
||||
</h2>
|
||||
<h2>{spec.cost_plan && <CostLabel cost={spec.cost_plan} />}</h2>
|
||||
<VpsPayButton spec={spec} />
|
||||
</div>
|
||||
);
|
||||
|
37
src/components/vps-instance.tsx
Normal file
37
src/components/vps-instance.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import { Link } from "react-router-dom";
|
||||
import { VmInstance } from "../api";
|
||||
import OsImageName from "./os-image-name";
|
||||
import VpsResources from "./vps-resources";
|
||||
import VmActions from "./vps-actions";
|
||||
|
||||
export default function VpsInstanceRow({ vm }: { vm: VmInstance }) {
|
||||
const expires = new Date(vm.expires);
|
||||
const isExpired = expires <= new Date();
|
||||
|
||||
return (
|
||||
<div className="flex justify-between items-center rounded-xl bg-neutral-900 px-3 py-2 cursor-pointer hover:bg-neutral-800">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div>
|
||||
<span className="text-sm text-neutral-400">#{vm.id}</span>
|
||||
|
||||
{vm.template?.name}
|
||||
|
||||
<span className="text-sm text-neutral-400">
|
||||
<OsImageName image={vm.image!} />
|
||||
</span>
|
||||
</div>
|
||||
<VpsResources vm={vm} />
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
{isExpired && (
|
||||
<>
|
||||
<Link to="/vm/renew" className="text-red-500 text-sm" state={vm}>
|
||||
Expired
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
{!isExpired && <VmActions vm={vm} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
58
src/components/vps-payment.tsx
Normal file
58
src/components/vps-payment.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
import { useEffect } from "react";
|
||||
import { LNVpsApi, VmPayment } from "../api";
|
||||
import QrCode from "./qr";
|
||||
import useLogin from "../hooks/login";
|
||||
import { ApiUrl } from "../const";
|
||||
import { EventPublisher } from "@snort/system";
|
||||
|
||||
export default function VpsPayment({
|
||||
payment,
|
||||
onPaid,
|
||||
}: {
|
||||
payment: VmPayment;
|
||||
onPaid?: () => void;
|
||||
}) {
|
||||
const login = useLogin();
|
||||
const ln = `lightning:${payment.invoice}`;
|
||||
|
||||
async function checkPayment(api: LNVpsApi) {
|
||||
try {
|
||||
const st = await api.paymentStatus(payment.id);
|
||||
if (st.is_paid) {
|
||||
onPaid?.();
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!login?.signer) return;
|
||||
const api = new LNVpsApi(
|
||||
ApiUrl,
|
||||
new EventPublisher(login.signer, login.pubkey),
|
||||
);
|
||||
const tx = setInterval(async () => {
|
||||
if (await checkPayment(api)) {
|
||||
clearInterval(tx);
|
||||
}
|
||||
}, 2_000);
|
||||
return () => clearInterval(tx);
|
||||
}, [login]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 rounded-xl p-3 bg-neutral-900 items-center">
|
||||
<QrCode
|
||||
data={ln}
|
||||
link={ln}
|
||||
width={512}
|
||||
height={512}
|
||||
avatar="/logo.jpg"
|
||||
className="cursor-pointer rounded-xl overflow-hidden"
|
||||
/>
|
||||
{(payment.amount / 1000).toLocaleString()} sats
|
||||
</div>
|
||||
);
|
||||
}
|
31
src/components/vps-resources.tsx
Normal file
31
src/components/vps-resources.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import { VmInstance, VmTemplate } from "../api";
|
||||
import BytesSize from "./bytes";
|
||||
|
||||
export default function VpsResources({ vm }: { vm: VmInstance | VmTemplate }) {
|
||||
const diskType = "template_id" in vm ? vm.template?.disk_type : vm.disk_type;
|
||||
const region =
|
||||
"region_id" in vm ? vm.region?.name : vm.template?.region?.name;
|
||||
const status = "status" in vm ? vm.status : undefined;
|
||||
return (
|
||||
<>
|
||||
<div className="text-xs text-neutral-400">
|
||||
{vm.cpu} vCPU, <BytesSize value={vm.memory} /> RAM,{" "}
|
||||
<BytesSize value={vm.disk_size} /> {diskType?.toUpperCase()},{" "}
|
||||
{region && <>Location: {region}</>}
|
||||
</div>
|
||||
{status && status.state === "running" && (
|
||||
<div className="text-sm text-neutral-200">
|
||||
<div className="w-2 h-2 rounded-full bg-green-800 inline-block"></div>{" "}
|
||||
{(100 * status.cpu_usage).toFixed(1)}% CPU,{" "}
|
||||
{(100 * status.mem_usage).toFixed(0)}% RAM
|
||||
</div>
|
||||
)}
|
||||
{status && status.state === "stopped" && (
|
||||
<div className="text-sm text-neutral-200">
|
||||
<div className="w-2 h-2 rounded-full bg-red-800 inline-block"></div>{" "}
|
||||
Stopped
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
@ -12,6 +12,9 @@ export const GB = KB * 1000;
|
||||
export const TB = GB * 1000;
|
||||
export const PB = TB * 1000;
|
||||
|
||||
//export const ApiUrl = "http://localhost:8000";
|
||||
export const ApiUrl = "https://api.lnvps.net";
|
||||
|
||||
export const NostrProfile = new NostrLink(
|
||||
NostrPrefix.Profile,
|
||||
"fcd818454002a6c47a980393f0549ac6e629d28d5688114bb60d831b5c1832a7",
|
||||
|
@ -3,10 +3,7 @@ import { Login } from "../login";
|
||||
|
||||
export default function useLogin() {
|
||||
return useSyncExternalStore(
|
||||
(c) => {
|
||||
Login?.on("change", c);
|
||||
return () => Login?.off("change", c);
|
||||
},
|
||||
() => Login,
|
||||
(c) => Login.hook(c),
|
||||
() => Login.snapshot(),
|
||||
);
|
||||
}
|
||||
|
20
src/icons.svg
Normal file
20
src/icons.svg
Normal file
@ -0,0 +1,20 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<svg id="start" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M24 0V24H0V0H24Z" fill="white" fill-opacity="0.01"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.5 3C13.5 2.17157 12.8284 1.5 12 1.5C11.1716 1.5 10.5 2.17157 10.5 3V13C10.5 13.8284 11.1716 14.5 12 14.5C12.8284 14.5 13.5 13.8284 13.5 13V3ZM7.85385 5.7491C8.54371 5.29043 8.73113 4.35936 8.27245 3.6695C7.81378 2.97963 6.8827 2.79222 6.19284 3.2509C3.36739 5.12948 1.5 8.34635 1.5 12C1.5 17.799 6.20101 22.5 12 22.5C17.799 22.5 22.5 17.799 22.5 12C22.5 8.34635 20.6326 5.12948 17.8072 3.2509C17.1173 2.79222 16.1862 2.97963 15.7275 3.6695C15.2689 4.35936 15.4563 5.29043 16.1461 5.7491C18.1708 7.09528 19.5 9.39275 19.5 12C19.5 16.1422 16.1421 19.5 12 19.5C7.85786 19.5 4.5 16.1422 4.5 12C4.5 9.39275 5.82917 7.09528 7.85385 5.7491Z" fill="#F7F9FC"/>
|
||||
</svg>
|
||||
<svg id="stop" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M24 0V24H0V0H24Z" fill="white" fill-opacity="0.01"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 6C4 4.89543 4.89543 4 6 4H18C19.1046 4 20 4.89543 20 6V18C20 19.1046 19.1046 20 18 20H6C4.89543 20 4 19.1046 4 18V6Z" fill="white"/>
|
||||
</svg>
|
||||
<svg id="delete" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M24 0V24H0V0H24Z" fill="white" fill-opacity="0.01"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 3C9 2.44772 9.44772 2 10 2H14C14.5523 2 15 2.44772 15 3C15 3.55228 14.5523 4 14 4H10C9.44772 4 9 3.55228 9 3ZM5.92032 5H4C3.44772 5 3 5.44772 3 6C3 6.55228 3.44772 7 4 7H4.99745L5.9362 20.1425C6.01096 21.1891 6.88184 22 7.93112 22H16.0689C17.1182 22 17.989 21.1891 18.0638 20.1425L19.0025 7H20C20.5523 7 21 6.55228 21 6C21 5.44772 20.5523 5 20 5H18.0797C18.0735 4.99994 18.0673 4.99994 18.0611 5H5.93889C5.93271 4.99994 5.92652 4.99994 5.92032 5Z" fill="#F7F9FC"/>
|
||||
</svg>
|
||||
<svg id="refresh-1" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M24 0V24H0V0H24Z" fill="white" fill-opacity="0.01"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.0589 19.2443C12.8824 20.0009 15.764 19.0421 17.5934 16.9994C18.146 16.3822 19.0943 16.33 19.7115 16.8826C20.3286 17.4353 20.3808 18.3836 19.8282 19.0008C17.2737 21.8532 13.2404 23.2026 9.28249 22.1421C3.6811 20.6412 0.35698 14.8837 1.85787 9.2823C3.35876 3.68091 9.1163 0.356795 14.7177 1.85768C18.9224 2.98433 21.8407 6.50832 22.4032 10.5596C22.4653 11.0066 22.4987 11.4603 22.502 11.9179C22.5117 13.2319 21.0529 13.9572 20.01 13.2545L17.3364 11.4531C15.8701 10.4651 16.8533 8.17943 18.579 8.56459L18.6789 8.58688C17.7458 6.76269 16.0738 5.32688 13.9412 4.75546C9.94024 3.6834 5.82771 6.05777 4.75565 10.0588C3.68358 14.0598 6.05795 18.1723 10.0589 19.2443Z" fill="#F7F9FC"/>
|
||||
</svg>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 2.9 KiB |
@ -15,13 +15,23 @@ body {
|
||||
h1 {
|
||||
@apply text-2xl font-medium my-2;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@apply text-xl font-medium my-2;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@apply text-lg font-medium my-2;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
hr {
|
||||
@apply border-neutral-800;
|
||||
}
|
||||
|
32
src/login.ts
32
src/login.ts
@ -1,13 +1,37 @@
|
||||
import { Nip7Signer, SystemInterface, UserState } from "@snort/system";
|
||||
import { ExternalStore } from "@snort/shared";
|
||||
import {
|
||||
EventSigner,
|
||||
Nip7Signer,
|
||||
SystemInterface,
|
||||
UserState,
|
||||
} from "@snort/system";
|
||||
|
||||
export let Login: UserState<void> | undefined;
|
||||
class LoginShell extends ExternalStore<UserState<void> | undefined> {
|
||||
#state?: UserState<void>;
|
||||
|
||||
async login(signer: EventSigner, system: SystemInterface) {
|
||||
if (this.#state !== undefined) {
|
||||
throw new Error("Already logged in");
|
||||
}
|
||||
const pubkey = await signer.getPubKey();
|
||||
this.#state = new UserState<void>(pubkey);
|
||||
await this.#state.init(signer, system);
|
||||
this.#state.on("change", () => this.notifyChange());
|
||||
this.notifyChange();
|
||||
}
|
||||
|
||||
takeSnapshot() {
|
||||
return this.#state;
|
||||
}
|
||||
}
|
||||
|
||||
export const Login = new LoginShell();
|
||||
|
||||
export async function loginNip7(system: SystemInterface) {
|
||||
const signer = new Nip7Signer();
|
||||
const pubkey = await signer.getPubKey();
|
||||
if (pubkey) {
|
||||
Login = new UserState<void>(pubkey);
|
||||
await Login.init(signer, system);
|
||||
await Login.login(signer, system);
|
||||
} else {
|
||||
throw new Error("No nostr extension found");
|
||||
}
|
||||
|
48
src/main.tsx
48
src/main.tsx
@ -1,10 +1,54 @@
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import App from "./App.tsx";
|
||||
import "./index.css";
|
||||
import { RouterProvider, createBrowserRouter } from "react-router-dom";
|
||||
import { NostrSystem } from "@snort/system";
|
||||
import { SnortContext } from "@snort/system-react";
|
||||
import Layout from "./pages/layout.tsx";
|
||||
import HomePage from "./pages/home.tsx";
|
||||
import OrderPage from "./pages/order.tsx";
|
||||
import VmPage from "./pages/vm.tsx";
|
||||
import AccountPage from "./pages/account.tsx";
|
||||
|
||||
const system = new NostrSystem({
|
||||
automaticOutboxModel: false,
|
||||
buildFollowGraph: false,
|
||||
});
|
||||
[
|
||||
"wss://relay.snort.social/",
|
||||
"wss://relay.damus.io/",
|
||||
"wss://relay.nostr.band/",
|
||||
"wss://nos.lol/",
|
||||
].forEach((a) => system.ConnectToRelay(a, { read: true, write: true }));
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
element: <Layout />,
|
||||
children: [
|
||||
{
|
||||
path: "/",
|
||||
element: <HomePage />,
|
||||
},
|
||||
{
|
||||
path: "/account",
|
||||
element: <AccountPage />,
|
||||
},
|
||||
{
|
||||
path: "/order",
|
||||
element: <OrderPage />,
|
||||
},
|
||||
{
|
||||
path: "/vm/:action?",
|
||||
element: <VmPage />,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
<SnortContext.Provider value={system}>
|
||||
<RouterProvider router={router} />
|
||||
</SnortContext.Provider>
|
||||
</StrictMode>,
|
||||
);
|
||||
|
31
src/pages/account.tsx
Normal file
31
src/pages/account.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { LNVpsApi, VmInstance } from "../api";
|
||||
import useLogin from "../hooks/login";
|
||||
import { EventPublisher } from "@snort/system";
|
||||
import { ApiUrl } from "../const";
|
||||
import VpsInstanceRow from "../components/vps-instance";
|
||||
|
||||
export default function AccountPage() {
|
||||
const login = useLogin();
|
||||
const [vms, setVms] = useState<Array<VmInstance>>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!login?.signer) return;
|
||||
const api = new LNVpsApi(
|
||||
ApiUrl,
|
||||
new EventPublisher(login.signer, login.pubkey),
|
||||
);
|
||||
api.listVms().then(setVms);
|
||||
}, [login]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3>My Resources</h3>
|
||||
<div className="flex flex-col gap-2">
|
||||
{vms.map((a) => (
|
||||
<VpsInstanceRow key={a.id} vm={a} />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
43
src/pages/home.tsx
Normal file
43
src/pages/home.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { VmTemplate, LNVpsApi } from "../api";
|
||||
import Profile from "../components/profile";
|
||||
import VpsCard from "../components/vps-card";
|
||||
import { ApiUrl, NostrProfile } from "../const";
|
||||
|
||||
export default function HomePage() {
|
||||
const [offers, setOffers] = useState<Array<VmTemplate>>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const api = new LNVpsApi(ApiUrl, undefined);
|
||||
api.listOffers().then((o) => setOffers(o));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>VPS Offers</h1>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{offers.map((a) => (
|
||||
<VpsCard spec={a} key={a.id} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<small>
|
||||
All VPS come with 1x IPv4 and 1x IPv6 address and unmetered traffic
|
||||
</small>
|
||||
<div className="flex flex-col gap-4">
|
||||
<b>You can also find us on nostr: </b>
|
||||
<a target="_blank" href={`nostr:${NostrProfile.encode()}`}>
|
||||
<Profile link={NostrProfile} />
|
||||
</a>
|
||||
<div>
|
||||
<a target="_blank" href="http://speedtest.v0l.io">
|
||||
Speedtest
|
||||
</a>{" "}
|
||||
| <a href="/public/lnvps.asc">PGP</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
15
src/pages/layout.tsx
Normal file
15
src/pages/layout.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { Link, Outlet } from "react-router-dom";
|
||||
import LoginButton from "../components/login-button";
|
||||
|
||||
export default function Layout() {
|
||||
return (
|
||||
<div className="w-[700px] mx-auto m-2 p-2">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<Link to="/">LNVPS</Link>
|
||||
<LoginButton />
|
||||
</div>
|
||||
|
||||
<Outlet />
|
||||
</div>
|
||||
);
|
||||
}
|
181
src/pages/order.tsx
Normal file
181
src/pages/order.tsx
Normal file
@ -0,0 +1,181 @@
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { LNVpsApi, UserSshKey, VmOsImage, VmTemplate } from "../api";
|
||||
import { useEffect, useState } from "react";
|
||||
import CostLabel from "../components/cost";
|
||||
import { ApiUrl } from "../const";
|
||||
import { EventPublisher } from "@snort/system";
|
||||
import useLogin from "../hooks/login";
|
||||
import { AsyncButton } from "../components/button";
|
||||
import classNames from "classnames";
|
||||
import VpsResources from "../components/vps-resources";
|
||||
import OsImageName from "../components/os-image-name";
|
||||
|
||||
export default function OrderPage() {
|
||||
const { state } = useLocation();
|
||||
const login = useLogin();
|
||||
const navigate = useNavigate();
|
||||
const template = state as VmTemplate | undefined;
|
||||
const [newKey, setNewKey] = useState("");
|
||||
const [newKeyError, setNewKeyError] = useState("");
|
||||
const [newKeyName, setNewKeyName] = useState("");
|
||||
const [useImage, setUseImage] = useState(-1);
|
||||
const [useSshKey, setUseSshKey] = useState(-1);
|
||||
const [showAddKey, setShowAddKey] = useState(false);
|
||||
const [images, setImages] = useState<Array<VmOsImage>>([]);
|
||||
const [sshKeys, setSshKeys] = useState<Array<UserSshKey>>([]);
|
||||
const [orderError, setOrderError] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
if (!login?.signer) return;
|
||||
const api = new LNVpsApi(
|
||||
ApiUrl,
|
||||
new EventPublisher(login.signer, login.pubkey),
|
||||
);
|
||||
api.listOsImages().then((a) => setImages(a));
|
||||
api.listSshKeys().then((a) => {
|
||||
setSshKeys(a);
|
||||
if (a.length > 0) {
|
||||
setUseSshKey(a[0].id);
|
||||
} else {
|
||||
setShowAddKey(true);
|
||||
}
|
||||
});
|
||||
}, [login]);
|
||||
|
||||
async function addNewKey() {
|
||||
if (!login?.signer) return;
|
||||
setNewKeyError("");
|
||||
const api = new LNVpsApi(
|
||||
ApiUrl,
|
||||
new EventPublisher(login.signer, login.pubkey),
|
||||
);
|
||||
|
||||
try {
|
||||
const nk = await api.addSshKey(newKeyName, newKey);
|
||||
setNewKey("");
|
||||
setNewKeyName("");
|
||||
setUseSshKey(nk.id);
|
||||
setShowAddKey(false);
|
||||
api.listSshKeys().then((a) => setSshKeys(a));
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
setNewKeyError(e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function createOrder() {
|
||||
if (!login?.signer || !template) return;
|
||||
const api = new LNVpsApi(
|
||||
ApiUrl,
|
||||
new EventPublisher(login.signer, login.pubkey),
|
||||
);
|
||||
|
||||
setOrderError("");
|
||||
try {
|
||||
const newVm = await api.orderVm(template.id, useImage, useSshKey);
|
||||
navigate("/vm/renew", {
|
||||
state: newVm,
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
setOrderError(e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const sortedImages = images.sort(
|
||||
(a, b) =>
|
||||
new Date(b.release_date).getTime() - new Date(a.release_date).getTime(),
|
||||
);
|
||||
|
||||
if (!template || !login) {
|
||||
return <h3>No order found</h3>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="text-xl">New Order</div>
|
||||
<div className="flex justify-between items-center rounded-xl bg-neutral-900 px-4 py-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div>{template.name}</div>
|
||||
<VpsResources vm={template} />
|
||||
</div>
|
||||
{template.cost_plan && <CostLabel cost={template.cost_plan} />}
|
||||
</div>
|
||||
<hr />
|
||||
<div className="flex flex-col gap-2">
|
||||
<b>Select OS:</b>
|
||||
{sortedImages.map((a) => (
|
||||
<div
|
||||
className={classNames(
|
||||
"flex justify-between items-center rounded-xl px-4 py-3 cursor-pointer",
|
||||
{
|
||||
"bg-neutral-900": useImage !== a.id,
|
||||
"bg-neutral-700": useImage === a.id,
|
||||
},
|
||||
)}
|
||||
onClick={() => setUseImage(a.id)}
|
||||
>
|
||||
<OsImageName image={a} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<hr />
|
||||
<div className="flex flex-col gap-2">
|
||||
{sshKeys.length > 0 && (
|
||||
<>
|
||||
<b>Select SSH Key:</b>
|
||||
<select
|
||||
className="bg-neutral-900 p-2 rounded-xl"
|
||||
value={useSshKey}
|
||||
onChange={(e) => setUseSshKey(Number(e.target.value))}
|
||||
>
|
||||
{sshKeys.map((a) => (
|
||||
<option value={a.id}>{a.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</>
|
||||
)}
|
||||
{!showAddKey && sshKeys.length > 0 && (
|
||||
<AsyncButton onClick={() => setShowAddKey(true)}>
|
||||
Add new SSH key
|
||||
</AsyncButton>
|
||||
)}
|
||||
{(showAddKey || sshKeys.length === 0) && (
|
||||
<>
|
||||
<b>Add SSH Key:</b>
|
||||
<textarea
|
||||
className="border-none rounded-xl bg-neutral-900 p-2"
|
||||
rows={5}
|
||||
placeholder="ssh-[rsa|ed25519] AA== id"
|
||||
value={newKey}
|
||||
onChange={(e) => setNewKey(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
className="border-none rounded-xl bg-neutral-900 p-2"
|
||||
placeholder="Key name"
|
||||
value={newKeyName}
|
||||
onChange={(e) => setNewKeyName(e.target.value)}
|
||||
/>
|
||||
<AsyncButton
|
||||
disabled={newKey.length < 10 || newKeyName.length < 2}
|
||||
onClick={addNewKey}
|
||||
>
|
||||
Add Key
|
||||
</AsyncButton>
|
||||
{newKeyError && <b className="text-red-500">{newKeyError}</b>}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<AsyncButton
|
||||
disabled={useSshKey === -1 || useImage === -1}
|
||||
onClick={createOrder}
|
||||
>
|
||||
Create Order
|
||||
</AsyncButton>
|
||||
{orderError && <b className="text-red-500">{orderError}</b>}
|
||||
</div>
|
||||
);
|
||||
}
|
66
src/pages/vm.tsx
Normal file
66
src/pages/vm.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
||||
import { LNVpsApi, VmInstance, VmPayment } from "../api";
|
||||
import VpsInstanceRow from "../components/vps-instance";
|
||||
import useLogin from "../hooks/login";
|
||||
import { ApiUrl } from "../const";
|
||||
import { EventPublisher } from "@snort/system";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import VpsPayment from "../components/vps-payment";
|
||||
|
||||
export default function VmPage() {
|
||||
const { state } = useLocation() as { state?: VmInstance };
|
||||
const { action } = useParams();
|
||||
const login = useLogin();
|
||||
const navigate = useNavigate();
|
||||
const [payment, setPayment] = useState<VmPayment>();
|
||||
|
||||
const renew = useCallback(
|
||||
async function () {
|
||||
if (!login?.signer || !state) return;
|
||||
const api = new LNVpsApi(
|
||||
ApiUrl,
|
||||
new EventPublisher(login.signer, login.pubkey),
|
||||
);
|
||||
const p = await api.renewVm(state.id);
|
||||
setPayment(p);
|
||||
},
|
||||
[login, state],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
switch (action) {
|
||||
case "renew":
|
||||
renew();
|
||||
}
|
||||
}, [renew, action]);
|
||||
|
||||
if (!state) {
|
||||
return <h2>No VM selected</h2>;
|
||||
}
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<VpsInstanceRow vm={state} />
|
||||
{action === "renew" && (
|
||||
<>
|
||||
<h3>Renew VPS</h3>
|
||||
{payment && (
|
||||
<VpsPayment
|
||||
payment={payment}
|
||||
onPaid={async () => {
|
||||
if (!login?.signer || !state) return;
|
||||
const api = new LNVpsApi(
|
||||
ApiUrl,
|
||||
new EventPublisher(login.signer, login.pubkey),
|
||||
);
|
||||
const newState = await api.getVm(state.id);
|
||||
navigate("/vm", {
|
||||
state: newState,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"isolatedModules": false,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
500
yarn.lock
500
yarn.lock
@ -532,13 +532,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@noble/ciphers@npm:^0.5.1":
|
||||
version: 0.5.3
|
||||
resolution: "@noble/ciphers@npm:0.5.3"
|
||||
checksum: 10c0/2303217304baf51ec6caa2d984f4e640a66d3d586162ed8fecf37a00268fbf362e22cd5bceae4b0ccda4fa06ad0cb294d6a6b158260bbd2eac6a3dc0448f5254
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@noble/ciphers@npm:^0.6.0":
|
||||
version: 0.6.0
|
||||
resolution: "@noble/ciphers@npm:0.6.0"
|
||||
@ -546,15 +539,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@noble/curves@npm:1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "@noble/curves@npm:1.2.0"
|
||||
dependencies:
|
||||
"@noble/hashes": "npm:1.3.2"
|
||||
checksum: 10c0/0bac7d1bbfb3c2286910b02598addd33243cb97c3f36f987ecc927a4be8d7d88e0fcb12b0f0ef8a044e7307d1844dd5c49bb724bfa0a79c8ec50ba60768c97f6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@noble/curves@npm:^1.4.0":
|
||||
version: 1.6.0
|
||||
resolution: "@noble/curves@npm:1.6.0"
|
||||
@ -564,50 +548,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@noble/curves@npm:~1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "@noble/curves@npm:1.1.0"
|
||||
dependencies:
|
||||
"@noble/hashes": "npm:1.3.1"
|
||||
checksum: 10c0/81115c3ebfa7e7da2d7e18d44d686f98dc6d35dbde3964412c05707c92d0994a01545bc265d5c0bc05c8c49333f75b99c9acef6750f5a79b3abcc8e0546acf88
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@noble/hashes@npm:1.3.1":
|
||||
version: 1.3.1
|
||||
resolution: "@noble/hashes@npm:1.3.1"
|
||||
checksum: 10c0/86512713aaf338bced594bc2046ab249fea4e1ba1e7f2ecd02151ef1b8536315e788c11608fafe1b56f04fad1aa3c602da7e5f8e5fcd5f8b0aa94435fe65278e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@noble/hashes@npm:1.3.2":
|
||||
version: 1.3.2
|
||||
resolution: "@noble/hashes@npm:1.3.2"
|
||||
checksum: 10c0/2482cce3bce6a596626f94ca296e21378e7a5d4c09597cbc46e65ffacc3d64c8df73111f2265444e36a3168208628258bbbaccba2ef24f65f58b2417638a20e7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@noble/hashes@npm:1.5.0, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:^1.4.0":
|
||||
"@noble/hashes@npm:1.5.0, @noble/hashes@npm:^1.4.0":
|
||||
version: 1.5.0
|
||||
resolution: "@noble/hashes@npm:1.5.0"
|
||||
checksum: 10c0/1b46539695fbfe4477c0822d90c881a04d4fa2921c08c552375b444a48cac9930cb1ee68de0a3c7859e676554d0f3771999716606dc4d8f826e414c11692cdd9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@noble/hashes@npm:~1.3.0, @noble/hashes@npm:~1.3.1":
|
||||
version: 1.3.3
|
||||
resolution: "@noble/hashes@npm:1.3.3"
|
||||
checksum: 10c0/23c020b33da4172c988e44100e33cd9f8f6250b68b43c467d3551f82070ebd9716e0d9d2347427aa3774c85934a35fa9ee6f026fca2117e3fa12db7bedae7668
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@noble/secp256k1@npm:^2.0.0":
|
||||
version: 2.1.0
|
||||
resolution: "@noble/secp256k1@npm:2.1.0"
|
||||
checksum: 10c0/b4c7edd2a5ec5acf294546cd06d08dc2a2a2b2ebe34a6da1f2f5104f56983f81dd31c261ad365c6b9757d1c54017fc3363331ee33bba8715ff94c2bc954313cc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@nodelib/fs.scandir@npm:2.1.5":
|
||||
version: 2.1.5
|
||||
resolution: "@nodelib/fs.scandir@npm:2.1.5"
|
||||
@ -635,26 +582,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@nostr-dev-kit/ndk@npm:^2.8.2":
|
||||
version: 2.10.0
|
||||
resolution: "@nostr-dev-kit/ndk@npm:2.10.0"
|
||||
dependencies:
|
||||
"@noble/curves": "npm:^1.4.0"
|
||||
"@noble/hashes": "npm:^1.3.1"
|
||||
"@noble/secp256k1": "npm:^2.0.0"
|
||||
"@scure/base": "npm:^1.1.1"
|
||||
debug: "npm:^4.3.4"
|
||||
light-bolt11-decoder: "npm:^3.0.0"
|
||||
node-fetch: "npm:^3.3.1"
|
||||
nostr-tools: "npm:^2.7.1"
|
||||
tseep: "npm:^1.1.1"
|
||||
typescript-lru-cache: "npm:^2.0.0"
|
||||
utf8-buffer: "npm:^1.0.0"
|
||||
websocket-polyfill: "npm:^0.0.3"
|
||||
checksum: 10c0/be71574ab583c8b337fd5089ac4b025e214d4977a3ca2b030c51ecbc9f5f68f2831881dbb4a7c45f8dac70830488a93a2df6cb691ae520fc257acd4640460f33
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@npmcli/agent@npm:^2.0.0":
|
||||
version: 2.2.2
|
||||
resolution: "@npmcli/agent@npm:2.2.2"
|
||||
@ -803,31 +730,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@scure/base@npm:^1.1.1, @scure/base@npm:^1.1.6, @scure/base@npm:~1.1.0":
|
||||
"@scure/base@npm:^1.1.6":
|
||||
version: 1.1.9
|
||||
resolution: "@scure/base@npm:1.1.9"
|
||||
checksum: 10c0/77a06b9a2db8144d22d9bf198338893d77367c51b58c72b99df990c0a11f7cadd066d4102abb15e3ca6798d1529e3765f55c4355742465e49aed7a0c01fe76e8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@scure/bip32@npm:1.3.1":
|
||||
version: 1.3.1
|
||||
resolution: "@scure/bip32@npm:1.3.1"
|
||||
dependencies:
|
||||
"@noble/curves": "npm:~1.1.0"
|
||||
"@noble/hashes": "npm:~1.3.1"
|
||||
"@scure/base": "npm:~1.1.0"
|
||||
checksum: 10c0/9ff0ad56f512794aed1ed62e582bf855db829e688235420a116b210169dc31e3e2a8cc4a908126aaa07b6dcbcc4cd085eb12f9d0a8b507a88946d6171a437195
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@scure/bip39@npm:1.2.1":
|
||||
"@scure/base@npm:^1.2.1":
|
||||
version: 1.2.1
|
||||
resolution: "@scure/bip39@npm:1.2.1"
|
||||
dependencies:
|
||||
"@noble/hashes": "npm:~1.3.0"
|
||||
"@scure/base": "npm:~1.1.0"
|
||||
checksum: 10c0/fe951f69dd5a7cdcefbe865bce1b160d6b59ba19bd01d09f0718e54fce37a7d8be158b32f5455f0e9c426a7fbbede3e019bf0baa99bacc88ef26a76a07e115d4
|
||||
resolution: "@scure/base@npm:1.2.1"
|
||||
checksum: 10c0/e61068854370855b89c50c28fa4092ea6780f1e0db64ea94075ab574ebcc964f719a3120dc708db324991f4b3e652d92ebda03fce2bf6a4900ceeacf9c0ff933
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -845,36 +758,35 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@snort/system-react@npm:^1.5.1":
|
||||
version: 1.5.1
|
||||
resolution: "@snort/system-react@npm:1.5.1"
|
||||
"@snort/system-react@npm:^1.5.7":
|
||||
version: 1.5.7
|
||||
resolution: "@snort/system-react@npm:1.5.7"
|
||||
dependencies:
|
||||
"@snort/shared": "npm:^1.0.17"
|
||||
"@snort/system": "npm:^1.5.1"
|
||||
"@snort/system": "npm:^1.5.7"
|
||||
react: "npm:^18.2.0"
|
||||
checksum: 10c0/62d55d928f5ef22081e93a96fcff4f30b599a84b67396c93687f51ffeae8cb7ca578b00d033c0e7f731182669f8ae47c86b4497500ace9cdc197d19d4caecb62
|
||||
checksum: 10c0/b8261d72bef88fc6baa91f3f3765a7e65e7775e0f87142079ec425fdc3639871da068b2bfc9c4a2c0f15a20c7c76ffe84d9bc8cbc1e6ef5a76989cd78d3f7049
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@snort/system@npm:^1.5.1":
|
||||
version: 1.5.1
|
||||
resolution: "@snort/system@npm:1.5.1"
|
||||
"@snort/system@npm:^1.5.7":
|
||||
version: 1.5.7
|
||||
resolution: "@snort/system@npm:1.5.7"
|
||||
dependencies:
|
||||
"@noble/ciphers": "npm:^0.6.0"
|
||||
"@noble/curves": "npm:^1.4.0"
|
||||
"@noble/hashes": "npm:^1.4.0"
|
||||
"@nostr-dev-kit/ndk": "npm:^2.8.2"
|
||||
"@scure/base": "npm:^1.1.6"
|
||||
"@snort/shared": "npm:^1.0.17"
|
||||
"@stablelib/xchacha20": "npm:^1.0.1"
|
||||
debug: "npm:^4.3.4"
|
||||
eventemitter3: "npm:^5.0.1"
|
||||
isomorphic-ws: "npm:^5.0.0"
|
||||
lokijs: "npm:^1.5.12"
|
||||
lru-cache: "npm:^10.2.0"
|
||||
nostr-social-graph: "npm:^1.0.3"
|
||||
uuid: "npm:^9.0.0"
|
||||
ws: "npm:^8.14.0"
|
||||
checksum: 10c0/e682b0b739b2d2d5177e37071d7ef2c71dcff42677dba149ac1271596504de2ddbda6c7e8fc3eee3cf2fc175c00cd97fb0cbdf40a96311b59a9e8029e15f03b9
|
||||
checksum: 10c0/9b1d6e36dfc3c0845754d4f2c10eb39665a2c4c4c61a07635e0b792a352f8566dbd79561561568c182272cd92b0d2c421ef137775b16872b7e28fa39366e2094
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -963,6 +875,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/cookie@npm:^0.6.0":
|
||||
version: 0.6.0
|
||||
resolution: "@types/cookie@npm:0.6.0"
|
||||
checksum: 10c0/5b326bd0188120fb32c0be086b141b1481fec9941b76ad537f9110e10d61ee2636beac145463319c71e4be67a17e85b81ca9e13ceb6e3bb63b93d16824d6c149
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/estree@npm:1.0.5":
|
||||
version: 1.0.5
|
||||
resolution: "@types/estree@npm:1.0.5"
|
||||
@ -1334,16 +1253,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"bufferutil@npm:^4.0.1":
|
||||
version: 4.0.8
|
||||
resolution: "bufferutil@npm:4.0.8"
|
||||
dependencies:
|
||||
node-gyp: "npm:latest"
|
||||
node-gyp-build: "npm:^4.3.0"
|
||||
checksum: 10c0/36cdc5b53a38d9f61f89fdbe62029a2ebcd020599862253fefebe31566155726df9ff961f41b8c97b02b4c12b391ef97faf94e2383392654cf8f0ed68f76e47c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cacache@npm:^18.0.0":
|
||||
version: 18.0.4
|
||||
resolution: "cacache@npm:18.0.4"
|
||||
@ -1432,6 +1341,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"classnames@npm:^2.5.1":
|
||||
version: 2.5.1
|
||||
resolution: "classnames@npm:2.5.1"
|
||||
checksum: 10c0/afff4f77e62cea2d79c39962980bf316bacb0d7c49e13a21adaadb9221e1c6b9d3cdb829d8bb1b23c406f4e740507f37e1dcf506f7e3b7113d17c5bab787aa69
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"clean-stack@npm:^2.0.0":
|
||||
version: 2.2.0
|
||||
resolution: "clean-stack@npm:2.2.0"
|
||||
@ -1492,6 +1408,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cookie@npm:^1.0.1":
|
||||
version: 1.0.2
|
||||
resolution: "cookie@npm:1.0.2"
|
||||
checksum: 10c0/fd25fe79e8fbcfcaf6aa61cd081c55d144eeeba755206c058682257cb38c4bd6795c6620de3f064c740695bb65b7949ebb1db7a95e4636efb8357a335ad3f54b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2":
|
||||
version: 7.0.3
|
||||
resolution: "cross-spawn@npm:7.0.3"
|
||||
@ -1519,23 +1442,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d@npm:1, d@npm:^1.0.1, d@npm:^1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "d@npm:1.0.2"
|
||||
dependencies:
|
||||
es5-ext: "npm:^0.10.64"
|
||||
type: "npm:^2.7.2"
|
||||
checksum: 10c0/3e6ede10cd3b77586c47da48423b62bed161bf1a48bdbcc94d87263522e22f5dfb0e678a6dba5323fdc14c5d8612b7f7eb9e7d9e37b2e2d67a7bf9f116dabe5a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"data-uri-to-buffer@npm:^4.0.0":
|
||||
version: 4.0.1
|
||||
resolution: "data-uri-to-buffer@npm:4.0.1"
|
||||
checksum: 10c0/20a6b93107597530d71d4cb285acee17f66bcdfc03fd81040921a81252f19db27588d87fc8fc69e1950c55cfb0bf8ae40d0e5e21d907230813eb5d5a7f9eb45b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4":
|
||||
version: 4.3.6
|
||||
resolution: "debug@npm:4.3.6"
|
||||
@ -1548,15 +1454,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"debug@npm:^2.2.0":
|
||||
version: 2.6.9
|
||||
resolution: "debug@npm:2.6.9"
|
||||
dependencies:
|
||||
ms: "npm:2.0.0"
|
||||
checksum: 10c0/121908fb839f7801180b69a7e218a40b5a0b718813b886b7d6bdb82001b931c938e2941d1e4450f33a1b1df1da653f5f7a0440c197f29fbf8a6e9d45ff6ef589
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"deep-is@npm:^0.1.3":
|
||||
version: 0.1.4
|
||||
resolution: "deep-is@npm:0.1.4"
|
||||
@ -1638,39 +1535,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"es5-ext@npm:^0.10.35, es5-ext@npm:^0.10.62, es5-ext@npm:^0.10.63, es5-ext@npm:^0.10.64, es5-ext@npm:~0.10.14":
|
||||
version: 0.10.64
|
||||
resolution: "es5-ext@npm:0.10.64"
|
||||
dependencies:
|
||||
es6-iterator: "npm:^2.0.3"
|
||||
es6-symbol: "npm:^3.1.3"
|
||||
esniff: "npm:^2.0.1"
|
||||
next-tick: "npm:^1.1.0"
|
||||
checksum: 10c0/4459b6ae216f3c615db086e02437bdfde851515a101577fd61b19f9b3c1ad924bab4d197981eb7f0ccb915f643f2fc10ff76b97a680e96cbb572d15a27acd9a3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"es6-iterator@npm:^2.0.3":
|
||||
version: 2.0.3
|
||||
resolution: "es6-iterator@npm:2.0.3"
|
||||
dependencies:
|
||||
d: "npm:1"
|
||||
es5-ext: "npm:^0.10.35"
|
||||
es6-symbol: "npm:^3.1.1"
|
||||
checksum: 10c0/91f20b799dba28fb05bf623c31857fc1524a0f1c444903beccaf8929ad196c8c9ded233e5ac7214fc63a92b3f25b64b7f2737fcca8b1f92d2d96cf3ac902f5d8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"es6-symbol@npm:^3.1.1, es6-symbol@npm:^3.1.3":
|
||||
version: 3.1.4
|
||||
resolution: "es6-symbol@npm:3.1.4"
|
||||
dependencies:
|
||||
d: "npm:^1.0.2"
|
||||
ext: "npm:^1.7.0"
|
||||
checksum: 10c0/777bf3388db5d7919e09a0fd175aa5b8a62385b17cb2227b7a137680cba62b4d9f6193319a102642aa23d5840d38a62e4784f19cfa5be4a2210a3f0e9b23d15d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"esbuild@npm:^0.21.3":
|
||||
version: 0.21.5
|
||||
resolution: "esbuild@npm:0.21.5"
|
||||
@ -1858,18 +1722,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"esniff@npm:^2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "esniff@npm:2.0.1"
|
||||
dependencies:
|
||||
d: "npm:^1.0.1"
|
||||
es5-ext: "npm:^0.10.62"
|
||||
event-emitter: "npm:^0.3.5"
|
||||
type: "npm:^2.7.2"
|
||||
checksum: 10c0/7efd8d44ac20e5db8cb0ca77eb65eca60628b2d0f3a1030bcb05e71cc40e6e2935c47b87dba3c733db12925aa5b897f8e0e7a567a2c274206f184da676ea2e65
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"espree@npm:^10.0.1, espree@npm:^10.1.0":
|
||||
version: 10.1.0
|
||||
resolution: "espree@npm:10.1.0"
|
||||
@ -1913,16 +1765,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"event-emitter@npm:^0.3.5":
|
||||
version: 0.3.5
|
||||
resolution: "event-emitter@npm:0.3.5"
|
||||
dependencies:
|
||||
d: "npm:1"
|
||||
es5-ext: "npm:~0.10.14"
|
||||
checksum: 10c0/75082fa8ffb3929766d0f0a063bfd6046bd2a80bea2666ebaa0cfd6f4a9116be6647c15667bea77222afc12f5b4071b68d393cf39fdaa0e8e81eda006160aff0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eventemitter3@npm:^5.0.1":
|
||||
version: 5.0.1
|
||||
resolution: "eventemitter3@npm:5.0.1"
|
||||
@ -1937,15 +1779,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ext@npm:^1.7.0":
|
||||
version: 1.7.0
|
||||
resolution: "ext@npm:1.7.0"
|
||||
dependencies:
|
||||
type: "npm:^2.7.2"
|
||||
checksum: 10c0/a8e5f34e12214e9eee3a4af3b5c9d05ba048f28996450975b369fc86e5d0ef13b6df0615f892f5396a9c65d616213c25ec5b0ad17ef42eac4a500512a19da6c7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3":
|
||||
version: 3.1.3
|
||||
resolution: "fast-deep-equal@npm:3.1.3"
|
||||
@ -1989,16 +1822,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fetch-blob@npm:^3.1.2, fetch-blob@npm:^3.1.4":
|
||||
version: 3.2.0
|
||||
resolution: "fetch-blob@npm:3.2.0"
|
||||
dependencies:
|
||||
node-domexception: "npm:^1.0.0"
|
||||
web-streams-polyfill: "npm:^3.0.3"
|
||||
checksum: 10c0/60054bf47bfa10fb0ba6cb7742acec2f37c1f56344f79a70bb8b1c48d77675927c720ff3191fa546410a0442c998d27ab05e9144c32d530d8a52fbe68f843b69
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"file-entry-cache@npm:^8.0.0":
|
||||
version: 8.0.0
|
||||
resolution: "file-entry-cache@npm:8.0.0"
|
||||
@ -2054,15 +1877,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"formdata-polyfill@npm:^4.0.10":
|
||||
version: 4.0.10
|
||||
resolution: "formdata-polyfill@npm:4.0.10"
|
||||
dependencies:
|
||||
fetch-blob: "npm:^3.1.2"
|
||||
checksum: 10c0/5392ec484f9ce0d5e0d52fb5a78e7486637d516179b0eb84d81389d7eccf9ca2f663079da56f761355c0a65792810e3b345dc24db9a8bbbcf24ef3c8c88570c6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fraction.js@npm:^4.3.7":
|
||||
version: 4.3.7
|
||||
resolution: "fraction.js@npm:4.3.7"
|
||||
@ -2366,13 +2180,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-typedarray@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "is-typedarray@npm:1.0.0"
|
||||
checksum: 10c0/4c096275ba041a17a13cca33ac21c16bc4fd2d7d7eb94525e7cd2c2f2c1a3ab956e37622290642501ff4310601e413b675cf399ad6db49855527d2163b3eeeec
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"isexe@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "isexe@npm:2.0.0"
|
||||
@ -2536,21 +2343,25 @@ __metadata:
|
||||
resolution: "lnvps_web@workspace:."
|
||||
dependencies:
|
||||
"@eslint/js": "npm:^9.8.0"
|
||||
"@scure/base": "npm:^1.2.1"
|
||||
"@snort/shared": "npm:^1.0.17"
|
||||
"@snort/system": "npm:^1.5.1"
|
||||
"@snort/system-react": "npm:^1.5.1"
|
||||
"@snort/system": "npm:^1.5.7"
|
||||
"@snort/system-react": "npm:^1.5.7"
|
||||
"@types/react": "npm:^18.3.3"
|
||||
"@types/react-dom": "npm:^18.3.0"
|
||||
"@vitejs/plugin-react": "npm:^4.3.1"
|
||||
autoprefixer: "npm:^10.4.20"
|
||||
classnames: "npm:^2.5.1"
|
||||
eslint: "npm:^9.8.0"
|
||||
eslint-plugin-react-hooks: "npm:^5.1.0-rc.0"
|
||||
eslint-plugin-react-refresh: "npm:^0.4.9"
|
||||
globals: "npm:^15.9.0"
|
||||
postcss: "npm:^8.4.41"
|
||||
prettier: "npm:^3.3.3"
|
||||
qr-code-styling: "npm:^1.8.4"
|
||||
react: "npm:^18.3.1"
|
||||
react-dom: "npm:^18.3.1"
|
||||
react-router-dom: "npm:^7.0.1"
|
||||
tailwindcss: "npm:^3.4.8"
|
||||
typescript: "npm:^5.5.3"
|
||||
typescript-eslint: "npm:^8.0.0"
|
||||
@ -2574,13 +2385,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lokijs@npm:^1.5.12":
|
||||
version: 1.5.12
|
||||
resolution: "lokijs@npm:1.5.12"
|
||||
checksum: 10c0/275ca25174d5174f2126559aad7eedccd8a9759906f650c1bda2f11edd7ed5139fdda8f09f312443261335fdf266883972edb910a948190961689cac7dbbff2a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"loose-envify@npm:^1.1.0":
|
||||
version: 1.4.0
|
||||
resolution: "loose-envify@npm:1.4.0"
|
||||
@ -2756,13 +2560,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ms@npm:2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "ms@npm:2.0.0"
|
||||
checksum: 10c0/f8fda810b39fd7255bbdc451c46286e549794fcc700dc9cd1d25658bbc4dc2563a5de6fe7c60f798a16a60c6ceb53f033cb353f493f0cf63e5199b702943159d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ms@npm:2.1.2":
|
||||
version: 2.1.2
|
||||
resolution: "ms@npm:2.1.2"
|
||||
@ -2804,42 +2601,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"next-tick@npm:^1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "next-tick@npm:1.1.0"
|
||||
checksum: 10c0/3ba80dd805fcb336b4f52e010992f3e6175869c8d88bf4ff0a81d5d66e6049f89993463b28211613e58a6b7fe93ff5ccbba0da18d4fa574b96289e8f0b577f28
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-domexception@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "node-domexception@npm:1.0.0"
|
||||
checksum: 10c0/5e5d63cda29856402df9472335af4bb13875e1927ad3be861dc5ebde38917aecbf9ae337923777af52a48c426b70148815e890a5d72760f1b4d758cc671b1a2b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-fetch@npm:^3.3.1":
|
||||
version: 3.3.2
|
||||
resolution: "node-fetch@npm:3.3.2"
|
||||
dependencies:
|
||||
data-uri-to-buffer: "npm:^4.0.0"
|
||||
fetch-blob: "npm:^3.1.4"
|
||||
formdata-polyfill: "npm:^4.0.10"
|
||||
checksum: 10c0/f3d5e56190562221398c9f5750198b34cf6113aa304e34ee97c94fd300ec578b25b2c2906edba922050fce983338fde0d5d34fcb0fc3336ade5bd0e429ad7538
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-gyp-build@npm:^4.3.0":
|
||||
version: 4.8.2
|
||||
resolution: "node-gyp-build@npm:4.8.2"
|
||||
bin:
|
||||
node-gyp-build: bin.js
|
||||
node-gyp-build-optional: optional.js
|
||||
node-gyp-build-test: build-test.js
|
||||
checksum: 10c0/d816b43974d31d6257b6e87d843f2626c72389a285208394bc57a7766b210454d2642860a5e5b5c333d8ecabaeabad3b31b94f58cf8ca1aabdef0c320d02baaa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-gyp@npm:latest":
|
||||
version: 10.2.0
|
||||
resolution: "node-gyp@npm:10.2.0"
|
||||
@ -2892,33 +2653,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"nostr-tools@npm:^2.7.1":
|
||||
version: 2.7.2
|
||||
resolution: "nostr-tools@npm:2.7.2"
|
||||
dependencies:
|
||||
"@noble/ciphers": "npm:^0.5.1"
|
||||
"@noble/curves": "npm:1.2.0"
|
||||
"@noble/hashes": "npm:1.3.1"
|
||||
"@scure/base": "npm:1.1.1"
|
||||
"@scure/bip32": "npm:1.3.1"
|
||||
"@scure/bip39": "npm:1.2.1"
|
||||
nostr-wasm: "npm:v0.1.0"
|
||||
peerDependencies:
|
||||
typescript: ">=5.0.0"
|
||||
dependenciesMeta:
|
||||
nostr-wasm:
|
||||
optional: true
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
checksum: 10c0/66872704b3748c85e195a7f58425b204088b4432d5ff2d6c196988332e5cea6af543dd9b4f26523c42676960c9cc227ee207d056767f8cd70a479774094778af
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"nostr-wasm@npm:v0.1.0":
|
||||
version: 0.1.0
|
||||
resolution: "nostr-wasm@npm:0.1.0"
|
||||
checksum: 10c0/a8a674c0e038d5f790840e442a80587f6eca0810e01f3101828c34517f5c3238f510ef49f53b3f596e8effb32eb64993c57248aa25b9ccfa9386e4421c837edb
|
||||
"nostr-social-graph@npm:^1.0.3":
|
||||
version: 1.0.5
|
||||
resolution: "nostr-social-graph@npm:1.0.5"
|
||||
checksum: 10c0/529865166127f1eba686453563458f948f5365200a1976f3b8eaa9203f6aa091c54d5fbe678267bf9e2a4a57cf364d7fc37861c373f6093cc1e38e3733e690f5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -3180,6 +2918,22 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"qr-code-styling@npm:^1.8.4":
|
||||
version: 1.8.4
|
||||
resolution: "qr-code-styling@npm:1.8.4"
|
||||
dependencies:
|
||||
qrcode-generator: "npm:^1.4.4"
|
||||
checksum: 10c0/1656833f0ebc7000b4376e1bbe9e1133699b391589f7917eb3b28eeeb32ed45c143f25c4a8a9d2feeb8b0724f1717b41019e012972ac1df022d559f55c6a2947
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"qrcode-generator@npm:^1.4.4":
|
||||
version: 1.4.4
|
||||
resolution: "qrcode-generator@npm:1.4.4"
|
||||
checksum: 10c0/3249fcff98cb9fa17c21329d3dfd895e294a2d6ea48161f7b377010779d41f0cd88668b7fb3478a659725061bb0a770b40a227c2f4853e8c4a6b947a9e8bf17a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"queue-microtask@npm:^1.2.2":
|
||||
version: 1.2.3
|
||||
resolution: "queue-microtask@npm:1.2.3"
|
||||
@ -3206,6 +2960,36 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-router-dom@npm:^7.0.1":
|
||||
version: 7.0.1
|
||||
resolution: "react-router-dom@npm:7.0.1"
|
||||
dependencies:
|
||||
react-router: "npm:7.0.1"
|
||||
peerDependencies:
|
||||
react: ">=18"
|
||||
react-dom: ">=18"
|
||||
checksum: 10c0/aebc0af4ea4ac4e9482b0501c06f3af42adc68b4064c556b00954dcb7a6ec233d41f3eac86e388b9a8068e8ba5d44b854b7e29c806af9ed6a8798682d6984b46
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-router@npm:7.0.1":
|
||||
version: 7.0.1
|
||||
resolution: "react-router@npm:7.0.1"
|
||||
dependencies:
|
||||
"@types/cookie": "npm:^0.6.0"
|
||||
cookie: "npm:^1.0.1"
|
||||
set-cookie-parser: "npm:^2.6.0"
|
||||
turbo-stream: "npm:2.4.0"
|
||||
peerDependencies:
|
||||
react: ">=18"
|
||||
react-dom: ">=18"
|
||||
peerDependenciesMeta:
|
||||
react-dom:
|
||||
optional: true
|
||||
checksum: 10c0/aac4c9989ae6b9cf989b5ddcda88f505ba0704a4e4b37ae04c819c2bd02f080361f9eb1793695e3ecf41080d91b79aee454c3163b586d1b19ceca13f6eacec0e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react@npm:^18.2.0, react@npm:^18.3.1":
|
||||
version: 18.3.1
|
||||
resolution: "react@npm:18.3.1"
|
||||
@ -3386,6 +3170,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"set-cookie-parser@npm:^2.6.0":
|
||||
version: 2.7.1
|
||||
resolution: "set-cookie-parser@npm:2.7.1"
|
||||
checksum: 10c0/060c198c4c92547ac15988256f445eae523f57f2ceefeccf52d30d75dedf6bff22b9c26f756bd44e8e560d44ff4ab2130b178bd2e52ef5571bf7be3bd7632d9a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"shebang-command@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "shebang-command@npm:2.0.0"
|
||||
@ -3661,17 +3452,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tseep@npm:^1.1.1":
|
||||
version: 1.2.2
|
||||
resolution: "tseep@npm:1.2.2"
|
||||
checksum: 10c0/f85c45ed7fbf6bbd6551819b7446db1fe45641896cfea2b7efc776c6388db8ede953ccd21eaa814b3edcce53ecc56a279c7c53e84ba9308dd4541e8c824dcef1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tstl@npm:^2.0.7":
|
||||
version: 2.5.16
|
||||
resolution: "tstl@npm:2.5.16"
|
||||
checksum: 10c0/3815f34a2bc062e35defb4d23768ea99a099847dd36d392c3ccef9d90f865331d9ea03677fc50a3e110da3a93af8abd5bcc43e4b409be012bad5a4773b6e11c1
|
||||
"turbo-stream@npm:2.4.0":
|
||||
version: 2.4.0
|
||||
resolution: "turbo-stream@npm:2.4.0"
|
||||
checksum: 10c0/e68b2569f1f16e6e9633d090c6024b2ae9f0e97bfeacb572451ca3732e120ebbb546f3bc4afc717c46cb57b5aea6104e04ef497f9912eef6a7641e809518e98a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -3684,22 +3468,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"type@npm:^2.7.2":
|
||||
version: 2.7.3
|
||||
resolution: "type@npm:2.7.3"
|
||||
checksum: 10c0/dec6902c2c42fcb86e3adf8cdabdf80e5ef9de280872b5fd547351e9cca2fe58dd2aa6d2547626ddff174145db272f62d95c7aa7038e27c11315657d781a688d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typedarray-to-buffer@npm:^3.1.5":
|
||||
version: 3.1.5
|
||||
resolution: "typedarray-to-buffer@npm:3.1.5"
|
||||
dependencies:
|
||||
is-typedarray: "npm:^1.0.0"
|
||||
checksum: 10c0/4ac5b7a93d604edabf3ac58d3a2f7e07487e9f6e98195a080e81dbffdc4127817f470f219d794a843b87052cedef102b53ac9b539855380b8c2172054b7d5027
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript-eslint@npm:^8.0.0":
|
||||
version: 8.0.1
|
||||
resolution: "typescript-eslint@npm:8.0.1"
|
||||
@ -3714,13 +3482,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript-lru-cache@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "typescript-lru-cache@npm:2.0.0"
|
||||
checksum: 10c0/69864dd8a3538f18002c50a644ef7a7f2d5e320a12fa6266b8c715d6530fec38e475349cd35f75b5196a39d1a28f8b12ebf16afce699b743bdf385dce7df1e0e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript@npm:^5.5.3":
|
||||
version: 5.5.4
|
||||
resolution: "typescript@npm:5.5.4"
|
||||
@ -3782,23 +3543,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"utf-8-validate@npm:^5.0.2":
|
||||
version: 5.0.10
|
||||
resolution: "utf-8-validate@npm:5.0.10"
|
||||
dependencies:
|
||||
node-gyp: "npm:latest"
|
||||
node-gyp-build: "npm:^4.3.0"
|
||||
checksum: 10c0/23cd6adc29e6901aa37ff97ce4b81be9238d0023c5e217515b34792f3c3edb01470c3bd6b264096dd73d0b01a1690b57468de3a24167dd83004ff71c51cc025f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"utf8-buffer@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "utf8-buffer@npm:1.0.0"
|
||||
checksum: 10c0/3cf47ba9ba9ca756734bbe3b139adf04c0e1419e36ecdeebfc930ae702c0b15042225cd483b9d933600353822201e0db3640e1206829823062a5accee0821027
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"util-deprecate@npm:^1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "util-deprecate@npm:1.0.2"
|
||||
@ -3858,37 +3602,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"web-streams-polyfill@npm:^3.0.3":
|
||||
version: 3.3.3
|
||||
resolution: "web-streams-polyfill@npm:3.3.3"
|
||||
checksum: 10c0/64e855c47f6c8330b5436147db1c75cb7e7474d924166800e8e2aab5eb6c76aac4981a84261dd2982b3e754490900b99791c80ae1407a9fa0dcff74f82ea3a7f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"websocket-polyfill@npm:^0.0.3":
|
||||
version: 0.0.3
|
||||
resolution: "websocket-polyfill@npm:0.0.3"
|
||||
dependencies:
|
||||
tstl: "npm:^2.0.7"
|
||||
websocket: "npm:^1.0.28"
|
||||
checksum: 10c0/b8a16af58489f0dd8e896ad986547979544d90c1b21a1777abf2432b899df9fca713b43a0ac50afc995ef7ba28bde01ec1a5391d746a9d14209e54c31054f428
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"websocket@npm:^1.0.28":
|
||||
version: 1.0.35
|
||||
resolution: "websocket@npm:1.0.35"
|
||||
dependencies:
|
||||
bufferutil: "npm:^4.0.1"
|
||||
debug: "npm:^2.2.0"
|
||||
es5-ext: "npm:^0.10.63"
|
||||
typedarray-to-buffer: "npm:^3.1.5"
|
||||
utf-8-validate: "npm:^5.0.2"
|
||||
yaeti: "npm:^0.0.6"
|
||||
checksum: 10c0/8be9a68dc0228f18058c9010d1308479f05050af8f6d68b9dbc6baebd9ab484c15a24b2521a5d742a9d78e62ee19194c532992f1047a9b9adf8c3eedb0b1fcdc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"which@npm:^2.0.1":
|
||||
version: 2.0.2
|
||||
resolution: "which@npm:2.0.2"
|
||||
@ -3955,13 +3668,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yaeti@npm:^0.0.6":
|
||||
version: 0.0.6
|
||||
resolution: "yaeti@npm:0.0.6"
|
||||
checksum: 10c0/4e88702d8b34d7b61c1c4ec674422b835d453b8f8a6232be41e59fc98bc4d9ab6d5abd2da55bab75dfc07ae897fdc0c541f856ce3ab3b17de1630db6161aa3f6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yallist@npm:^3.0.2":
|
||||
version: 3.1.1
|
||||
resolution: "yallist@npm:3.1.1"
|
||||
|
Loading…
x
Reference in New Issue
Block a user