diff --git a/package-lock.json b/package-lock.json index c08d6db..4dad18b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@aws-sdk/client-dynamodb": "^3.772.0", "@aws-sdk/client-ec2": "^3.767.0", "@aws-sdk/client-iam": "^3.758.0", + "@aws-sdk/client-service-quotas": "^3.792.0", "@aws-sdk/client-ssm": "^3.774.0", "@aws-sdk/lib-dynamodb": "^3.772.0", "argon2": "^0.41.1", @@ -366,6 +367,56 @@ "node": ">=18.0.0" } }, + "node_modules/@aws-sdk/client-service-quotas": { + "version": "3.792.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-service-quotas/-/client-service-quotas-3.792.0.tgz", + "integrity": "sha512-uT1sNlbHFcka6AwS8wDk5NGVeypDC8xl/qg51pFI7jVdWCuoAvT/3cG3z9fAB1deYiL6IKHas+mP53+k5GgElA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.775.0", + "@aws-sdk/credential-provider-node": "3.787.0", + "@aws-sdk/middleware-host-header": "3.775.0", + "@aws-sdk/middleware-logger": "3.775.0", + "@aws-sdk/middleware-recursion-detection": "3.775.0", + "@aws-sdk/middleware-user-agent": "3.787.0", + "@aws-sdk/region-config-resolver": "3.775.0", + "@aws-sdk/types": "3.775.0", + "@aws-sdk/util-endpoints": "3.787.0", + "@aws-sdk/util-user-agent-browser": "3.775.0", + "@aws-sdk/util-user-agent-node": "3.787.0", + "@smithy/config-resolver": "^4.1.0", + "@smithy/core": "^3.2.0", + "@smithy/fetch-http-handler": "^5.0.2", + "@smithy/hash-node": "^4.0.2", + "@smithy/invalid-dependency": "^4.0.2", + "@smithy/middleware-content-length": "^4.0.2", + "@smithy/middleware-endpoint": "^4.1.0", + "@smithy/middleware-retry": "^4.1.0", + "@smithy/middleware-serde": "^4.0.3", + "@smithy/middleware-stack": "^4.0.2", + "@smithy/node-config-provider": "^4.0.2", + "@smithy/node-http-handler": "^4.0.4", + "@smithy/protocol-http": "^5.1.0", + "@smithy/smithy-client": "^4.2.0", + "@smithy/types": "^4.2.0", + "@smithy/url-parser": "^4.0.2", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.8", + "@smithy/util-defaults-mode-node": "^4.0.8", + "@smithy/util-endpoints": "^3.0.2", + "@smithy/util-middleware": "^4.0.2", + "@smithy/util-retry": "^4.0.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@aws-sdk/client-ssm": { "version": "3.787.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-ssm/-/client-ssm-3.787.0.tgz", diff --git a/package.json b/package.json index 364a940..7fc66e7 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@aws-sdk/client-dynamodb": "^3.772.0", "@aws-sdk/client-ec2": "^3.767.0", "@aws-sdk/client-iam": "^3.758.0", + "@aws-sdk/client-service-quotas": "^3.792.0", "@aws-sdk/client-ssm": "^3.774.0", "@aws-sdk/lib-dynamodb": "^3.772.0", "argon2": "^0.41.1", diff --git a/src/app/api/instanceTypes/route.ts b/src/app/api/instanceTypes/route.ts index 32eb87e..d6e6c89 100644 --- a/src/app/api/instanceTypes/route.ts +++ b/src/app/api/instanceTypes/route.ts @@ -1,12 +1,18 @@ -import { NextResponse } from "next/server"; +import { NextRequest, NextResponse } from "next/server"; import { getEC2InstanceTypes } from "@/utils/AWS/EC2/getEC2InstanceTypes"; -export async function GET() { +export async function GET(request: NextRequest) { try { - const instanceTypes = await getEC2InstanceTypes(); + const searchParams = request.nextUrl.searchParams; + const architecture = + (searchParams.get("architecture") as "all" | "amd64" | "arm64") || "all"; + const instanceTypes = await getEC2InstanceTypes(architecture); return NextResponse.json({ instanceTypes }); } catch (error) { console.error("API Error:", error); - return NextResponse.json({ message: "Internal Server Error" }, { status: 500 }); + return NextResponse.json( + { message: "Internal Server Error" }, + { status: 500 }, + ); } -} \ No newline at end of file +} diff --git a/src/app/api/instances/[name]/configuration/route.ts b/src/app/api/instances/[name]/configuration/route.ts index 801b585..05f2f88 100644 --- a/src/app/api/instances/[name]/configuration/route.ts +++ b/src/app/api/instances/[name]/configuration/route.ts @@ -8,7 +8,7 @@ import updateConfiguration from "./utils/updateConfiguration"; export async function GET( request: NextRequest, - { params }: { params: Promise<{ name: string }> } + { params }: { params: Promise<{ name: string }> }, ) { const searchParams = request.nextUrl.searchParams; const region = searchParams.get("region"); @@ -17,7 +17,7 @@ export async function GET( if (!region) { return NextResponse.json( { message: "Missing region parameter" }, - { status: 400 } + { status: 400 }, ); } @@ -29,14 +29,14 @@ export async function GET( console.error("Error fetching configuration:", error); return NextResponse.json( { message: "Error fetching configuration", error: String(error) }, - { status: 500 } + { status: 500 }, ); } } export async function POST( request: NextRequest, - { params }: { params: Promise<{ name: string }> } + { params }: { params: Promise<{ name: string }> }, ) { const searchParams = request.nextUrl.searchParams; const region = searchParams.get("region"); @@ -45,7 +45,7 @@ export async function POST( if (!region) { return NextResponse.json( { message: "Missing region parameter" }, - { status: 400 } + { status: 400 }, ); } @@ -58,7 +58,7 @@ export async function POST( console.error("Invalid configuration:", errors); return NextResponse.json( { message: "Invalid configuration", errors }, - { status: 400 } + { status: 400 }, ); } @@ -72,9 +72,9 @@ export async function POST( eventEmitter.emit("notification", { type: "configuration", status: "success", - instanceName: instanceName, + instanceName, - message: "Configuration updated successfully", + message: `Configuration for ${instanceName} updated successfully`, }); deleteEvent(instanceName, "configuration"); @@ -91,7 +91,7 @@ export async function POST( console.error("Error updating configuration:", error); return NextResponse.json( { message: "Error updating configuration", error: String(error) }, - { status: 500 } + { status: 500 }, ); } } diff --git a/src/app/api/instances/[name]/plugins/route.ts b/src/app/api/instances/[name]/plugins/route.ts index 070d6c1..66d5fff 100644 --- a/src/app/api/instances/[name]/plugins/route.ts +++ b/src/app/api/instances/[name]/plugins/route.ts @@ -8,7 +8,7 @@ import { deleteEvent } from "@/utils/eventBackups"; export async function GET( request: NextRequest, - { params }: { params: Promise<{ name: string }> } + { params }: { params: Promise<{ name: string }> }, ) { const searchParams = request.nextUrl.searchParams; const region = searchParams.get("region"); @@ -20,7 +20,7 @@ export async function GET( if (!region || !username || !password) { return NextResponse.json( { message: "Missing parameters" }, - { status: 400 } + { status: 400 }, ); } @@ -37,14 +37,14 @@ export async function GET( console.error("Error fetching plugins:", error); return NextResponse.json( { message: "Error fetching plugins", error: String(error) }, - { status: 500 } + { status: 500 }, ); } } export async function POST( request: NextRequest, - { params }: { params: Promise<{ name: string }> } + { params }: { params: Promise<{ name: string }> }, ) { const searchParams = request.nextUrl.searchParams; const region = searchParams.get("region"); @@ -53,7 +53,7 @@ export async function POST( if (!region) { return NextResponse.json( { message: "Missing region parameter" }, - { status: 400 } + { status: 400 }, ); } @@ -73,8 +73,8 @@ export async function POST( eventEmitter.emit("notification", { type: "plugin", status: "success", - instanceName: instanceName, - message: `${enabled ? "Enabled" : "Disabled"} ${name}`, + instanceName, + message: `${enabled ? "Enabled" : "Disabled"} ${name} on ${instanceName}`, }); deleteEvent(instanceName, "plugin"); @@ -93,7 +93,7 @@ export async function POST( console.error("Error updating plugins:", error); return NextResponse.json( { message: "Error updating plugins", error: String(error) }, - { status: 500 } + { status: 500 }, ); } } diff --git a/src/app/instances/[name]/hardware/components/InstanceTypePage.tsx b/src/app/instances/[name]/hardware/components/InstanceTypePage.tsx index 478f882..baa0058 100644 --- a/src/app/instances/[name]/hardware/components/InstanceTypePage.tsx +++ b/src/app/instances/[name]/hardware/components/InstanceTypePage.tsx @@ -1,6 +1,8 @@ "use client"; import { useInstanceContext } from "../../InstanceContext"; +import { useNotificationsContext } from "@/app/NotificationContext"; +import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; import axios from "axios"; import ErrorBanner from "@/app/instances/components/ErrorBanner"; @@ -10,8 +12,9 @@ type InstanceTypes = Record; export function InstanceTypePage() { const [instanceTypes, setInstanceTypes] = useState({}); + const { addNotification, formPending } = useNotificationsContext(); const { instance } = useInstanceContext(); - const [saving, setSaving] = useState(false); + const router = useRouter(); const [isLoading, setIsLoading] = useState(true); const [selectedInstanceType, setSelectedInstanceType] = useState(""); const [filteredInstanceTypes, setFilteredInstanceTypes] = useState( @@ -23,7 +26,10 @@ export function InstanceTypePage() { useEffect(() => { const fetchInstanceTypes = async () => { try { - const { data } = await axios.get("/api/instanceTypes"); + const architecture = instance?.type.includes("t2") ? "amd64" : "arm64"; + const { data } = await axios.get( + `/api/instanceTypes?architecture=${architecture}`, + ); setIsLoading(false); setInstanceTypes(data.instanceTypes); } catch (error) { @@ -32,7 +38,7 @@ export function InstanceTypePage() { }; fetchInstanceTypes(); - }, []); + }, [instance?.type]); useEffect(() => { setFilteredInstanceTypes(instanceTypes[selectedInstanceType] ?? []); @@ -62,20 +68,32 @@ export function InstanceTypePage() { const updateHardware = async () => { const validationErrors = validateInstanceTypeAndSize(); if (validationErrors.length > 0) { - setSaving(false); + return false; + } + + if (!instance?.name) { return false; } try { + await addNotification({ + type: "instanceType", + status: "pending", + instanceName: instance?.name, + path: "instances", + message: `Updating type of ${instance?.name} to ${instanceSize}`, + }); + await axios.put(`/api/instances/${instance?.name}/hardware/type`, { instanceId: instance?.id, instanceType: instanceSize, region: instance?.region, + instanceName: instance?.name, }); return true; } catch (error) { + console.log(error); if (!error) return; - setSaving(false); setErrors([ "Failed to update instance hardware. You must be upgrading the size.", ]); @@ -120,6 +138,10 @@ export function InstanceTypePage() { size. Changing this will cause the instance to be taken down and re-deployed on the new hardware - this can take a couple minutes.

+

+ Changing this will cause an instance restart - sending you to the home + page. +

{errors.length > 0 && (
@@ -147,7 +169,7 @@ export function InstanceTypePage() {
) : ( -
+