From 213455b05077d7f24ab79affc872e99c8ff08ecb Mon Sep 17 00:00:00 2001 From: greysoh Date: Wed, 8 May 2024 22:16:26 -0400 Subject: [PATCH] feature: Begins work on backend command. --- api/src/routes/backends/lookup.ts | 2 + lom/src/commands/backends.ts | 202 ++++++++++++++++++++++++++++-- 2 files changed, 196 insertions(+), 8 deletions(-) diff --git a/api/src/routes/backends/lookup.ts b/api/src/routes/backends/lookup.ts index bb24bfd..659163f 100644 --- a/api/src/routes/backends/lookup.ts +++ b/api/src/routes/backends/lookup.ts @@ -68,6 +68,8 @@ export function route(routeOptions: RouteOptions) { return { success: true, data: prismaBackends.map(i => ({ + id: i.id, + name: i.name, description: i.description, diff --git a/lom/src/commands/backends.ts b/lom/src/commands/backends.ts index dae953f..baba3d1 100644 --- a/lom/src/commands/backends.ts +++ b/lom/src/commands/backends.ts @@ -3,19 +3,31 @@ import type { Axios } from "axios"; import { SSHCommand } from "../libs/patchCommander.js"; import type { PrintLine } from "../commands.js"; +type BackendLookupSuccess = { + success: boolean, + data: { + id: number, + + name: string, + description: string, + backend: string, + connectionDetails?: string, + logs: string[] + }[]; +}; + export async function run( argv: string[], println: PrintLine, axios: Axios, - apiKey: string, + token: string, ) { - if (argv.length == 1) return println("error: no arguments specified! run %s --help to see commands.\n", argv[0]); - const program = new SSHCommand(println); program.description("Manages backends for NextNet"); program.version("v0.1.0-preprod"); const addBackend = new SSHCommand(println, "add"); + addBackend.description("Adds a backend"); addBackend.argument("", "Name of the backend"); @@ -25,7 +37,7 @@ export async function run( ); addBackend.option( - "-d, --description", + "-d, --description ", "Description for the backend", ); @@ -35,13 +47,13 @@ export async function run( ); addBackend.option( - "-c, --custom-parameters", + "-c, --custom-parameters ", "Custom parameters. Use this if the backend you're using isn't native to SSH yet, or if you manually turn on -f.", ); // SSH provider addBackend.option( - "-k, --ssh-key", + "-k, --ssh-key ", "(SSH) SSH private key to use to authenticate with the server", ); @@ -62,7 +74,7 @@ export async function run( ); addBackend.option( - "-pp, --proxied-port", + "-pp, --proxied-port ", "(PassyFire) If you're behind a proxy, and the port is different, specify the port to return", ); @@ -80,15 +92,189 @@ export async function run( const removeBackend = new SSHCommand(println, "rm"); removeBackend.description("Removes a backend"); - removeBackend.argument("", "Id of the backend"); + removeBackend.argument("", "ID of the backend"); + + removeBackend.action(async(idStr: string) => { + const id: number = parseInt(idStr); + + if (Number.isNaN(id)) { + println("ID (%s) is not a number.\n", idStr); + return; + } + + const response = await axios.post("/api/v1/backends/remove", { + token, + id + }); + + if (response.status != 200) { + if (process.env.NODE_ENV != "production") console.log(response); + + if (response.data.error) { + println(`Error: ${response.data.error}\n`); + } else { + println("Error deleting backend!\n"); + } + + return; + } + + println("Backend has been successfully deleted.\n"); + }); const lookupBackend = new SSHCommand(println, "find"); lookupBackend.description("Looks up a backend based on your arguments"); + lookupBackend.option("-n, --name ", "Name of the backend"); + + lookupBackend.option( + "-p, --provider ", + "Provider of the backend (ex. passyfire, ssh)", + ); + + lookupBackend.option( + "-d, --description ", + "Description for the backend", + ); + + lookupBackend.option( + "-e, --parse-connection-details", + "If specified, we automatically parse the connection details to make them human readable, if standard JSON." + ); + + lookupBackend.action(async(options: { + name?: string, + provider?: string, + description?: string, + parseConnectionDetails?: boolean + }) => { + const response = await axios.post("/api/v1/backends/lookup", { + token, + + name: options.name, + description: options.description, + + backend: options.provider + }); + + if (response.status != 200) { + if (process.env.NODE_ENV != "production") console.log(response); + + if (response.data.error) { + println(`Error: ${response.data.error}\n`); + } else { + println("Error stopping a connection!\n"); + } + + return; + } + + const { data }: BackendLookupSuccess = response.data; + + for (const backend of data) { + println("ID: %s:\n", backend.id); + println(" - Name: %s\n", backend.name); + println(" - Description: %s\n", backend.description); + println(" - Using Backend: %s\n", backend.backend); + + if (backend.connectionDetails) { + if (options.parseConnectionDetails) { + // We don't know what we're recieving. We just try to parse it (hence the any type) + // {} is more accurate but TS yells at us if we do that :( + + let parsedJSONData: any | undefined; + + try { + parsedJSONData = JSON.parse(backend.connectionDetails); + } catch (e) { + println(" - Connection Details: %s\n", backend.connectionDetails); + continue; + } + + if (!parsedJSONData) { + // Not really an assertion but I don't care right now + println("Assertion failed: parsedJSONData should not be undefined\n"); + continue; + } + + println(" - Connection details:\n"); + + for (const key of Object.keys(parsedJSONData)) { + let value: string | number = parsedJSONData[key]; + + if (typeof value == "string") { + value = value.replaceAll("\n", "\n" + " ".repeat(16)); + } + + if (typeof value == "object") { + // TODO: implement? + value = JSON.stringify(value); + } + + println(" - %s: %s\n", key, value); + } + } else { + println(" - Connection Details: %s\n", backend.connectionDetails); + } + } + + println("\n"); + } + + println("%s backends found.\n", data.length); + }); + + const logsCommand = new SSHCommand(println, "logs"); + logsCommand.description("View logs for a backend"); + logsCommand.argument("", "ID of the backend"); + + logsCommand.action(async(idStr: string) => { + const id: number = parseInt(idStr); + + if (Number.isNaN(id)) { + println("ID (%s) is not a number.\n", idStr); + return; + } + + const response = await axios.post("/api/v1/backends/lookup", { + token, + id + }); + + if (response.status != 200) { + if (process.env.NODE_ENV != "production") console.log(response); + + if (response.data.error) { + println(`Error: ${response.data.error}\n`); + } else { + println("Error stopping a connection!\n"); + } + + return; + } + + const { data }: BackendLookupSuccess = response.data; + const ourBackend = data.find((i) => i.id == id); + + if (!ourBackend) return println("Could not find the backend!\n"); + ourBackend.logs.forEach((log) => println("%s\n", log)); + }); + program.addCommand(addBackend); program.addCommand(removeBackend); program.addCommand(lookupBackend); + program.addCommand(logsCommand); program.parse(argv); + + // It would make sense to check this, then parse argv, however this causes issues with + // the application name not displaying correctly. + + if (argv.length == 1) { + println("No arguments specified!\n\n"); + program.help(); + return; + } + await new Promise((resolve) => program.onExit(resolve)); }