diff --git a/Cargo.lock b/Cargo.lock index 2bb17b2..25648a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -320,6 +320,45 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "asn1-rs" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom 7.1.3", + "num-traits", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "async-channel" version = "1.9.0" @@ -1257,6 +1296,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom 7.1.3", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "deranged" version = "0.5.8" @@ -1812,16 +1865,23 @@ dependencies = [ "grass-node-protocol", "grass-validator", "hex", + "hyper-util", "mime_guess", + "rcgen", "reqwest 0.12.28", + "rustls", + "rustls-pemfile", "serde", "serde_json", "sha2", "subtle", "sysinfo", "tar", + "tempfile", "thiserror", + "time", "tokio", + "tokio-rustls", "tokio-tungstenite 0.26.2", "tokio-util", "toml 0.8.23", @@ -1830,6 +1890,7 @@ dependencies = [ "url", "uuid", "walkdir", + "x509-parser", ] [[package]] @@ -2512,7 +2573,7 @@ dependencies = [ "httpdate", "idna", "mime", - "nom", + "nom 8.0.0", "percent-encoding", "quoted_printable", "rustls", @@ -2737,6 +2798,12 @@ dependencies = [ "unicase", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2813,6 +2880,16 @@ dependencies = [ "libc", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nom" version = "8.0.0" @@ -2972,6 +3049,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -3500,6 +3586,20 @@ dependencies = [ "rand_core 0.10.1", ] +[[package]] +name = "rcgen" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "x509-parser", + "yasna", +] + [[package]] name = "redis" version = "0.31.0" @@ -3807,6 +3907,15 @@ dependencies = [ "semver", ] +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom 7.1.3", +] + [[package]] name = "rustix" version = "1.1.4" @@ -3848,6 +3957,15 @@ dependencies = [ "security-framework", ] +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "rustls-pki-types" version = "1.14.1" @@ -4769,6 +4887,19 @@ dependencies = [ "xattr", ] +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "thiserror" version = "2.0.18" @@ -6037,6 +6168,24 @@ dependencies = [ "tap", ] +[[package]] +name = "x509-parser" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom 7.1.3", + "oid-registry", + "ring", + "rusticata-macros", + "thiserror", + "time", +] + [[package]] name = "xattr" version = "1.6.1" @@ -6053,6 +6202,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + [[package]] name = "yoke" version = "0.8.2" diff --git a/Cargo.toml b/Cargo.toml index 4e54e23..91ff43c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ bytes = "1" clap = { version = "4.5.50", features = ["derive", "env"] } config = { version = "0.15.19", default-features = false, features = ["toml"] } hex = "0.4" +hyper-util = { version = "0.1", features = ["server-auto", "service", "tokio"] } image = { version = "0.25", default-features = false, features = ["png"] } lettre = { version = "0.11", default-features = false, features = ["builder", "hostname", "pool", "sendmail-transport", "smtp-transport", "tokio1", "tokio1-rustls-tls"] } jsonwebtoken = "9" @@ -42,6 +43,9 @@ rand = "0.8" redis = { version = "0.31.0", default-features = false, features = ["tokio-comp", "aio", "script"] } reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls", "stream"] } ring = "0.17" +rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] } +rustls-pemfile = "2" +tokio-rustls = { version = "0.26", default-features = false, features = ["ring", "tls12"] } tokio-tungstenite = { version = "0.26", default-features = false, features = ["connect", "rustls-tls-native-roots"] } rust-embed = "8" sea-orm = { version = "2.0.0-rc.38", default-features = false, features = ["macros", "sqlx-postgres", "runtime-tokio-rustls", "with-json", "with-time", "with-uuid"] } @@ -62,6 +66,7 @@ tracing-subscriber = { version = "0.3.20", features = ["env-filter", "fmt", "jso url = "2.5.4" uuid = { version = "1.18.1", features = ["serde", "v7"] } walkdir = "2" +x509-parser = "0.18" webp = "0.3" zip = { version = "2", default-features = false, features = ["deflate"] } bollard = "0.19" diff --git a/apps/console/src/features/admin/admin-route.test.tsx b/apps/console/src/features/admin/admin-route.test.tsx index 8603cc6..bc378d1 100644 --- a/apps/console/src/features/admin/admin-route.test.tsx +++ b/apps/console/src/features/admin/admin-route.test.tsx @@ -77,6 +77,7 @@ it("lists nodes with health state for administrators", async () => { { id: "node-1", name: "build-node-1", + region: "default", status: "active", healthy: true, build_enabled: true, diff --git a/apps/console/src/features/admin/admin.api.ts b/apps/console/src/features/admin/admin.api.ts index 0994b35..2f3ee44 100644 --- a/apps/console/src/features/admin/admin.api.ts +++ b/apps/console/src/features/admin/admin.api.ts @@ -18,6 +18,7 @@ export type NodeStatus = "pending" | "active" | "draining" | "offline" | "disabl export interface AdminNode { id: string; name: string; + region: string; status: NodeStatus; healthy: boolean; build_enabled: boolean; @@ -85,6 +86,7 @@ export interface AdminNodeConfigurationSync { export interface NodeConfiguration { node: { id: string; + region: string; control_api: string; work_root: string; capabilities: { build: boolean; serve: boolean }; @@ -716,7 +718,7 @@ export const adminApi = { listNodes: () => request<{ nodes: AdminNode[]; local_process: AdminLocalProcessInfo }>("/api/v1/admin/nodes"), - createNode: (input: { name: string; start_local?: boolean }) => + createNode: (input: { name: string; region?: string; start_local?: boolean }) => request<{ node: AdminNode; token: string; diff --git a/apps/console/src/features/admin/components/nodes-panel.test.tsx b/apps/console/src/features/admin/components/nodes-panel.test.tsx index 07648b9..4b60d17 100644 --- a/apps/console/src/features/admin/components/nodes-panel.test.tsx +++ b/apps/console/src/features/admin/components/nodes-panel.test.tsx @@ -15,6 +15,7 @@ function jsonResponse(data: unknown): Response { const configuration = { node: { id: "serve-node-1", + region: "default", control_api: "http://127.0.0.1:7817", work_root: "/data/node", capabilities: { build: true, serve: true }, @@ -54,6 +55,7 @@ const configuration = { const nodeFixture = { id: "node-1", name: "serve-node-1", + region: "default", status: "active", healthy: true, build_enabled: false, diff --git a/apps/console/src/features/admin/components/nodes-panel.tsx b/apps/console/src/features/admin/components/nodes-panel.tsx index d8a662e..b7c4016 100644 --- a/apps/console/src/features/admin/components/nodes-panel.tsx +++ b/apps/console/src/features/admin/components/nodes-panel.tsx @@ -336,6 +336,7 @@ export function NodesPanel() { Name + Region Health Capabilities Serve load @@ -374,6 +375,9 @@ export function NodesPanel() {

)} + + {node.region} + {healthBadge(node)}
@@ -770,6 +774,16 @@ function EditConfigurationDialog({ node }: { node: AdminNode }) { ) } /> + + setConfiguration((current) => + current ? { ...current, node: { ...current.node, region } } : current, + ) + } + /> adminApi.createNode({ name, start_local: startLocal }), + mutationFn: () => adminApi.createNode({ name, region, start_local: startLocal }), onSuccess: ({ token, warnings }) => { setOpen(false); setName(""); + setRegion("default"); onCreated(token, warnings ?? []); }, }); @@ -1479,6 +1495,16 @@ function CreateNodeDialog({ required /> + + Region + setRegion(event.target.value)} + required + /> +